Merge pull request #564 from cshore/pull-request-app-firewall-timedate
[project/luci.git] / modules / luci-base / luasrc / cbi.lua
index ae570b1..8fd0a33 100644 (file)
@@ -1,29 +1,6 @@
---[[
-LuCI - Configuration Bind Interface
-
-Description:
-Offers an interface for binding configuration values to certain
-data types. Supports value and range validation and basic dependencies.
-
-FileId:
-$Id$
-
-License:
-Copyright 2008 Steven Barth <steven@midlink.org>
-
-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
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
+-- Copyright 2008 Steven Barth <steven@midlink.org>
+-- Licensed to the public under the Apache License 2.0.
 
-]]--
 module("luci.cbi", package.seeall)
 
 require("luci.template")
@@ -35,6 +12,7 @@ require("luci.http")
 local fs         = require("nixio.fs")
 local uci        = require("luci.model.uci")
 local datatypes  = require("luci.cbi.datatypes")
+local dispatcher = require("luci.dispatcher")
 local class      = util.class
 local instanceof = util.instanceof
 
@@ -60,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
 
@@ -330,8 +308,30 @@ function Map.__init__(self, config, ...)
 
        self.changed = false
 
-       if not self.uci:load(self.config) then
-               error("Unable to read UCI data: " .. self.config)
+       local path = "%s/%s" %{ self.uci:get_confdir(), self.config }
+       if fs.stat(path, "type") ~= "reg" then
+               fs.writefile(path, "")
+       end
+
+       local ok, err = self.uci:load(self.config)
+       if not ok then
+               local url = dispatcher.build_url(unpack(dispatcher.context.request))
+               local source = self:formvalue("cbi.source")
+               if type(source) == "string" then
+                       fs.writefile(path, source:gsub("\r\n", "\n"))
+                       ok, err = self.uci:load(self.config)
+                       if ok then
+                               luci.http.redirect(url)
+                       end
+               end
+               self.save = false
+       end
+
+       if not ok then
+               self.template   = "cbi/error"
+               self.error      = err
+               self.source     = fs.readfile(path) or ""
+               self.pageaction = false
        end
 end
 
@@ -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,26 +1533,39 @@ 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
-               if fvalue ~= self.default or (not self.optional and not self.rmempty) then
-                       self:write(section, fvalue)
-               else
+               local cvalue = self:cfgvalue(section)
+               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
                self:remove(section)
+               self.section.changed = true
        end
 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