diff options
author | Jo-Philipp Wich <jow@openwrt.org> | 2008-06-19 02:53:09 +0000 |
---|---|---|
committer | Jo-Philipp Wich <jow@openwrt.org> | 2008-06-19 02:53:09 +0000 |
commit | f712a1f2c284bfecff775ceff60c0e7a182ed114 (patch) | |
tree | 1ca09a178c1cafaac5048ce39f179b287c530266 /libs/web/luasrc/http/protocol.lua | |
parent | 304ce583c7f2ba53bdef1438368519aba5fef79d (diff) |
* luci/libs: moved http.protocol from libs/web to libs/httpd, rewrote http.protocol to rely on LTN12 chains
Diffstat (limited to 'libs/web/luasrc/http/protocol.lua')
-rw-r--r-- | libs/web/luasrc/http/protocol.lua | 572 |
1 files changed, 0 insertions, 572 deletions
diff --git a/libs/web/luasrc/http/protocol.lua b/libs/web/luasrc/http/protocol.lua deleted file mode 100644 index 6901291b9..000000000 --- a/libs/web/luasrc/http/protocol.lua +++ /dev/null @@ -1,572 +0,0 @@ ---[[ - -HTTP protocol implementation for LuCI -(c) 2008 Freifunk Leipzig / Jo-Philipp Wich <xm@leipzig.freifunk.net> - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -$Id$ - -]]-- - -module("luci.http.protocol", package.seeall) - -require("luci.util") - - -HTTP_MAX_CONTENT = 1024^2 -- 1 MB maximum content size -HTTP_MAX_READBUF = 1024 -- 1 kB read buffer size - -HTTP_DEFAULT_CTYPE = "text/html" -- default content type -HTTP_DEFAULT_VERSION = "1.0" -- HTTP default version - - --- Decode an urlencoded string. --- Returns the decoded value. -function urldecode( str ) - - local function __chrdec( hex ) - return string.char( tonumber( hex, 16 ) ) - end - - if type(str) == "string" then - str = str:gsub( "+", " " ):gsub( "%%([a-fA-F0-9][a-fA-F0-9])", __chrdec ) - end - - return str -end - - --- Extract and split urlencoded data pairs, separated bei either "&" or ";" from given url. --- Returns a table value with urldecoded values. -function urldecode_params( url ) - - local params = { } - - if url:find("?") then - url = url:gsub( "^.+%?([^?]+)", "%1" ) - end - - for i, pair in ipairs(luci.util.split( url, "[&;]+", nil, true )) do - - -- find key and value - local key = urldecode( pair:match("^([^=]+)") ) - local val = urldecode( pair:match("^[^=]+=(.+)$") ) - - -- store - if type(key) == "string" and key:len() > 0 then - if type(val) ~= "string" then val = "" end - - if not params[key] then - params[key] = val - elseif type(params[key]) ~= "table" then - params[key] = { params[key], val } - else - table.insert( params[key], val ) - end - end - end - - return params -end - - --- Encode given string in urlencoded format. --- Returns the encoded string. -function urlencode( str ) - - local function __chrenc( chr ) - return string.format( - "%%%02x", string.byte( chr ) - ) - end - - if type(str) == "string" then - str = str:gsub( - "([^a-zA-Z0-9$_%-%.+!*'(),])", - __chrenc - ) - end - - return str -end - - --- Encode given table to urlencoded string. --- Returns the encoded string. -function urlencode_params( tbl ) - local enc = "" - - for k, v in pairs(tbl) do - enc = enc .. ( enc and "&" or "" ) .. - urlencode(k) .. "=" .. - urlencode(v) - end - - return enc -end - - --- Decode MIME encoded data. --- Returns a table with decoded values. -function mimedecode( data, boundary, filecb ) - - local params = { } - - -- create a line reader - local reader = _linereader( data, HTTP_MAX_READBUF ) - - -- state variables - local in_part = false - local in_file = false - local in_fbeg = false - local in_size = true - - local filename - local buffer - local field - local clen = 0 - - -- try to read all mime parts - for line, eol in reader do - - -- update content length - clen = clen + line:len() - - if clen >= HTTP_MAX_CONTENT then - in_size = false - end - - -- when no boundary is given, try to find it - if not boundary then - boundary = line:match("^%-%-([^\r\n]+)\r?\n$") - end - - -- Got a valid boundary line or reached max allowed size. - if ( boundary and line:sub(1,2) == "--" and line:len() > #boundary + 2 and - line:sub( 3, 2 + #boundary ) == boundary ) or not in_size - then - -- Flush the data of the previous mime part. - -- When field and/or buffer are set to nil we should discard - -- the previous section entirely due to format violations. - if type(field) == "string" and field:len() > 0 and - type(buffer) == "string" - then - -- According to the rfc the \r\n preceeding a boundary - -- is assumed to be part of the boundary itself. - -- Since we are reading line by line here, this crlf - -- is part of the last line of our section content, - -- so strip it before storing the buffer. - buffer = buffer:gsub("\r?\n$","") - - -- If we're in a file part and a file callback has been provided - -- then do a final call and send eof. - if in_file and type(filecb) == "function" then - filecb( field, filename, buffer, true ) - params[field] = filename - - -- Store buffer. - else - params[field] = buffer - end - end - - -- Reset vars - buffer = "" - filename = nil - field = nil - in_file = false - - -- Abort here if we reached maximum allowed size - if not in_size then break end - - -- Do we got the last boundary? - if line:len() > #boundary + 4 and - line:sub( #boundary + 2, #boundary + 4 ) == "--" - then - -- No more processing - in_part = false - - -- It's a middle boundary - else - - -- Read headers - local hlen, headers = extract_headers( reader ) - - -- Check for valid headers - if headers['Content-Disposition'] then - - -- Got no content type header, assume content-type "text/plain" - if not headers['Content-Type'] then - headers['Content-Type'] = 'text/plain' - end - - -- Find field name - local hdrvals = luci.util.split( - headers['Content-Disposition'], '; ' - ) - - -- Valid form data part? - if hdrvals[1] == "form-data" and hdrvals[2]:match("^name=") then - - -- Store field identifier - field = hdrvals[2]:match('^name="(.+)"$') - - -- Do we got a file upload field? - if #hdrvals == 3 and hdrvals[3]:match("^filename=") then - in_file = true - if_fbeg = true - filename = hdrvals[3]:match('^filename="(.+)"$') - end - - -- Entering next part processing - in_part = true - end - end - end - - -- Processing content - elseif in_part then - - -- XXX: Would be really good to switch from line based to - -- buffered reading here. - - - -- If we're in a file part and a file callback has been provided - -- then call the callback and reset the buffer. - if in_file and type(filecb) == "function" then - - -- If we're not processing the first chunk, then call - if not in_fbeg then - filecb( field, filename, buffer, false ) - buffer = "" - - -- Clear in_fbeg flag after first run - else - in_fbeg = false - end - end - - -- Append date to buffer - buffer = buffer .. line - end - end - - return params -end - - --- Extract "magic", the first line of a http message. --- Returns the message type ("get", "post" or "response"), the requested uri --- if it is a valid http request or the status code if the line descripes a --- http response. For requests the third parameter is nil, for responses it --- contains the human readable status description. -function extract_magic( reader ) - - for line in reader do - -- Is it a request? - local method, uri = line:match("^([A-Z]+) ([^ ]+) HTTP/[01]%.[019]\r?\n$") - - -- Yup, it is - if method then - return method:lower(), uri, nil - - -- Is it a response? - else - local code, message = line:match("^HTTP/[01]%.[019] ([0-9]+) ([^\r\n]+)\r?\n$") - - -- Is a response - if code then - return "response", code + 0, message - - -- Can't handle it - else - return nil - end - end - end -end - - --- Extract headers from given string. --- Returns a table of extracted headers and the remainder of the parsed data. -function extract_headers( reader, tbl ) - - local headers = tbl or { } - local count = 0 - - -- Iterate line by line - for line in reader do - - -- Look for a valid header format - local hdr, val = line:match( "^([A-Z][A-Za-z0-9%-_]+): +([^\r\n]+)\r?\n$" ) - - if type(hdr) == "string" and hdr:len() > 0 and - type(val) == "string" and val:len() > 0 - then - count = count + line:len() - headers[hdr] = val - - elseif line:match("^\r?\n$") then - - return count + line:len(), headers - - else - -- junk data, don't add length - return count, headers - end - end - - return count, headers -end - - --- Parse a http message -function parse_message( data, filecb ) - - local reader = _linereader( data, HTTP_MAX_READBUF ) - local message = parse_message_header( reader ) - - if message then - parse_message_body( reader, message, filecb ) - end - - return message -end - - --- Parse a http message header -function parse_message_header( data ) - - -- Create a line reader - local reader = _linereader( data, HTTP_MAX_READBUF ) - local message = { } - - -- Try to extract magic - local method, arg1, arg2 = extract_magic( reader ) - - -- Does it looks like a valid message? - if method then - - message.request_method = method - message.status_code = arg2 and arg1 or 200 - message.status_message = arg2 or nil - message.request_uri = arg2 and nil or arg1 - - if method == "response" then - message.type = "response" - else - message.type = "request" - end - - -- Parse headers? - local hlen, hdrs = extract_headers( reader ) - - -- Valid headers? - if hlen > 2 and type(hdrs) == "table" then - - message.headers = hdrs - - -- Process get parameters - if ( method == "get" or method == "post" ) and - message.request_uri:match("?") - then - message.params = urldecode_params( message.request_uri ) - else - message.params = { } - end - - -- Populate common environment variables - message.env = { - CONTENT_LENGTH = hdrs['Content-Length']; - CONTENT_TYPE = hdrs['Content-Type']; - REQUEST_METHOD = message.request_method; - REQUEST_URI = message.request_uri; - SCRIPT_NAME = message.request_uri:gsub("?.+$",""); - SCRIPT_FILENAME = "" -- XXX implement me - } - - -- Populate HTTP_* environment variables - for i, hdr in ipairs( { - 'Accept', - 'Accept-Charset', - 'Accept-Encoding', - 'Accept-Language', - 'Connection', - 'Cookie', - 'Host', - 'Referer', - 'User-Agent', - } ) do - local var = 'HTTP_' .. hdr:upper():gsub("%-","_") - local val = hdrs[hdr] - - message.env[var] = val - end - - - return message - end - end -end - - --- Parse a http message body -function parse_message_body( reader, message, filecb ) - - if type(message) == "table" then - local env = message.env - - local clen = ( env.CONTENT_LENGTH or HTTP_MAX_CONTENT ) + 0 - - -- Process post method - if env.REQUEST_METHOD:lower() == "post" and env.CONTENT_TYPE then - - -- Is it multipart/form-data ? - if env.CONTENT_TYPE:match("^multipart/form%-data") then - - -- Read multipart/mime data - for k, v in pairs( mimedecode( - reader, - env.CONTENT_TYPE:match("boundary=(.+)"), - filecb - ) ) do - message.params[k] = v - end - - -- Is it x-www-form-urlencoded? - elseif env.CONTENT_TYPE:match('^application/x%-www%-form%-urlencoded') then - - -- Read post data - local post_data = "" - - for chunk, eol in reader do - - post_data = post_data .. chunk - - -- Abort on eol or if maximum allowed size or content length is reached - if eol or #post_data >= HTTP_MAX_CONTENT or #post_data > clen then - break - end - end - - -- Parse params - for k, v in pairs( urldecode_params( post_data ) ) do - message.params[k] = v - end - - -- Unhandled encoding - -- If a file callback is given then feed it line by line, else - -- store whole buffer in message.content - else - - local len = 0 - - for chunk in reader do - - len = len + #chunk - - -- We have a callback, feed it. - if type(filecb) == "function" then - - filecb( "_post", nil, chunk, false ) - - -- Append to .content buffer. - else - message.content = - type(message.content) == "string" - and message.content .. chunk - or chunk - end - - -- Abort if maximum allowed size or content length is reached - if len >= HTTP_MAX_CONTENT or len >= clen then - break - end - end - - -- Send eof to callback - if type(filecb) == "function" then - filecb( "_post", nil, "", true ) - end - end - end - end -end - - --- Wrap given object into a line read iterator -function _linereader( obj, bufsz ) - - bufsz = ( bufsz and bufsz >= 256 ) and bufsz or 256 - - local __read = function() return nil end - local __eof = function(x) return type(x) ~= "string" or #x == 0 end - - local _pos = 1 - local _buf = "" - local _eof = nil - - -- object is string - if type(obj) == "string" then - - __read = function() return obj:sub( _pos, _pos + bufsz - #_buf - 1 ) end - - -- object implements a receive() or read() function - elseif (type(obj) == "userdata" or type(obj) == "table") and ( type(obj.receive) == "function" or type(obj.read) == "function" ) then - - if type(obj.read) == "function" then - __read = function() return obj:read( bufsz - #_buf ) end - else - __read = function() return obj:receive( bufsz - #_buf ) end - end - - -- object is a function - elseif type(obj) == "function" then - - return obj - - -- no usable data type - else - - -- dummy iterator - return __read - end - - - -- generic block to line algorithm - return function() - if not _eof then - local buffer = __read() - - if __eof( buffer ) then - buffer = "" - end - - _pos = _pos + #buffer - buffer = _buf .. buffer - - local crlf, endpos = buffer:find("\r?\n") - - - if crlf then - _buf = buffer:sub( endpos + 1, #buffer ) - return buffer:sub( 1, endpos ), true - else - -- check for eof - _eof = __eof( buffer ) - - -- clear overflow buffer - _buf = "" - - return buffer, false - end - else - return nil - end - end -end |