/src/dovecot/src/lib/wildcard-match.c
Line | Count | Source |
1 | | /* |
2 | | * This code would not have been possible without the prior work and |
3 | | * suggestions of various sourced. Special thanks to Robey for |
4 | | * all his time/help tracking down bugs and his ever-helpful advice. |
5 | | * |
6 | | * 04/09: Fixed the "*\*" against "*a" bug (caused an endless loop) |
7 | | * |
8 | | * Chris Fuller (aka Fred1@IRC & Fwitz@IRC) |
9 | | * crf@cfox.bchs.uh.edu |
10 | | * |
11 | | * I hereby release this code into the public domain |
12 | | * |
13 | | */ |
14 | | |
15 | | #include "lib.h" |
16 | | #include "str.h" |
17 | | #include "wildcard-match.h" |
18 | | |
19 | | #include <ctype.h> |
20 | | |
21 | 0 | #define WILDS '*' /* matches 0 or more characters (including spaces) */ |
22 | 0 | #define WILDQ '?' /* matches exactly one character */ |
23 | 0 | #define WILDE '\\' /* escapes one wildcard */ |
24 | | |
25 | 0 | #define NOMATCH 0 |
26 | 0 | #define MATCH (match+sofar) |
27 | | |
28 | | static bool is_escaped(const char *p, const char *start) |
29 | 0 | { |
30 | 0 | bool is_escaped = FALSE; |
31 | 0 | while (p > start && p[-1] == WILDE) { |
32 | 0 | is_escaped = !is_escaped; |
33 | 0 | p--; |
34 | 0 | } |
35 | 0 | return is_escaped; |
36 | 0 | } |
37 | | |
38 | | static int |
39 | | wildcard_match_int(const char *data, const char *mask, bool icase, bool escaped) |
40 | 0 | { |
41 | 0 | const char *ma = mask, *na = data, *lsm = NULL, *lsn = NULL; |
42 | 0 | int match = 1; |
43 | 0 | int sofar = 0; |
44 | |
|
45 | 0 | if (na[0] == '\0') { |
46 | | /* empty string can match only "*" wildcard(s) */ |
47 | 0 | while (ma[0] == '*') ma++; |
48 | 0 | return ma[0] == '\0' ? MATCH : NOMATCH; |
49 | 0 | } |
50 | | /* find the end of each string */ |
51 | 0 | while (*(mask++) != '\0'); |
52 | 0 | mask-=2; |
53 | 0 | while (*(data++) != '\0'); |
54 | 0 | data-=2; |
55 | |
|
56 | 0 | while (data >= na) { |
57 | | /* If the mask runs out of chars before the string, fall back on |
58 | | * a wildcard or fail. */ |
59 | 0 | if (mask < ma) { |
60 | 0 | if (lsm != NULL) { |
61 | 0 | data = --lsn; |
62 | 0 | mask = lsm; |
63 | 0 | if (data < na) |
64 | 0 | lsm = NULL; |
65 | 0 | sofar = 0; |
66 | 0 | } |
67 | 0 | else |
68 | 0 | return NOMATCH; |
69 | 0 | } |
70 | | |
71 | 0 | switch (*mask) { |
72 | 0 | case WILDE: |
73 | 0 | if (escaped && is_escaped(mask, ma)) { |
74 | 0 | if (*mask != *data) |
75 | 0 | goto nomatch; |
76 | 0 | mask -= 2; |
77 | 0 | data--; |
78 | 0 | sofar++; |
79 | 0 | continue; |
80 | 0 | } |
81 | 0 | break; |
82 | 0 | case WILDS: /* Matches anything */ |
83 | 0 | if (escaped && is_escaped(mask, ma)) { |
84 | 0 | if (*mask != *data) |
85 | 0 | goto nomatch; |
86 | 0 | mask -= 2; |
87 | 0 | data--; |
88 | 0 | sofar++; |
89 | 0 | continue; |
90 | 0 | } |
91 | 0 | do |
92 | 0 | mask--; /* Zap redundant wilds */ |
93 | 0 | while ((mask >= ma) && (*mask == WILDS) && |
94 | 0 | (!escaped || !is_escaped(mask, ma))); |
95 | 0 | lsm = mask; |
96 | 0 | lsn = data; |
97 | 0 | match += sofar; |
98 | 0 | sofar = 0; /* Update fallback pos */ |
99 | 0 | if (mask < ma) |
100 | 0 | return MATCH; |
101 | 0 | continue; /* Next char, please */ |
102 | 0 | case WILDQ: |
103 | 0 | if (escaped && is_escaped(mask, ma)) { |
104 | 0 | if (*mask != *data) |
105 | 0 | goto nomatch; |
106 | 0 | mask -= 2; |
107 | 0 | data--; |
108 | 0 | sofar++; |
109 | 0 | continue; |
110 | 0 | } |
111 | 0 | mask--; |
112 | 0 | data--; |
113 | 0 | continue; /* '?' always matches */ |
114 | 0 | } |
115 | 0 | if (icase ? (i_toupper(*mask) == i_toupper(*data)) : |
116 | 0 | (*mask == *data)) { /* If matching char */ |
117 | 0 | mask--; |
118 | 0 | data--; |
119 | 0 | sofar++; /* Tally the match */ |
120 | 0 | continue; /* Next char, please */ |
121 | 0 | } |
122 | 0 | nomatch: |
123 | 0 | if (lsm != NULL) { /* To to fallback on '*' */ |
124 | 0 | data = --lsn; |
125 | 0 | mask = lsm; |
126 | 0 | if (data < na) |
127 | 0 | lsm = NULL; /* Rewind to saved pos */ |
128 | 0 | sofar = 0; |
129 | 0 | continue; /* Next char, please */ |
130 | 0 | } |
131 | 0 | return NOMATCH; /* No fallback=No match */ |
132 | 0 | } |
133 | 0 | while ((mask >= ma) && (*mask == WILDS) && |
134 | 0 | (!escaped || !is_escaped(mask, ma))) |
135 | 0 | mask--; /* Zap leftover %s & *s */ |
136 | 0 | return (mask >= ma) ? NOMATCH : MATCH; /* Start of both = match */ |
137 | 0 | } |
138 | | |
139 | | bool wildcard_match(const char *data, const char *mask) |
140 | 0 | { |
141 | 0 | return wildcard_match_int(data, mask, FALSE, FALSE) != 0; |
142 | 0 | } |
143 | | |
144 | | bool wildcard_match_icase(const char *data, const char *mask) |
145 | 0 | { |
146 | 0 | return wildcard_match_int(data, mask, TRUE, FALSE) != 0; |
147 | 0 | } |
148 | | |
149 | | bool wildcard_match_escaped(const char *data, const char *mask) |
150 | 0 | { |
151 | 0 | return wildcard_match_int(data, mask, FALSE, TRUE) != 0; |
152 | 0 | } |
153 | | |
154 | | bool wildcard_match_escaped_icase(const char *data, const char *mask) |
155 | 0 | { |
156 | 0 | return wildcard_match_int(data, mask, TRUE, TRUE) != 0; |
157 | 0 | } |
158 | | |
159 | | bool wildcard_is_escaped_literal(const char *mask) |
160 | 0 | { |
161 | 0 | const char *p = mask; |
162 | |
|
163 | 0 | while ((p = strpbrk(p, "*?\\")) != NULL) { |
164 | 0 | if (*p != '\\') |
165 | 0 | return FALSE; |
166 | 0 | if (p[1] == '\0') |
167 | 0 | break; |
168 | 0 | p += 2; |
169 | 0 | } |
170 | 0 | return TRUE; |
171 | 0 | } |
172 | | |
173 | | const char *wildcard_str_escape(const char *str) |
174 | 0 | { |
175 | 0 | const char *p = strpbrk(str, "*?\\\"'"); |
176 | 0 | if (p == NULL) |
177 | 0 | return str; |
178 | | |
179 | 0 | string_t *esc = t_str_new((p - str) + strlen(p) + 8); |
180 | 0 | do { |
181 | 0 | str_append_data(esc, str, p - str); |
182 | 0 | str_append_c(esc, '\\'); |
183 | 0 | str_append_c(esc, *p); |
184 | |
|
185 | 0 | str = p + 1; |
186 | 0 | p = strpbrk(str, "*?\\\"'"); |
187 | 0 | } while (p != NULL); |
188 | 0 | str_append(esc, str); |
189 | 0 | return str_c(esc); |
190 | 0 | } |