luci-base: switch to lucihttp.urldecode() and lucihttp.urlencode()
[project/luci.git] / modules / luci-base / luasrc / http / protocol.lua
index aeb7ea6..096ae46 100644 (file)
@@ -1,47 +1,19 @@
 -- Copyright 2008 Freifunk Leipzig / Jo-Philipp Wich <jow@openwrt.org>
 -- Licensed to the public under the Apache License 2.0.
 
---- LuCI http protocol class.
 -- This class contains several functions useful for http message- and content
 -- decoding and to retrive form data from raw http messages.
 module("luci.http.protocol", package.seeall)
 
 local ltn12 = require("luci.ltn12")
+local util = require("luci.util")
 
 HTTP_MAX_CONTENT      = 1024*8         -- 8 kB maximum content size
 
---- Decode an urlencoded string - optionally without decoding
--- the "+" sign to " " - and return the decoded string.
--- @param str          Input string in x-www-urlencoded format
--- @param no_plus      Don't decode "+" signs to spaces
--- @return                     The decoded string
--- @see                                urlencode
-function urldecode( str, no_plus )
-
-       local function __chrdec( hex )
-               return string.char( tonumber( hex, 16 ) )
-       end
-
-       if type(str) == "string" then
-               if not no_plus then
-                       str = str:gsub( "+", " " )
-               end
-
-               str = str: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 or string. Returns a table with urldecoded values.
 -- Simple parameters are stored as string values associated with the parameter
 -- name within the table. Parameters with multiple values are stored as array
 -- containing the corresponding values.
--- @param url  The url or string which contains x-www-urlencoded form data
--- @param tbl  Use the given table for storing values (optional)
--- @return             Table containing the urldecoded parameters
--- @see                        urlencode_params
 function urldecode_params( url, tbl )
 
        local params = tbl or { }
@@ -53,8 +25,8 @@ function urldecode_params( url, tbl )
        for pair in url:gmatch( "[^&;]+" ) do
 
                -- find key and value
-               local key = urldecode( pair:match("^([^=]+)")     )
-               local val = urldecode( pair:match("^[^=]+=(.+)$") )
+               local key = util.urldecode( pair:match("^([^=]+)")     )
+               local val = util.urldecode( pair:match("^[^=]+=(.+)$") )
 
                -- store
                if type(key) == "string" and key:len() > 0 then
@@ -73,34 +45,8 @@ function urldecode_params( url, tbl )
        return params
 end
 
---- Encode given string to x-www-urlencoded format.
--- @param str  String to encode
--- @return             String containing the encoded data
--- @see                        urldecode
-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 each key-value-pair in given table to x-www-urlencoded format,
 -- separated by "&". Tables are encoded as parameters with multiple values by
 -- repeating the parameter name with each value.
--- @param tbl  Table with the values
--- @return             String containing encoded values
--- @see                        urldecode_params
 function urlencode_params( tbl )
        local enc = ""
 
@@ -108,11 +54,11 @@ function urlencode_params( tbl )
                if type(v) == "table" then
                        for i, v2 in ipairs(v) do
                                enc = enc .. ( #enc > 0 and "&" or "" ) ..
-                                       urlencode(k) .. "=" .. urlencode(v2)
+                                       util.urlencode(k) .. "=" .. util.urlencode(v2)
                        end
                else
                        enc = enc .. ( #enc > 0 and "&" or "" ) ..
-                               urlencode(k) .. "=" .. urlencode(v)
+                               util.urlencode(k) .. "=" .. util.urlencode(v)
                end
        end
 
@@ -122,9 +68,6 @@ end
 -- (Internal function)
 -- Initialize given parameter and coerce string into table when the parameter
 -- already exists.
--- @param tbl  Table where parameter should be created
--- @param key  Parameter name
--- @return             Always nil
 local function __initval( tbl, key )
        if tbl[key] == nil then
                tbl[key] = ""
@@ -136,13 +79,18 @@ local function __initval( tbl, key )
 end
 
 -- (Internal function)
+-- Initialize given file parameter.
+local function __initfileval( tbl, key, filename, fd )
+       if tbl[key] == nil then
+               tbl[key] = { file=filename, fd=fd, name=key, "" }
+       else
+               table.insert( tbl[key], "" )
+       end
+end
+
+-- (Internal function)
 -- Append given data to given parameter, either by extending the string value
 -- or by appending it to the last string in the parameter's value table.
--- @param tbl  Table containing the previously initialized parameter value
--- @param key  Parameter name
--- @param chunk        String containing the data to append
--- @return             Always nil
--- @see                        __initval
 local function __appendval( tbl, key, chunk )
        if type(tbl[key]) == "table" then
                tbl[key][#tbl[key]] = tbl[key][#tbl[key]] .. chunk
@@ -155,12 +103,6 @@ end
 -- Finish the value of given parameter, either by transforming the string value
 -- or - in the case of multi value parameters - the last element in the
 -- associated values table.
--- @param tbl          Table containing the previously initialized parameter value
--- @param key          Parameter name
--- @param handler      Function which transforms the parameter value
--- @return                     Always nil
--- @see                                __initval
--- @see                                __appendval
 local function __finishval( tbl, key, handler )
        if handler then
                if type(tbl[key]) == "table" then
@@ -259,10 +201,7 @@ process_states['headers'] = function( msg, chunk )
 end
 
 
---- Creates a ltn12 source from the given socket. The source will return it's
 -- data line by line with the trailing \r\n stripped of.
--- @param sock Readable network socket
--- @return             Ltn12 source function
 function header_source( sock )
        return ltn12.source.simplify( function()
 
@@ -289,9 +228,8 @@ function header_source( sock )
        end )
 end
 
---- Decode a mime encoded http message body with multipart/form-data
 -- Content-Type. Stores all extracted data associated with its parameter name
--- in the params table withing the given message object. Multiple parameter
+-- in the params table within the given message object. Multiple parameter
 -- values are stored as tables, ordinary ones as strings.
 -- If an optional file callback function is given then it is feeded with the
 -- file contents chunk by chunk and only the extracted file name is stored
@@ -300,12 +238,6 @@ end
 --  o Table containing decoded (name, file) and raw (headers) mime header data
 --  o String value containing a chunk of the file data
 --  o Boolean which indicates wheather the current chunk is the last one (eof)
--- @param src          Ltn12 source function
--- @param msg          HTTP message object
--- @param filecb       File callback function (optional)
--- @return                     Value indicating successful operation (not nil means "ok")
--- @return                     String containing the error if unsuccessful
--- @see                                parse_message_header
 function mimedecode_message_body( src, msg, filecb )
 
        if msg and msg.env.CONTENT_TYPE then
@@ -356,6 +288,22 @@ function mimedecode_message_body( src, msg, filecb )
                                __appendval( msg.params, field.name, field.file )
 
                                store = filecb
+                       elseif field.name and field.file then
+                               local nxf = require "nixio"
+                               local fd = nxf.mkstemp(field.name)
+                               __initfileval ( msg.params, field.name, field.file, fd )
+                               if fd then
+                                       store = function(hdr, buf, eof)
+                                               fd:write(buf)
+                                               if (eof) then
+                                                       fd:seek(0, "set")
+                                               end
+                                       end
+                               else
+                                       store = function( hdr, buf, eof )
+                                               __appendval( msg.params, field.name, buf )
+                                       end
+                               end
                        elseif field.name then
                                __initval( msg.params, field.name )
 
@@ -449,15 +397,9 @@ function mimedecode_message_body( src, msg, filecb )
        return ltn12.pump.all( src, snk )
 end
 
---- Decode an urlencoded http message body with application/x-www-urlencoded
 -- Content-Type. Stores all extracted data associated with its parameter name
--- in the params table withing the given message object. Multiple parameter
+-- in the params table within the given message object. Multiple parameter
 -- values are stored as tables, ordinary ones as strings.
--- @param src  Ltn12 source function
--- @param msg  HTTP message object
--- @return             Value indicating successful operation (not nil means "ok")
--- @return             String containing the error if unsuccessful
--- @see                        parse_message_header
 function urldecode_message_body( src, msg )
 
        local tlen   = 0
@@ -507,12 +449,8 @@ function urldecode_message_body( src, msg )
        return ltn12.pump.all( src, snk )
 end
 
---- Try to extract an http message header including information like protocol
 -- version, message headers and resulting CGI environment variables from the
 -- given ltn12 source.
--- @param src  Ltn12 source function
--- @return             HTTP message object
--- @see                        parse_message_body
 function parse_message_header( src )
 
        local ok   = true
@@ -582,19 +520,12 @@ function parse_message_header( src )
        return msg
 end
 
---- Try to extract and decode a http message body from the given ltn12 source.
 -- This function will examine the Content-Type within the given message object
 -- to select the appropriate content decoder.
 -- Currently the application/x-www-urlencoded and application/form-data
 -- mime types are supported. If the encountered content encoding can't be
 -- handled then the whole message body will be stored unaltered as "content"
 -- property within the given message object.
--- @param src          Ltn12 source function
--- @param msg          HTTP message object
--- @param filecb       File data callback (optional, see mimedecode_message_body())
--- @return                     Value indicating successful operation (not nil means "ok")
--- @return                     String containing the error if unsuccessful
--- @see                                parse_message_header
 function parse_message_body( src, msg, filecb )
        -- Is it multipart/mime ?
        if msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and
@@ -619,14 +550,23 @@ function parse_message_body( src, msg, filecb )
 
                -- If we have a file callback then feed it
                if type(filecb) == "function" then
-                       sink = filecb
-
+                       local meta = {
+                               name = "raw",
+                               encoding = msg.env.CONTENT_TYPE
+                       }
+                       sink = function( chunk )
+                               if chunk then
+                                       return filecb(meta, chunk, false)
+                               else
+                                       return filecb(meta, nil, true)
+                               end
+                       end
                -- ... else append to .content
                else
                        msg.content = ""
                        msg.content_length = 0
 
-                       sink = function( chunk, err )
+                       sink = function( chunk )
                                if chunk then
                                        if ( msg.content_length + #chunk ) <= HTTP_MAX_CONTENT then
                                                msg.content        = msg.content        .. chunk
@@ -655,8 +595,6 @@ function parse_message_body( src, msg, filecb )
        end
 end
 
---- Table containing human readable messages for several http status codes.
--- @class table
 statusmsg = {
        [200] = "OK",
        [206] = "Partial Content",