lib-nixio / luci-base: Fix for reading csrf token prevents file upload 563/head
authorDaniel Dickinson <openwrt@daniel.thecshore.com>
Tue, 15 Dec 2015 16:37:33 +0000 (11:37 -0500)
committerDaniel Dickinson <openwrt@daniel.thecshore.com>
Tue, 15 Dec 2015 18:12:29 +0000 (13:12 -0500)
The call to http.formvalue in order to read the csrf token causes
_parse_input to be triggered *before* controllers and cbi maps have
been built.  This results in the failure of file uploads because
the file handler is not yet in place when _parse_input gets called,
and it is in _parse_input that POST data is parsed (including files).

To fix this we add the ability to write file fields to temporary
files (using mkstemp and unlink in nixio.file) and use this to
store file data until the filehandler is registered, with a
fallback to reading the file data into memory.

Once the filehandler callback gets registered we iterate
though all previously parsed (saved) files and copy the
data to the file handler, and then close the temporary
file (which finally removes because we unlinked after
creating the file, but didn't close the file so unlink
was deferred).

Signed-off-by: Daniel Dickinson <openwrt@daniel.thecshore.com>
libs/luci-lib-nixio/src/file.c
modules/luci-base/luasrc/http.lua
modules/luci-base/luasrc/http/protocol.lua

index b86e040e1d02e628105c3f88d7ed7081cef09eb6..cfa35dfd17470b309f5ae050910f57734b5a15e7 100644 (file)
@@ -79,6 +79,38 @@ static int nixio_open(lua_State *L) {
        return 1;
 }
 
+static int nixio_mkstemp(lua_State *L) {
+       const char *intemplate = luaL_checklstring(L, 1, NULL);
+       size_t len = lua_strlen(L, 1);
+       char *template = (char *)lua_newuserdata(L, 13 + len);
+       if (!template) {
+               return luaL_error(L, "out of memory");
+       }
+       snprintf(template, 13 + len, "/tmp/%s.XXXXXX", intemplate);
+
+       int fd;
+
+       do {
+               fd = mkstemp(template);
+       } while (fd == -1 && errno == EINTR);
+       if (fd == -1) {
+               return nixio__perror(L);
+       }
+       unlink(template);
+
+       int *udata = lua_newuserdata(L, sizeof(int));
+       if (!udata) {
+               return luaL_error(L, "out of memory");
+       }
+
+       *udata = fd;
+
+       luaL_getmetatable(L, NIXIO_FILE_META);
+       lua_setmetatable(L, -2);
+
+       return 1;
+}
+
 static int nixio_open_flags(lua_State *L) {
        int mode = 0;
        const int j = lua_gettop(L);
@@ -366,6 +398,7 @@ static const luaL_reg R[] = {
        {"dup",                 nixio_dup},
        {"open",                nixio_open},
        {"open_flags",  nixio_open_flags},
+       {"mkstemp",             nixio_mkstemp},
        {"pipe",                nixio_pipe},
        {NULL,                  NULL}
 };
index 4b357317272eec507110f8b674be85ec84d9541b..8795dfc4b27984059b1f697d0a9157795d49ea21 100644 (file)
@@ -89,6 +89,37 @@ end
 
 function Request.setfilehandler(self, callback)
        self.filehandler = callback
+
+       -- If input has already been parsed then any files are either in temporary files
+       -- or are in self.message.params[key]
+       if self.parsed_input then
+               for param, value in pairs(self.message.params) do
+               repeat
+                       -- We're only interested in files
+                       if (not value["file"]) then break end
+                       -- If we were able to write to temporary file
+                       if (value["fd"]) then 
+                               fd = value["fd"]
+                               local eof = false
+                               repeat  
+                                       filedata = fd:read(1024)
+                                       if (filedata:len() < 1024) then
+                                               eof = true
+                                       end
+                                       callback({ name=value["name"], file=value["file"] }, filedata, eof)
+                               until (eof)
+                               fd:close()
+                               value["fd"] = nil
+                       -- We had to read into memory
+                       else
+                               -- There should only be one numbered value in table - the data
+                               for k, v in ipairs(value) do
+                                       callback({ name=value["name"], file=value["file"] }, v, true)
+                               end
+                       end
+               until true
+               end
+       end
 end
 
 function Request._parse_input(self)
index 0cb62aeec9dc60f909c181de2c8ea482db2d0a07..061c6ad544a0df71bab38976537365cc9b13bc46 100644 (file)
@@ -113,6 +113,16 @@ local function __initval( tbl, key )
        end
 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.
@@ -313,6 +323,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 )