xref: /freebsd/contrib/libarchive/libarchive_fe/lafe_fnmatch.c (revision 185becb1e1bd2657c156f78aeb52edac05ba5fb5)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 1989, 1993, 1994
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Guido van Rossum.
9  *
10  * Copyright (c) 2011 The FreeBSD Foundation
11  *
12  * Portions of this software were developed by David Chisnall
13  * under sponsorship from the FreeBSD Foundation.
14  *
15  * Redistribution and use in source and binary forms, with or without
16  * modification, are permitted provided that the following conditions
17  * are met:
18  * 1. Redistributions of source code must retain the above copyright
19  *    notice, this list of conditions and the following disclaimer.
20  * 2. Redistributions in binary form must reproduce the above copyright
21  *    notice, this list of conditions and the following disclaimer in the
22  *    documentation and/or other materials provided with the distribution.
23  * 3. Neither the name of the University nor the names of its contributors
24  *    may be used to endorse or promote products derived from this software
25  *    without specific prior written permission.
26  *
27  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37  * SUCH DAMAGE.
38  */
39 
40 #include "lafe_platform.h"
41 
42 #ifndef HAVE_FNMATCH
43 /*
44  * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6.
45  * Compares a filename or pathname to a pattern.
46  */
47 
48 /*
49  * Some notes on multibyte character support:
50  * 1. Patterns with illegal byte sequences match nothing.
51  * 2. Illegal byte sequences in the "string" argument are handled by treating
52  *    them as single-byte characters with a value of the first byte of the
53  *    sequence cast to wchar_t.
54  * 3. Multibyte conversion state objects (mbstate_t) are passed around and
55  *    used for most, but not all, conversions. Further work will be required
56  *    to support state-dependent encodings.
57  */
58 
59 #ifdef HAVE_LIMITS_H
60 #include <limits.h>
61 #endif
62 #ifdef HAVE_STRING_H
63 #include <string.h>
64 #endif
65 #ifdef HAVE_WCHAR_H
66 #include <wchar.h>
67 #endif
68 #ifdef HAVE_WCTYPE_H
69 #include <wctype.h>
70 #endif
71 
72 #include "lafe_fnmatch.h"
73 
74 #define	EOS	'\0'
75 
76 #define RANGE_MATCH     1
77 #define RANGE_NOMATCH   0
78 #define RANGE_ERROR     (-1)
79 
80 static int rangematch(const char *, wchar_t, int, const char **, mbstate_t *);
81 static int fnmatch1(const char *, const char *, const char *, int, mbstate_t,
82 		mbstate_t);
83 
84 int
fnmatch(const char * pattern,const char * string,int flags)85 fnmatch(const char *pattern, const char *string, int flags)
86 {
87 	static const mbstate_t initial;
88 
89 	return (fnmatch1(pattern, string, string, flags, initial, initial));
90 }
91 
92 static int
fnmatch1(const char * pattern,const char * string,const char * stringstart,int flags,mbstate_t patmbs,mbstate_t strmbs)93 fnmatch1(const char *pattern, const char *string, const char *stringstart,
94     int flags, mbstate_t patmbs, mbstate_t strmbs)
95 {
96 	const char *bt_pattern, *bt_string;
97 	mbstate_t bt_patmbs, bt_strmbs;
98 	const char *newp;
99 	char c;
100 	wchar_t pc, sc;
101 	size_t pclen, sclen;
102 
103 	bt_pattern = bt_string = NULL;
104 	for (;;) {
105 		pclen = mbrtowc(&pc, pattern, MB_LEN_MAX, &patmbs);
106 		if (pclen == (size_t)-1 || pclen == (size_t)-2)
107 			return (FNM_NOMATCH);
108 		pattern += pclen;
109 		sclen = mbrtowc(&sc, string, MB_LEN_MAX, &strmbs);
110 		if (sclen == (size_t)-1 || sclen == (size_t)-2) {
111 			sc = (unsigned char)*string;
112 			sclen = 1;
113 			memset(&strmbs, 0, sizeof(strmbs));
114 		}
115 		switch (pc) {
116 		case EOS:
117 			if ((flags & FNM_LEADING_DIR) && sc == '/')
118 				return (0);
119 			if (sc == EOS)
120 				return (0);
121 			goto backtrack;
122 		case '?':
123 			if (sc == EOS)
124 				return (FNM_NOMATCH);
125 			if (sc == '/' && (flags & FNM_PATHNAME))
126 				goto backtrack;
127 			if (sc == '.' && (flags & FNM_PERIOD) &&
128 			    (string == stringstart ||
129 			    ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
130 				goto backtrack;
131 			string += sclen;
132 			break;
133 		case '*':
134 			c = *pattern;
135 			/* Collapse multiple stars. */
136 			while (c == '*')
137 				c = *++pattern;
138 
139 			if (sc == '.' && (flags & FNM_PERIOD) &&
140 			    (string == stringstart ||
141 			    ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
142 				goto backtrack;
143 
144 			/* Optimize for pattern with * at end or before /. */
145 			if (c == EOS)
146 				if (flags & FNM_PATHNAME)
147 					return ((flags & FNM_LEADING_DIR) ||
148 					    strchr(string, '/') == NULL ?
149 					    0 : FNM_NOMATCH);
150 				else
151 					return (0);
152 			else if (c == '/' && flags & FNM_PATHNAME) {
153 				if ((string = strchr(string, '/')) == NULL)
154 					return (FNM_NOMATCH);
155 				break;
156 			}
157 
158 			/*
159 			 * First try the shortest match for the '*' that
160 			 * could work. We can forget any earlier '*' since
161 			 * there is no way having it match more characters
162 			 * can help us, given that we are already here.
163 			 */
164 			bt_pattern = pattern, bt_patmbs = patmbs;
165 			bt_string = string, bt_strmbs = strmbs;
166 			break;
167 		case '[':
168 			if (sc == EOS)
169 				return (FNM_NOMATCH);
170 			if (sc == '/' && (flags & FNM_PATHNAME))
171 				goto backtrack;
172 			if (sc == '.' && (flags & FNM_PERIOD) &&
173 			    (string == stringstart ||
174 			    ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
175 				goto backtrack;
176 
177 			switch (rangematch(pattern, sc, flags, &newp,
178 			    &patmbs)) {
179 			case RANGE_ERROR:
180 				goto norm;
181 			case RANGE_MATCH:
182 				pattern = newp;
183 				break;
184 			case RANGE_NOMATCH:
185 				goto backtrack;
186 			}
187 			string += sclen;
188 			break;
189 		case '\\':
190 			if (!(flags & FNM_NOESCAPE)) {
191 				pclen = mbrtowc(&pc, pattern, MB_LEN_MAX,
192 				    &patmbs);
193 				if (pclen == 0 || pclen == (size_t)-1 ||
194 				    pclen == (size_t)-2)
195 					return (FNM_NOMATCH);
196 				pattern += pclen;
197 			}
198 			/* FALLTHROUGH */
199 		default:
200 		norm:
201 			string += sclen;
202 			if (pc == sc)
203 				;
204 			else if ((flags & FNM_CASEFOLD) &&
205 				 (towlower(pc) == towlower(sc)))
206 				;
207 			else {
208 		backtrack:
209 				/*
210 				 * If we have a mismatch (other than hitting
211 				 * the end of the string), go back to the last
212 				 * '*' seen and have it match one additional
213 				 * character.
214 				 */
215 				if (bt_pattern == NULL)
216 					return (FNM_NOMATCH);
217 				sclen = mbrtowc(&sc, bt_string, MB_LEN_MAX,
218 				    &bt_strmbs);
219 				if (sclen == (size_t)-1 ||
220 				    sclen == (size_t)-2) {
221 					sc = (unsigned char)*bt_string;
222 					sclen = 1;
223 					memset(&bt_strmbs, 0,
224 					    sizeof(bt_strmbs));
225 				}
226 				if (sc == EOS)
227 					return (FNM_NOMATCH);
228 				if (sc == '/' && flags & FNM_PATHNAME)
229 					return (FNM_NOMATCH);
230 				bt_string += sclen;
231 				pattern = bt_pattern, patmbs = bt_patmbs;
232 				string = bt_string, strmbs = bt_strmbs;
233 			}
234 			break;
235 		}
236 	}
237 	/* NOTREACHED */
238 }
239 
240 static int
rangematch(const char * pattern,wchar_t test,int flags,const char ** newp,mbstate_t * patmbs)241 rangematch(const char *pattern, wchar_t test, int flags, const char **newp,
242     mbstate_t *patmbs)
243 {
244 	int negate, ok;
245 	wchar_t s1[2], s2[2], t[2];
246 	size_t pclen;
247 	const char *origpat;
248 
249 	s1[1] = L'\0';
250         s2[1] = L'\0';
251 
252 	t[0] = test;
253 	t[1] = L'\0';
254 
255 	/*
256 	 * A bracket expression starting with an unquoted circumflex
257 	 * character produces unspecified results (IEEE 1003.2-1992,
258 	 * 3.13.2).  This implementation treats it like '!', for
259 	 * consistency with the regular expression syntax.
260 	 * J.T. Conklin (conklin@ngai.kaleida.com)
261 	 */
262 	if ((negate = (*pattern == '!' || *pattern == '^')))
263 		++pattern;
264 
265 	if (flags & FNM_CASEFOLD)
266 		t[0] = towlower(t[0]);
267 
268 	/*
269 	 * A right bracket shall lose its special meaning and represent
270 	 * itself in a bracket expression if it occurs first in the list.
271 	 * -- POSIX.2 2.8.3.2
272 	 */
273 	ok = 0;
274 	origpat = pattern;
275 	for (;;) {
276 		if (*pattern == ']' && pattern > origpat) {
277 			pattern++;
278 			break;
279 		} else if (*pattern == '\0') {
280 			return (RANGE_ERROR);
281 		} else if (*pattern == '/' && (flags & FNM_PATHNAME)) {
282 			return (RANGE_NOMATCH);
283 		} else if (*pattern == '\\' && !(flags & FNM_NOESCAPE))
284 			pattern++;
285 		pclen = mbrtowc(s1, pattern, MB_LEN_MAX, patmbs);
286 		if (pclen == (size_t)-1 || pclen == (size_t)-2)
287 			return (RANGE_NOMATCH);
288 		pattern += pclen;
289 
290 		if (flags & FNM_CASEFOLD)
291 			s1[0] = towlower(s1[0]);
292 
293 		if (*pattern == '-' && *(pattern + 1) != EOS &&
294 		    *(pattern + 1) != ']') {
295 			if (*++pattern == '\\' && !(flags & FNM_NOESCAPE))
296 				if (*pattern != EOS)
297 					pattern++;
298 			pclen = mbrtowc(s2, pattern, MB_LEN_MAX, patmbs);
299 			if (pclen == (size_t)-1 || pclen == (size_t)-2)
300 				return (RANGE_NOMATCH);
301 			pattern += pclen;
302 			if (s2[0] == EOS)
303 				return (RANGE_ERROR);
304 
305 			if (flags & FNM_CASEFOLD)
306 				s2[0] = towlower(s2[0]);
307 
308 			if (wcscoll(s1, t) && wcscoll(t, s2) <= 0)
309 				ok = 1;
310 		} else if (s1[0] == t[0])
311 			ok = 1;
312 	}
313 
314 	*newp = pattern;
315 	return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH);
316 }
317 #endif
318