* luci/libs/http: added inline documentation to luci.http.protocol & friends, fixed...
[project/luci.git] / libs / http / luasrc / http / protocol / conditionals.lua
1 --[[
2
3 HTTP protocol implementation for LuCI - RFC2616 / 14.19, 14.24 - 14.28
4 (c) 2008 Freifunk Leipzig / Jo-Philipp Wich <xm@leipzig.freifunk.net>
5
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
9
10         http://www.apache.org/licenses/LICENSE-2.0
11
12 $Id$
13
14 ]]--
15
16 --- LuCI http protocol implementation - HTTP/1.1 bits.
17 -- This class provides basic ETag handling and implements most of the
18 -- conditional HTTP/1.1 headers specified in RFC2616 Sct. 14.24 - 14.28 .
19 module("luci.http.protocol.conditionals", package.seeall)
20
21 local date = require("luci.http.protocol.date")
22
23
24 --- Implement 14.19 / ETag.
25 -- @param stat  A file.stat structure
26 -- @return              String containing the generated tag suitable for ETag headers
27 function mk_etag( stat )
28         if stat ~= nil then
29                 return string.format( '"%x-%x-%x"', stat.ino, stat.size, stat.mtime )
30         end
31 end
32
33 --- 14.24 / If-Match
34 -- Test whether the given message object contains an "If-Match" header and
35 -- compare it against the given stat object.
36 -- @param req   HTTP request message object
37 -- @param stat  A file.stat object
38 -- @return              Boolean indicating wheather the precondition is ok
39 -- @return              Alternative status code if the precondition failed
40 function if_match( req, stat )
41         local h    = req.headers
42         local etag = mk_etag( stat )
43
44         -- Check for matching resource
45         if type(h['If-Match']) == "string" then
46                 for ent in h['If-Match']:gmatch("([^, ]+)") do
47                         if ( ent == '*' or ent == etag ) and stat ~= nil then
48                                 return true
49                         end
50                 end
51
52                 return false, 412
53         end
54
55         return true
56 end
57
58 --- 14.25 / If-Modified-Since
59 -- Test whether the given message object contains an "If-Modified-Since" header
60 -- and compare it against the given stat object.
61 -- @param req   HTTP request message object
62 -- @param stat  A file.stat object
63 -- @return              Boolean indicating wheather the precondition is ok
64 -- @return              Alternative status code if the precondition failed
65 -- @return              Table containing extra HTTP headers if the precondition failed
66 function if_modified_since( req, stat )
67         local h = req.headers
68
69         -- Compare mtimes
70         if type(h['If-Modified-Since']) == "string" then
71                 local since = date.to_unix( h['If-Modified-Since'] )
72
73                 if stat == nil or since < stat.mtime then
74                         return true
75                 end
76
77                 return false, 304, {
78                         ["ETag"]          = mk_etag( stat );
79                         ["Date"]          = date.to_http( os.time() );
80                         ["Last-Modified"] = date.to_http( stat.mtime )
81                 }
82         end
83
84         return true
85 end
86
87 --- 14.26 / If-None-Match
88 -- Test whether the given message object contains an "If-None-Match" header and
89 -- compare it against the given stat object.
90 -- @param req   HTTP request message object
91 -- @param stat  A file.stat object
92 -- @return              Boolean indicating wheather the precondition is ok
93 -- @return              Alternative status code if the precondition failed
94 -- @return              Table containing extra HTTP headers if the precondition failed
95 function if_none_match( req, stat )
96         local h    = req.headers
97         local etag = mk_etag( stat )
98
99         -- Check for matching resource
100         if type(h['If-None-Match']) == "string" then
101                 for ent in h['If-None-Match']:gmatch("([^, ]+)") do
102                         if ( ent == '*' or ent == etag ) and stat ~= nil then
103                                 if req.request_method == "get"  or
104                                    req.request_method == "head"
105                                 then
106                                         return false, 304, {
107                                                 ["ETag"]          = mk_etag( stat );
108                                                 ["Date"]          = date.to_http( os.time() );
109                                                 ["Last-Modified"] = date.to_http( stat.mtime )
110                                         }
111                                 else
112                                         return false, 412
113                                 end
114                         end
115                 end
116         end
117
118         return true
119 end
120
121 -- 14.27 / If-Range
122 -- The If-Range header is currently not implemented due to the lack of general
123 -- byte range stuff in luci.http.protocol . This function will always return
124 -- false, 412 to indicate a failed precondition.
125 -- @param req   HTTP request message object
126 -- @param stat  A file.stat object
127 -- @return              Boolean indicating wheather the precondition is ok
128 -- @return              Alternative status code if the precondition failed
129 function if_range( req, stat )
130         -- Sorry, no subranges (yet)
131         return false, 412
132 end
133
134 -- 14.28 / If-Unmodified-Since
135 -- Test whether the given message object contains an "If-Unmodified-Since"
136 -- header and compare it against the given stat object.
137 -- @param req   HTTP request message object
138 -- @param stat  A file.stat object
139 -- @return              Boolean indicating wheather the precondition is ok
140 -- @return              Alternative status code if the precondition failed
141 function if_unmodified_since( req, stat )
142         local h = req.headers
143
144         -- Compare mtimes
145         if type(h['If-Unmodified-Since']) == "string" then
146                 local since = date.to_unix( h['If-Unmodified-Since'] )
147
148                 if stat ~= nil and since <= stat.mtime then
149                         return false, 412
150                 end
151         end
152
153         return true
154 end