1 /*
2  * Copyright (C) the libgit2 contributors. All rights reserved.
3  *
4  * This file is part of libgit2, distributed under the GNU GPL v2 with
5  * a Linking Exception. For full terms see the included COPYING file.
6  */
7 
8 /*
9  * This file contains code originally derrived from OpenBSD fnmatch.c
10  *
11  * Copyright (c) 1989, 1993, 1994
12  *      The Regents of the University of California.  All rights reserved.
13  *
14  * This code is derived from software contributed to Berkeley by
15  * Guido van Rossum.
16  *
17  * Redistribution and use in source and binary forms, with or without
18  * modification, are permitted provided that the following conditions
19  * are met:
20  * 1. Redistributions of source code must retain the above copyright
21  *    notice, this list of conditions and the following disclaimer.
22  * 2. Redistributions in binary form must reproduce the above copyright
23  *    notice, this list of conditions and the following disclaimer in the
24  *    documentation and/or other materials provided with the distribution.
25  * 3. Neither the name of the University nor the names of its contributors
26  *    may be used to endorse or promote products derived from this software
27  *    without specific prior written permission.
28  *
29  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
30  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
32  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
33  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
35  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
37  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
38  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
39  * SUCH DAMAGE.
40  */
41 
42 /*
43  * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6.
44  * Compares a filename or pathname to a pattern.
45  */
46 
47 #include "fnmatch.h"
48 
49 #include <ctype.h>
50 #include <stdio.h>
51 #include <string.h>
52 
53 #define EOS		'\0'
54 
55 #define RANGE_MATCH		1
56 #define RANGE_NOMATCH		0
57 #define RANGE_ERROR		(-1)
58 
59 static int rangematch(const char *, char, int, char **);
60 
61 static int
p_fnmatchx(const char * pattern,const char * string,int flags,size_t recurs)62 p_fnmatchx(const char *pattern, const char *string, int flags, size_t recurs)
63 {
64 		const char *stringstart;
65 		char *newp;
66 		char c, test;
67 		int recurs_flags = flags & ~FNM_PERIOD;
68 
69 		if (recurs-- == 0)
70 				return FNM_NORES;
71 
72 		for (stringstart = string;;)
73 				switch (c = *pattern++) {
74 				case EOS:
75 						if ((flags & FNM_LEADING_DIR) && *string == '/')
76 								return (0);
77 						return (*string == EOS ? 0 : FNM_NOMATCH);
78 				case '?':
79 						if (*string == EOS)
80 								return (FNM_NOMATCH);
81 						if (*string == '/' && (flags & FNM_PATHNAME))
82 								return (FNM_NOMATCH);
83 						if (*string == '.' && (flags & FNM_PERIOD) &&
84 							(string == stringstart ||
85 							((flags & FNM_PATHNAME) && *(string - 1) == '/')))
86 								return (FNM_NOMATCH);
87 						++string;
88 						break;
89 				case '*':
90 						c = *pattern;
91 
92 						/* Let '**' override PATHNAME match for this segment.
93 						 * It will be restored if/when we recurse below.
94 						 */
95 						if (c == '*') {
96 							c = *++pattern;
97 							/* star-star-slash is at the end, match by default */
98 							if (c == EOS)
99 								return 0;
100 							/* Double-star must be at end or between slashes */
101 							if (c != '/')
102 								return (FNM_NOMATCH);
103 
104 							c = *++pattern;
105 							do {
106 								int e = p_fnmatchx(pattern, string, recurs_flags, recurs);
107 								if (e != FNM_NOMATCH)
108 									return e;
109 								string = strchr(string, '/');
110 							} while (string++);
111 
112 							/* If we get here, we didn't find a match */
113 							return FNM_NOMATCH;
114 						}
115 
116 						if (*string == '.' && (flags & FNM_PERIOD) &&
117 							(string == stringstart ||
118 							((flags & FNM_PATHNAME) && *(string - 1) == '/')))
119 								return (FNM_NOMATCH);
120 
121 						/* Optimize for pattern with * at end or before /. */
122 						if (c == EOS) {
123 								if (flags & FNM_PATHNAME)
124 										return ((flags & FNM_LEADING_DIR) ||
125 											strchr(string, '/') == NULL ?
126 											0 : FNM_NOMATCH);
127 								else
128 										return (0);
129 						} else if (c == '/' && (flags & FNM_PATHNAME)) {
130 								if ((string = strchr(string, '/')) == NULL)
131 										return (FNM_NOMATCH);
132 								break;
133 						}
134 
135 						/* General case, use recursion. */
136 						while ((test = *string) != EOS) {
137 								int e;
138 
139 								e = p_fnmatchx(pattern, string, recurs_flags, recurs);
140 								if (e != FNM_NOMATCH)
141 										return e;
142 								if (test == '/' && (flags & FNM_PATHNAME))
143 										break;
144 								++string;
145 						}
146 						return (FNM_NOMATCH);
147 				case '[':
148 						if (*string == EOS)
149 								return (FNM_NOMATCH);
150 						if (*string == '/' && (flags & FNM_PATHNAME))
151 								return (FNM_NOMATCH);
152 						if (*string == '.' && (flags & FNM_PERIOD) &&
153 							(string == stringstart ||
154 							((flags & FNM_PATHNAME) && *(string - 1) == '/')))
155 								return (FNM_NOMATCH);
156 
157 						switch (rangematch(pattern, *string, flags, &newp)) {
158 						case RANGE_ERROR:
159 								/* not a good range, treat as normal text */
160 								goto normal;
161 						case RANGE_MATCH:
162 								pattern = newp;
163 								break;
164 						case RANGE_NOMATCH:
165 								return (FNM_NOMATCH);
166 						}
167 						++string;
168 						break;
169 				case '\\':
170 						if (!(flags & FNM_NOESCAPE)) {
171 								if ((c = *pattern++) == EOS) {
172 										c = '\\';
173 										--pattern;
174 								}
175 						}
176 						/* FALLTHROUGH */
177 				default:
178 				normal:
179 						if (c != *string && !((flags & FNM_CASEFOLD) &&
180 									(git__tolower((unsigned char)c) ==
181 									git__tolower((unsigned char)*string))))
182 								return (FNM_NOMATCH);
183 						++string;
184 						break;
185 				}
186 		/* NOTREACHED */
187 }
188 
189 static int
rangematch(const char * pattern,char test,int flags,char ** newp)190 rangematch(const char *pattern, char test, int flags, char **newp)
191 {
192 		int negate, ok;
193 		char c, c2;
194 
195 		/*
196 			* A bracket expression starting with an unquoted circumflex
197 			* character produces unspecified results (IEEE 1003.2-1992,
198 			* 3.13.2). This implementation treats it like '!', for
199 			* consistency with the regular expression syntax.
200 			* J.T. Conklin (conklin@ngai.kaleida.com)
201 			*/
202 		if ((negate = (*pattern == '!' || *pattern == '^')) != 0)
203 				++pattern;
204 
205 		if (flags & FNM_CASEFOLD)
206 				test = (char)git__tolower((unsigned char)test);
207 
208 		/*
209 			* A right bracket shall lose its special meaning and represent
210 			* itself in a bracket expression if it occurs first in the list.
211 			* -- POSIX.2 2.8.3.2
212 			*/
213 		ok = 0;
214 		c = *pattern++;
215 		do {
216 				if (c == '\\' && !(flags & FNM_NOESCAPE))
217 						c = *pattern++;
218 				if (c == EOS)
219 						return (RANGE_ERROR);
220 				if (c == '/' && (flags & FNM_PATHNAME))
221 						return (RANGE_NOMATCH);
222 				if ((flags & FNM_CASEFOLD))
223 						c = (char)git__tolower((unsigned char)c);
224 				if (*pattern == '-'
225 					&& (c2 = *(pattern+1)) != EOS && c2 != ']') {
226 						pattern += 2;
227 						if (c2 == '\\' && !(flags & FNM_NOESCAPE))
228 								c2 = *pattern++;
229 						if (c2 == EOS)
230 								return (RANGE_ERROR);
231 						if (flags & FNM_CASEFOLD)
232 								c2 = (char)git__tolower((unsigned char)c2);
233 						if (c <= test && test <= c2)
234 								ok = 1;
235 				} else if (c == test)
236 						ok = 1;
237 		} while ((c = *pattern++) != ']');
238 
239 		*newp = (char *)pattern;
240 		return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH);
241 }
242 
243 int
p_fnmatch(const char * pattern,const char * string,int flags)244 p_fnmatch(const char *pattern, const char *string, int flags)
245 {
246 		return p_fnmatchx(pattern, string, flags, 64);
247 }
248 
249