contrib: remove recursive deps between theme-bootstrap and luci-base
[project/luci.git] / modules / base / luasrc / 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 whether 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 whether 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 whether 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         local method = req.env and req.env.REQUEST_METHOD or "GET"
99
100         -- Check for matching resource
101         if type(h['If-None-Match']) == "string" then
102                 for ent in h['If-None-Match']:gmatch("([^, ]+)") do
103                         if ( ent == '*' or ent == etag ) and stat ~= nil then
104                                 if method == "GET" or method == "HEAD" then
105                                         return false, 304, {
106                                                 ["ETag"]          = etag;
107                                                 ["Date"]          = date.to_http( os.time() );
108                                                 ["Last-Modified"] = date.to_http( stat.mtime )
109                                         }
110                                 else
111                                         return false, 412
112                                 end
113                         end
114                 end
115         end
116
117         return true
118 end
119
120 --- 14.27 / If-Range
121 -- The If-Range header is currently not implemented due to the lack of general
122 -- byte range stuff in luci.http.protocol . This function will always return
123 -- false, 412 to indicate a failed precondition.
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_range( req, stat )
129         -- Sorry, no subranges (yet)
130         return false, 412
131 end
132
133 --- 14.28 / If-Unmodified-Since
134 -- Test whether the given message object contains an "If-Unmodified-Since"
135 -- header and compare it against the given stat object.
136 -- @param req   HTTP request message object
137 -- @param stat  A file.stat object
138 -- @return              Boolean indicating whether the precondition is ok
139 -- @return              Alternative status code if the precondition failed
140 function if_unmodified_since( req, stat )
141         local h = req.headers
142
143         -- Compare mtimes
144         if type(h['If-Unmodified-Since']) == "string" then
145                 local since = date.to_unix( h['If-Unmodified-Since'] )
146
147                 if stat ~= nil and since <= stat.mtime then
148                         return false, 412
149                 end
150         end
151
152         return true
153 end