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