3 HTTP protocol implementation for LuCI
4 (c) 2008 Freifunk Leipzig / Jo-Philipp Wich <xm@leipzig.freifunk.net>
6 Licensed under the Apache License, Version 2.0 (the "License");
7 you may not use this file except in compliance with the License.
8 You may obtain a copy of the License at
10 http://www.apache.org/licenses/LICENSE-2.0
16 module("luci.http.protocol", package.seeall)
21 HTTP_MAX_CONTENT = 1024*4 -- 4 kB maximum content size
22 HTTP_URLENC_MAXKEYLEN = 1024 -- maximum allowd size of urlencoded parameter names
25 -- Decode an urlencoded string.
26 -- Returns the decoded value.
27 function urldecode( str )
29 local function __chrdec( hex )
30 return string.char( tonumber( hex, 16 ) )
33 if type(str) == "string" then
34 str = str:gsub( "+", " " ):gsub( "%%([a-fA-F0-9][a-fA-F0-9])", __chrdec )
41 -- Extract and split urlencoded data pairs, separated bei either "&" or ";" from given url.
42 -- Returns a table value with urldecoded values.
43 function urldecode_params( url, tbl )
45 local params = tbl or { }
48 url = url:gsub( "^.+%?([^?]+)", "%1" )
51 for i, pair in ipairs(luci.util.split( url, "[&;]+", nil, true )) do
54 local key = urldecode( pair:match("^([^=]+)") )
55 local val = urldecode( pair:match("^[^=]+=(.+)$") )
58 if type(key) == "string" and key:len() > 0 then
59 if type(val) ~= "string" then val = "" end
61 if not params[key] then
63 elseif type(params[key]) ~= "table" then
64 params[key] = { params[key], val }
66 table.insert( params[key], val )
75 -- Encode given string in urlencoded format.
76 -- Returns the encoded string.
77 function urlencode( str )
79 local function __chrenc( chr )
81 "%%%02x", string.byte( chr )
85 if type(str) == "string" then
87 "([^a-zA-Z0-9$_%-%.+!*'(),])",
96 -- Encode given table to urlencoded string.
97 -- Returns the encoded string.
98 function urlencode_params( tbl )
101 for k, v in pairs(tbl) do
102 enc = enc .. ( enc and "&" or "" ) ..
103 urlencode(k) .. "=" ..
111 -- Table of our process states
112 local process_states = { }
114 -- Extract "magic", the first line of a http message.
115 -- Extracts the message type ("get", "post" or "response"), the requested uri
116 -- or the status code if the line descripes a http response.
117 process_states['magic'] = function( msg, chunk )
122 local method, uri, http_ver = chunk:match("^([A-Z]+) ([^ ]+) HTTP/([01]%.[019])$")
128 msg.request_method = method:lower()
129 msg.request_uri = uri
130 msg.http_version = http_ver
133 -- We're done, next state is header parsing
134 return true, function( chunk )
135 return process_states['headers']( msg, chunk )
141 local http_ver, code, message = chunk:match("^HTTP/([01]%.[019]) ([0-9]+) ([^\r\n]+)$")
146 msg.type = "response"
147 msg.status_code = code
148 msg.status_message = message
149 msg.http_version = http_ver
152 -- We're done, next state is header parsing
153 return true, function( chunk )
154 return process_states['headers']( msg, chunk )
161 return nil, "Invalid HTTP message magic"
165 -- Extract headers from given string.
166 process_states['headers'] = function( msg, chunk )
170 -- Look for a valid header format
171 local hdr, val = chunk:match( "^([A-Z][A-Za-z0-9%-_]+): +(.+)$" )
173 if type(hdr) == "string" and hdr:len() > 0 and
174 type(val) == "string" and val:len() > 0
176 msg.headers[hdr] = val
178 -- Valid header line, proceed
181 elseif #chunk == 0 then
182 -- Empty line, we won't accept data anymore
186 return nil, "Invalid HTTP header received"
189 return nil, "Unexpected EOF"
194 -- Find first MIME boundary
195 process_states['mime-init'] = function( msg, chunk, filecb )
198 if #chunk >= #msg.mime_boundary + 2 then
199 local boundary = chunk:sub( 1, #msg.mime_boundary + 4 )
201 if boundary == "--" .. msg.mime_boundary .. "\r\n" then
203 -- Store remaining data in buffer
204 msg._mimebuffer = chunk:sub( #msg.mime_boundary + 5, #chunk )
206 -- Switch to header processing state
207 return true, function( chunk )
208 return process_states['mime-headers']( msg, chunk, filecb )
211 return nil, "Invalid MIME boundary"
217 return nil, "Unexpected EOF"
222 -- Read MIME part headers
223 process_states['mime-headers'] = function( msg, chunk, filecb )
227 -- Combine look-behind buffer with current chunk
228 chunk = msg._mimebuffer .. chunk
230 if not msg._mimeheaders then
231 msg._mimeheaders = { }
234 local function __storehdr( k, v )
235 msg._mimeheaders[k] = v
239 -- Read all header lines
240 local ok, count = 1, 0
242 chunk, ok = chunk:gsub( "^([A-Z][A-Za-z0-9%-_]+): +([^\r\n]+)\r\n", __storehdr )
246 -- Headers processed, check for empty line
247 chunk, ok = chunk:gsub( "^\r\n", "" )
249 -- Store remaining buffer contents
250 msg._mimebuffer = chunk
255 -- When no Content-Type header is given assume text/plain
256 if not msg._mimeheaders['Content-Type'] then
257 msg._mimeheaders['Content-Type'] = 'text/plain'
260 -- Check Content-Disposition
261 if msg._mimeheaders['Content-Disposition'] then
262 -- Check for "form-data" token
263 if msg._mimeheaders['Content-Disposition']:match("^form%-data; ") then
264 -- Check for field name, filename
265 local field = msg._mimeheaders['Content-Disposition']:match('name="(.-)"')
266 local file = msg._mimeheaders['Content-Disposition']:match('filename="(.+)"$')
268 -- Is a file field and we have a callback
269 if file and filecb then
270 msg.params[field] = file
271 msg._mimecallback = function(chunk,eof)
275 headers = msg._mimeheaders
279 -- Treat as form field
281 msg.params[field] = ""
282 msg._mimecallback = function(chunk,eof)
283 msg.params[field] = msg.params[field] .. chunk
287 -- Header was valid, continue with mime-data
288 return true, function( chunk )
289 return process_states['mime-data']( msg, chunk, filecb )
292 -- Unknown Content-Disposition, abort
293 return nil, "Unexpected Content-Disposition MIME section header"
296 -- Content-Disposition is required, abort without
297 return nil, "Missing Content-Disposition MIME section header"
300 -- We parsed no headers yet and buffer is almost empty
301 elseif count > 0 or #chunk < 128 then
302 -- Keep feeding me with chunks
306 -- Buffer looks like garbage
307 return nil, "Malformed MIME section header"
309 return nil, "Unexpected EOF"
314 -- Read MIME part data
315 process_states['mime-data'] = function( msg, chunk, filecb )
319 -- Combine look-behind buffer with current chunk
320 local buffer = msg._mimebuffer .. chunk
322 -- Look for MIME boundary
323 local spos, epos = buffer:find( "\r\n--" .. msg.mime_boundary .. "\r\n", 1, true )
327 msg._mimecallback( buffer:sub( 1, spos - 1 ), true )
330 msg._mimebuffer = buffer:sub( epos + 1, #buffer )
332 -- Next state is mime-header processing
333 return true, function( chunk )
334 return process_states['mime-headers']( msg, chunk, filecb )
338 local spos, epos = buffer:find( "\r\n--" .. msg.mime_boundary .. "--\r\n", 1, true )
342 msg._mimecallback( buffer:sub( 1, spos - 1 ), true )
344 -- We processed the final MIME boundary, cleanup
345 msg._mimebuffer = nil
346 msg._mimeheaders = nil
347 msg._mimecallback = nil
349 -- We won't accept data anymore
352 -- We're somewhere within a data section and our buffer is full
353 if #buffer > #chunk then
354 -- Flush buffered data
355 msg._mimecallback( buffer:sub( 1, #buffer - #chunk ), false )
358 msg._mimebuffer = buffer:sub( #buffer - #chunk + 1, #buffer )
360 -- Buffer is not full yet, append new data
362 msg._mimebuffer = buffer
370 return nil, "Unexpected EOF"
375 -- Init urldecoding stream
376 process_states['urldecode-init'] = function( msg, chunk, filecb )
380 -- Check for Content-Length
381 if msg.headers['Content-Length'] then
382 msg.content_length = tonumber(msg.headers['Content-Length'])
384 if msg.content_length <= HTTP_MAX_CONTENT then
386 msg._urldecbuffer = chunk
387 msg._urldeclength = 0
389 -- Switch to urldecode-key state
390 return true, function(chunk)
391 return process_states['urldecode-key']( msg, chunk, filecb )
394 return nil, "Request exceeds maximum allowed size"
397 return nil, "Missing Content-Length header"
400 return nil, "Unexpected EOF"
405 -- Process urldecoding stream, read and validate parameter key
406 process_states['urldecode-key'] = function( msg, chunk, filecb )
410 -- Prevent oversized requests
411 if msg._urldeclength >= msg.content_length then
412 return nil, "Request exceeds maximum allowed size"
415 -- Combine look-behind buffer with current chunk
416 local buffer = msg._urldecbuffer .. chunk
417 local spos, epos = buffer:find("=")
422 -- Check that key doesn't exceed maximum allowed key length
423 if ( spos - 1 ) <= HTTP_URLENC_MAXKEYLEN then
424 local key = urldecode( buffer:sub( 1, spos - 1 ) )
428 msg._urldeclength = msg._urldeclength + epos
429 msg._urldecbuffer = buffer:sub( epos + 1, #buffer )
431 -- Use file callback or store values inside msg.params
433 msg._urldeccallback = function( chunk, eof )
434 filecb( field, chunk, eof )
437 msg._urldeccallback = function( chunk, eof )
438 msg.params[key] = msg.params[key] .. chunk
442 -- Proceed with urldecode-value state
443 return true, function( chunk )
444 return process_states['urldecode-value']( msg, chunk, filecb )
447 return nil, "POST parameter exceeds maximum allowed length"
450 return nil, "POST data exceeds maximum allowed length"
453 return nil, "Unexpected EOF"
458 -- Process urldecoding stream, read parameter value
459 process_states['urldecode-value'] = function( msg, chunk, filecb )
463 -- Combine look-behind buffer with current chunk
464 local buffer = msg._urldecbuffer .. chunk
468 -- Compare processed length
469 if msg._urldeclength == msg.content_length then
471 msg._urldeclength = nil
472 msg._urldecbuffer = nil
473 msg._urldeccallback = nil
475 -- We won't accept data anymore
478 return nil, "Content-Length mismatch"
482 -- Check for end of value
483 local spos, epos = buffer:find("[&;]")
486 -- Flush buffer, send eof
487 msg._urldeccallback( buffer:sub( 1, spos - 1 ), true )
488 msg._urldecbuffer = buffer:sub( epos + 1, #buffer )
489 msg._urldeclength = msg._urldeclength + epos
491 -- Back to urldecode-key state
492 return true, function( chunk )
493 return process_states['urldecode-key']( msg, chunk, filecb )
496 -- We're somewhere within a data section and our buffer is full
497 if #buffer > #chunk then
498 -- Flush buffered data
499 msg._urldeccallback( buffer:sub( 1, #buffer - #chunk ), false )
502 msg._urldeclength = msg._urldeclength + #buffer - #chunk
503 msg._urldecbuffer = buffer:sub( #buffer - #chunk + 1, #buffer )
505 -- Buffer is not full yet, append new data
507 msg._urldecbuffer = buffer
514 return nil, "Unexpected EOF"
519 -- Decode MIME encoded data.
520 function mimedecode_message_body( source, msg, filecb )
522 -- Find mime boundary
523 if msg and msg.headers['Content-Type'] then
525 local bound = msg.headers['Content-Type']:match("^multipart/form%-data; boundary=(.+)")
528 msg.mime_boundary = bound
530 return nil, "No MIME boundary found or invalid content type given"
534 -- Create an initial LTN12 sink
535 -- The whole MIME parsing process is implemented as fancy sink, sinks replace themself
536 -- depending on current processing state (init, header, data). Return the initial state.
537 local sink = ltn12.sink.simplify(
539 return process_states['mime-init']( msg, chunk, filecb )
543 -- Create a throttling LTN12 source
544 -- Frequent state switching in the mime parsing process leads to unwanted buffer aggregation.
545 -- This source checks wheather there's still data in our internal read buffer and returns an
546 -- empty string if there's already enough data in the processing queue. If the internal buffer
547 -- runs empty we're calling the original source to get the next chunk of data.
548 local tsrc = function()
550 -- XXX: we schould propably keep the maximum buffer size in sync with
551 -- the blocksize of our original source... but doesn't really matter
552 if msg._mimebuffer ~= null and #msg._mimebuffer > 256 then
559 -- Pump input data...
562 local ok, err = ltn12.pump.step( tsrc, sink )
565 if not ok and err then
576 -- Decode urlencoded data.
577 function urldecode_message_body( source, msg )
579 -- Create an initial LTN12 sink
580 -- Return the initial state.
581 local sink = ltn12.sink.simplify(
583 return process_states['urldecode-init']( msg, chunk )
587 -- Create a throttling LTN12 source
588 -- See explaination in mimedecode_message_body().
589 local tsrc = function()
590 if msg._urldecbuffer ~= null and #msg._urldecbuffer > 0 then
597 -- Pump input data...
600 local ok, err = ltn12.pump.step( tsrc, sink )
603 if not ok and err then
614 -- Parse a http message
615 function parse_message( data, filecb )
617 local reader = _linereader( data, HTTP_MAX_READBUF )
618 local message = parse_message_header( reader )
621 parse_message_body( reader, message, filecb )
628 -- Parse a http message header
629 function parse_message_header( source )
634 local sink = ltn12.sink.simplify(
636 return process_states['magic']( msg, chunk )
640 -- Pump input data...
644 ok, err = ltn12.pump.step( source, sink )
647 if not ok and err then
653 -- Process get parameters
654 if ( msg.request_method == "get" or msg.request_method == "post" ) and
655 msg.request_uri:match("?")
657 msg.params = urldecode_params( msg.request_uri )
662 -- Populate common environment variables
664 CONTENT_LENGTH = msg.headers['Content-Length'];
665 CONTENT_TYPE = msg.headers['Content-Type'];
666 REQUEST_METHOD = msg.request_method:upper();
667 REQUEST_URI = msg.request_uri;
668 SCRIPT_NAME = msg.request_uri:gsub("?.+$","");
669 SCRIPT_FILENAME = "" -- XXX implement me
672 -- Populate HTTP_* environment variables
673 for i, hdr in ipairs( {
684 local var = 'HTTP_' .. hdr:upper():gsub("%-","_")
685 local val = msg.headers[hdr]
696 -- Parse a http message body
697 function parse_message_body( source, msg, filecb )
699 -- Is it multipart/mime ?
700 if msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
701 msg.env.CONTENT_TYPE:match("^multipart/form%-data")
704 return mimedecode_message_body( source, msg, filecb )
706 -- Is it application/x-www-form-urlencoded ?
707 elseif msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
708 msg.env.CONTENT_TYPE == "application/x-www-form-urlencoded"
711 return urldecode_message_body( source, msg, filecb )
713 -- Unhandled encoding
714 -- If a file callback is given then feed it line by line, else
715 -- store whole buffer in message.content
721 -- If we have a file callback then feed it
722 if type(filecb) == "function" then
725 -- ... else append to .content
728 msg.content_length = 0
730 sink = function( chunk )
731 if ( msg.content_length ) + #chunk <= HTTP_MAX_CONTENT then
733 msg.content = msg.content .. chunk
734 msg.content_length = msg.content_length + #chunk
738 return nil, "POST data exceeds maximum allowed length"
745 local ok, err = ltn12.pump.step( source, sink )
747 if not ok and err then