/src/nss-nspr/nss/lib/util/portreg.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
2 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
4 | | |
5 | | /* |
6 | | * shexp.c: shell-like wildcard match routines |
7 | | * |
8 | | * See shexp.h for public documentation. |
9 | | */ |
10 | | |
11 | | #include "seccomon.h" |
12 | | #include "portreg.h" |
13 | | |
14 | | /* ----------------------------- shexp_valid ------------------------------ */ |
15 | | |
16 | | static int |
17 | | _valid_subexp(const char *exp, char stop1, char stop2) |
18 | 0 | { |
19 | 0 | register int x; |
20 | 0 | int nsc = 0; /* Number of special characters */ |
21 | 0 | int np; /* Number of pipe characters in union */ |
22 | 0 | int tld = 0; /* Number of tilde characters */ |
23 | |
|
24 | 0 | for (x = 0; exp[x] && (exp[x] != stop1) && (exp[x] != stop2); ++x) { |
25 | 0 | switch (exp[x]) { |
26 | 0 | case '~': |
27 | 0 | if (tld) /* at most one exclusion */ |
28 | 0 | return INVALID_SXP; |
29 | 0 | if (stop1) /* no exclusions within unions */ |
30 | 0 | return INVALID_SXP; |
31 | 0 | if (!exp[x + 1]) /* exclusion cannot be last character */ |
32 | 0 | return INVALID_SXP; |
33 | 0 | if (!x) /* exclusion cannot be first character */ |
34 | 0 | return INVALID_SXP; |
35 | 0 | ++tld; |
36 | | /* fall through */ |
37 | 0 | case '*': |
38 | 0 | case '?': |
39 | 0 | case '$': |
40 | 0 | ++nsc; |
41 | 0 | break; |
42 | 0 | case '[': |
43 | 0 | ++nsc; |
44 | 0 | if ((!exp[++x]) || (exp[x] == ']')) |
45 | 0 | return INVALID_SXP; |
46 | 0 | for (; exp[x] && (exp[x] != ']'); ++x) { |
47 | 0 | if (exp[x] == '\\' && !exp[++x]) |
48 | 0 | return INVALID_SXP; |
49 | 0 | } |
50 | 0 | if (!exp[x]) |
51 | 0 | return INVALID_SXP; |
52 | 0 | break; |
53 | 0 | case '(': |
54 | 0 | ++nsc; |
55 | 0 | if (stop1) /* no nested unions */ |
56 | 0 | return INVALID_SXP; |
57 | 0 | np = -1; |
58 | 0 | do { |
59 | 0 | int t = _valid_subexp(&exp[++x], ')', '|'); |
60 | 0 | if (t == 0 || t == INVALID_SXP) |
61 | 0 | return INVALID_SXP; |
62 | 0 | x += t; |
63 | 0 | if (!exp[x]) |
64 | 0 | return INVALID_SXP; |
65 | 0 | ++np; |
66 | 0 | } while (exp[x] == '|'); |
67 | 0 | if (np < 1) /* must be at least one pipe */ |
68 | 0 | return INVALID_SXP; |
69 | 0 | break; |
70 | 0 | case ')': |
71 | 0 | case '|': |
72 | 0 | case ']': |
73 | 0 | return INVALID_SXP; |
74 | 0 | case '\\': |
75 | 0 | ++nsc; |
76 | 0 | if (!exp[++x]) |
77 | 0 | return INVALID_SXP; |
78 | 0 | break; |
79 | 0 | default: |
80 | 0 | break; |
81 | 0 | } |
82 | 0 | } |
83 | 0 | if ((!stop1) && (!nsc)) /* must be at least one special character */ |
84 | 0 | return NON_SXP; |
85 | 0 | return ((exp[x] == stop1 || exp[x] == stop2) ? x : INVALID_SXP); |
86 | 0 | } |
87 | | |
88 | | int |
89 | | PORT_RegExpValid(const char *exp) |
90 | 0 | { |
91 | 0 | int x; |
92 | |
|
93 | 0 | x = _valid_subexp(exp, '\0', '\0'); |
94 | 0 | return (x < 0 ? x : VALID_SXP); |
95 | 0 | } |
96 | | |
97 | | /* ----------------------------- shexp_match ----------------------------- */ |
98 | | |
99 | 0 | #define MATCH 0 |
100 | 0 | #define NOMATCH 1 |
101 | 0 | #define ABORTED -1 |
102 | | |
103 | | static int |
104 | | _shexp_match(const char *str, const char *exp, PRBool case_insensitive, |
105 | | unsigned int level); |
106 | | |
107 | | /* Count characters until we reach a NUL character or either of the |
108 | | * two delimiter characters, stop1 or stop2. If we encounter a bracketed |
109 | | * expression, look only for NUL or ']' inside it. Do not look for stop1 |
110 | | * or stop2 inside it. Return ABORTED if bracketed expression is unterminated. |
111 | | * Handle all escaping. |
112 | | * Return index in input string of first stop found, or ABORTED if not found. |
113 | | * If "dest" is non-NULL, copy counted characters to it and NUL terminate. |
114 | | */ |
115 | | static int |
116 | | _scan_and_copy(const char *exp, char stop1, char stop2, char *dest) |
117 | 0 | { |
118 | 0 | register int sx; /* source index */ |
119 | 0 | register char cc; |
120 | |
|
121 | 0 | for (sx = 0; (cc = exp[sx]) && cc != stop1 && cc != stop2; sx++) { |
122 | 0 | if (cc == '\\') { |
123 | 0 | if (!exp[++sx]) |
124 | 0 | return ABORTED; /* should be impossible */ |
125 | 0 | } else if (cc == '[') { |
126 | 0 | while ((cc = exp[++sx]) && cc != ']') { |
127 | 0 | if (cc == '\\' && !exp[++sx]) |
128 | 0 | return ABORTED; |
129 | 0 | } |
130 | 0 | if (!cc) |
131 | 0 | return ABORTED; /* should be impossible */ |
132 | 0 | } |
133 | 0 | } |
134 | 0 | if (dest && sx) { |
135 | | /* Copy all but the closing delimiter. */ |
136 | 0 | memcpy(dest, exp, sx); |
137 | 0 | dest[sx] = 0; |
138 | 0 | } |
139 | 0 | return cc ? sx : ABORTED; /* index of closing delimiter */ |
140 | 0 | } |
141 | | |
142 | | /* On input, exp[0] is the opening parenthesis of a union. |
143 | | * See if any of the alternatives in the union matches as a pattern. |
144 | | * The strategy is to take each of the alternatives, in turn, and append |
145 | | * the rest of the expression (after the closing ')' that marks the end of |
146 | | * this union) to that alternative, and then see if the resultant expression |
147 | | * matches the input string. Repeat this until some alternative matches, |
148 | | * or we have an abort. |
149 | | */ |
150 | | static int |
151 | | _handle_union(const char *str, const char *exp, PRBool case_insensitive, |
152 | | unsigned int level) |
153 | 0 | { |
154 | 0 | register int sx; /* source index */ |
155 | 0 | int cp; /* source index of closing parenthesis */ |
156 | 0 | int count; |
157 | 0 | int ret = NOMATCH; |
158 | 0 | char *e2; |
159 | | |
160 | | /* Find the closing parenthesis that ends this union in the expression */ |
161 | 0 | cp = _scan_and_copy(exp, ')', '\0', NULL); |
162 | 0 | if (cp == ABORTED || cp < 4) /* must be at least "(a|b" before ')' */ |
163 | 0 | return ABORTED; |
164 | 0 | ++cp; /* now index of char after closing parenthesis */ |
165 | 0 | e2 = (char *)PORT_Alloc(1 + strlen(exp)); |
166 | 0 | if (!e2) |
167 | 0 | return ABORTED; |
168 | 0 | for (sx = 1;; ++sx) { |
169 | | /* Here, exp[sx] is one character past the preceding '(' or '|'. */ |
170 | | /* Copy everything up to the next delimiter to e2 */ |
171 | 0 | count = _scan_and_copy(exp + sx, ')', '|', e2); |
172 | 0 | if (count == ABORTED || !count) { |
173 | 0 | ret = ABORTED; |
174 | 0 | break; |
175 | 0 | } |
176 | 0 | sx += count; |
177 | | /* Append everything after closing parenthesis to e2. This is safe. */ |
178 | 0 | strcpy(e2 + count, exp + cp); |
179 | 0 | ret = _shexp_match(str, e2, case_insensitive, level + 1); |
180 | 0 | if (ret != NOMATCH || !exp[sx] || exp[sx] == ')') |
181 | 0 | break; |
182 | 0 | } |
183 | 0 | PORT_Free(e2); |
184 | 0 | if (sx < 2) |
185 | 0 | ret = ABORTED; |
186 | 0 | return ret; |
187 | 0 | } |
188 | | |
189 | | /* returns 1 if val is in range from start..end, case insensitive. */ |
190 | | static int |
191 | | _is_char_in_range(int start, int end, int val) |
192 | 0 | { |
193 | 0 | char map[256]; |
194 | 0 | memset(map, 0, sizeof map); |
195 | 0 | while (start <= end) |
196 | 0 | map[tolower(start++)] = 1; |
197 | 0 | return map[tolower(val)]; |
198 | 0 | } |
199 | | |
200 | | static int |
201 | | _shexp_match(const char *str, const char *exp, PRBool case_insensitive, |
202 | | unsigned int level) |
203 | 0 | { |
204 | 0 | register int x; /* input string index */ |
205 | 0 | register int y; /* expression index */ |
206 | 0 | int ret, neg; |
207 | |
|
208 | 0 | if (level > 20) /* Don't let the stack get too deep. */ |
209 | 0 | return ABORTED; |
210 | 0 | for (x = 0, y = 0; exp[y]; ++y, ++x) { |
211 | 0 | if ((!str[x]) && (exp[y] != '$') && (exp[y] != '*')) { |
212 | 0 | return NOMATCH; |
213 | 0 | } |
214 | 0 | switch (exp[y]) { |
215 | 0 | case '$': |
216 | 0 | if (str[x]) |
217 | 0 | return NOMATCH; |
218 | 0 | --x; /* we don't want loop to increment x */ |
219 | 0 | break; |
220 | 0 | case '*': |
221 | 0 | while (exp[++y] == '*') { |
222 | 0 | } |
223 | 0 | if (!exp[y]) |
224 | 0 | return MATCH; |
225 | 0 | while (str[x]) { |
226 | 0 | ret = _shexp_match(&str[x++], &exp[y], case_insensitive, |
227 | 0 | level + 1); |
228 | 0 | switch (ret) { |
229 | 0 | case NOMATCH: |
230 | 0 | continue; |
231 | 0 | case ABORTED: |
232 | 0 | return ABORTED; |
233 | 0 | default: |
234 | 0 | return MATCH; |
235 | 0 | } |
236 | 0 | } |
237 | 0 | if ((exp[y] == '$') && (exp[y + 1] == '\0') && (!str[x])) |
238 | 0 | return MATCH; |
239 | 0 | else |
240 | 0 | return NOMATCH; |
241 | 0 | case '[': { |
242 | 0 | int start, end = 0, i; |
243 | 0 | neg = ((exp[++y] == '^') && (exp[y + 1] != ']')); |
244 | 0 | if (neg) |
245 | 0 | ++y; |
246 | 0 | i = y; |
247 | 0 | start = (unsigned char)(exp[i++]); |
248 | 0 | if (start == '\\') |
249 | 0 | start = (unsigned char)(exp[i++]); |
250 | 0 | if (isalnum(start) && exp[i++] == '-') { |
251 | 0 | end = (unsigned char)(exp[i++]); |
252 | 0 | if (end == '\\') |
253 | 0 | end = (unsigned char)(exp[i++]); |
254 | 0 | } |
255 | 0 | if (isalnum(end) && exp[i] == ']') { |
256 | | /* This is a range form: a-b */ |
257 | 0 | int val = (unsigned char)(str[x]); |
258 | 0 | if (end < start) { /* swap them */ |
259 | 0 | start ^= end; |
260 | 0 | end ^= start; |
261 | 0 | start ^= end; |
262 | 0 | } |
263 | 0 | if (case_insensitive && isalpha(val)) { |
264 | 0 | val = _is_char_in_range(start, end, val); |
265 | 0 | if (neg == val) |
266 | 0 | return NOMATCH; |
267 | 0 | } else if (neg != ((val < start) || (val > end))) { |
268 | 0 | return NOMATCH; |
269 | 0 | } |
270 | 0 | y = i; |
271 | 0 | } else { |
272 | | /* Not range form */ |
273 | 0 | int matched = 0; |
274 | 0 | for (; exp[y] != ']'; y++) { |
275 | 0 | if (exp[y] == '\\') |
276 | 0 | ++y; |
277 | 0 | if (case_insensitive) { |
278 | 0 | matched |= (toupper((unsigned char)str[x]) == |
279 | 0 | toupper((unsigned char)exp[y])); |
280 | 0 | } else { |
281 | 0 | matched |= (str[x] == exp[y]); |
282 | 0 | } |
283 | 0 | } |
284 | 0 | if (neg == matched) |
285 | 0 | return NOMATCH; |
286 | 0 | } |
287 | 0 | } break; |
288 | 0 | case '(': |
289 | 0 | if (!exp[y + 1]) |
290 | 0 | return ABORTED; |
291 | 0 | return _handle_union(&str[x], &exp[y], case_insensitive, level); |
292 | 0 | case '?': |
293 | 0 | break; |
294 | 0 | case '|': |
295 | 0 | case ']': |
296 | 0 | case ')': |
297 | 0 | return ABORTED; |
298 | 0 | case '\\': |
299 | 0 | ++y; |
300 | | /* fall through */ |
301 | 0 | default: |
302 | 0 | if (case_insensitive) { |
303 | 0 | if (toupper((unsigned char)str[x]) != toupper((unsigned char)exp[y])) |
304 | 0 | return NOMATCH; |
305 | 0 | } else { |
306 | 0 | if (str[x] != exp[y]) |
307 | 0 | return NOMATCH; |
308 | 0 | } |
309 | 0 | break; |
310 | 0 | } |
311 | 0 | } |
312 | 0 | return (str[x] ? NOMATCH : MATCH); |
313 | 0 | } |
314 | | |
315 | | static int |
316 | | port_RegExpMatch(const char *str, const char *xp, PRBool case_insensitive) |
317 | 0 | { |
318 | 0 | char *exp = 0; |
319 | 0 | int x, ret = MATCH; |
320 | |
|
321 | 0 | if (!strchr(xp, '~')) |
322 | 0 | return _shexp_match(str, xp, case_insensitive, 0); |
323 | | |
324 | 0 | exp = PORT_Strdup(xp); |
325 | 0 | if (!exp) |
326 | 0 | return NOMATCH; |
327 | | |
328 | 0 | x = _scan_and_copy(exp, '~', '\0', NULL); |
329 | 0 | if (x != ABORTED && exp[x] == '~') { |
330 | 0 | exp[x++] = '\0'; |
331 | 0 | ret = _shexp_match(str, &exp[x], case_insensitive, 0); |
332 | 0 | switch (ret) { |
333 | 0 | case NOMATCH: |
334 | 0 | ret = MATCH; |
335 | 0 | break; |
336 | 0 | case MATCH: |
337 | 0 | ret = NOMATCH; |
338 | 0 | break; |
339 | 0 | default: |
340 | 0 | break; |
341 | 0 | } |
342 | 0 | } |
343 | 0 | if (ret == MATCH) |
344 | 0 | ret = _shexp_match(str, exp, case_insensitive, 0); |
345 | |
|
346 | 0 | PORT_Free(exp); |
347 | 0 | return ret; |
348 | 0 | } |
349 | | |
350 | | /* ------------------------------ shexp_cmp ------------------------------- */ |
351 | | |
352 | | int |
353 | | PORT_RegExpSearch(const char *str, const char *exp) |
354 | 0 | { |
355 | 0 | switch (PORT_RegExpValid(exp)) { |
356 | 0 | case INVALID_SXP: |
357 | 0 | return -1; |
358 | 0 | case NON_SXP: |
359 | 0 | return (strcmp(exp, str) ? 1 : 0); |
360 | 0 | default: |
361 | 0 | return port_RegExpMatch(str, exp, PR_FALSE); |
362 | 0 | } |
363 | 0 | } |
364 | | |
365 | | int |
366 | | PORT_RegExpCaseSearch(const char *str, const char *exp) |
367 | 0 | { |
368 | 0 | switch (PORT_RegExpValid(exp)) { |
369 | 0 | case INVALID_SXP: |
370 | 0 | return -1; |
371 | 0 | case NON_SXP: |
372 | 0 | return (PORT_Strcasecmp(exp, str) ? 1 : 0); |
373 | 0 | default: |
374 | 0 | return port_RegExpMatch(str, exp, PR_TRUE); |
375 | 0 | } |
376 | 0 | } |