xref: /f-stack/tools/compat/fnmatch.c (revision d4a07e70)
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  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. Neither the name of the University nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/cdefs.h>
36 #include <sys/cdefs.h>
37 #ifndef FSTACK
38 __FBSDID("$FreeBSD$");
39 #endif
40 
41 /*
42  * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6.
43  * Compares a filename or pathname to a pattern.
44  */
45 
46 #include <sys/param.h>
47 #ifndef FSTACK
48 #include <sys/ctype.h>
49 #include <sys/libkern.h>
50 #else
51 #include <string.h>
52 #include <ctype.h>
53 #include <stdint.h>
54 
55 /* fnmatch() return values. */
56 #define	FNM_NOMATCH	1	/* Match failed. */
57 
58 /* fnmatch() flags. */
59 #define	FNM_NOESCAPE	0x01	/* Disable backslash escaping. */
60 #define	FNM_PATHNAME	0x02	/* Slash must be matched by slash. */
61 #define	FNM_PERIOD	0x04	/* Period must be matched by period. */
62 #define	FNM_LEADING_DIR	0x08	/* Ignore /<tail> after Imatch. */
63 #define	FNM_CASEFOLD	0x10	/* Case insensitive search. */
64 #define	FNM_IGNORECASE	FNM_CASEFOLD
65 #define	FNM_FILE_NAME	FNM_PATHNAME
66 
67 #endif
68 
69 #define	EOS	'\0'
70 
71 #define RANGE_MATCH     1
72 #define RANGE_NOMATCH   0
73 #define RANGE_ERROR     (-1)
74 
75 
76 static int rangematch(const char *, char, int, char **);
77 
78 int
fnmatch(const char * pattern,const char * string,int flags)79 fnmatch(const char *pattern, const char *string, int flags)
80 {
81 	const char *stringstart;
82 	char *newp;
83 	char c, test;
84 
85 	for (stringstart = string;;)
86 		switch (c = *pattern++) {
87 		case EOS:
88 			if ((flags & FNM_LEADING_DIR) && *string == '/')
89 				return (0);
90 			return (*string == EOS ? 0 : FNM_NOMATCH);
91 		case '?':
92 			if (*string == EOS)
93 				return (FNM_NOMATCH);
94 			if (*string == '/' && (flags & FNM_PATHNAME))
95 				return (FNM_NOMATCH);
96 			if (*string == '.' && (flags & FNM_PERIOD) &&
97 			    (string == stringstart ||
98 			    ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
99 				return (FNM_NOMATCH);
100 			++string;
101 			break;
102 		case '*':
103 			c = *pattern;
104 			/* Collapse multiple stars. */
105 			while (c == '*')
106 				c = *++pattern;
107 
108 			if (*string == '.' && (flags & FNM_PERIOD) &&
109 			    (string == stringstart ||
110 			    ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
111 				return (FNM_NOMATCH);
112 
113 			/* Optimize for pattern with * at end or before /. */
114 			if (c == EOS)
115 				if (flags & FNM_PATHNAME)
116 					return ((flags & FNM_LEADING_DIR) ||
117 					    strchr(string, '/') == NULL ?
118 					    0 : FNM_NOMATCH);
119 				else
120 					return (0);
121 			else if (c == '/' && flags & FNM_PATHNAME) {
122 				if ((string = strchr(string, '/')) == NULL)
123 					return (FNM_NOMATCH);
124 				break;
125 			}
126 
127 			/* General case, use recursion. */
128 			while ((test = *string) != EOS) {
129 				if (!fnmatch(pattern, string, flags & ~FNM_PERIOD))
130 					return (0);
131 				if (test == '/' && flags & FNM_PATHNAME)
132 					break;
133 				++string;
134 			}
135 			return (FNM_NOMATCH);
136 		case '[':
137 			if (*string == EOS)
138 				return (FNM_NOMATCH);
139 			if (*string == '/' && (flags & FNM_PATHNAME))
140 				return (FNM_NOMATCH);
141 			if (*string == '.' && (flags & FNM_PERIOD) &&
142 			    (string == stringstart ||
143 			    ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
144 				return (FNM_NOMATCH);
145 
146 			switch (rangematch(pattern, *string, flags, &newp)) {
147 			case RANGE_ERROR:
148 				goto norm;
149 			case RANGE_MATCH:
150 				pattern = newp;
151 				break;
152 			case RANGE_NOMATCH:
153 				return (FNM_NOMATCH);
154 			}
155 			++string;
156 			break;
157 		case '\\':
158 			if (!(flags & FNM_NOESCAPE)) {
159 				if ((c = *pattern++) == EOS) {
160 					c = '\\';
161 					--pattern;
162 				}
163 			}
164 			/* FALLTHROUGH */
165 		default:
166 		norm:
167 			if (c == *string)
168 				;
169 			else if ((flags & FNM_CASEFOLD) &&
170 				 (tolower((unsigned char)c) ==
171 				  tolower((unsigned char)*string)))
172 				;
173 			else
174 				return (FNM_NOMATCH);
175 			string++;
176 			break;
177 		}
178 	/* NOTREACHED */
179 }
180 
181 static int
rangematch(const char * pattern,char test,int flags,char ** newp)182 rangematch(const char *pattern, char test, int flags, char **newp)
183 {
184 	int negate, ok;
185 	char c, c2;
186 
187 	/*
188 	 * A bracket expression starting with an unquoted circumflex
189 	 * character produces unspecified results (IEEE 1003.2-1992,
190 	 * 3.13.2).  This implementation treats it like '!', for
191 	 * consistency with the regular expression syntax.
192 	 * J.T. Conklin ([email protected])
193 	 */
194 	if ( (negate = (*pattern == '!' || *pattern == '^')) )
195 		++pattern;
196 
197 	if (flags & FNM_CASEFOLD)
198 		test = tolower((unsigned char)test);
199 
200 	/*
201 	 * A right bracket shall lose its special meaning and represent
202 	 * itself in a bracket expression if it occurs first in the list.
203 	 * -- POSIX.2 2.8.3.2
204 	 */
205 	ok = 0;
206 	c = *pattern++;
207 	do {
208 		if (c == '\\' && !(flags & FNM_NOESCAPE))
209 			c = *pattern++;
210 		if (c == EOS)
211 			return (RANGE_ERROR);
212 
213 		if (c == '/' && (flags & FNM_PATHNAME))
214 			return (RANGE_NOMATCH);
215 
216 		if (flags & FNM_CASEFOLD)
217 			c = tolower((unsigned char)c);
218 
219 		if (*pattern == '-'
220 		    && (c2 = *(pattern+1)) != EOS && c2 != ']') {
221 			pattern += 2;
222 			if (c2 == '\\' && !(flags & FNM_NOESCAPE))
223 				c2 = *pattern++;
224 			if (c2 == EOS)
225 				return (RANGE_ERROR);
226 
227 			if (flags & FNM_CASEFOLD)
228 				c2 = tolower((unsigned char)c2);
229 
230 			if (c <= test && test <= c2)
231 				ok = 1;
232 		} else if (c == test)
233 			ok = 1;
234 	} while ((c = *pattern++) != ']');
235 
236 	*newp = (char *)(uintptr_t)pattern;
237 	return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH);
238 }
239