1 -- Copyright 2008-2018 Jo-Philipp Wich <jo@mein.io>
2 -- Licensed to the public under the Apache License 2.0.
4 -- This class contains several functions useful for http message- and content
5 -- decoding and to retrive form data from raw http messages.
7 local require, type, tonumber = require, type, tonumber
8 local table, pairs, ipairs, pcall = table, pairs, ipairs, pcall
10 module "luci.http.protocol"
12 local ltn12 = require "luci.ltn12"
13 local lhttp = require "lucihttp"
15 HTTP_MAX_CONTENT = 1024*8 -- 8 kB maximum content size
17 -- from given url or string. Returns a table with urldecoded values.
18 -- Simple parameters are stored as string values associated with the parameter
19 -- name within the table. Parameters with multiple values are stored as array
20 -- containing the corresponding values.
21 function urldecode_params(url, tbl)
23 local params = tbl or { }
25 parser = lhttp.urlencoded_parser(function (what, buffer, length)
26 if what == parser.TUPLE then
27 name, value = nil, nil
28 elseif what == parser.NAME then
29 name = lhttp.urldecode(buffer)
30 elseif what == parser.VALUE and name then
31 params[name] = lhttp.urldecode(buffer) or ""
38 parser:parse((url or ""):match("[^?]*$"))
45 -- separated by "&". Tables are encoded as parameters with multiple values by
46 -- repeating the parameter name with each value.
47 function urlencode_params(tbl)
50 for k, v in pairs(tbl) do
51 if type(v) == "table" then
53 for i, v2 in ipairs(v) do
59 enc[n+0] = lhttp.urlencode(k)
61 enc[n+2] = lhttp.urlencode(v2)
70 enc[n+0] = lhttp.urlencode(k)
72 enc[n+2] = lhttp.urlencode(v)
77 return table.concat(enc, "")
80 -- Content-Type. Stores all extracted data associated with its parameter name
81 -- in the params table within the given message object. Multiple parameter
82 -- values are stored as tables, ordinary ones as strings.
83 -- If an optional file callback function is given then it is feeded with the
84 -- file contents chunk by chunk and only the extracted file name is stored
85 -- within the params table. The callback function will be called subsequently
86 -- with three arguments:
87 -- o Table containing decoded (name, file) and raw (headers) mime header data
88 -- o String value containing a chunk of the file data
89 -- o Boolean which indicates wheather the current chunk is the last one (eof)
90 function mimedecode_message_body(src, msg, file_cb)
91 local parser, header, field
92 local len, maxlen = 0, tonumber(msg.env.CONTENT_LENGTH or nil)
94 parser, err = lhttp.multipart_parser(msg.env.CONTENT_TYPE, function (what, buffer, length)
95 if what == parser.PART_INIT then
98 elseif what == parser.HEADER_NAME then
99 header = buffer:lower()
101 elseif what == parser.HEADER_VALUE and header then
102 if header:lower() == "content-disposition" and
103 lhttp.header_attribute(buffer, nil) == "form-data"
105 field.name = lhttp.header_attribute(buffer, "name")
106 field.file = lhttp.header_attribute(buffer, "filename")
109 if field.headers then
110 field.headers[header] = buffer
112 field.headers = { [header] = buffer }
115 elseif what == parser.PART_BEGIN then
116 return not field.file
118 elseif what == parser.PART_DATA and field.name and length > 0 then
121 file_cb(field, buffer, false)
122 msg.params[field.name] = msg.params[field.name] or field
125 local ok, nx = pcall(require, "nixio")
126 field.fd = ok and nx.mkstemp(field.name)
130 field.fd:write(buffer)
131 msg.params[field.name] = msg.params[field.name] or field
138 elseif what == parser.PART_END and field.name then
139 if field.file and msg.params[field.name] then
141 file_cb(field, "", true)
143 field.fd:seek(0, "set")
146 msg.params[field.name] = field.value or ""
151 elseif what == parser.ERROR then
158 return ltn12.pump.all(src, function (chunk)
159 len = len + (chunk and #chunk or 0)
161 if maxlen and len > maxlen + 2 then
162 return nil, "Message body size exceeds Content-Length"
165 if not parser or not parser:parse(chunk) then
173 -- Content-Type. Stores all extracted data associated with its parameter name
174 -- in the params table within the given message object. Multiple parameter
175 -- values are stored as tables, ordinary ones as strings.
176 function urldecode_message_body(src, msg)
177 local err, name, value, parser
178 local len, maxlen = 0, tonumber(msg.env.CONTENT_LENGTH or nil)
180 parser = lhttp.urlencoded_parser(function (what, buffer, length)
181 if what == parser.TUPLE then
182 name, value = nil, nil
183 elseif what == parser.NAME then
184 name = lhttp.urldecode(buffer)
185 elseif what == parser.VALUE and name then
186 msg.params[name] = lhttp.urldecode(buffer) or ""
187 elseif what == parser.ERROR then
194 return ltn12.pump.all(src, function (chunk)
195 len = len + (chunk and #chunk or 0)
197 if maxlen and len > maxlen + 2 then
198 return nil, "Message body size exceeds Content-Length"
199 elseif len > HTTP_MAX_CONTENT then
200 return nil, "Message body size exceeds maximum allowed length"
203 if not parser or not parser:parse(chunk) then
211 -- This function will examine the Content-Type within the given message object
212 -- to select the appropriate content decoder.
213 -- Currently the application/x-www-urlencoded and application/form-data
214 -- mime types are supported. If the encountered content encoding can't be
215 -- handled then the whole message body will be stored unaltered as "content"
216 -- property within the given message object.
217 function parse_message_body(src, msg, filecb)
218 local ctype = lhttp.header_attribute(msg.env.CONTENT_TYPE, nil)
220 -- Is it multipart/mime ?
221 if msg.env.REQUEST_METHOD == "POST" and
222 ctype == "multipart/form-data"
224 return mimedecode_message_body( src, msg, filecb )
226 -- Is it application/x-www-form-urlencoded ?
227 elseif msg.env.REQUEST_METHOD == "POST" and
228 ctype == "application/x-www-form-urlencoded"
230 return urldecode_message_body( src, msg, filecb )
233 -- Unhandled encoding
234 -- If a file callback is given then feed it chunk by chunk, else
235 -- store whole buffer in message.content
240 -- If we have a file callback then feed it
241 if type(filecb) == "function" then
244 encoding = msg.env.CONTENT_TYPE
246 sink = function( chunk )
248 return filecb(meta, chunk, false)
250 return filecb(meta, nil, true)
253 -- ... else append to .content
256 msg.content_length = 0
258 sink = function( chunk )
260 if ( msg.content_length + #chunk ) <= HTTP_MAX_CONTENT then
261 msg.content = msg.content .. chunk
262 msg.content_length = msg.content_length + #chunk
265 return nil, "POST data exceeds maximum allowed length"
274 local ok, err = ltn12.pump.step( src, sink )
276 if not ok and err then
278 elseif not ok then -- eof