General optimizations, simplifications and improvements
[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 mime == "application/xhtml+xml" then
196                 if not getenv("HTTP_ACCEPT") or
197                   not getenv("HTTP_ACCEPT"):find("application/xhtml+xml", nil, true) then
198                         mime = "text/html; charset=UTF-8"
199                 end
200                 header("Vary", "Accept")
201         end
202         header("Content-Type", mime)
203 end
204
205 --- Get the RAW HTTP input source
206 -- @return      HTTP LTN12 source
207 function source()
208         return context.request.input
209 end
210
211 --- Set the HTTP status code and status message.
212 -- @param code          Status code
213 -- @param message       Status message
214 function status(code, message)
215         code = code or 200
216         message = message or "OK"
217         context.status = code
218         coroutine.yield(1, code, message)
219 end
220
221 --- Send a chunk of content data to the client.
222 -- This function is as a valid LTN12 sink.
223 -- If the content chunk is nil this function will automatically invoke close.
224 -- @param content       Content chunk
225 -- @param src_err       Error object from source (optional)
226 -- @see close
227 function write(content, src_err)
228         if not content then
229                 if src_err then
230                         error(src_err)
231                 else
232                         close()
233                 end
234                 return true
235         elseif #content == 0 then
236                 return true
237         else
238                 if not context.eoh then
239                         if not context.status then
240                                 status()
241                         end
242                         if not context.headers or not context.headers["content-type"] then
243                                 header("Content-Type", "text/html; charset=utf-8")
244                         end
245                         if not context.headers["cache-control"] then
246                                 header("Cache-Control", "no-cache")
247                                 header("Expires", "0")
248                         end
249                         
250                         
251                         context.eoh = true
252                         coroutine.yield(3)
253                 end
254                 coroutine.yield(4, content)
255                 return true
256         end
257 end
258
259 --- Redirects the client to a new URL and closes the connection.
260 -- @param url   Target URL
261 function redirect(url)
262         status(302, "Found")
263         header("Location", url)
264         close()
265 end
266
267 --- Create a querystring out of a table of key - value pairs.
268 -- @param table         Query string source table
269 -- @return                      Encoded HTTP query string
270 function build_querystring(table)
271         local s="?"
272         
273         for k, v in pairs(table) do
274                 s = s .. urlencode(k) .. "=" .. urlencode(v) .. "&"
275         end
276         
277         return s
278 end
279
280 --- Return the URL-decoded equivalent of a string.
281 -- @param str           URL-encoded string
282 -- @param no_plus       Don't decode + to " "
283 -- @return                      URL-decoded string
284 -- @see urlencode
285 urldecode = protocol.urldecode
286
287 --- Return the URL-encoded equivalent of a string.
288 -- @param str           Source string
289 -- @return                      URL-encoded string
290 -- @see urldecode
291 urlencode = protocol.urlencode