summaryrefslogtreecommitdiffhomepage
path: root/libs/httpd/luasrc
diff options
context:
space:
mode:
Diffstat (limited to 'libs/httpd/luasrc')
-rw-r--r--libs/httpd/luasrc/http/protocol.lua754
1 files changed, 0 insertions, 754 deletions
diff --git a/libs/httpd/luasrc/http/protocol.lua b/libs/httpd/luasrc/http/protocol.lua
deleted file mode 100644
index 01d3128b2..000000000
--- a/libs/httpd/luasrc/http/protocol.lua
+++ /dev/null
@@ -1,754 +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("ltn12")
-require("luci.util")
-
-HTTP_MAX_CONTENT = 1024*4 -- 4 kB maximum content size
-HTTP_URLENC_MAXKEYLEN = 1024 -- maximum allowd size of urlencoded parameter names
-
-
--- 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, tbl )
-
- local params = tbl or { }
-
- 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
-
-
--- Table of our process states
-local process_states = { }
-
--- Extract "magic", the first line of a http message.
--- Extracts the message type ("get", "post" or "response"), the requested uri
--- or the status code if the line descripes a http response.
-process_states['magic'] = function( msg, chunk )
-
- if chunk ~= nil then
-
- -- Is it a request?
- local method, uri, http_ver = chunk:match("^([A-Z]+) ([^ ]+) HTTP/([01]%.[019])$")
-
- -- Yup, it is
- if method then
-
- msg.type = "request"
- msg.request_method = method:lower()
- msg.request_uri = uri
- msg.http_version = http_ver
- msg.headers = { }
-
- -- We're done, next state is header parsing
- return true, function( chunk )
- return process_states['headers']( msg, chunk )
- end
-
- -- Is it a response?
- else
-
- local http_ver, code, message = chunk:match("^HTTP/([01]%.[019]) ([0-9]+) ([^\r\n]+)$")
-
- -- Is a response
- if code then
-
- msg.type = "response"
- msg.status_code = code
- msg.status_message = message
- msg.http_version = http_ver
- msg.headers = { }
-
- -- We're done, next state is header parsing
- return true, function( chunk )
- return process_states['headers']( msg, chunk )
- end
- end
- end
- end
-
- -- Can't handle it
- return nil, "Invalid HTTP message magic"
-end
-
-
--- Extract headers from given string.
-process_states['headers'] = function( msg, chunk )
-
- if chunk ~= nil then
-
- -- Look for a valid header format
- local hdr, val = chunk:match( "^([A-Z][A-Za-z0-9%-_]+): +(.+)$" )
-
- if type(hdr) == "string" and hdr:len() > 0 and
- type(val) == "string" and val:len() > 0
- then
- msg.headers[hdr] = val
-
- -- Valid header line, proceed
- return true, nil
-
- elseif #chunk == 0 then
- -- Empty line, we won't accept data anymore
- return false, nil
- else
- -- Junk data
- return nil, "Invalid HTTP header received"
- end
- else
- return nil, "Unexpected EOF"
- end
-end
-
-
--- Find first MIME boundary
-process_states['mime-init'] = function( msg, chunk, filecb )
-
- if chunk ~= nil then
- if #chunk >= #msg.mime_boundary + 2 then
- local boundary = chunk:sub( 1, #msg.mime_boundary + 4 )
-
- if boundary == "--" .. msg.mime_boundary .. "\r\n" then
-
- -- Store remaining data in buffer
- msg._mimebuffer = chunk:sub( #msg.mime_boundary + 5, #chunk )
-
- -- Switch to header processing state
- return true, function( chunk )
- return process_states['mime-headers']( msg, chunk, filecb )
- end
- else
- return nil, "Invalid MIME boundary"
- end
- else
- return true
- end
- else
- return nil, "Unexpected EOF"
- end
-end
-
-
--- Read MIME part headers
-process_states['mime-headers'] = function( msg, chunk, filecb )
-
- if chunk ~= nil then
-
- -- Combine look-behind buffer with current chunk
- chunk = msg._mimebuffer .. chunk
-
- if not msg._mimeheaders then
- msg._mimeheaders = { }
- end
-
- local function __storehdr( k, v )
- msg._mimeheaders[k] = v
- return ""
- end
-
- -- Read all header lines
- local ok, count = 1, 0
- while ok > 0 do
- chunk, ok = chunk:gsub( "^([A-Z][A-Za-z0-9%-_]+): +([^\r\n]+)\r\n", __storehdr )
- count = count + ok
- end
-
- -- Headers processed, check for empty line
- chunk, ok = chunk:gsub( "^\r\n", "" )
-
- -- Store remaining buffer contents
- msg._mimebuffer = chunk
-
- -- End of headers
- if ok > 0 then
-
- -- When no Content-Type header is given assume text/plain
- if not msg._mimeheaders['Content-Type'] then
- msg._mimeheaders['Content-Type'] = 'text/plain'
- end
-
- -- Check Content-Disposition
- if msg._mimeheaders['Content-Disposition'] then
- -- Check for "form-data" token
- if msg._mimeheaders['Content-Disposition']:match("^form%-data; ") then
- -- Check for field name, filename
- local field = msg._mimeheaders['Content-Disposition']:match('name="(.-)"')
- local file = msg._mimeheaders['Content-Disposition']:match('filename="(.+)"$')
-
- -- Is a file field and we have a callback
- if file and filecb then
- msg.params[field] = file
- msg._mimecallback = function(chunk,eof)
- filecb( {
- name = field;
- file = file;
- headers = msg._mimeheaders
- }, chunk, eof )
- end
-
- -- Treat as form field
- else
- msg.params[field] = ""
- msg._mimecallback = function(chunk,eof)
- msg.params[field] = msg.params[field] .. chunk
- end
- end
-
- -- Header was valid, continue with mime-data
- return true, function( chunk )
- return process_states['mime-data']( msg, chunk, filecb )
- end
- else
- -- Unknown Content-Disposition, abort
- return nil, "Unexpected Content-Disposition MIME section header"
- end
- else
- -- Content-Disposition is required, abort without
- return nil, "Missing Content-Disposition MIME section header"
- end
-
- -- We parsed no headers yet and buffer is almost empty
- elseif count > 0 or #chunk < 128 then
- -- Keep feeding me with chunks
- return true, nil
- end
-
- -- Buffer looks like garbage
- return nil, "Malformed MIME section header"
- else
- return nil, "Unexpected EOF"
- end
-end
-
-
--- Read MIME part data
-process_states['mime-data'] = function( msg, chunk, filecb )
-
- if chunk ~= nil then
-
- -- Combine look-behind buffer with current chunk
- local buffer = msg._mimebuffer .. chunk
-
- -- Look for MIME boundary
- local spos, epos = buffer:find( "\r\n--" .. msg.mime_boundary .. "\r\n", 1, true )
-
- if spos then
- -- Content data
- msg._mimecallback( buffer:sub( 1, spos - 1 ), true )
-
- -- Store remainder
- msg._mimebuffer = buffer:sub( epos + 1, #buffer )
-
- -- Next state is mime-header processing
- return true, function( chunk )
- return process_states['mime-headers']( msg, chunk, filecb )
- end
- else
- -- Look for EOF?
- local spos, epos = buffer:find( "\r\n--" .. msg.mime_boundary .. "--\r\n", 1, true )
-
- if spos then
- -- Content data
- msg._mimecallback( buffer:sub( 1, spos - 1 ), true )
-
- -- We processed the final MIME boundary, cleanup
- msg._mimebuffer = nil
- msg._mimeheaders = nil
- msg._mimecallback = nil
-
- -- We won't accept data anymore
- return false
- else
- -- We're somewhere within a data section and our buffer is full
- if #buffer > #chunk then
- -- Flush buffered data
- msg._mimecallback( buffer:sub( 1, #buffer - #chunk ), false )
-
- -- Store new data
- msg._mimebuffer = buffer:sub( #buffer - #chunk + 1, #buffer )
-
- -- Buffer is not full yet, append new data
- else
- msg._mimebuffer = buffer
- end
-
- -- Keep feeding me
- return true
- end
- end
- else
- return nil, "Unexpected EOF"
- end
-end
-
-
--- Init urldecoding stream
-process_states['urldecode-init'] = function( msg, chunk, filecb )
-
- if chunk ~= nil then
-
- -- Check for Content-Length
- if msg.headers['Content-Length'] then
- msg.content_length = tonumber(msg.headers['Content-Length'])
-
- if msg.content_length <= HTTP_MAX_CONTENT then
- -- Initialize buffer
- msg._urldecbuffer = chunk
- msg._urldeclength = 0
-
- -- Switch to urldecode-key state
- return true, function(chunk)
- return process_states['urldecode-key']( msg, chunk, filecb )
- end
- else
- return nil, "Request exceeds maximum allowed size"
- end
- else
- return nil, "Missing Content-Length header"
- end
- else
- return nil, "Unexpected EOF"
- end
-end
-
-
--- Process urldecoding stream, read and validate parameter key
-process_states['urldecode-key'] = function( msg, chunk, filecb )
-
- if chunk ~= nil then
-
- -- Prevent oversized requests
- if msg._urldeclength >= msg.content_length then
- return nil, "Request exceeds maximum allowed size"
- end
-
- -- Combine look-behind buffer with current chunk
- local buffer = msg._urldecbuffer .. chunk
- local spos, epos = buffer:find("=")
-
- -- Found param
- if spos then
-
- -- Check that key doesn't exceed maximum allowed key length
- if ( spos - 1 ) <= HTTP_URLENC_MAXKEYLEN then
- local key = urldecode( buffer:sub( 1, spos - 1 ) )
-
- -- Prepare buffers
- msg.params[key] = ""
- msg._urldeclength = msg._urldeclength + epos
- msg._urldecbuffer = buffer:sub( epos + 1, #buffer )
-
- -- Use file callback or store values inside msg.params
- if filecb then
- msg._urldeccallback = function( chunk, eof )
- filecb( field, chunk, eof )
- end
- else
- msg._urldeccallback = function( chunk, eof )
- msg.params[key] = msg.params[key] .. chunk
- end
- end
-
- -- Proceed with urldecode-value state
- return true, function( chunk )
- return process_states['urldecode-value']( msg, chunk, filecb )
- end
- else
- return nil, "POST parameter exceeds maximum allowed length"
- end
- else
- return nil, "POST data exceeds maximum allowed length"
- end
- else
- return nil, "Unexpected EOF"
- end
-end
-
-
--- Process urldecoding stream, read parameter value
-process_states['urldecode-value'] = function( msg, chunk, filecb )
-
- if chunk ~= nil then
-
- -- Combine look-behind buffer with current chunk
- local buffer = msg._urldecbuffer .. chunk
-
- -- Check for EOF
- if #buffer == 0 then
- -- Compare processed length
- if msg._urldeclength == msg.content_length then
- -- Cleanup
- msg._urldeclength = nil
- msg._urldecbuffer = nil
- msg._urldeccallback = nil
-
- -- We won't accept data anymore
- return false
- else
- return nil, "Content-Length mismatch"
- end
- end
-
- -- Check for end of value
- local spos, epos = buffer:find("[&;]")
- if spos then
-
- -- Flush buffer, send eof
- msg._urldeccallback( buffer:sub( 1, spos - 1 ), true )
- msg._urldecbuffer = buffer:sub( epos + 1, #buffer )
- msg._urldeclength = msg._urldeclength + epos
-
- -- Back to urldecode-key state
- return true, function( chunk )
- return process_states['urldecode-key']( msg, chunk, filecb )
- end
- else
- -- We're somewhere within a data section and our buffer is full
- if #buffer > #chunk then
- -- Flush buffered data
- msg._urldeccallback( buffer:sub( 1, #buffer - #chunk ), false )
-
- -- Store new data
- msg._urldeclength = msg._urldeclength + #buffer - #chunk
- msg._urldecbuffer = buffer:sub( #buffer - #chunk + 1, #buffer )
-
- -- Buffer is not full yet, append new data
- else
- msg._urldecbuffer = buffer
- end
-
- -- Keep feeding me
- return true
- end
- else
- return nil, "Unexpected EOF"
- end
-end
-
-
--- Decode MIME encoded data.
-function mimedecode_message_body( source, msg, filecb )
-
- -- Find mime boundary
- if msg and msg.headers['Content-Type'] then
-
- local bound = msg.headers['Content-Type']:match("^multipart/form%-data; boundary=(.+)")
-
- if bound then
- msg.mime_boundary = bound
- else
- return nil, "No MIME boundary found or invalid content type given"
- end
- end
-
- -- Create an initial LTN12 sink
- -- The whole MIME parsing process is implemented as fancy sink, sinks replace themself
- -- depending on current processing state (init, header, data). Return the initial state.
- local sink = ltn12.sink.simplify(
- function( chunk )
- return process_states['mime-init']( msg, chunk, filecb )
- end
- )
-
- -- Create a throttling LTN12 source
- -- Frequent state switching in the mime parsing process leads to unwanted buffer aggregation.
- -- This source checks wheather there's still data in our internal read buffer and returns an
- -- empty string if there's already enough data in the processing queue. If the internal buffer
- -- runs empty we're calling the original source to get the next chunk of data.
- local tsrc = function()
-
- -- XXX: we schould propably keep the maximum buffer size in sync with
- -- the blocksize of our original source... but doesn't really matter
- if msg._mimebuffer ~= null and #msg._mimebuffer > 256 then
- return ""
- else
- return source()
- end
- end
-
- -- Pump input data...
- while true do
- -- get data
- local ok, err = ltn12.pump.step( tsrc, sink )
-
- -- error
- if not ok and err then
- return nil, err
-
- -- eof
- elseif not ok then
- return true
- end
- end
-end
-
-
--- Decode urlencoded data.
-function urldecode_message_body( source, msg )
-
- -- Create an initial LTN12 sink
- -- Return the initial state.
- local sink = ltn12.sink.simplify(
- function( chunk )
- return process_states['urldecode-init']( msg, chunk )
- end
- )
-
- -- Create a throttling LTN12 source
- -- See explaination in mimedecode_message_body().
- local tsrc = function()
- if msg._urldecbuffer ~= null and #msg._urldecbuffer > 0 then
- return ""
- else
- return source()
- end
- end
-
- -- Pump input data...
- while true do
- -- get data
- local ok, err = ltn12.pump.step( tsrc, sink )
-
- -- step
- if not ok and err then
- return nil, err
-
- -- eof
- elseif not ok then
- return true
- end
- end
-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( source )
-
- local ok = true
- local msg = { }
-
- local sink = ltn12.sink.simplify(
- function( chunk )
- return process_states['magic']( msg, chunk )
- end
- )
-
- -- Pump input data...
- while ok do
-
- -- get data
- ok, err = ltn12.pump.step( source, sink )
-
- -- error
- if not ok and err then
- return nil, err
-
- -- eof
- elseif not ok then
-
- -- Process get parameters
- if ( msg.request_method == "get" or msg.request_method == "post" ) and
- msg.request_uri:match("?")
- then
- msg.params = urldecode_params( msg.request_uri )
- else
- msg.params = { }
- end
-
- -- Populate common environment variables
- msg.env = {
- CONTENT_LENGTH = msg.headers['Content-Length'];
- CONTENT_TYPE = msg.headers['Content-Type'];
- REQUEST_METHOD = msg.request_method:upper();
- REQUEST_URI = msg.request_uri;
- SCRIPT_NAME = msg.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 = msg.headers[hdr]
-
- msg.env[var] = val
- end
- end
- end
-
- return msg
-end
-
-
--- Parse a http message body
-function parse_message_body( source, msg, filecb )
-
- -- Is it multipart/mime ?
- if msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
- msg.env.CONTENT_TYPE:match("^multipart/form%-data")
- then
-
- return mimedecode_message_body( source, msg, filecb )
-
- -- Is it application/x-www-form-urlencoded ?
- elseif msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
- msg.env.CONTENT_TYPE == "application/x-www-form-urlencoded"
- then
-
- return urldecode_message_body( source, msg, filecb )
-
- -- Unhandled encoding
- -- If a file callback is given then feed it line by line, else
- -- store whole buffer in message.content
- else
-
- local sink
- local length = 0
-
- -- If we have a file callback then feed it
- if type(filecb) == "function" then
- sink = filecb
-
- -- ... else append to .content
- else
- msg.content = ""
- msg.content_length = 0
-
- sink = function( chunk )
- if ( msg.content_length ) + #chunk <= HTTP_MAX_CONTENT then
-
- msg.content = msg.content .. chunk
- msg.content_length = msg.content_length + #chunk
-
- return true
- else
- return nil, "POST data exceeds maximum allowed length"
- end
- end
- end
-
- -- Pump data...
- while true do
- local ok, err = ltn12.pump.step( source, sink )
-
- if not ok and err then
- return nil, err
- elseif not err then
- return true
- end
- end
- end
-end