Commit | Line | Data |
---|---|---|
e85a0294 PP |
1 | /* |
2 | * Copyright (C) 2017 - Philippe Proulx <pproulx@efficios.com> | |
3 | * | |
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
5 | * of this software and associated documentation files (the "Software"), to deal | |
6 | * in the Software without restriction, including without limitation the rights | |
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
8 | * copies of the Software, and to permit persons to whom the Software is | |
9 | * furnished to do so, subject to the following conditions: | |
10 | * | |
11 | * The above copyright notice and this permission notice shall be included in | |
12 | * all copies or substantial portions of the Software. | |
13 | * | |
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
20 | * SOFTWARE. | |
21 | */ | |
22 | ||
23 | #define _LGPL_SOURCE | |
24 | #include <stdlib.h> | |
25 | #include <string.h> | |
26 | #include <stdbool.h> | |
27 | #include <assert.h> | |
28 | ||
29 | #include "string-utils.h" | |
30 | ||
31 | enum star_glob_pattern_type_flags { | |
32 | STAR_GLOB_PATTERN_TYPE_FLAG_NONE = 0, | |
33 | STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN = 1, | |
34 | STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY = 2, | |
35 | }; | |
36 | ||
37 | static | |
38 | enum star_glob_pattern_type_flags strutils_test_glob_pattern(const char *pattern) | |
39 | { | |
40 | enum star_glob_pattern_type_flags ret = | |
41 | STAR_GLOB_PATTERN_TYPE_FLAG_NONE; | |
42 | const char *p; | |
43 | ||
44 | assert(pattern); | |
45 | ||
46 | for (p = pattern; *p != '\0'; p++) { | |
47 | switch (*p) { | |
48 | case '*': | |
49 | ret = STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN; | |
50 | ||
51 | if (p[1] == '\0') { | |
52 | ret |= STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY; | |
53 | } | |
54 | ||
55 | goto end; | |
56 | case '\\': | |
57 | p++; | |
58 | ||
59 | if (*p == '\0') { | |
60 | goto end; | |
61 | } | |
62 | break; | |
63 | default: | |
64 | break; | |
65 | } | |
66 | } | |
67 | ||
68 | end: | |
69 | return ret; | |
70 | } | |
71 | ||
72 | /* | |
73 | * Returns true if `pattern` is a star-only globbing pattern, that is, | |
74 | * it contains at least one non-escaped `*`. | |
75 | */ | |
76 | bool strutils_is_star_glob_pattern(const char *pattern) | |
77 | { | |
78 | return strutils_test_glob_pattern(pattern) & | |
79 | STAR_GLOB_PATTERN_TYPE_FLAG_PATTERN; | |
80 | } | |
81 | ||
82 | /* | |
83 | * Returns true if `pattern` is a globbing pattern with a globbing, | |
84 | * non-escaped star only at its very end. | |
85 | */ | |
86 | bool strutils_is_star_at_the_end_only_glob_pattern(const char *pattern) | |
87 | { | |
88 | return strutils_test_glob_pattern(pattern) & | |
89 | STAR_GLOB_PATTERN_TYPE_FLAG_END_ONLY; | |
90 | } | |
91 | ||
92 | static inline | |
93 | bool at_end_of_pattern(const char *p, const char *pattern, size_t pattern_len) | |
94 | { | |
95 | return (p - pattern) == pattern_len || *p == '\0'; | |
96 | } | |
97 | ||
98 | /* | |
99 | * Globbing matching function with the star feature only (`?` and | |
100 | * character sets are not supported). This matches `candidate` (plain | |
101 | * string) against `pattern`. A literal star can be escaped with `\` in | |
102 | * `pattern`. | |
103 | * | |
104 | * `pattern_len` or `candidate_len` can be greater than the actual | |
105 | * string length of `pattern` or `candidate` if the string is | |
106 | * null-terminated. | |
107 | */ | |
108 | bool strutils_star_glob_match(const char *pattern, size_t pattern_len, | |
109 | const char *candidate, size_t candidate_len) { | |
110 | const char *retry_c = candidate, *retry_p = pattern, *c, *p; | |
111 | bool got_a_star = false; | |
112 | ||
113 | retry: | |
114 | c = retry_c; | |
115 | p = retry_p; | |
116 | ||
117 | /* | |
118 | * The concept here is to retry a match in the specific case | |
119 | * where we already got a star. The retry position for the | |
120 | * pattern is just after the most recent star, and the retry | |
121 | * position for the candidate is the character following the | |
122 | * last try's first character. | |
123 | * | |
124 | * Example: | |
125 | * | |
126 | * candidate: hi ev every onyx one | |
d0cba1f1 | 127 | * ^ |
e85a0294 | 128 | * pattern: hi*every*one |
d0cba1f1 | 129 | * ^ |
e85a0294 PP |
130 | * |
131 | * candidate: hi ev every onyx one | |
d0cba1f1 | 132 | * ^ |
e85a0294 | 133 | * pattern: hi*every*one |
d0cba1f1 | 134 | * ^ |
e85a0294 PP |
135 | * |
136 | * candidate: hi ev every onyx one | |
d0cba1f1 | 137 | * ^ |
e85a0294 | 138 | * pattern: hi*every*one |
d0cba1f1 | 139 | * ^ |
e85a0294 PP |
140 | * |
141 | * candidate: hi ev every onyx one | |
d0cba1f1 | 142 | * ^ |
e85a0294 | 143 | * pattern: hi*every*one |
d0cba1f1 | 144 | * ^ MISMATCH |
e85a0294 PP |
145 | * |
146 | * candidate: hi ev every onyx one | |
d0cba1f1 | 147 | * ^ |
e85a0294 | 148 | * pattern: hi*every*one |
d0cba1f1 | 149 | * ^ |
e85a0294 PP |
150 | * |
151 | * candidate: hi ev every onyx one | |
d0cba1f1 | 152 | * ^^ |
e85a0294 | 153 | * pattern: hi*every*one |
d0cba1f1 | 154 | * ^^ |
e85a0294 PP |
155 | * |
156 | * candidate: hi ev every onyx one | |
d0cba1f1 | 157 | * ^ ^ |
e85a0294 | 158 | * pattern: hi*every*one |
d0cba1f1 | 159 | * ^ ^ MISMATCH |
e85a0294 PP |
160 | * |
161 | * candidate: hi ev every onyx one | |
d0cba1f1 | 162 | * ^ |
e85a0294 | 163 | * pattern: hi*every*one |
d0cba1f1 | 164 | * ^ MISMATCH |
e85a0294 PP |
165 | * |
166 | * candidate: hi ev every onyx one | |
d0cba1f1 | 167 | * ^ |
e85a0294 | 168 | * pattern: hi*every*one |
d0cba1f1 | 169 | * ^ MISMATCH |
e85a0294 PP |
170 | * |
171 | * candidate: hi ev every onyx one | |
d0cba1f1 | 172 | * ^ |
e85a0294 | 173 | * pattern: hi*every*one |
d0cba1f1 | 174 | * ^ |
e85a0294 PP |
175 | * |
176 | * candidate: hi ev every onyx one | |
d0cba1f1 | 177 | * ^^ |
e85a0294 | 178 | * pattern: hi*every*one |
d0cba1f1 | 179 | * ^^ |
e85a0294 PP |
180 | * |
181 | * candidate: hi ev every onyx one | |
d0cba1f1 | 182 | * ^ ^ |
e85a0294 | 183 | * pattern: hi*every*one |
d0cba1f1 | 184 | * ^ ^ |
e85a0294 PP |
185 | * |
186 | * candidate: hi ev every onyx one | |
d0cba1f1 | 187 | * ^ ^ |
e85a0294 | 188 | * pattern: hi*every*one |
d0cba1f1 | 189 | * ^ ^ |
e85a0294 PP |
190 | * |
191 | * candidate: hi ev every onyx one | |
d0cba1f1 | 192 | * ^ ^ |
e85a0294 | 193 | * pattern: hi*every*one |
d0cba1f1 | 194 | * ^ ^ |
e85a0294 PP |
195 | * |
196 | * candidate: hi ev every onyx one | |
d0cba1f1 | 197 | * ^ |
e85a0294 | 198 | * pattern: hi*every*one |
d0cba1f1 | 199 | * ^ |
e85a0294 PP |
200 | * |
201 | * candidate: hi ev every onyx one | |
d0cba1f1 | 202 | * ^ |
e85a0294 | 203 | * pattern: hi*every*one |
d0cba1f1 | 204 | * ^ MISMATCH |
e85a0294 PP |
205 | * |
206 | * candidate: hi ev every onyx one | |
d0cba1f1 | 207 | * ^ |
e85a0294 | 208 | * pattern: hi*every*one |
d0cba1f1 | 209 | * ^ |
e85a0294 PP |
210 | * |
211 | * candidate: hi ev every onyx one | |
d0cba1f1 | 212 | * ^^ |
e85a0294 | 213 | * pattern: hi*every*one |
d0cba1f1 | 214 | * ^^ |
e85a0294 PP |
215 | * |
216 | * candidate: hi ev every onyx one | |
d0cba1f1 | 217 | * ^ ^ |
e85a0294 | 218 | * pattern: hi*every*one |
d0cba1f1 | 219 | * ^ ^ MISMATCH |
e85a0294 PP |
220 | * |
221 | * candidate: hi ev every onyx one | |
d0cba1f1 | 222 | * ^ |
e85a0294 | 223 | * pattern: hi*every*one |
d0cba1f1 | 224 | * ^ MISMATCH |
e85a0294 PP |
225 | * |
226 | * candidate: hi ev every onyx one | |
d0cba1f1 | 227 | * ^ |
e85a0294 | 228 | * pattern: hi*every*one |
d0cba1f1 | 229 | * ^ MISMATCH |
e85a0294 PP |
230 | * |
231 | * candidate: hi ev every onyx one | |
d0cba1f1 | 232 | * ^ |
e85a0294 | 233 | * pattern: hi*every*one |
d0cba1f1 | 234 | * ^ MISMATCH |
e85a0294 PP |
235 | * |
236 | * candidate: hi ev every onyx one | |
d0cba1f1 | 237 | * ^ |
e85a0294 | 238 | * pattern: hi*every*one |
d0cba1f1 | 239 | * ^ MISMATCH |
e85a0294 PP |
240 | * |
241 | * candidate: hi ev every onyx one | |
d0cba1f1 | 242 | * ^ |
e85a0294 | 243 | * pattern: hi*every*one |
d0cba1f1 | 244 | * ^ |
e85a0294 PP |
245 | * |
246 | * candidate: hi ev every onyx one | |
d0cba1f1 | 247 | * ^^ |
e85a0294 | 248 | * pattern: hi*every*one |
d0cba1f1 | 249 | * ^^ |
e85a0294 PP |
250 | * |
251 | * candidate: hi ev every onyx one | |
d0cba1f1 | 252 | * ^ ^ |
e85a0294 | 253 | * pattern: hi*every*one |
d0cba1f1 | 254 | * ^ ^ |
e85a0294 PP |
255 | * |
256 | * candidate: hi ev every onyx one | |
d0cba1f1 | 257 | * ^ ^ |
e85a0294 | 258 | * pattern: hi*every*one |
d0cba1f1 | 259 | * ^ ^ SUCCESS |
e85a0294 PP |
260 | */ |
261 | while ((c - candidate) < candidate_len && *c != '\0') { | |
262 | assert(*c); | |
263 | ||
264 | if (at_end_of_pattern(p, pattern, pattern_len)) { | |
265 | goto end_of_pattern; | |
266 | } | |
267 | ||
268 | switch (*p) { | |
269 | case '*': | |
270 | got_a_star = true; | |
271 | ||
272 | /* | |
273 | * Our first try starts at the current candidate | |
274 | * character and after the star in the pattern. | |
275 | */ | |
276 | retry_c = c; | |
277 | retry_p = p + 1; | |
278 | ||
279 | if (at_end_of_pattern(retry_p, pattern, pattern_len)) { | |
280 | /* | |
281 | * Star at the end of the pattern at | |
282 | * this point: automatic match. | |
283 | */ | |
284 | return true; | |
285 | } | |
286 | ||
287 | goto retry; | |
288 | case '\\': | |
289 | /* Go to escaped character. */ | |
290 | p++; | |
291 | ||
292 | /* | |
293 | * Fall through the default case which will | |
294 | * compare the escaped character now. | |
295 | */ | |
296 | default: | |
297 | if (at_end_of_pattern(p, pattern, pattern_len) || | |
298 | *c != *p) { | |
299 | end_of_pattern: | |
300 | /* Character mismatch OR end of pattern. */ | |
301 | if (!got_a_star) { | |
302 | /* | |
303 | * We didn't get any star yet, | |
304 | * so this first mismatch | |
305 | * automatically makes the whole | |
306 | * test fail. | |
307 | */ | |
308 | return false; | |
309 | } | |
310 | ||
311 | /* | |
312 | * Next try: next candidate character, | |
313 | * original pattern character (following | |
314 | * the most recent star). | |
315 | */ | |
316 | retry_c++; | |
317 | goto retry; | |
318 | } | |
319 | break; | |
320 | } | |
321 | ||
322 | /* Next pattern and candidate characters. */ | |
323 | c++; | |
324 | p++; | |
325 | } | |
326 | ||
327 | /* | |
328 | * We checked every candidate character and we're still in a | |
329 | * success state: the only pattern character allowed to remain | |
330 | * is a star. | |
331 | */ | |
332 | if (at_end_of_pattern(p, pattern, pattern_len)) { | |
333 | return true; | |
334 | } | |
335 | ||
336 | p++; | |
337 | return p[-1] == '*' && at_end_of_pattern(p, pattern, pattern_len); | |
338 | } |