SAPI: Throw runtime error instead of if l_get_mapgen_object called in incorrect thread
[oweals/minetest.git] / builtin / mainmenu / tab_settings.lua
index e96fa1c4dec385edc222dc423b2fc7b3330bc278..6d71904cde47042de34c24034b8de9021407b062 100644 (file)
@@ -26,11 +26,25 @@ local CHAR_CLASSES = {
 }
 
 -- returns error message, or nil
-local function parse_setting_line(settings, line, read_all, base_level)
+local function parse_setting_line(settings, line, read_all, base_level, allow_secure)
+       -- comment
+       local comment = line:match("^#" .. CHAR_CLASSES.SPACE .. "*(.*)$")
+       if comment then
+               if settings.current_comment == "" then
+                       settings.current_comment = comment
+               else
+                       settings.current_comment = settings.current_comment .. "\n" .. comment
+               end
+               return
+       end
+
+       -- clear current_comment so only comments directly above a setting are bound to it
+       -- but keep a local reference to it for variables in the current line
+       local current_comment = settings.current_comment
+       settings.current_comment = ""
+
        -- empty lines
        if line:match("^" .. CHAR_CLASSES.SPACE .. "*$") then
-               -- clear current_comment so only comments directly above a setting are bound to it
-               settings.current_comment = ""
                return
        end
 
@@ -45,19 +59,6 @@ local function parse_setting_line(settings, line, read_all, base_level)
                return
        end
 
-       -- comment
-       local comment = line:match("^#" .. CHAR_CLASSES.SPACE .. "*(.*)$")
-       if comment then
-               if settings.current_comment == "" then
-                       settings.current_comment = comment
-               else
-                       settings.current_comment = settings.current_comment .. "\n" .. comment
-               end
-               return
-       end
-
-       local error_msg
-
        -- settings
        local first_part, name, readable_name, setting_type = line:match("^"
                        -- this first capture group matches the whole first part,
@@ -71,158 +72,174 @@ local function parse_setting_line(settings, line, read_all, base_level)
                                .. CHAR_CLASSES.SPACE .. "?"
                        .. ")")
 
-       if first_part then
-               if readable_name == "" then
-                       readable_name = nil
+       if not first_part then
+               return "Invalid line"
+       end
+
+       if name:match("secure%.[.]*") and not allow_secure then
+               return "Tried to add \"secure.\" setting"
+       end
+
+       if readable_name == "" then
+               readable_name = nil
+       end
+       local remaining_line = line:sub(first_part:len() + 1)
+
+       if setting_type == "int" then
+               local default, min, max = remaining_line:match("^"
+                               -- first int is required, the last 2 are optional
+                               .. "(" .. CHAR_CLASSES.INTEGER .. "+)" .. CHAR_CLASSES.SPACE .. "?"
+                               .. "(" .. CHAR_CLASSES.INTEGER .. "*)" .. CHAR_CLASSES.SPACE .. "?"
+                               .. "(" .. CHAR_CLASSES.INTEGER .. "*)"
+                               .. "$")
+
+               if not default or not tonumber(default) then
+                       return "Invalid integer setting"
                end
-               local remaining_line = line:sub(first_part:len() + 1)
-
-               if setting_type == "int" then
-                       local default, min, max = remaining_line:match("^"
-                                       -- first int is required, the last 2 are optional
-                                       .. "(" .. CHAR_CLASSES.INTEGER .. "+)" .. CHAR_CLASSES.SPACE .. "?"
-                                       .. "(" .. CHAR_CLASSES.INTEGER .. "*)" .. CHAR_CLASSES.SPACE .. "?"
-                                       .. "(" .. CHAR_CLASSES.INTEGER .. "*)"
-                                       .. "$")
-                       if default and tonumber(default) then
-                               min = tonumber(min)
-                               max = tonumber(max)
-                               table.insert(settings, {
-                                       name = name,
-                                       readable_name = readable_name,
-                                       type = "int",
-                                       default = default,
-                                       min = min,
-                                       max = max,
-                                       comment = settings.current_comment,
-                               })
-                       else
-                               error_msg = "Invalid integer setting"
-                       end
 
-               elseif setting_type == "string" or setting_type == "noise_params"
-                               or setting_type == "key" then
-                       local default = remaining_line:match("^(.*)$")
-                       if default then
-                               if setting_type ~= "key" or read_all then -- ignore key type if read_all is false
-                                       table.insert(settings, {
-                                               name = name,
-                                               readable_name = readable_name,
-                                               type = setting_type,
-                                               default = default,
-                                               comment = settings.current_comment,
-                                       })
-                               end
-                       else
-                               error_msg =  "Invalid string setting"
-                       end
+               min = tonumber(min)
+               max = tonumber(max)
+               table.insert(settings, {
+                       name = name,
+                       readable_name = readable_name,
+                       type = "int",
+                       default = default,
+                       min = min,
+                       max = max,
+                       comment = current_comment,
+               })
+               return
+       end
 
-               elseif setting_type == "bool" then
-                       if remaining_line == "false" or remaining_line == "true" then
-                               table.insert(settings, {
-                                       name = name,
-                                       readable_name = readable_name,
-                                       type = "bool",
-                                       default = remaining_line,
-                                       comment = settings.current_comment,
-                               })
-                       else
-                               error_msg =  "Invalid boolean setting"
-                       end
+       if setting_type == "string" or setting_type == "noise_params"
+                       or setting_type == "key" then
+               local default = remaining_line:match("^(.*)$")
 
-               elseif setting_type == "float" then
-                       local default, min, max = remaining_line:match("^"
-                                       -- first float is required, the last 2 are optional
-                                       .. "(" .. CHAR_CLASSES.FLOAT .. "+)" .. CHAR_CLASSES.SPACE .. "?"
-                                       .. "(" .. CHAR_CLASSES.FLOAT .. "*)" .. CHAR_CLASSES.SPACE .. "?"
-                                       .. "(" .. CHAR_CLASSES.FLOAT .. "*)"
-                                       .."$")
-                       if default and tonumber(default) then
-                               min = tonumber(min)
-                               max = tonumber(max)
-                               table.insert(settings, {
-                                       name = name,
-                                       readable_name = readable_name,
-                                       type = "float",
-                                       default = default,
-                                       min = min,
-                                       max = max,
-                                       comment = settings.current_comment,
-                               })
-                       else
-                               error_msg =  "Invalid float setting"
-                       end
+               if not default then
+                       return "Invalid string setting"
+               end
+               if setting_type == "key" and not read_all then
+                       -- ignore key type if read_all is false
+                       return
+               end
 
-               elseif setting_type == "enum" then
-                       local default, values = remaining_line:match("^(.+)" .. CHAR_CLASSES.SPACE .. "(.+)$")
-                       if default and values ~= "" then
-                               table.insert(settings, {
-                                       name = name,
-                                       readable_name = readable_name,
-                                       type = "enum",
-                                       default = default,
-                                       values = values:split(",", true),
-                                       comment = settings.current_comment,
-                               })
-                       else
-                               error_msg =  "Invalid enum setting"
-                       end
+               table.insert(settings, {
+                       name = name,
+                       readable_name = readable_name,
+                       type = setting_type,
+                       default = default,
+                       comment = current_comment,
+               })
+               return
+       end
 
-               elseif setting_type == "path" then
-                       local default = remaining_line:match("^(.*)$")
-                       if default then
-                               table.insert(settings, {
-                                       name = name,
-                                       readable_name = readable_name,
-                                       type = "path",
-                                       default = default,
-                                       comment = settings.current_comment,
-                               })
-                       else
-                               error_msg =  "Invalid path setting"
-                       end
+       if setting_type == "bool" then
+               if remaining_line ~= "false" and remaining_line ~= "true" then
+                       return "Invalid boolean setting"
+               end
 
-               elseif setting_type == "flags" then
-                       local default, possible = remaining_line:match("^"
-                                       .. "(" .. CHAR_CLASSES.FLAGS .. "+)" .. CHAR_CLASSES.SPACE .. ""
-                                       .. "(" .. CHAR_CLASSES.FLAGS .. "+)"
-                                       .. "$")
-                       if default and possible then
-                               table.insert(settings, {
-                                       name = name,
-                                       readable_name = readable_name,
-                                       type = "flags",
-                                       default = default,
-                                       possible = possible,
-                                       comment = settings.current_comment,
-                               })
-                       else
-                               error_msg =  "Invalid flags setting"
-                       end
+               table.insert(settings, {
+                       name = name,
+                       readable_name = readable_name,
+                       type = "bool",
+                       default = remaining_line,
+                       comment = current_comment,
+               })
+               return
+       end
 
-               -- TODO: flags, noise_params (, struct)
+       if setting_type == "float" then
+               local default, min, max = remaining_line:match("^"
+                               -- first float is required, the last 2 are optional
+                               .. "(" .. CHAR_CLASSES.FLOAT .. "+)" .. CHAR_CLASSES.SPACE .. "?"
+                               .. "(" .. CHAR_CLASSES.FLOAT .. "*)" .. CHAR_CLASSES.SPACE .. "?"
+                               .. "(" .. CHAR_CLASSES.FLOAT .. "*)"
+                               .."$")
 
-               else
-                       error_msg =  "Invalid setting type \"" .. type .. "\""
+               if not default or not tonumber(default) then
+                       return "Invalid float setting"
                end
-       else
-               error_msg =  "Invalid line"
+
+               min = tonumber(min)
+               max = tonumber(max)
+               table.insert(settings, {
+                       name = name,
+                       readable_name = readable_name,
+                       type = "float",
+                       default = default,
+                       min = min,
+                       max = max,
+                       comment = current_comment,
+               })
+               return
+       end
+
+       if setting_type == "enum" then
+               local default, values = remaining_line:match("^(.+)" .. CHAR_CLASSES.SPACE .. "(.+)$")
+
+               if not default or values == "" then
+                       return "Invalid enum setting"
+               end
+
+               table.insert(settings, {
+                       name = name,
+                       readable_name = readable_name,
+                       type = "enum",
+                       default = default,
+                       values = values:split(",", true),
+                       comment = current_comment,
+               })
+               return
+       end
+
+       if setting_type == "path" then
+               local default = remaining_line:match("^(.*)$")
+
+               if not default then
+                       return "Invalid path setting"
+               end
+
+               table.insert(settings, {
+                       name = name,
+                       readable_name = readable_name,
+                       type = "path",
+                       default = default,
+                       comment = current_comment,
+               })
+               return
+       end
+
+       if setting_type == "flags" then
+               local default, possible = remaining_line:match("^"
+                               .. "(" .. CHAR_CLASSES.FLAGS .. "+)" .. CHAR_CLASSES.SPACE .. ""
+                               .. "(" .. CHAR_CLASSES.FLAGS .. "+)"
+                               .. "$")
+
+               if not default or not possible then
+                       return "Invalid flags setting"
+               end
+
+               table.insert(settings, {
+                       name = name,
+                       readable_name = readable_name,
+                       type = "flags",
+                       default = default,
+                       possible = possible,
+                       comment = current_comment,
+               })
+               return
        end
-       -- clear current_comment since we just used it
-       -- if we not just used it, then clear it since we only want comments
-       --  directly above the setting to be bound to it
-       settings.current_comment = ""
 
-       return error_msg
+       return "Invalid setting type \"" .. setting_type .. "\""
 end
 
-local function parse_single_file(file, filepath, read_all, result, base_level)
+local function parse_single_file(file, filepath, read_all, result, base_level, allow_secure)
        -- store this helper variable in the table so it's easier to pass to parse_setting_line()
        result.current_comment = ""
 
        local line = file:read("*line")
        while line do
-               local error_msg = parse_setting_line(result, line, read_all, base_level)
+               local error_msg = parse_setting_line(result, line, read_all, base_level, allow_secure)
                if error_msg then
                        core.log("error", error_msg .. " in " .. filepath .. " \"" .. line .. "\"")
                end
@@ -243,7 +260,7 @@ local function parse_config_file(read_all, parse_mods)
                return settings
        end
 
-       parse_single_file(file, builtin_path, read_all, settings, 0)
+       parse_single_file(file, builtin_path, read_all, settings, 0, true)
 
        file:close()
 
@@ -272,7 +289,7 @@ local function parse_config_file(read_all, parse_mods)
                                        type = "category",
                                })
 
-                               parse_single_file(file, path, read_all, settings, 2)
+                               parse_single_file(file, path, read_all, settings, 2, false)
 
                                file:close()
                        end
@@ -305,7 +322,7 @@ local function parse_config_file(read_all, parse_mods)
                                        type = "category",
                                })
 
-                               parse_single_file(file, path, read_all, settings, 2)
+                               parse_single_file(file, path, read_all, settings, 2, false)
 
                                file:close()
                        end
@@ -476,7 +493,7 @@ local function handle_change_setting_buttons(this, fields)
                                value = value:trim()
                                if not value:match(CHAR_CLASSES.FLAGS .. "+")
                                                or not setting.possible:match("[,]?" .. value .. "[,]?") then
-                                       this.data.error_message = fgettext_ne("\"" .. value .. "\" is not a valid flag.")
+                                       this.data.error_message = fgettext_ne("\"$1\" is not a valid flag.", value)
                                        this.data.entered_text = fields["te_setting_value"]
                                        core.update_formspec(this:get_formspec())
                                        return true
@@ -686,14 +703,17 @@ local function create_translation_file()
        local settings = parse_config_file(true, false)
        for _, entry in ipairs(settings) do
                if entry.type == "category" then
-                       result = result .. "\tgettext(\"" .. entry.name .. "\");\n"
+                       local name_escaped = entry.name:gsub("\"", "\\\"")
+                       result = result .. "\tgettext(\"" .. name_escaped .. "\");\n"
                else
                        if entry.readable_name then
-                               result = result .. "\tgettext(\"" .. entry.readable_name .. "\");\n"
+                               local readable_name_escaped = entry.readable_name:gsub("\"", "\\\"")
+                               result = result .. "\tgettext(\"" .. readable_name_escaped .. "\");\n"
                        end
                        if entry.comment ~= "" then
-                               local comment = entry.comment:gsub("\n", "\\n")
-                               result = result .. "\tgettext(\"" .. comment .. "\");\n"
+                               local comment_escaped = entry.comment:gsub("\n", "\\n")
+                               comment_escaped = comment_escaped:gsub("\"", "\\\"")
+                               result = result .. "\tgettext(\"" .. comment_escaped .. "\");\n"
                        end
                end
        end