56d0064825a8ea47a9027bbbe380c5dccce3cfaf
[project/luci.git] / modules / luci-base / luasrc / http.lua
1 -- Copyright 2008 Steven Barth <steven@midlink.org>
2 -- Copyright 2010-2018 Jo-Philipp Wich <jo@mein.io>
3 -- Licensed to the public under the Apache License 2.0.
4
5 local util  = require "luci.util"
6 local coroutine = require "coroutine"
7 local table = require "table"
8 local lhttp = require "lucihttp"
9 local nixio = require "nixio"
10 local ltn12 = require "luci.ltn12"
11
12 local table, ipairs, pairs, type, tostring, tonumber, error =
13         table, ipairs, pairs, type, tostring, tonumber, error
14
15 module "luci.http"
16
17 HTTP_MAX_CONTENT      = 1024*8          -- 8 kB maximum content size
18
19 context = util.threadlocal()
20
21 Request = util.class()
22 function Request.__init__(self, env, sourcein, sinkerr)
23         self.input = sourcein
24         self.error = sinkerr
25
26
27         -- File handler nil by default to let .content() work
28         self.filehandler = nil
29
30         -- HTTP-Message table
31         self.message = {
32                 env = env,
33                 headers = {},
34                 params = urldecode_params(env.QUERY_STRING or ""),
35         }
36
37         self.parsed_input = false
38 end
39
40 function Request.formvalue(self, name, noparse)
41         if not noparse and not self.parsed_input then
42                 self:_parse_input()
43         end
44
45         if name then
46                 return self.message.params[name]
47         else
48                 return self.message.params
49         end
50 end
51
52 function Request.formvaluetable(self, prefix)
53         local vals = {}
54         prefix = prefix and prefix .. "." or "."
55
56         if not self.parsed_input then
57                 self:_parse_input()
58         end
59
60         local void = self.message.params[nil]
61         for k, v in pairs(self.message.params) do
62                 if k:find(prefix, 1, true) == 1 then
63                         vals[k:sub(#prefix + 1)] = tostring(v)
64                 end
65         end
66
67         return vals
68 end
69
70 function Request.content(self)
71         if not self.parsed_input then
72                 self:_parse_input()
73         end
74
75         return self.message.content, self.message.content_length
76 end
77
78 function Request.getcookie(self, name)
79         return lhttp.header_attribute("cookie; " .. (self:getenv("HTTP_COOKIE") or ""), name)
80 end
81
82 function Request.getenv(self, name)
83         if name then
84                 return self.message.env[name]
85         else
86                 return self.message.env
87         end
88 end
89
90 function Request.setfilehandler(self, callback)
91         self.filehandler = callback
92
93         if not self.parsed_input then
94                 return
95         end
96
97         -- If input has already been parsed then uploads are stored as unlinked
98         -- temporary files pointed to by open file handles in the parameter
99         -- value table. Loop all params, and invoke the file callback for any
100         -- param with an open file handle.
101         local name, value
102         for name, value in pairs(self.message.params) do
103                 if type(value) == "table" then
104                         while value.fd do
105                                 local data = value.fd:read(1024)
106                                 local eof = (not data or data == "")
107
108                                 callback(value, data, eof)
109
110                                 if eof then
111                                         value.fd:close()
112                                         value.fd = nil
113                                 end
114                         end
115                 end
116         end
117 end
118
119 function Request._parse_input(self)
120         parse_message_body(
121                  self.input,
122                  self.message,
123                  self.filehandler
124         )
125         self.parsed_input = true
126 end
127
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 function content()
141         return context.request:content()
142 end
143
144 function formvalue(name, noparse)
145         return context.request:formvalue(name, noparse)
146 end
147
148 function formvaluetable(prefix)
149         return context.request:formvaluetable(prefix)
150 end
151
152 function getcookie(name)
153         return context.request:getcookie(name)
154 end
155
156 -- or the environment table itself.
157 function getenv(name)
158         return context.request:getenv(name)
159 end
160
161 function setfilehandler(callback)
162         return context.request:setfilehandler(callback)
163 end
164
165 function header(key, value)
166         if not context.headers then
167                 context.headers = {}
168         end
169         context.headers[key:lower()] = value
170         coroutine.yield(2, key, value)
171 end
172
173 function prepare_content(mime)
174         if not context.headers or not context.headers["content-type"] then
175                 if mime == "application/xhtml+xml" then
176                         if not getenv("HTTP_ACCEPT") or
177                           not getenv("HTTP_ACCEPT"):find("application/xhtml+xml", nil, true) then
178                                 mime = "text/html; charset=UTF-8"
179                         end
180                         header("Vary", "Accept")
181                 end
182                 header("Content-Type", mime)
183         end
184 end
185
186 function source()
187         return context.request.input
188 end
189
190 function status(code, message)
191         code = code or 200
192         message = message or "OK"
193         context.status = code
194         coroutine.yield(1, code, message)
195 end
196
197 -- This function is as a valid LTN12 sink.
198 -- If the content chunk is nil this function will automatically invoke close.
199 function write(content, src_err)
200         if not content then
201                 if src_err then
202                         error(src_err)
203                 else
204                         close()
205                 end
206                 return true
207         elseif #content == 0 then
208                 return true
209         else
210                 if not context.eoh then
211                         if not context.status then
212                                 status()
213                         end
214                         if not context.headers or not context.headers["content-type"] then
215                                 header("Content-Type", "text/html; charset=utf-8")
216                         end
217                         if not context.headers["cache-control"] then
218                                 header("Cache-Control", "no-cache")
219                                 header("Expires", "0")
220                         end
221                         if not context.headers["x-frame-options"] then
222                                 header("X-Frame-Options", "SAMEORIGIN")
223                         end
224                         if not context.headers["x-xss-protection"] then
225                                 header("X-XSS-Protection", "1; mode=block")
226                         end
227                         if not context.headers["x-content-type-options"] then
228                                 header("X-Content-Type-Options", "nosniff")
229                         end
230
231                         context.eoh = true
232                         coroutine.yield(3)
233                 end
234                 coroutine.yield(4, content)
235                 return true
236         end
237 end
238
239 function splice(fd, size)
240         coroutine.yield(6, fd, size)
241 end
242
243 function redirect(url)
244         if url == "" then url = "/" end
245         status(302, "Found")
246         header("Location", url)
247         close()
248 end
249
250 function build_querystring(q)
251         local s, n, k, v = {}, 1, nil, nil
252
253         for k, v in pairs(q) do
254                 s[n+0] = (n == 1) and "?" or "&"
255                 s[n+1] = util.urlencode(k)
256                 s[n+2] = "="
257                 s[n+3] = util.urlencode(v)
258                 n = n + 4
259         end
260
261         return table.concat(s, "")
262 end
263
264 urldecode = util.urldecode
265
266 urlencode = util.urlencode
267
268 function write_json(x)
269         util.serialize_json(x, write)
270 end
271
272 -- from given url or string. Returns a table with urldecoded values.
273 -- Simple parameters are stored as string values associated with the parameter
274 -- name within the table. Parameters with multiple values are stored as array
275 -- containing the corresponding values.
276 function urldecode_params(url, tbl)
277         local parser, name
278         local params = tbl or { }
279
280         parser = lhttp.urlencoded_parser(function (what, buffer, length)
281                 if what == parser.TUPLE then
282                         name, value = nil, nil
283                 elseif what == parser.NAME then
284                         name = lhttp.urldecode(buffer)
285                 elseif what == parser.VALUE and name then
286                         params[name] = lhttp.urldecode(buffer) or ""
287                 end
288
289                 return true
290         end)
291
292         if parser then
293                 parser:parse((url or ""):match("[^?]*$"))
294                 parser:parse(nil)
295         end
296
297         return params
298 end
299
300 -- separated by "&". Tables are encoded as parameters with multiple values by
301 -- repeating the parameter name with each value.
302 function urlencode_params(tbl)
303         local k, v
304         local n, enc = 1, {}
305         for k, v in pairs(tbl) do
306                 if type(v) == "table" then
307                         local i, v2
308                         for i, v2 in ipairs(v) do
309                                 if enc[1] then
310                                         enc[n] = "&"
311                                         n = n + 1
312                                 end
313
314                                 enc[n+0] = lhttp.urlencode(k)
315                                 enc[n+1] = "="
316                                 enc[n+2] = lhttp.urlencode(v2)
317                                 n = n + 3
318                         end
319                 else
320                         if enc[1] then
321                                 enc[n] = "&"
322                                 n = n + 1
323                         end
324
325                         enc[n+0] = lhttp.urlencode(k)
326                         enc[n+1] = "="
327                         enc[n+2] = lhttp.urlencode(v)
328                         n = n + 3
329                 end
330         end
331
332         return table.concat(enc, "")
333 end
334
335 -- Content-Type. Stores all extracted data associated with its parameter name
336 -- in the params table within the given message object. Multiple parameter
337 -- values are stored as tables, ordinary ones as strings.
338 -- If an optional file callback function is given then it is feeded with the
339 -- file contents chunk by chunk and only the extracted file name is stored
340 -- within the params table. The callback function will be called subsequently
341 -- with three arguments:
342 --  o Table containing decoded (name, file) and raw (headers) mime header data
343 --  o String value containing a chunk of the file data
344 --  o Boolean which indicates wheather the current chunk is the last one (eof)
345 function mimedecode_message_body(src, msg, file_cb)
346         local parser, header, field
347         local len, maxlen = 0, tonumber(msg.env.CONTENT_LENGTH or nil)
348
349         parser, err = lhttp.multipart_parser(msg.env.CONTENT_TYPE, function (what, buffer, length)
350                 if what == parser.PART_INIT then
351                         field = { }
352
353                 elseif what == parser.HEADER_NAME then
354                         header = buffer:lower()
355
356                 elseif what == parser.HEADER_VALUE and header then
357                         if header:lower() == "content-disposition" and
358                            lhttp.header_attribute(buffer, nil) == "form-data"
359                         then
360                                 field.name = lhttp.header_attribute(buffer, "name")
361                                 field.file = lhttp.header_attribute(buffer, "filename")
362                         end
363
364                         if field.headers then
365                                 field.headers[header] = buffer
366                         else
367                                 field.headers = { [header] = buffer }
368                         end
369
370                 elseif what == parser.PART_BEGIN then
371                         return not field.file
372
373                 elseif what == parser.PART_DATA and field.name and length > 0 then
374                         if field.file then
375                                 if file_cb then
376                                         file_cb(field, buffer, false)
377                                         msg.params[field.name] = msg.params[field.name] or field
378                                 else
379                                         if not field.fd then
380                                                 field.fd = nixio.mkstemp(field.name)
381                                         end
382
383                                         if field.fd then
384                                                 field.fd:write(buffer)
385                                                 msg.params[field.name] = msg.params[field.name] or field
386                                         end
387                                 end
388                         else
389                                 field.value = buffer
390                         end
391
392                 elseif what == parser.PART_END and field.name then
393                         if field.file and msg.params[field.name] then
394                                 if file_cb then
395                                         file_cb(field, "", true)
396                                 elseif field.fd then
397                                         field.fd:seek(0, "set")
398                                 end
399                         else
400                                 local val = msg.params[field.name]
401
402                                 if type(val) == "table" then
403                                         val[#val+1] = field.value or ""
404                                 elseif val ~= nil then
405                                         msg.params[field.name] = { val, field.value or "" }
406                                 else
407                                         msg.params[field.name] = field.value or ""
408                                 end
409                         end
410
411                         field = nil
412
413                 elseif what == parser.ERROR then
414                         err = buffer
415                 end
416
417                 return true
418         end)
419
420         return ltn12.pump.all(src, function (chunk)
421                 len = len + (chunk and #chunk or 0)
422
423                 if maxlen and len > maxlen + 2 then
424                         return nil, "Message body size exceeds Content-Length"
425                 end
426
427                 if not parser or not parser:parse(chunk) then
428                         return nil, err
429                 end
430
431                 return true
432         end)
433 end
434
435 -- Content-Type. Stores all extracted data associated with its parameter name
436 -- in the params table within the given message object. Multiple parameter
437 -- values are stored as tables, ordinary ones as strings.
438 function urldecode_message_body(src, msg)
439         local err, name, value, parser
440         local len, maxlen = 0, tonumber(msg.env.CONTENT_LENGTH or nil)
441
442         parser = lhttp.urlencoded_parser(function (what, buffer, length)
443                 if what == parser.TUPLE then
444                         name, value = nil, nil
445                 elseif what == parser.NAME then
446                         name = lhttp.urldecode(buffer, lhttp.DECODE_PLUS)
447                 elseif what == parser.VALUE and name then
448                         local val = msg.params[name]
449
450                         if type(val) == "table" then
451                                 val[#val+1] = lhttp.urldecode(buffer, lhttp.DECODE_PLUS) or ""
452                         elseif val ~= nil then
453                                 msg.params[name] = { val, lhttp.urldecode(buffer, lhttp.DECODE_PLUS) or "" }
454                         else
455                                 msg.params[name] = lhttp.urldecode(buffer, lhttp.DECODE_PLUS) or ""
456                         end
457                 elseif what == parser.ERROR then
458                         err = buffer
459                 end
460
461                 return true
462         end)
463
464         return ltn12.pump.all(src, function (chunk)
465                 len = len + (chunk and #chunk or 0)
466
467                 if maxlen and len > maxlen + 2 then
468                         return nil, "Message body size exceeds Content-Length"
469                 elseif len > HTTP_MAX_CONTENT then
470                         return nil, "Message body size exceeds maximum allowed length"
471                 end
472
473                 if not parser or not parser:parse(chunk) then
474                         return nil, err
475                 end
476
477                 return true
478         end)
479 end
480
481 -- This function will examine the Content-Type within the given message object
482 -- to select the appropriate content decoder.
483 -- Currently the application/x-www-urlencoded and application/form-data
484 -- mime types are supported. If the encountered content encoding can't be
485 -- handled then the whole message body will be stored unaltered as "content"
486 -- property within the given message object.
487 function parse_message_body(src, msg, filecb)
488         local ctype = lhttp.header_attribute(msg.env.CONTENT_TYPE, nil)
489
490         -- Is it multipart/mime ?
491         if msg.env.REQUEST_METHOD == "POST" and
492            ctype == "multipart/form-data"
493         then
494                 return mimedecode_message_body(src, msg, filecb)
495
496         -- Is it application/x-www-form-urlencoded ?
497         elseif msg.env.REQUEST_METHOD == "POST" and
498                ctype == "application/x-www-form-urlencoded"
499         then
500                 return urldecode_message_body(src, msg)
501
502
503         -- Unhandled encoding
504         -- If a file callback is given then feed it chunk by chunk, else
505         -- store whole buffer in message.content
506         else
507
508                 local sink
509
510                 -- If we have a file callback then feed it
511                 if type(filecb) == "function" then
512                         local meta = {
513                                 name = "raw",
514                                 encoding = msg.env.CONTENT_TYPE
515                         }
516                         sink = function( chunk )
517                                 if chunk then
518                                         return filecb(meta, chunk, false)
519                                 else
520                                         return filecb(meta, nil, true)
521                                 end
522                         end
523                 -- ... else append to .content
524                 else
525                         msg.content = ""
526                         msg.content_length = 0
527
528                         sink = function( chunk )
529                                 if chunk then
530                                         if ( msg.content_length + #chunk ) <= HTTP_MAX_CONTENT then
531                                                 msg.content        = msg.content        .. chunk
532                                                 msg.content_length = msg.content_length + #chunk
533                                                 return true
534                                         else
535                                                 return nil, "POST data exceeds maximum allowed length"
536                                         end
537                                 end
538                                 return true
539                         end
540                 end
541
542                 -- Pump data...
543                 while true do
544                         local ok, err = ltn12.pump.step( src, sink )
545
546                         if not ok and err then
547                                 return nil, err
548                         elseif not ok then -- eof
549                                 return true
550                         end
551                 end
552
553                 return true
554         end
555 end