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 | host_type_or_global | pool_name | shaper | access_rule |
14 |
|
| {enum, list()}. |
15 |
|
|
16 |
|
-type section_validator() :: any | non_empty. |
17 |
|
|
18 |
|
-type list_validator() :: any | non_empty | unique | unique_non_empty. |
19 |
|
|
20 |
|
-export_type([validator/0, section_validator/0, list_validator/0]). |
21 |
|
|
22 |
|
-spec validate(mongoose_config_parser_toml:option_value(), |
23 |
|
mongoose_config_spec:option_type(), validator()) -> any(). |
24 |
:-( |
validate(V, binary, domain) -> validate_domain(V); |
25 |
:-( |
validate(V, binary, url) -> validate_non_empty_binary(V); |
26 |
964 |
validate(V, binary, non_empty) -> validate_non_empty_binary(V); |
27 |
:-( |
validate(V, binary, subdomain_template) -> validate_subdomain_template(V); |
28 |
|
validate(V, binary, {module, Prefix}) -> |
29 |
:-( |
validate_module(list_to_atom(atom_to_list(Prefix) ++ "_" ++ binary_to_list(V))); |
30 |
:-( |
validate(V, binary, jid) -> validate_jid(V); |
31 |
:-( |
validate(V, binary, ldap_filter) -> validate_ldap_filter(V); |
32 |
42 |
validate(V, integer, non_negative) -> validate_non_negative_integer(V); |
33 |
1358 |
validate(V, integer, positive) -> validate_positive_integer(V); |
34 |
1140 |
validate(V, integer, port) -> validate_port(V); |
35 |
616 |
validate(V, int_or_infinity, non_negative) -> validate_non_negative_integer_or_infinity(V); |
36 |
394 |
validate(V, int_or_infinity, positive) -> validate_positive_integer_or_infinity(V); |
37 |
:-( |
validate(V, string, url) -> validate_url(V); |
38 |
:-( |
validate(V, string, domain) -> validate_domain(V); |
39 |
88 |
validate(V, string, subdomain_template) -> validate_subdomain_template(V); |
40 |
612 |
validate(V, string, ip_address) -> validate_ip_address(V); |
41 |
176 |
validate(V, string, ip_mask) -> validate_ip_mask_string(V); |
42 |
:-( |
validate(V, string, network_address) -> validate_network_address(V); |
43 |
980 |
validate(V, string, filename) -> validate_filename(V); |
44 |
1316 |
validate(V, string, non_empty) -> validate_non_empty_string(V); |
45 |
:-( |
validate(V, string, dirname) -> validate_dirname(V); |
46 |
1889 |
validate(V, atom, module) -> validate_module(V); |
47 |
|
validate(V, atom, {module, Prefix}) -> |
48 |
195 |
validate_module(list_to_atom(atom_to_list(Prefix) ++ "_" ++ atom_to_list(V))); |
49 |
88 |
validate(V, atom, loglevel) -> validate_loglevel(V); |
50 |
88 |
validate(V, atom, pool_name) -> validate_non_empty_atom(V); |
51 |
:-( |
validate(global, binary_or_global, host_type_or_global) -> ok; |
52 |
88 |
validate(V, binary_or_global, host_type_or_global) -> validate_non_empty_binary(V); |
53 |
:-( |
validate(V, atom, host_type_or_global) -> validate_non_empty_atom(V); |
54 |
:-( |
validate(V, atom, shaper) -> validate_non_empty_atom(V); |
55 |
88 |
validate(V, atom, access_rule) -> validate_non_empty_atom(V); |
56 |
3248 |
validate(V, atom, non_empty) -> validate_non_empty_atom(V); |
57 |
1458 |
validate(V, _, {enum, Values}) -> validate_enum(V, Values); |
58 |
26416 |
validate(_V, _, any) -> ok. |
59 |
|
|
60 |
|
-spec validate_list([mongoose_config_parser_toml:config_part()], list_validator()) -> any(). |
61 |
:-( |
validate_list([_|_], non_empty) -> ok; |
62 |
42 |
validate_list(L = [_|_], unique_non_empty) -> validate_unique_items(L); |
63 |
10412 |
validate_list(L, unique) -> validate_unique_items(L); |
64 |
3204 |
validate_list(L, any) when is_list(L) -> ok. |
65 |
|
|
66 |
|
-spec validate_section([mongoose_config_parser_toml:config_part()], section_validator()) -> any(). |
67 |
:-( |
validate_section([_|_], non_empty) -> ok; |
68 |
11509 |
validate_section(L, any) when is_list(L) -> ok. |
69 |
|
|
70 |
|
%% validators |
71 |
|
|
72 |
|
validate_loglevel(Level) -> |
73 |
88 |
mongoose_logs:loglevel_keyword_to_number(Level). |
74 |
|
|
75 |
1052 |
validate_non_empty_binary(Value) when is_binary(Value), Value =/= <<>> -> ok. |
76 |
|
|
77 |
|
validate_unique_items(Items) -> |
78 |
10454 |
L = sets:size(sets:from_list(Items)), |
79 |
10454 |
L = length(Items). |
80 |
|
|
81 |
|
validate_module(Mod) -> |
82 |
2084 |
case code:ensure_loaded(Mod) of |
83 |
|
{module, _} -> |
84 |
2084 |
ok; |
85 |
|
Other -> |
86 |
:-( |
error(#{what => module_not_found, module => Mod, reason => Other}) |
87 |
|
end. |
88 |
|
|
89 |
1358 |
validate_positive_integer(Value) when is_integer(Value), Value > 0 -> ok. |
90 |
|
|
91 |
42 |
validate_non_negative_integer(Value) when is_integer(Value), Value >= 0 -> ok. |
92 |
|
|
93 |
616 |
validate_non_negative_integer_or_infinity(Value) when is_integer(Value), Value >= 0 -> ok; |
94 |
:-( |
validate_non_negative_integer_or_infinity(infinity) -> ok. |
95 |
|
|
96 |
306 |
validate_positive_integer_or_infinity(Value) when is_integer(Value), Value > 0 -> ok; |
97 |
88 |
validate_positive_integer_or_infinity(infinity) -> ok. |
98 |
|
|
99 |
|
validate_enum(Value, Values) -> |
100 |
1458 |
case lists:member(Value, Values) of |
101 |
|
true -> |
102 |
1458 |
ok; |
103 |
|
false -> |
104 |
:-( |
error(#{what => validate_enum_failed, |
105 |
|
value => Value, |
106 |
|
allowed_values => Values}) |
107 |
|
end. |
108 |
|
|
109 |
|
validate_ip_address(Value) -> |
110 |
612 |
{ok, _} = inet:parse_address(Value). |
111 |
|
|
112 |
1140 |
validate_port(Value) when is_integer(Value), Value >= 0, Value =< 65535 -> ok. |
113 |
|
|
114 |
3424 |
validate_non_empty_atom(Value) when is_atom(Value), Value =/= '' -> ok. |
115 |
|
|
116 |
1492 |
validate_non_empty_string(Value) when is_list(Value), Value =/= "" -> ok. |
117 |
|
|
118 |
|
validate_jid(Jid) -> |
119 |
:-( |
case jid:from_binary(Jid) of |
120 |
|
#jid{} -> |
121 |
:-( |
ok; |
122 |
|
_ -> |
123 |
:-( |
error(#{what => validate_jid_failed, value => Jid}) |
124 |
|
end. |
125 |
|
|
126 |
|
validate_ldap_filter(Value) -> |
127 |
:-( |
{ok, _} = eldap_filter:parse(Value). |
128 |
|
|
129 |
|
validate_subdomain_template(SubdomainTemplate) -> |
130 |
88 |
case mongoose_subdomain_utils:make_subdomain_pattern(SubdomainTemplate) of |
131 |
|
{fqdn, Domain} -> |
132 |
:-( |
validate_domain(Domain); |
133 |
|
Pattern -> |
134 |
88 |
Domain = binary_to_list(mongoose_subdomain_utils:get_fqdn(Pattern, <<"example.com">>)), |
135 |
88 |
case inet_parse:domain(Domain) of |
136 |
|
true -> |
137 |
88 |
ok; |
138 |
|
false -> |
139 |
:-( |
error(#{what => validate_subdomain_template_failed, |
140 |
|
text => <<"Invalid subdomain template">>, |
141 |
|
subdomain_template => SubdomainTemplate}) |
142 |
|
end |
143 |
|
end. |
144 |
|
|
145 |
|
validate_domain(Domain) when is_binary(Domain) -> |
146 |
:-( |
validate_domain(binary_to_list(Domain)); |
147 |
|
validate_domain(Domain) -> |
148 |
:-( |
validate_domain_name(Domain), |
149 |
:-( |
resolve_domain(Domain). |
150 |
|
|
151 |
|
validate_domain_name(Domain) -> |
152 |
:-( |
case inet_parse:domain(Domain) of |
153 |
|
true -> |
154 |
:-( |
ok; |
155 |
|
false -> |
156 |
:-( |
error(#{what => validate_domain_failed, |
157 |
|
text => <<"Invalid domain name">>, |
158 |
|
domain => Domain}) |
159 |
|
end. |
160 |
|
|
161 |
|
resolve_domain(Domain) -> |
162 |
:-( |
case inet_res:gethostbyname(Domain) of |
163 |
|
{ok, _} -> |
164 |
:-( |
ok; |
165 |
|
{error, Reason} -> %% timeout, nxdomain |
166 |
:-( |
?LOG_WARNING(#{what => cfg_validate_domain, |
167 |
|
reason => Reason, domain => Domain, |
168 |
|
text => <<"Couldn't resolve domain. " |
169 |
:-( |
"It could cause issues with production installations">>}), |
170 |
:-( |
ignore |
171 |
|
end. |
172 |
|
|
173 |
|
|
174 |
|
validate_url(Url) -> |
175 |
:-( |
validate_non_empty_string(Url). |
176 |
|
|
177 |
|
validate_string(Value) -> |
178 |
176 |
is_binary(unicode:characters_to_binary(Value)). |
179 |
|
|
180 |
|
validate_ip_mask_string(IPMaskString) -> |
181 |
176 |
validate_non_empty_string(IPMaskString), |
182 |
176 |
{ok, IPMask} = mongoose_lib:parse_ip_netmask(IPMaskString), |
183 |
176 |
validate_ip_mask(IPMask). |
184 |
|
|
185 |
|
validate_ip_mask({IP, Mask}) -> |
186 |
176 |
validate_string(inet:ntoa(IP)), |
187 |
176 |
case IP of |
188 |
|
{_,_,_,_} -> |
189 |
176 |
validate_ipv4_mask(Mask); |
190 |
|
_ -> |
191 |
:-( |
validate_ipv6_mask(Mask) |
192 |
|
end. |
193 |
|
|
194 |
|
validate_ipv4_mask(Mask) -> |
195 |
176 |
validate_range(Mask, 0, 32). |
196 |
|
|
197 |
|
validate_ipv6_mask(Mask) -> |
198 |
:-( |
validate_range(Mask, 0, 128). |
199 |
|
|
200 |
|
validate_network_address(Value) -> |
201 |
:-( |
?LOG_DEBUG(#{what => validate_network_address, |
202 |
:-( |
value => Value}), |
203 |
:-( |
validate_oneof(Value, [fun validate_domain/1, fun validate_ip_address/1]). |
204 |
|
|
205 |
|
validate_oneof(Value, Funs) -> |
206 |
:-( |
Results = [safe_call_validator(F, Value) || F <- Funs], |
207 |
:-( |
case lists:any(fun(R) -> R =:= ok end, Results) of |
208 |
|
true -> |
209 |
:-( |
ok; |
210 |
|
false -> |
211 |
:-( |
error(#{what => validate_oneof_failed, |
212 |
|
validation_results => Results}) |
213 |
|
end. |
214 |
|
|
215 |
|
safe_call_validator(F, Value) -> |
216 |
:-( |
try |
217 |
:-( |
F(Value), |
218 |
:-( |
ok |
219 |
|
catch error:Reason:Stacktrace -> |
220 |
:-( |
#{reason => Reason, stacktrace => Stacktrace} |
221 |
|
end. |
222 |
|
|
223 |
|
validate_range(Value, Min, Max) when Value >= Min, Value =< Max -> |
224 |
176 |
ok. |
225 |
|
|
226 |
|
validate_filename(Filename) -> |
227 |
980 |
case file:read_file_info(Filename) of |
228 |
|
{ok, _} -> |
229 |
980 |
ok; |
230 |
|
Reason -> |
231 |
:-( |
error(#{what => invalid_filename, filename => Filename, reason => Reason}) |
232 |
|
end. |
233 |
|
|
234 |
|
validate_dirname(Dirname) -> |
235 |
:-( |
case file:list_dir(Dirname) of |
236 |
|
{ok, _} -> |
237 |
:-( |
ok; |
238 |
|
Reason -> |
239 |
:-( |
error(#{what => invalid_dirname, dirname => Dirname, reason => Reason}) |
240 |
|
end. |