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_domain(V); |
24 |
:-( |
validate(V, binary, url) -> validate_non_empty_binary(V); |
25 |
1016 |
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 |
43 |
validate(V, integer, non_negative) -> validate_non_negative_integer(V); |
32 |
1524 |
validate(V, integer, positive) -> validate_positive_integer(V); |
33 |
1295 |
validate(V, integer, port) -> validate_port(V); |
34 |
651 |
validate(V, int_or_infinity, non_negative) -> validate_non_negative_integer_or_infinity(V); |
35 |
415 |
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 |
93 |
validate(V, string, subdomain_template) -> validate_subdomain_template(V); |
39 |
644 |
validate(V, string, ip_address) -> validate_ip_address(V); |
40 |
186 |
validate(V, string, ip_mask) -> validate_ip_mask_string(V); |
41 |
:-( |
validate(V, string, network_address) -> validate_network_address(V); |
42 |
941 |
validate(V, string, filename) -> validate_filename(V); |
43 |
1109 |
validate(V, string, non_empty) -> validate_non_empty_string(V); |
44 |
:-( |
validate(V, string, dirname) -> validate_dirname(V); |
45 |
2085 |
validate(V, atom, module) -> validate_module(V); |
46 |
|
validate(V, atom, {module, Prefix}) -> |
47 |
205 |
validate_module(list_to_atom(atom_to_list(Prefix) ++ "_" ++ atom_to_list(V))); |
48 |
93 |
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 |
93 |
validate(V, atom, access_rule) -> validate_non_empty_atom(V); |
52 |
3427 |
validate(V, atom, non_empty) -> validate_non_empty_atom(V); |
53 |
1629 |
validate(V, _, {enum, Values}) -> validate_enum(V, Values); |
54 |
27884 |
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 |
43 |
validate_list(L = [_|_], unique_non_empty) -> validate_unique_items(L); |
59 |
11456 |
validate_list(L, unique) -> validate_unique_items(L); |
60 |
3477 |
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 |
12707 |
validate_section(L, any) when is_list(L) -> ok. |
65 |
|
|
66 |
|
%% validators |
67 |
|
|
68 |
|
validate_loglevel(Level) -> |
69 |
93 |
mongoose_logs:loglevel_keyword_to_number(Level). |
70 |
|
|
71 |
1016 |
validate_non_empty_binary(Value) when is_binary(Value), Value =/= <<>> -> ok. |
72 |
|
|
73 |
|
validate_unique_items(Items) -> |
74 |
11499 |
L = sets:size(sets:from_list(Items)), |
75 |
11499 |
L = length(Items). |
76 |
|
|
77 |
|
validate_module(Mod) -> |
78 |
2290 |
case code:ensure_loaded(Mod) of |
79 |
|
{module, _} -> |
80 |
2290 |
ok; |
81 |
|
Other -> |
82 |
:-( |
error(#{what => module_not_found, module => Mod, reason => Other}) |
83 |
|
end. |
84 |
|
|
85 |
1524 |
validate_positive_integer(Value) when is_integer(Value), Value > 0 -> ok. |
86 |
|
|
87 |
43 |
validate_non_negative_integer(Value) when is_integer(Value), Value >= 0 -> ok. |
88 |
|
|
89 |
651 |
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 |
322 |
validate_positive_integer_or_infinity(Value) when is_integer(Value), Value > 0 -> ok; |
93 |
93 |
validate_positive_integer_or_infinity(infinity) -> ok. |
94 |
|
|
95 |
|
validate_enum(Value, Values) -> |
96 |
1629 |
case lists:member(Value, Values) of |
97 |
|
true -> |
98 |
1629 |
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 |
644 |
{ok, _} = inet:parse_address(Value). |
107 |
|
|
108 |
1295 |
validate_port(Value) when is_integer(Value), Value >= 0, Value =< 65535 -> ok. |
109 |
|
|
110 |
3520 |
validate_non_empty_atom(Value) when is_atom(Value), Value =/= '' -> ok. |
111 |
|
|
112 |
1295 |
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_subdomain_template(SubdomainTemplate) -> |
126 |
93 |
case mongoose_subdomain_utils:make_subdomain_pattern(SubdomainTemplate) of |
127 |
|
{fqdn, Domain} -> |
128 |
:-( |
validate_domain(Domain); |
129 |
|
Pattern -> |
130 |
93 |
Domain = binary_to_list(mongoose_subdomain_utils:get_fqdn(Pattern, <<"example.com">>)), |
131 |
93 |
case inet_parse:domain(Domain) of |
132 |
|
true -> |
133 |
93 |
ok; |
134 |
|
false -> |
135 |
:-( |
error(#{what => validate_subdomain_template_failed, |
136 |
|
text => <<"Invalid subdomain template">>, |
137 |
|
subdomain_template => SubdomainTemplate}) |
138 |
|
end |
139 |
|
end. |
140 |
|
|
141 |
|
validate_domain(Domain) when is_binary(Domain) -> |
142 |
:-( |
validate_domain(binary_to_list(Domain)); |
143 |
|
validate_domain(Domain) -> |
144 |
:-( |
validate_domain_name(Domain), |
145 |
:-( |
resolve_domain(Domain). |
146 |
|
|
147 |
|
validate_domain_name(Domain) -> |
148 |
:-( |
case inet_parse:domain(Domain) of |
149 |
|
true -> |
150 |
:-( |
ok; |
151 |
|
false -> |
152 |
:-( |
error(#{what => validate_domain_failed, |
153 |
|
text => <<"Invalid domain name">>, |
154 |
|
domain => Domain}) |
155 |
|
end. |
156 |
|
|
157 |
|
resolve_domain(Domain) -> |
158 |
:-( |
case inet_res:gethostbyname(Domain) of |
159 |
|
{ok, _} -> |
160 |
:-( |
ok; |
161 |
|
{error, Reason} -> %% timeout, nxdomain |
162 |
:-( |
?LOG_WARNING(#{what => cfg_validate_domain, |
163 |
|
reason => Reason, domain => Domain, |
164 |
|
text => <<"Couldn't resolve domain. " |
165 |
:-( |
"It could cause issues with production installations">>}), |
166 |
:-( |
ignore |
167 |
|
end. |
168 |
|
|
169 |
|
|
170 |
|
validate_url(Url) -> |
171 |
:-( |
validate_non_empty_string(Url). |
172 |
|
|
173 |
|
validate_string(Value) -> |
174 |
186 |
is_binary(unicode:characters_to_binary(Value)). |
175 |
|
|
176 |
|
validate_ip_mask_string(IPMaskString) -> |
177 |
186 |
validate_non_empty_string(IPMaskString), |
178 |
186 |
{ok, IPMask} = mongoose_lib:parse_ip_netmask(IPMaskString), |
179 |
186 |
validate_ip_mask(IPMask). |
180 |
|
|
181 |
|
validate_ip_mask({IP, Mask}) -> |
182 |
186 |
validate_string(inet:ntoa(IP)), |
183 |
186 |
case IP of |
184 |
|
{_,_,_,_} -> |
185 |
186 |
validate_ipv4_mask(Mask); |
186 |
|
_ -> |
187 |
:-( |
validate_ipv6_mask(Mask) |
188 |
|
end. |
189 |
|
|
190 |
|
validate_ipv4_mask(Mask) -> |
191 |
186 |
validate_range(Mask, 0, 32). |
192 |
|
|
193 |
|
validate_ipv6_mask(Mask) -> |
194 |
:-( |
validate_range(Mask, 0, 128). |
195 |
|
|
196 |
|
validate_network_address(Value) -> |
197 |
:-( |
?LOG_DEBUG(#{what => validate_network_address, |
198 |
:-( |
value => Value}), |
199 |
:-( |
validate_oneof(Value, [fun validate_domain/1, fun validate_ip_address/1]). |
200 |
|
|
201 |
|
validate_oneof(Value, Funs) -> |
202 |
:-( |
Results = [safe_call_validator(F, Value) || F <- Funs], |
203 |
:-( |
case lists:any(fun(R) -> R =:= ok end, Results) of |
204 |
|
true -> |
205 |
:-( |
ok; |
206 |
|
false -> |
207 |
:-( |
error(#{what => validate_oneof_failed, |
208 |
|
validation_results => Results}) |
209 |
|
end. |
210 |
|
|
211 |
|
safe_call_validator(F, Value) -> |
212 |
:-( |
try |
213 |
:-( |
F(Value), |
214 |
:-( |
ok |
215 |
|
catch error:Reason:Stacktrace -> |
216 |
:-( |
#{reason => Reason, stacktrace => Stacktrace} |
217 |
|
end. |
218 |
|
|
219 |
|
validate_range(Value, Min, Max) when Value >= Min, Value =< Max -> |
220 |
186 |
ok. |
221 |
|
|
222 |
|
validate_filename(Filename) -> |
223 |
941 |
case file:read_file_info(Filename) of |
224 |
|
{ok, _} -> |
225 |
941 |
ok; |
226 |
|
Reason -> |
227 |
:-( |
error(#{what => invalid_filename, filename => Filename, reason => Reason}) |
228 |
|
end. |
229 |
|
|
230 |
|
validate_dirname(Dirname) -> |
231 |
:-( |
case file:list_dir(Dirname) of |
232 |
|
{ok, _} -> |
233 |
:-( |
ok; |
234 |
|
Reason -> |
235 |
:-( |
error(#{what => invalid_dirname, dirname => Dirname, reason => Reason}) |
236 |
|
end. |