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