Rewrote state based redirection
[project/luci.git] / libs / web / luasrc / http.lua
1 --[[
2 LuCI - HTTP-Interaction
3
4 Description:
5 HTTP-Header manipulator and form variable preprocessor
6
7 FileId:
8 $Id$
9
10 License:
11 Copyright 2008 Steven Barth <steven@midlink.org>
12
13 Licensed under the Apache License, Version 2.0 (the "License");
14 you may not use this file except in compliance with the License.
15 You may obtain a copy of the License at 
16
17         http://www.apache.org/licenses/LICENSE-2.0 
18
19 Unless required by applicable law or agreed to in writing, software
20 distributed under the License is distributed on an "AS IS" BASIS,
21 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 See the License for the specific language governing permissions and
23 limitations under the License.
24
25 ]]--
26
27 local ltn12 = require "luci.ltn12"
28 local proto = require "luci.http.protocol"
29 local util  = require "luci.util"
30 local string = require "string"
31 local coroutine = require "coroutine"
32
33 local pairs, tostring, error = pairs, tostring, error
34
35 --- LuCI Web Framework high-level HTTP functions.
36 module "luci.http"
37
38 context = util.threadlocal()
39
40 Request = util.class()
41 function Request.__init__(self, env, sourcein, sinkerr)
42         self.input = sourcein
43         self.error = sinkerr
44
45
46         -- File handler
47         self.filehandler = function() end
48         
49         -- HTTP-Message table
50         self.message = {
51                 env = env,
52                 headers = {},
53                 params = protocol.urldecode_params(env.QUERY_STRING or ""),
54         }
55         
56         self.parsed_input = false
57 end
58
59 function Request.formvalue(self, name, noparse)
60         if not noparse and not self.parsed_input then
61                 self:_parse_input()
62         end
63         
64         if name then
65                 return self.message.params[name]
66         else
67                 return self.message.params
68         end
69 end
70
71 function Request.formvaluetable(self, prefix)
72         local vals = {}
73         prefix = prefix and prefix .. "." or "."
74         
75         if not self.parsed_input then
76                 self:_parse_input()
77         end
78         
79         local void = self.message.params[nil]
80         for k, v in pairs(self.message.params) do
81                 if k:find(prefix, 1, true) == 1 then
82                         vals[k:sub(#prefix + 1)] = tostring(v)
83                 end
84         end
85         
86         return vals
87 end
88
89 function Request.content(self)
90         if not self.parsed_input then
91                 self:_parse_input()
92         end
93         
94         return self.message.content, self.message.content_length
95 end
96
97 function Request.getcookie(self, name)
98   local c = string.gsub(";" .. (self:getenv("HTTP_COOKIE") or "") .. ";", "%s*;%s*", ";")
99   local p = ";" .. name .. "=(.-);"
100   local i, j, value = c:find(p)
101   return value and urldecode(value)
102 end
103
104 function Request.getenv(self, name)
105         if name then
106                 return self.message.env[name]
107         else
108                 return self.message.env
109         end
110 end
111
112 function Request.setfilehandler(self, callback)
113         self.filehandler = callback
114 end
115
116 function Request._parse_input(self)
117         protocol.parse_message_body(
118                  self.input,
119                  self.message,
120                  self.filehandler
121         )
122         self.parsed_input = true
123 end
124
125 --- Close the HTTP-Connection.
126 function close()
127         if not context.eoh then
128                 context.eoh = true
129                 coroutine.yield(3)
130         end
131         
132         if not context.closed then
133                 context.closed = true
134                 coroutine.yield(5)
135         end
136 end
137
138 --- Return the request content if the request was of unknown type.
139 -- @return      HTTP request body
140 -- @return      HTTP request body length
141 function content()
142         return context.request:content()
143 end
144
145 --- Get a certain HTTP input value or a table of all input values.
146 -- @param name          Name of the GET or POST variable to fetch
147 -- @param noparse       Don't parse POST data before getting the value
148 -- @return                      HTTP input value or table of all input value
149 function formvalue(name, noparse)
150         return context.request:formvalue(name, noparse)
151 end
152
153 --- Get a table of all HTTP input values with a certain prefix.
154 -- @param prefix        Prefix
155 -- @return                      Table of all HTTP input values with given prefix
156 function formvaluetable(prefix)
157         return context.request:formvaluetable(prefix)
158 end
159
160 --- Get the value of a certain HTTP-Cookie.
161 -- @param name          Cookie Name
162 -- @return                      String containing cookie data
163 function getcookie(name)
164         return context.request:getcookie(name)
165 end
166
167 --- Get the value of a certain HTTP environment variable 
168 -- or the environment table itself.
169 -- @param name          Environment variable
170 -- @return                      HTTP environment value or environment table
171 function getenv(name)
172         return context.request:getenv(name)
173 end
174
175 --- Set a handler function for incoming user file uploads.
176 -- @param callback      Handler function
177 function setfilehandler(callback)
178         return context.request:setfilehandler(callback)
179 end
180
181 --- Send a HTTP-Header.
182 -- @param key   Header key
183 -- @param value Header value
184 function header(key, value)
185         if not context.headers then
186                 context.headers = {}
187         end
188         context.headers[key:lower()] = value
189         coroutine.yield(2, key, value)
190 end
191
192 --- Set the mime type of following content data.
193 -- @param mime  Mimetype of following content
194 function prepare_content(mime)
195         if not context.headers or not context.headers["content-type"] then
196                 if mime == "application/xhtml+xml" then
197                         if not getenv("HTTP_ACCEPT") or
198                           not getenv("HTTP_ACCEPT"):find("application/xhtml+xml", nil, true) then
199                                 mime = "text/html; charset=UTF-8"
200                         end
201                         header("Vary", "Accept")
202                 end
203                 header("Content-Type", mime)
204         end
205 end
206
207 --- Get the RAW HTTP input source
208 -- @return      HTTP LTN12 source
209 function source()
210         return context.request.input
211 end
212
213 --- Set the HTTP status code and status message.
214 -- @param code          Status code
215 -- @param message       Status message
216 function status(code, message)
217         code = code or 200
218         message = message or "OK"
219         context.status = code
220         coroutine.yield(1, code, message)
221 end
222
223 --- Send a chunk of content data to the client.
224 -- This function is as a valid LTN12 sink.
225 -- If the content chunk is nil this function will automatically invoke close.
226 -- @param content       Content chunk
227 -- @param src_err       Error object from source (optional)
228 -- @see close
229 function write(content, src_err)
230         if not content then
231                 if src_err then
232                         error(src_err)
233                 else
234                         close()
235                 end
236                 return true
237         elseif #content == 0 then
238                 return true
239         else
240                 if not context.eoh then
241                         if not context.status then
242                                 status()
243                         end
244                         if not context.headers or not context.headers["content-type"] then
245                                 header("Content-Type", "text/html; charset=utf-8")
246                         end
247                         if not context.headers["cache-control"] then
248                                 header("Cache-Control", "no-cache")
249                                 header("Expires", "0")
250                         end
251                         
252                         
253                         context.eoh = true
254                         coroutine.yield(3)
255                 end
256                 coroutine.yield(4, content)
257                 return true
258         end
259 end
260
261 --- Redirects the client to a new URL and closes the connection.
262 -- @param url   Target URL
263 function redirect(url)
264         status(302, "Found")
265         header("Location", url)
266         close()
267 end
268
269 --- Create a querystring out of a table of key - value pairs.
270 -- @param table         Query string source table
271 -- @return                      Encoded HTTP query string
272 function build_querystring(table)
273         local s="?"
274         
275         for k, v in pairs(table) do
276                 s = s .. urlencode(k) .. "=" .. urlencode(v) .. "&"
277         end
278         
279         return s
280 end
281
282 --- Return the URL-decoded equivalent of a string.
283 -- @param str           URL-encoded string
284 -- @param no_plus       Don't decode + to " "
285 -- @return                      URL-decoded string
286 -- @see urlencode
287 urldecode = protocol.urldecode
288
289 --- Return the URL-encoded equivalent of a string.
290 -- @param str           Source string
291 -- @return                      URL-encoded string
292 -- @see urldecode
293 urlencode = protocol.urlencode