Merge 0.9->0.10
[prosody.git] / core / configmanager.lua
1 -- Prosody IM
2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
4 --
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
7 --
8
9 local _G = _G;
10 local setmetatable, rawget, rawset, io, error, dofile, type, pairs, table =
11       setmetatable, rawget, rawset, io, error, dofile, type, pairs, table;
12 local format, math_max = string.format, math.max;
13
14 local fire_event = prosody and prosody.events.fire_event or function () end;
15
16 local envload = require"util.envload".envload;
17 local deps = require"util.dependencies";
18 local resolve_relative_path = require"util.paths".resolve_relative_path;
19 local glob_to_pattern = require"util.paths".glob_to_pattern;
20 local path_sep = package.config:sub(1,1);
21
22 local have_encodings, encodings = pcall(require, "util.encodings");
23 local nameprep = have_encodings and encodings.stringprep.nameprep or function (host) return host:lower(); end
24
25 module "configmanager"
26
27 _M.resolve_relative_path = resolve_relative_path; -- COMPAT
28
29 local parsers = {};
30
31 local config_mt = { __index = function (t, _) return rawget(t, "*"); end};
32 local config = setmetatable({ ["*"] = { } }, config_mt);
33
34 -- When host not found, use global
35 local host_mt = { __index = function(_, k) return config["*"][k] end }
36
37 function getconfig()
38         return config;
39 end
40
41 function get(host, key, _oldkey)
42         if key == "core" then
43                 key = _oldkey; -- COMPAT with code that still uses "core"
44         end
45         return config[host][key];
46 end
47 function _M.rawget(host, key, _oldkey)
48         if key == "core" then
49                 key = _oldkey; -- COMPAT with code that still uses "core"
50         end
51         local hostconfig = rawget(config, host);
52         if hostconfig then
53                 return rawget(hostconfig, key);
54         end
55 end
56
57 local function set(config_table, host, key, value)
58         if host and key then
59                 local hostconfig = rawget(config_table, host);
60                 if not hostconfig then
61                         hostconfig = rawset(config_table, host, setmetatable({}, host_mt))[host];
62                 end
63                 hostconfig[key] = value;
64                 return true;
65         end
66         return false;
67 end
68
69 function _M.set(host, key, value, _oldvalue)
70         if key == "core" then
71                 key, value = value, _oldvalue; --COMPAT with code that still uses "core"
72         end
73         return set(config, host, key, value);
74 end
75
76 function load(filename, config_format)
77         config_format = config_format or filename:match("%w+$");
78
79         if parsers[config_format] and parsers[config_format].load then
80                 local f, err = io.open(filename);
81                 if f then
82                         local new_config = setmetatable({ ["*"] = { } }, config_mt);
83                         local ok, err = parsers[config_format].load(f:read("*a"), filename, new_config);
84                         f:close();
85                         if ok then
86                                 config = new_config;
87                                 fire_event("config-reloaded", {
88                                         filename = filename,
89                                         format = config_format,
90                                         config = config
91                                 });
92                         end
93                         return ok, "parser", err;
94                 end
95                 return f, "file", err;
96         end
97
98         if not config_format then
99                 return nil, "file", "no parser specified";
100         else
101                 return nil, "file", "no parser for "..(config_format);
102         end
103 end
104
105 function addparser(config_format, parser)
106         if config_format and parser then
107                 parsers[config_format] = parser;
108         end
109 end
110
111 -- _M needed to avoid name clash with local 'parsers'
112 function _M.parsers()
113         local p = {};
114         for config_format in pairs(parsers) do
115                 table.insert(p, config_format);
116         end
117         return p;
118 end
119
120 -- Built-in Lua parser
121 do
122         local pcall = _G.pcall;
123         parsers.lua = {};
124         function parsers.lua.load(data, config_file, config_table)
125                 local env;
126                 -- The ' = true' are needed so as not to set off __newindex when we assign the functions below
127                 env = setmetatable({
128                         Host = true, host = true, VirtualHost = true,
129                         Component = true, component = true,
130                         Include = true, include = true, RunScript = true }, {
131                                 __index = function (_, k)
132                                         return rawget(_G, k);
133                                 end,
134                                 __newindex = function (_, k, v)
135                                         set(config_table, env.__currenthost or "*", k, v);
136                                 end
137                 });
138
139                 rawset(env, "__currenthost", "*") -- Default is global
140                 function env.VirtualHost(name)
141                         name = nameprep(name);
142                         if rawget(config_table, name) and rawget(config_table[name], "component_module") then
143                                 error(format("Host %q clashes with previously defined %s Component %q, for services use a sub-domain like conference.%s",
144                                         name, config_table[name].component_module:gsub("^%a+$", { component = "external", muc = "MUC"}), name, name), 0);
145                         end
146                         rawset(env, "__currenthost", name);
147                         -- Needs at least one setting to logically exist :)
148                         set(config_table, name or "*", "defined", true);
149                         return function (config_options)
150                                 rawset(env, "__currenthost", "*"); -- Return to global scope
151                                 for option_name, option_value in pairs(config_options) do
152                                         set(config_table, name or "*", option_name, option_value);
153                                 end
154                         end;
155                 end
156                 env.Host, env.host = env.VirtualHost, env.VirtualHost;
157
158                 function env.Component(name)
159                         name = nameprep(name);
160                         if rawget(config_table, name) and rawget(config_table[name], "defined") and not rawget(config_table[name], "component_module") then
161                                 error(format("Component %q clashes with previously defined Host %q, for services use a sub-domain like conference.%s",
162                                         name, name, name), 0);
163                         end
164                         set(config_table, name, "component_module", "component");
165                         -- Don't load the global modules by default
166                         set(config_table, name, "load_global_modules", false);
167                         rawset(env, "__currenthost", name);
168                         local function handle_config_options(config_options)
169                                 rawset(env, "__currenthost", "*"); -- Return to global scope
170                                 for option_name, option_value in pairs(config_options) do
171                                         set(config_table, name or "*", option_name, option_value);
172                                 end
173                         end
174
175                         return function (module)
176                                         if type(module) == "string" then
177                                                 set(config_table, name, "component_module", module);
178                                                 return handle_config_options;
179                                         end
180                                         return handle_config_options(module);
181                                 end
182                 end
183                 env.component = env.Component;
184
185                 function env.Include(file)
186                         -- Check whether this is a wildcard Include
187                         if file:match("[*?]") then
188                                 local lfs = deps.softreq "lfs";
189                                 if not lfs then
190                                         error(format("Error expanding wildcard pattern in Include %q - LuaFileSystem not available", file));
191                                 end
192                                 local path_pos, glob = file:match("()([^"..path_sep.."]+)$");
193                                 local path = file:sub(1, math_max(path_pos-2,0));
194                                 local config_path = config_file:gsub("[^"..path_sep.."]+$", "");
195                                 if #path > 0 then
196                                         path = resolve_relative_path(config_path, path);
197                                 else
198                                         path = config_path;
199                                 end
200                                 local patt = glob_to_pattern(glob);
201                                 for f in lfs.dir(path) do
202                                         if f:sub(1,1) ~= "." and f:match(patt) then
203                                                 env.Include(path..path_sep..f);
204                                         end
205                                 end
206                                 return;
207                         end
208                         -- Not a wildcard, so resolve (potentially) relative path and run through config parser
209                         file = resolve_relative_path(config_file:gsub("[^"..path_sep.."]+$", ""), file);
210                         local f, err = io.open(file);
211                         if f then
212                                 local ret, err = parsers.lua.load(f:read("*a"), file, config_table);
213                                 if not ret then error(err:gsub("%[string.-%]", file), 0); end
214                         end
215                         if not f then error("Error loading included "..file..": "..err, 0); end
216                         return f, err;
217                 end
218                 env.include = env.Include;
219
220                 function env.RunScript(file)
221                         return dofile(resolve_relative_path(config_file:gsub("[^"..path_sep.."]+$", ""), file));
222                 end
223
224                 local chunk, err = envload(data, "@"..config_file, env);
225
226                 if not chunk then
227                         return nil, err;
228                 end
229
230                 local ok, err = pcall(chunk);
231
232                 if not ok then
233                         return nil, err;
234                 end
235
236                 return true;
237         end
238
239 end
240
241 return _M;