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