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