luci-base: refactor luci.http
[project/luci.git] / modules / luci-base / luasrc / http / protocol.lua
1 -- Copyright 2008-2018 Jo-Philipp Wich <jo@mein.io>
2 -- Licensed to the public under the Apache License 2.0.
3
4 -- This class contains several functions useful for http message- and content
5 -- decoding and to retrive form data from raw http messages.
6
7 local require, type, tonumber = require, type, tonumber
8 local table, pairs, ipairs, pcall = table, pairs, ipairs, pcall
9
10 module "luci.http.protocol"
11
12 local ltn12 = require "luci.ltn12"
13 local lhttp = require "lucihttp"
14
15 HTTP_MAX_CONTENT      = 1024*8          -- 8 kB maximum content size
16
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)
22         local parser, name
23         local params = tbl or { }
24
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 ""
32                 end
33
34                 return true
35         end)
36
37         if parser then
38                 parser:parse((url or ""):match("[^?]*$"))
39                 parser:parse(nil)
40         end
41
42         return params
43 end
44
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)
48         local k, v
49         local n, enc = 1, {}
50         for k, v in pairs(tbl) do
51                 if type(v) == "table" then
52                         local i, v2
53                         for i, v2 in ipairs(v) do
54                                 if enc[1] then
55                                         enc[n] = "&"
56                                         n = n + 1
57                                 end
58
59                                 enc[n+0] = lhttp.urlencode(k)
60                                 enc[n+1] = "="
61                                 enc[n+2] = lhttp.urlencode(v2)
62                                 n = n + 3
63                         end
64                 else
65                         if enc[1] then
66                                 enc[n] = "&"
67                                 n = n + 1
68                         end
69
70                         enc[n+0] = lhttp.urlencode(k)
71                         enc[n+1] = "="
72                         enc[n+2] = lhttp.urlencode(v)
73                         n = n + 3
74                 end
75         end
76
77         return table.concat(enc, "")
78 end
79
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)
93
94         parser, err = lhttp.multipart_parser(msg.env.CONTENT_TYPE, function (what, buffer, length)
95                 if what == parser.PART_INIT then
96                         field = { }
97
98                 elseif what == parser.HEADER_NAME then
99                         header = buffer:lower()
100
101                 elseif what == parser.HEADER_VALUE and header then
102                         if header:lower() == "content-disposition" and
103                            lhttp.header_attribute(buffer, nil) == "form-data"
104                         then
105                                 field.name = lhttp.header_attribute(buffer, "name")
106                                 field.file = lhttp.header_attribute(buffer, "filename")
107                         end
108
109                         if field.headers then
110                                 field.headers[header] = buffer
111                         else
112                                 field.headers = { [header] = buffer }
113                         end
114
115                 elseif what == parser.PART_BEGIN then
116                         return not field.file
117
118                 elseif what == parser.PART_DATA and field.name and length > 0 then
119                         if field.file then
120                                 if file_cb then
121                                         file_cb(field, buffer, false)
122                                         msg.params[field.name] = msg.params[field.name] or field
123                                 else
124                                         if not field.fd then
125                                                 local ok, nx = pcall(require, "nixio")
126                                                 field.fd = ok and nx.mkstemp(field.name)
127                                         end
128
129                                         if field.fd then
130                                                 field.fd:write(buffer)
131                                                 msg.params[field.name] = msg.params[field.name] or field
132                                         end
133                                 end
134                         else
135                                 field.value = buffer
136                         end
137
138                 elseif what == parser.PART_END and field.name then
139                         if field.file and msg.params[field.name] then
140                                 if file_cb then
141                                         file_cb(field, "", true)
142                                 elseif field.fd then
143                                         field.fd:seek(0, "set")
144                                 end
145                         else
146                                 msg.params[field.name] = field.value or ""
147                         end
148
149                         field = nil
150
151                 elseif what == parser.ERROR then
152                         err = buffer
153                 end
154
155                 return true
156         end)
157
158         return ltn12.pump.all(src, function (chunk)
159                 len = len + (chunk and #chunk or 0)
160
161                 if maxlen and len > maxlen + 2 then
162                         return nil, "Message body size exceeds Content-Length"
163                 end
164
165                 if not parser or not parser:parse(chunk) then
166                         return nil, err
167                 end
168
169                 return true
170         end)
171 end
172
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)
179
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
188                         err = buffer
189                 end
190
191                 return true
192         end)
193
194         return ltn12.pump.all(src, function (chunk)
195                 len = len + (chunk and #chunk or 0)
196
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"
201                 end
202
203                 if not parser or not parser:parse(chunk) then
204                         return nil, err
205                 end
206
207                 return true
208         end)
209 end
210
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)
219
220         -- Is it multipart/mime ?
221         if msg.env.REQUEST_METHOD == "POST" and
222            ctype == "multipart/form-data"
223         then
224                 return mimedecode_message_body( src, msg, filecb )
225
226         -- Is it application/x-www-form-urlencoded ?
227         elseif msg.env.REQUEST_METHOD == "POST" and
228                ctype == "application/x-www-form-urlencoded"
229         then
230                 return urldecode_message_body( src, msg, filecb )
231
232
233         -- Unhandled encoding
234         -- If a file callback is given then feed it chunk by chunk, else
235         -- store whole buffer in message.content
236         else
237
238                 local sink
239
240                 -- If we have a file callback then feed it
241                 if type(filecb) == "function" then
242                         local meta = {
243                                 name = "raw",
244                                 encoding = msg.env.CONTENT_TYPE
245                         }
246                         sink = function( chunk )
247                                 if chunk then
248                                         return filecb(meta, chunk, false)
249                                 else
250                                         return filecb(meta, nil, true)
251                                 end
252                         end
253                 -- ... else append to .content
254                 else
255                         msg.content = ""
256                         msg.content_length = 0
257
258                         sink = function( chunk )
259                                 if chunk then
260                                         if ( msg.content_length + #chunk ) <= HTTP_MAX_CONTENT then
261                                                 msg.content        = msg.content        .. chunk
262                                                 msg.content_length = msg.content_length + #chunk
263                                                 return true
264                                         else
265                                                 return nil, "POST data exceeds maximum allowed length"
266                                         end
267                                 end
268                                 return true
269                         end
270                 end
271
272                 -- Pump data...
273                 while true do
274                         local ok, err = ltn12.pump.step( src, sink )
275
276                         if not ok and err then
277                                 return nil, err
278                         elseif not ok then -- eof
279                                 return true
280                         end
281                 end
282
283                 return true
284         end
285 end