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