1 |
|
-module(mongoose_config_validator). |
2 |
|
|
3 |
|
-export([validate/3, |
4 |
|
validate_section/2, |
5 |
|
validate_list/2]). |
6 |
|
|
7 |
|
-include("mongoose.hrl"). |
8 |
|
-include_lib("jid/include/jid.hrl"). |
9 |
|
|
10 |
|
-type validator() :: |
11 |
|
any | non_empty | non_negative | positive | module | {module, Prefix :: atom()} |
12 |
|
| jid | domain | subdomain_template | url | ip_address | ip_mask | network_address | port |
13 |
|
| filename | dirname | loglevel | pool_name | shaper | access_rule | {enum, list()}. |
14 |
|
|
15 |
|
-type section_validator() :: any | non_empty. |
16 |
|
|
17 |
|
-type list_validator() :: any | non_empty | unique | unique_non_empty. |
18 |
|
|
19 |
|
-export_type([validator/0, section_validator/0, list_validator/0]). |
20 |
|
|
21 |
|
-spec validate(mongoose_config_parser_toml:option_value(), |
22 |
|
mongoose_config_spec:option_type(), validator()) -> any(). |
23 |
:-( |
validate(V, binary, domain) -> validate_binary_domain(V); |
24 |
:-( |
validate(V, binary, url) -> validate_non_empty_binary(V); |
25 |
467 |
validate(V, binary, non_empty) -> validate_non_empty_binary(V); |
26 |
:-( |
validate(V, binary, subdomain_template) -> validate_subdomain_template(V); |
27 |
|
validate(V, binary, {module, Prefix}) -> |
28 |
:-( |
validate_module(list_to_atom(atom_to_list(Prefix) ++ "_" ++ binary_to_list(V))); |
29 |
:-( |
validate(V, binary, jid) -> validate_jid(V); |
30 |
:-( |
validate(V, binary, ldap_filter) -> validate_ldap_filter(V); |
31 |
25 |
validate(V, integer, non_negative) -> validate_non_negative_integer(V); |
32 |
705 |
validate(V, integer, positive) -> validate_positive_integer(V); |
33 |
553 |
validate(V, integer, port) -> validate_port(V); |
34 |
294 |
validate(V, int_or_infinity, non_negative) -> validate_non_negative_integer_or_infinity(V); |
35 |
193 |
validate(V, int_or_infinity, positive) -> validate_positive_integer_or_infinity(V); |
36 |
:-( |
validate(V, string, url) -> validate_url(V); |
37 |
:-( |
validate(V, string, domain) -> validate_domain(V); |
38 |
42 |
validate(V, string, subdomain_template) -> validate_subdomain_template(V); |
39 |
301 |
validate(V, string, ip_address) -> validate_ip_address(V); |
40 |
84 |
validate(V, string, ip_mask) -> validate_ip_mask_string(V); |
41 |
:-( |
validate(V, string, network_address) -> validate_network_address(V); |
42 |
491 |
validate(V, string, filename) -> validate_filename(V); |
43 |
637 |
validate(V, string, non_empty) -> validate_non_empty_string(V); |
44 |
:-( |
validate(V, string, dirname) -> validate_dirname(V); |
45 |
917 |
validate(V, atom, module) -> validate_module(V); |
46 |
|
validate(V, atom, {module, Prefix}) -> |
47 |
145 |
validate_module(list_to_atom(atom_to_list(Prefix) ++ "_" ++ atom_to_list(V))); |
48 |
42 |
validate(V, atom, loglevel) -> validate_loglevel(V); |
49 |
:-( |
validate(V, atom, pool_name) -> validate_non_empty_atom(V); |
50 |
:-( |
validate(V, atom, shaper) -> validate_non_empty_atom(V); |
51 |
42 |
validate(V, atom, access_rule) -> validate_non_empty_atom(V); |
52 |
1610 |
validate(V, atom, non_empty) -> validate_non_empty_atom(V); |
53 |
802 |
validate(V, _, {enum, Values}) -> validate_enum(V, Values); |
54 |
12715 |
validate(_V, _, any) -> ok. |
55 |
|
|
56 |
|
-spec validate_list([mongoose_config_parser_toml:config_part()], list_validator()) -> any(). |
57 |
:-( |
validate_list([_|_], non_empty) -> ok; |
58 |
25 |
validate_list(L = [_|_], unique_non_empty) -> validate_unique_items(L); |
59 |
5051 |
validate_list(L, unique) -> validate_unique_items(L); |
60 |
1589 |
validate_list(L, any) when is_list(L) -> ok. |
61 |
|
|
62 |
|
-spec validate_section([mongoose_config_parser_toml:config_part()], section_validator()) -> any(). |
63 |
:-( |
validate_section([_|_], non_empty) -> ok; |
64 |
5578 |
validate_section(L, any) when is_list(L) -> ok. |
65 |
|
|
66 |
|
%% validators |
67 |
|
|
68 |
|
validate_loglevel(Level) -> |
69 |
42 |
mongoose_logs:loglevel_keyword_to_number(Level). |
70 |
|
|
71 |
467 |
validate_non_empty_binary(Value) when is_binary(Value), Value =/= <<>> -> ok. |
72 |
|
|
73 |
|
validate_unique_items(Items) -> |
74 |
5076 |
L = sets:size(sets:from_list(Items)), |
75 |
5076 |
L = length(Items). |
76 |
|
|
77 |
|
validate_module(Mod) -> |
78 |
1062 |
case code:ensure_loaded(Mod) of |
79 |
|
{module, _} -> |
80 |
1062 |
ok; |
81 |
|
Other -> |
82 |
:-( |
error(#{what => module_not_found, module => Mod, reason => Other}) |
83 |
|
end. |
84 |
|
|
85 |
705 |
validate_positive_integer(Value) when is_integer(Value), Value > 0 -> ok. |
86 |
|
|
87 |
25 |
validate_non_negative_integer(Value) when is_integer(Value), Value >= 0 -> ok. |
88 |
|
|
89 |
294 |
validate_non_negative_integer_or_infinity(Value) when is_integer(Value), Value >= 0 -> ok; |
90 |
:-( |
validate_non_negative_integer_or_infinity(infinity) -> ok. |
91 |
|
|
92 |
151 |
validate_positive_integer_or_infinity(Value) when is_integer(Value), Value > 0 -> ok; |
93 |
42 |
validate_positive_integer_or_infinity(infinity) -> ok. |
94 |
|
|
95 |
|
validate_enum(Value, Values) -> |
96 |
802 |
case lists:member(Value, Values) of |
97 |
|
true -> |
98 |
802 |
ok; |
99 |
|
false -> |
100 |
:-( |
error(#{what => validate_enum_failed, |
101 |
|
value => Value, |
102 |
|
allowed_values => Values}) |
103 |
|
end. |
104 |
|
|
105 |
|
validate_ip_address(Value) -> |
106 |
301 |
{ok, _} = inet:parse_address(Value). |
107 |
|
|
108 |
553 |
validate_port(Value) when is_integer(Value), Value >= 0, Value =< 65535 -> ok. |
109 |
|
|
110 |
1652 |
validate_non_empty_atom(Value) when is_atom(Value), Value =/= '' -> ok. |
111 |
|
|
112 |
721 |
validate_non_empty_string(Value) when is_list(Value), Value =/= "" -> ok. |
113 |
|
|
114 |
|
validate_jid(Jid) -> |
115 |
:-( |
case jid:from_binary(Jid) of |
116 |
|
#jid{} -> |
117 |
:-( |
ok; |
118 |
|
_ -> |
119 |
:-( |
error(#{what => validate_jid_failed, value => Jid}) |
120 |
|
end. |
121 |
|
|
122 |
|
validate_ldap_filter(Value) -> |
123 |
:-( |
{ok, _} = eldap_filter:parse(Value). |
124 |
|
|
125 |
|
validate_domain(Domain) when is_list(Domain) -> |
126 |
:-( |
#jid{luser = <<>>, lresource = <<>>} = jid:from_binary(list_to_binary(Domain)), |
127 |
:-( |
validate_domain_res(Domain). |
128 |
|
|
129 |
|
validate_domain_res(Domain) -> |
130 |
42 |
case inet_res:gethostbyname(Domain) of |
131 |
|
{ok, _} -> |
132 |
:-( |
ok; |
133 |
|
{error,formerr} -> |
134 |
:-( |
error(#{what => cfg_validate_domain_failed, |
135 |
|
reason => formerr, text => <<"Invalid domain name">>, |
136 |
|
domain => Domain}); |
137 |
|
{error,Reason} -> %% timeout, nxdomain |
138 |
42 |
?LOG_WARNING(#{what => cfg_validate_domain, |
139 |
|
reason => Reason, domain => Domain, |
140 |
|
text => <<"Couldn't resolve domain. " |
141 |
:-( |
"It could cause issues with production installations">>}), |
142 |
42 |
ignore |
143 |
|
end. |
144 |
|
|
145 |
|
validate_binary_domain(Domain) when is_binary(Domain) -> |
146 |
42 |
#jid{luser = <<>>, lresource = <<>>} = jid:from_binary(Domain), |
147 |
42 |
validate_domain_res(binary_to_list(Domain)). |
148 |
|
|
149 |
|
validate_subdomain_template(SubdomainTemplate) -> |
150 |
42 |
Pattern = mongoose_subdomain_utils:make_subdomain_pattern(SubdomainTemplate), |
151 |
42 |
Domain = mongoose_subdomain_utils:get_fqdn(Pattern, <<"example.com">>), |
152 |
|
%% TODO: do we want warning printed by validate_domain_res, especially when |
153 |
|
%% validating modules.mod_event_pusher_push.virtual_pubsub_hosts option |
154 |
42 |
validate_binary_domain(Domain). |
155 |
|
|
156 |
|
validate_url(Url) -> |
157 |
:-( |
validate_non_empty_string(Url). |
158 |
|
|
159 |
|
validate_string(Value) -> |
160 |
84 |
is_binary(unicode:characters_to_binary(Value)). |
161 |
|
|
162 |
|
validate_ip_mask_string(IPMaskString) -> |
163 |
84 |
validate_non_empty_string(IPMaskString), |
164 |
84 |
{ok, IPMask} = mongoose_lib:parse_ip_netmask(IPMaskString), |
165 |
84 |
validate_ip_mask(IPMask). |
166 |
|
|
167 |
|
validate_ip_mask({IP, Mask}) -> |
168 |
84 |
validate_string(inet:ntoa(IP)), |
169 |
84 |
case IP of |
170 |
|
{_,_,_,_} -> |
171 |
84 |
validate_ipv4_mask(Mask); |
172 |
|
_ -> |
173 |
:-( |
validate_ipv6_mask(Mask) |
174 |
|
end. |
175 |
|
|
176 |
|
validate_ipv4_mask(Mask) -> |
177 |
84 |
validate_range(Mask, 0, 32). |
178 |
|
|
179 |
|
validate_ipv6_mask(Mask) -> |
180 |
:-( |
validate_range(Mask, 0, 128). |
181 |
|
|
182 |
|
validate_network_address(Value) -> |
183 |
:-( |
?LOG_DEBUG(#{what => validate_network_address, |
184 |
:-( |
value => Value}), |
185 |
:-( |
validate_oneof(Value, [fun validate_domain/1, fun validate_ip_address/1]). |
186 |
|
|
187 |
|
validate_oneof(Value, Funs) -> |
188 |
:-( |
Results = [safe_call_validator(F, Value) || F <- Funs], |
189 |
:-( |
case lists:any(fun(R) -> R =:= ok end, Results) of |
190 |
|
true -> |
191 |
:-( |
ok; |
192 |
|
false -> |
193 |
:-( |
error(#{what => validate_oneof_failed, |
194 |
|
validation_results => Results}) |
195 |
|
end. |
196 |
|
|
197 |
|
safe_call_validator(F, Value) -> |
198 |
:-( |
try |
199 |
:-( |
F(Value), |
200 |
:-( |
ok |
201 |
|
catch error:Reason:Stacktrace -> |
202 |
:-( |
#{reason => Reason, stacktrace => Stacktrace} |
203 |
|
end. |
204 |
|
|
205 |
|
validate_range(Value, Min, Max) when Value >= Min, Value =< Max -> |
206 |
84 |
ok. |
207 |
|
|
208 |
|
validate_filename(Filename) -> |
209 |
491 |
case file:read_file_info(Filename) of |
210 |
|
{ok, _} -> |
211 |
491 |
ok; |
212 |
|
Reason -> |
213 |
:-( |
error(#{what => invalid_filename, filename => Filename, reason => Reason}) |
214 |
|
end. |
215 |
|
|
216 |
|
validate_dirname(Dirname) -> |
217 |
:-( |
case file:list_dir(Dirname) of |
218 |
|
{ok, _} -> |
219 |
:-( |
ok; |
220 |
|
Reason -> |
221 |
:-( |
error(#{what => invalid_dirname, dirname => Dirname, reason => Reason}) |
222 |
|
end. |