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)
18 local ltn12 = require("luci.ltn12")
20 HTTP_MAX_CONTENT = 1024*4 -- 4 kB maximum content size
21 HTTP_URLENC_MAXKEYLEN = 1024 -- maximum allowd size of urlencoded parameter names
24 -- Decode an urlencoded string.
25 -- Returns the decoded value.
26 function urldecode( str, no_plus )
28 local function __chrdec( hex )
29 return string.char( tonumber( hex, 16 ) )
32 if type(str) == "string" then
34 str = str:gsub( "+", " " )
37 str = str:gsub( "%%([a-fA-F0-9][a-fA-F0-9])", __chrdec )
44 -- Extract and split urlencoded data pairs, separated bei either "&" or ";" from given url.
45 -- Returns a table value with urldecoded values.
46 function urldecode_params( url, tbl )
48 local params = tbl or { }
51 url = url:gsub( "^.+%?([^?]+)", "%1" )
54 for pair in url:gmatch( "[^&;]+" ) do
57 local key = urldecode( pair:match("^([^=]+)") )
58 local val = urldecode( pair:match("^[^=]+=(.+)$") )
61 if type(key) == "string" and key:len() > 0 then
62 if type(val) ~= "string" then val = "" end
64 if not params[key] then
66 elseif type(params[key]) ~= "table" then
67 params[key] = { params[key], val }
69 table.insert( params[key], val )
78 -- Encode given string in urlencoded format.
79 -- Returns the encoded string.
80 function urlencode( str )
82 local function __chrenc( chr )
84 "%%%02x", string.byte( chr )
88 if type(str) == "string" then
90 "([^a-zA-Z0-9$_%-%.%+!*'(),])",
99 -- Encode given table to urlencoded string.
100 -- Returns the encoded string.
101 function urlencode_params( tbl )
104 for k, v in pairs(tbl) do
105 enc = enc .. ( enc and "&" or "" ) ..
106 urlencode(k) .. "=" ..
114 -- Table of our process states
115 local process_states = { }
117 -- Extract "magic", the first line of a http message.
118 -- Extracts the message type ("get", "post" or "response"), the requested uri
119 -- or the status code if the line descripes a http response.
120 process_states['magic'] = function( msg, chunk, err )
123 -- ignore empty lines before request
129 local method, uri, http_ver = chunk:match("^([A-Z]+) ([^ ]+) HTTP/([01]%.[019])$")
135 msg.request_method = method:lower()
136 msg.request_uri = uri
137 msg.http_version = tonumber( http_ver )
140 -- We're done, next state is header parsing
141 return true, function( chunk )
142 return process_states['headers']( msg, chunk )
148 local http_ver, code, message = chunk:match("^HTTP/([01]%.[019]) ([0-9]+) ([^\r\n]+)$")
153 msg.type = "response"
154 msg.status_code = code
155 msg.status_message = message
156 msg.http_version = tonumber( http_ver )
159 -- We're done, next state is header parsing
160 return true, function( chunk )
161 return process_states['headers']( msg, chunk )
168 return nil, "Invalid HTTP message magic"
172 -- Extract headers from given string.
173 process_states['headers'] = function( msg, chunk )
177 -- Look for a valid header format
178 local hdr, val = chunk:match( "^([A-Z][A-Za-z0-9%-_]+): +(.+)$" )
180 if type(hdr) == "string" and hdr:len() > 0 and
181 type(val) == "string" and val:len() > 0
183 msg.headers[hdr] = val
185 -- Valid header line, proceed
188 elseif #chunk == 0 then
189 -- Empty line, we won't accept data anymore
193 return nil, "Invalid HTTP header received"
196 return nil, "Unexpected EOF"
201 -- Find first MIME boundary
202 process_states['mime-init'] = function( msg, chunk, filecb )
205 if #chunk >= #msg.mime_boundary + 2 then
206 local boundary = chunk:sub( 1, #msg.mime_boundary + 4 )
208 if boundary == "--" .. msg.mime_boundary .. "\r\n" then
210 -- Store remaining data in buffer
211 msg._mimebuffer = chunk:sub( #msg.mime_boundary + 5, #chunk )
213 -- Switch to header processing state
214 return true, function( chunk )
215 return process_states['mime-headers']( msg, chunk, filecb )
218 return nil, "Invalid MIME boundary"
224 return nil, "Unexpected EOF"
229 -- Read MIME part headers
230 process_states['mime-headers'] = function( msg, chunk, filecb )
234 -- Combine look-behind buffer with current chunk
235 chunk = msg._mimebuffer .. chunk
237 if not msg._mimeheaders then
238 msg._mimeheaders = { }
241 local function __storehdr( k, v )
242 msg._mimeheaders[k] = v
246 -- Read all header lines
247 local ok, count = 1, 0
249 chunk, ok = chunk:gsub( "^([A-Z][A-Za-z0-9%-_]+): +([^\r\n]+)\r\n", __storehdr )
253 -- Headers processed, check for empty line
254 chunk, ok = chunk:gsub( "^\r\n", "" )
256 -- Store remaining buffer contents
257 msg._mimebuffer = chunk
262 -- When no Content-Type header is given assume text/plain
263 if not msg._mimeheaders['Content-Type'] then
264 msg._mimeheaders['Content-Type'] = 'text/plain'
267 -- Check Content-Disposition
268 if msg._mimeheaders['Content-Disposition'] then
269 -- Check for "form-data" token
270 if msg._mimeheaders['Content-Disposition']:match("^form%-data; ") then
271 -- Check for field name, filename
272 local field = msg._mimeheaders['Content-Disposition']:match('name="(.-)"')
273 local file = msg._mimeheaders['Content-Disposition']:match('filename="(.+)"$')
275 -- Is a file field and we have a callback
276 if file and filecb then
277 msg.params[field] = file
278 msg._mimecallback = function(chunk,eof)
282 headers = msg._mimeheaders
286 -- Treat as form field
288 msg.params[field] = ""
289 msg._mimecallback = function(chunk,eof)
290 msg.params[field] = msg.params[field] .. chunk
294 -- Header was valid, continue with mime-data
295 return true, function( chunk )
296 return process_states['mime-data']( msg, chunk, filecb )
299 -- Unknown Content-Disposition, abort
300 return nil, "Unexpected Content-Disposition MIME section header"
303 -- Content-Disposition is required, abort without
304 return nil, "Missing Content-Disposition MIME section header"
307 -- We parsed no headers yet and buffer is almost empty
308 elseif count > 0 or #chunk < 128 then
309 -- Keep feeding me with chunks
313 -- Buffer looks like garbage
314 return nil, "Malformed MIME section header"
316 return nil, "Unexpected EOF"
321 -- Read MIME part data
322 process_states['mime-data'] = function( msg, chunk, filecb )
326 -- Combine look-behind buffer with current chunk
327 local buffer = msg._mimebuffer .. chunk
329 -- Look for MIME boundary
330 local spos, epos = buffer:find( "\r\n--" .. msg.mime_boundary .. "\r\n", 1, true )
334 msg._mimecallback( buffer:sub( 1, spos - 1 ), true )
337 msg._mimebuffer = buffer:sub( epos + 1, #buffer )
339 -- Next state is mime-header processing
340 return true, function( chunk )
341 return process_states['mime-headers']( msg, chunk, filecb )
345 local spos, epos = buffer:find( "\r\n--" .. msg.mime_boundary .. "--\r\n", 1, true )
349 msg._mimecallback( buffer:sub( 1, spos - 1 ), true )
351 -- We processed the final MIME boundary, cleanup
352 msg._mimebuffer = nil
353 msg._mimeheaders = nil
354 msg._mimecallback = nil
356 -- We won't accept data anymore
359 -- We're somewhere within a data section and our buffer is full
360 if #buffer > #chunk then
361 -- Flush buffered data
362 msg._mimecallback( buffer:sub( 1, #buffer - #chunk ), false )
365 msg._mimebuffer = buffer:sub( #buffer - #chunk + 1, #buffer )
367 -- Buffer is not full yet, append new data
369 msg._mimebuffer = buffer
377 return nil, "Unexpected EOF"
382 -- Init urldecoding stream
383 process_states['urldecode-init'] = function( msg, chunk, filecb )
387 -- Check for Content-Length
388 if msg.env.CONTENT_LENGTH then
389 msg.content_length = tonumber(msg.env.CONTENT_LENGTH)
391 if msg.content_length <= HTTP_MAX_CONTENT then
393 msg._urldecbuffer = chunk
394 msg._urldeclength = 0
396 -- Switch to urldecode-key state
397 return true, function(chunk)
398 return process_states['urldecode-key']( msg, chunk, filecb )
401 return nil, "Request exceeds maximum allowed size"
404 return nil, "Missing Content-Length header"
407 return nil, "Unexpected EOF"
412 -- Process urldecoding stream, read and validate parameter key
413 process_states['urldecode-key'] = function( msg, chunk, filecb )
416 -- Prevent oversized requests
417 if msg._urldeclength >= msg.content_length then
418 return nil, "Request exceeds maximum allowed size"
421 -- Combine look-behind buffer with current chunk
422 local buffer = msg._urldecbuffer .. chunk
423 local spos, epos = buffer:find("=")
428 -- Check that key doesn't exceed maximum allowed key length
429 if ( spos - 1 ) <= HTTP_URLENC_MAXKEYLEN then
430 local key = urldecode( buffer:sub( 1, spos - 1 ) )
434 msg._urldeclength = msg._urldeclength + epos
435 msg._urldecbuffer = buffer:sub( epos + 1, #buffer )
437 -- Use file callback or store values inside msg.params
439 msg._urldeccallback = function( chunk, eof )
440 filecb( field, chunk, eof )
443 msg._urldeccallback = function( chunk, eof )
444 msg.params[key] = msg.params[key] .. chunk
446 -- FIXME: Use a filter
448 msg.params[key] = urldecode( msg.params[key] )
453 -- Proceed with urldecode-value state
454 return true, function( chunk )
455 return process_states['urldecode-value']( msg, chunk, filecb )
458 return nil, "POST parameter exceeds maximum allowed length"
461 return nil, "POST data exceeds maximum allowed length"
464 return nil, "Unexpected EOF"
469 -- Process urldecoding stream, read parameter value
470 process_states['urldecode-value'] = function( msg, chunk, filecb )
474 -- Combine look-behind buffer with current chunk
475 local buffer = msg._urldecbuffer .. chunk
479 -- Compare processed length
480 if msg._urldeclength == msg.content_length then
482 msg._urldeclength = nil
483 msg._urldecbuffer = nil
484 msg._urldeccallback = nil
486 -- We won't accept data anymore
489 return nil, "Content-Length mismatch"
493 -- Check for end of value
494 local spos, epos = buffer:find("[&;]")
497 -- Flush buffer, send eof
498 msg._urldeccallback( buffer:sub( 1, spos - 1 ), true )
499 msg._urldecbuffer = buffer:sub( epos + 1, #buffer )
500 msg._urldeclength = msg._urldeclength + epos
502 -- Back to urldecode-key state
503 return true, function( chunk )
504 return process_states['urldecode-key']( msg, chunk, filecb )
507 -- We're somewhere within a data section and our buffer is full
508 if #buffer > #chunk then
509 -- Flush buffered data
510 msg._urldeccallback( buffer:sub( 1, #buffer - #chunk ), false )
513 msg._urldeclength = msg._urldeclength + #buffer - #chunk
514 msg._urldecbuffer = buffer:sub( #buffer - #chunk + 1, #buffer )
516 -- Buffer is not full yet, append new data
518 msg._urldecbuffer = buffer
526 msg._urldeccallback( "", true )
532 -- Creates a header source from a given socket
533 function header_source( sock )
534 return ltn12.source.simplify( function()
536 local chunk, err, part = sock:receive("*l")
540 if err ~= "timeout" then
542 and "Line exceeds maximum allowed length"
549 elseif chunk ~= nil then
552 chunk = chunk:gsub("\r$","")
560 -- Decode MIME encoded data.
561 function mimedecode_message_body( source, msg, filecb )
563 -- Find mime boundary
564 if msg and msg.env.CONTENT_TYPE then
566 local bound = msg.env.CONTENT_TYPE:match("^multipart/form%-data; boundary=(.+)")
569 msg.mime_boundary = bound
571 return nil, "No MIME boundary found or invalid content type given"
575 -- Create an initial LTN12 sink
576 -- The whole MIME parsing process is implemented as fancy sink, sinks replace themself
577 -- depending on current processing state (init, header, data). Return the initial state.
578 local sink = ltn12.sink.simplify(
580 return process_states['mime-init']( msg, chunk, filecb )
584 -- Create a throttling LTN12 source
585 -- Frequent state switching in the mime parsing process leads to unwanted buffer aggregation.
586 -- This source checks wheather there's still data in our internal read buffer and returns an
587 -- empty string if there's already enough data in the processing queue. If the internal buffer
588 -- runs empty we're calling the original source to get the next chunk of data.
589 local tsrc = function()
591 -- XXX: we schould propably keep the maximum buffer size in sync with
592 -- the blocksize of our original source... but doesn't really matter
593 if msg._mimebuffer ~= null and #msg._mimebuffer > 256 then
600 -- Pump input data...
603 local ok, err = ltn12.pump.step( tsrc, sink )
606 if not ok and err then
617 -- Decode urlencoded data.
618 function urldecode_message_body( source, msg )
620 -- Create an initial LTN12 sink
621 -- Return the initial state.
622 local sink = ltn12.sink.simplify(
624 return process_states['urldecode-init']( msg, chunk )
628 -- Create a throttling LTN12 source
629 -- See explaination in mimedecode_message_body().
630 local tsrc = function()
631 if msg._urldecbuffer ~= null and #msg._urldecbuffer > 0 then
638 -- Pump input data...
641 local ok, err = ltn12.pump.step( tsrc, sink )
644 if not ok and err then
655 -- Parse a http message header
656 function parse_message_header( source )
661 local sink = ltn12.sink.simplify(
663 return process_states['magic']( msg, chunk )
667 -- Pump input data...
671 ok, err = ltn12.pump.step( source, sink )
674 if not ok and err then
680 -- Process get parameters
681 if ( msg.request_method == "get" or msg.request_method == "post" ) and
682 msg.request_uri:match("?")
684 msg.params = urldecode_params( msg.request_uri )
689 -- Populate common environment variables
691 CONTENT_LENGTH = msg.headers['Content-Length'];
692 CONTENT_TYPE = msg.headers['Content-Type'];
693 REQUEST_METHOD = msg.request_method:upper();
694 REQUEST_URI = msg.request_uri;
695 SCRIPT_NAME = msg.request_uri:gsub("?.+$","");
696 SCRIPT_FILENAME = ""; -- XXX implement me
697 SERVER_PROTOCOL = "HTTP/" .. string.format("%.1f", msg.http_version)
700 -- Populate HTTP_* environment variables
701 for i, hdr in ipairs( {
712 local var = 'HTTP_' .. hdr:upper():gsub("%-","_")
713 local val = msg.headers[hdr]
724 -- Parse a http message body
725 function parse_message_body( source, msg, filecb )
726 -- Is it multipart/mime ?
727 if msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
728 msg.env.CONTENT_TYPE:match("^multipart/form%-data")
731 return mimedecode_message_body( source, msg, filecb )
733 -- Is it application/x-www-form-urlencoded ?
734 elseif msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
735 msg.env.CONTENT_TYPE == "application/x-www-form-urlencoded"
737 return urldecode_message_body( source, msg, filecb )
740 -- Unhandled encoding
741 -- If a file callback is given then feed it chunk by chunk, else
742 -- store whole buffer in message.content
747 -- If we have a file callback then feed it
748 if type(filecb) == "function" then
751 -- ... else append to .content
754 msg.content_length = 0
756 sink = function( chunk )
757 if ( msg.content_length + #chunk ) <= HTTP_MAX_CONTENT then
759 msg.content = msg.content .. chunk
760 msg.content_length = msg.content_length + #chunk
764 return nil, "POST data exceeds maximum allowed length"
771 local ok, err = ltn12.pump.step( source, sink )
773 if not ok and err then
785 [301] = "Moved Permanently",
786 [304] = "Not Modified",
787 [400] = "Bad Request",
790 [405] = "Method Not Allowed",
791 [411] = "Length Required",
792 [412] = "Precondition Failed",
793 [500] = "Internal Server Error",
794 [503] = "Server Unavailable",