* luci/libs: moved http.protocol from libs/web to libs/httpd, rewrote http.protocol...
[project/luci.git] / libs / web / luasrc / http / protocol.lua
diff --git a/libs/web/luasrc/http/protocol.lua b/libs/web/luasrc/http/protocol.lua
deleted file mode 100644 (file)
index 6901291..0000000
+++ /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