luci-base: Make default for FileUpload 'safe'
[project/luci.git] / modules / luci-base / luasrc / cbi.lua
index f3d4618..2c1bb4d 100644 (file)
@@ -38,7 +38,7 @@ function load(cbimap, ...)
        require("luci.config")
        require("luci.util")
 
-       local upldir = "/lib/uci/upload/"
+       local upldir = "/etc/luci-uploads/"
        local cbidir = luci.util.libpath() .. "/model/cbi/"
        local func, err
 
@@ -367,63 +367,64 @@ end
 
 -- Use optimized UCI writing
 function Map.parse(self, readinput, ...)
-       self.readinput = (readinput ~= false)
-       self:_run_hooks("on_parse")
-
        if self:formvalue("cbi.skip") then
                self.state = FORM_SKIP
+       elseif not self.save then
+               self.state = FORM_INVALID
+       elseif not self:submitstate() then
+               self.state = FORM_NODATA
+       end
+
+       -- Back out early to prevent unauthorized changes on the subsequent parse
+       if self.state ~= nil then
                return self:state_handler(self.state)
        end
 
+       self.readinput = (readinput ~= false)
+       self:_run_hooks("on_parse")
+
        Node.parse(self, ...)
 
-       if self.save then
-               self:_run_hooks("on_save", "on_before_save")
+       self:_run_hooks("on_save", "on_before_save")
+       for i, config in ipairs(self.parsechain) do
+               self.uci:save(config)
+       end
+       self:_run_hooks("on_after_save")
+       if (not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply") then
+               self:_run_hooks("on_before_commit")
                for i, config in ipairs(self.parsechain) do
-                       self.uci:save(config)
-               end
-               self:_run_hooks("on_after_save")
-               if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then
-                       self:_run_hooks("on_before_commit")
-                       for i, config in ipairs(self.parsechain) do
-                               self.uci:commit(config)
-
-                               -- Refresh data because commit changes section names
-                               self.uci:load(config)
-                       end
-                       self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
-                       if self.apply_on_parse then
-                               self.uci:apply(self.parsechain)
-                               self:_run_hooks("on_apply", "on_after_apply")
-                       else
-                               -- This is evaluated by the dispatcher and delegated to the
-                               -- template which in turn fires XHR to perform the actual
-                               -- apply actions.
-                               self.apply_needed = true
-                       end
-
-                       -- Reparse sections
-                       Node.parse(self, true)
+                       self.uci:commit(config)
 
+                       -- Refresh data because commit changes section names
+                       self.uci:load(config)
                end
-               for i, config in ipairs(self.parsechain) do
-                       self.uci:unload(config)
-               end
-               if type(self.commit_handler) == "function" then
-                       self:commit_handler(self:submitstate())
+               self:_run_hooks("on_commit", "on_after_commit", "on_before_apply")
+               if self.apply_on_parse then
+                       self.uci:apply(self.parsechain)
+                       self:_run_hooks("on_apply", "on_after_apply")
+               else
+                       -- This is evaluated by the dispatcher and delegated to the
+                       -- template which in turn fires XHR to perform the actual
+                       -- apply actions.
+                       self.apply_needed = true
                end
+
+               -- Reparse sections
+               Node.parse(self, true)
+       end
+       for i, config in ipairs(self.parsechain) do
+               self.uci:unload(config)
+       end
+       if type(self.commit_handler) == "function" then
+               self:commit_handler(self:submitstate())
        end
 
-       if self:submitstate() then
-               if not self.save then
-                       self.state = FORM_INVALID
-               elseif self.proceed then
-                       self.state = FORM_PROCEED
-               else
-                       self.state = self.changed and FORM_CHANGED or FORM_VALID
-               end
+       if self.proceed then
+               self.state = FORM_PROCEED
+       elseif self.changed then
+               self.state = FORM_CHANGED
        else
-               self.state = FORM_NODATA
+               self.state = FORM_VALID
        end
 
        return self:state_handler(self.state)
@@ -1470,6 +1471,7 @@ function Value.__init__(self, ...)
        self.template  = "cbi/value"
        self.keylist = {}
        self.vallist = {}
+       self.readonly = nil
 end
 
 function Value.reset_values(self)
@@ -1483,6 +1485,10 @@ function Value.value(self, key, val)
        table.insert(self.vallist, tostring(val))
 end
 
+function Value.parse(self, section, novld)
+       if self.readonly then return end
+       AbstractValue.parse(self, section, novld)
+end
 
 -- DummyValue - This does nothing except being there
 DummyValue = class(AbstractValue)
@@ -1527,17 +1533,25 @@ function Flag.__init__(self, ...)
 end
 
 -- A flag can only have two states: set or unset
-function Flag.parse(self, section)
+function Flag.parse(self, section, novld)
        local fexists = self.map:formvalue(
                FEXIST_PREFIX .. self.config .. "." .. section .. "." .. self.option)
 
        if fexists then
                local fvalue = self:formvalue(section) and self.enabled or self.disabled
                local cvalue = self:cfgvalue(section)
-               if fvalue ~= self.default or (not self.optional and not self.rmempty) then
-                       self:write(section, fvalue)
-               else
+               local val_err
+               fvalue, val_err = self:validate(fvalue, section)
+               if not fvalue then
+                       if not novld then
+                               self:add_error(section, "invalid", val_err)
+                       end
+                       return
+               end
+               if fvalue == self.default and (self.optional or self.rmempty) then
                        self:remove(section)
+               else
+                       self:write(section, fvalue)
                end
                if (fvalue ~= cvalue) then self.section.changed = true end
        else
@@ -1549,7 +1563,9 @@ end
 function Flag.cfgvalue(self, section)
        return AbstractValue.cfgvalue(self, section) or self.default
 end
-
+function Flag.validate(self, value)
+       return value
+end
 
 --[[
 ListValue - A one-line value predefined in a list
@@ -1795,6 +1811,7 @@ function Button.__init__(self, ...)
        self.template  = "cbi/button"
        self.inputstyle = nil
        self.rmempty = true
+        self.unsafeupload = false
 end
 
 
@@ -1811,9 +1828,15 @@ function FileUpload.__init__(self, ...)
 end
 
 function FileUpload.formcreated(self, section)
-       return AbstractValue.formcreated(self, section) or
-               self.map:formvalue("cbi.rlf."..section.."."..self.option) or
-               self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
+       if self.unsafeupload then
+               return AbstractValue.formcreated(self, section) or
+                       self.map:formvalue("cbi.rlf."..section.."."..self.option) or
+                       self.map:formvalue("cbi.rlf."..section.."."..self.option..".x") or
+                       self.map:formvalue("cbid."..self.map.config.."."..section.."."..self.option..".textbox")
+       else
+               return AbstractValue.formcreated(self, section) or
+                       self.map:formvalue("cbid."..self.map.config.."."..section.."."..self.option..".textbox")
+       end
 end
 
 function FileUpload.cfgvalue(self, section)
@@ -1824,27 +1847,50 @@ function FileUpload.cfgvalue(self, section)
        return nil
 end
 
+-- If we have a new value, use it
+-- otherwise use old value
+-- deletion should be managed by a separate button object
+-- unless self.unsafeupload is set in which case if the user
+-- choose to remove the old file we do so.
+-- Also, allow to specify (via textbox) a file already on router
 function FileUpload.formvalue(self, section)
        local val = AbstractValue.formvalue(self, section)
        if val then
-               if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
-                  not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
-               then
+               if self.unsafeupload then
+                       if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and
+                           not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x")
+                       then
+                               return val
+                       end
+                       fs.unlink(val)
+                       self.value = nil
+                       return nil
+                elseif val ~= "" then
                        return val
-               end
-               fs.unlink(val)
-               self.value = nil
+                end
        end
-       return nil
+       val = luci.http.formvalue("cbid."..self.map.config.."."..section.."."..self.option..".textbox")
+       if val == "" then
+               val = nil
+       end
+        if not self.unsafeupload then
+               if not val then
+                       val = self.map:formvalue("cbi.rlf."..section.."."..self.option)
+               end
+        end
+       return val
 end
 
 function FileUpload.remove(self, section)
-       local val = AbstractValue.formvalue(self, section)
-       if val and fs.access(val) then fs.unlink(val) end
-       return AbstractValue.remove(self, section)
+       if self.unsafeupload then
+               local val = AbstractValue.formvalue(self, section)
+               if val and fs.access(val) then fs.unlink(val) end
+               return AbstractValue.remove(self, section)
+       else
+               return nil
+       end
 end
 
-
 FileBrowser = class(AbstractValue)
 
 function FileBrowser.__init__(self, ...)