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