1 /* SPDX-License-Identifier: BSD-3-Clause
2 * Copyright (c) 1989, 1993, 1994
3 * The Regents of the University of California. All rights reserved.
4 *
5 * This code is derived from software contributed to Berkeley by
6 * Guido van Rossum.
7 */
8
9 #if defined(LIBC_SCCS) && !defined(lint)
10 static const char sccsid[] = "@(#)fnmatch.c 8.2 (Berkeley) 4/16/94";
11 #endif /* LIBC_SCCS and not lint */
12
13 /*
14 * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6.
15 * Compares a filename or pathname to a pattern.
16 */
17
18 #include <ctype.h>
19 #include <string.h>
20 #include <stdio.h>
21
22 #include "fnmatch.h"
23
24 #define EOS '\0'
25
26 static const char *rangematch(const char *, char, int);
27
28 int
fnmatch(const char * pattern,const char * string,int flags)29 fnmatch(const char *pattern, const char *string, int flags)
30 {
31 const char *stringstart;
32 char c, test;
33
34 for (stringstart = string;;)
35 switch (c = *pattern++) {
36 case EOS:
37 if ((flags & FNM_LEADING_DIR) && *string == '/')
38 return (0);
39 return (*string == EOS ? 0 : FNM_NOMATCH);
40 case '?':
41 if (*string == EOS)
42 return (FNM_NOMATCH);
43 if (*string == '/' && (flags & FNM_PATHNAME))
44 return (FNM_NOMATCH);
45 if (*string == '.' && (flags & FNM_PERIOD) &&
46 (string == stringstart ||
47 ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
48 return (FNM_NOMATCH);
49 ++string;
50 break;
51 case '*':
52 c = *pattern;
53 /* Collapse multiple stars. */
54 while (c == '*')
55 c = *++pattern;
56
57 if (*string == '.' && (flags & FNM_PERIOD) &&
58 (string == stringstart ||
59 ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
60 return (FNM_NOMATCH);
61
62 /* Optimize for pattern with * at end or before /. */
63 if (c == EOS)
64 if (flags & FNM_PATHNAME)
65 return ((flags & FNM_LEADING_DIR) ||
66 strchr(string, '/') == NULL ?
67 0 : FNM_NOMATCH);
68 else
69 return (0);
70 else if (c == '/' && flags & FNM_PATHNAME) {
71 string = strchr(string, '/');
72 if (string == NULL)
73 return (FNM_NOMATCH);
74 break;
75 }
76
77 /* General case, use recursion. */
78 while ((test = *string) != EOS) {
79 if (!fnmatch(pattern, string,
80 flags & ~FNM_PERIOD))
81 return (0);
82 if (test == '/' && flags & FNM_PATHNAME)
83 break;
84 ++string;
85 }
86 return (FNM_NOMATCH);
87 case '[':
88 if (*string == EOS)
89 return (FNM_NOMATCH);
90 if (*string == '/' && flags & FNM_PATHNAME)
91 return (FNM_NOMATCH);
92 pattern = rangematch(pattern, *string, flags);
93 if (pattern == NULL)
94 return (FNM_NOMATCH);
95 ++string;
96 break;
97 case '\\':
98 if (!(flags & FNM_NOESCAPE)) {
99 c = *pattern++;
100 if (c == EOS) {
101 c = '\\';
102 --pattern;
103 }
104 }
105 /* FALLTHROUGH */
106 default:
107 if (c == *string)
108 ;
109 else if ((flags & FNM_CASEFOLD) &&
110 (tolower((unsigned char)c) ==
111 tolower((unsigned char)*string)))
112 ;
113 else if ((flags & FNM_PREFIX_DIRS) && *string == EOS &&
114 ((c == '/' && string != stringstart) ||
115 (string == stringstart+1 && *stringstart == '/')))
116 return (0);
117 else
118 return (FNM_NOMATCH);
119 string++;
120 break;
121 }
122 /* NOTREACHED */
123 }
124
125 static const char *
rangematch(const char * pattern,char test,int flags)126 rangematch(const char *pattern, char test, int flags)
127 {
128 int negate, ok;
129 char c, c2;
130
131 /*
132 * A bracket expression starting with an unquoted circumflex
133 * character produces unspecified results (IEEE 1003.2-1992,
134 * 3.13.2). This implementation treats it like '!', for
135 * consistency with the regular expression syntax.
136 * J.T. Conklin ([email protected])
137 */
138 negate = (*pattern == '!' || *pattern == '^');
139 if (negate)
140 ++pattern;
141
142 if (flags & FNM_CASEFOLD)
143 test = tolower((unsigned char)test);
144
145 for (ok = 0; (c = *pattern++) != ']';) {
146 if (c == '\\' && !(flags & FNM_NOESCAPE))
147 c = *pattern++;
148 if (c == EOS)
149 return (NULL);
150
151 if (flags & FNM_CASEFOLD)
152 c = tolower((unsigned char)c);
153
154 c2 = *(pattern + 1);
155 if (*pattern == '-' && c2 != EOS && c2 != ']') {
156 pattern += 2;
157 if (c2 == '\\' && !(flags & FNM_NOESCAPE))
158 c2 = *pattern++;
159 if (c2 == EOS)
160 return (NULL);
161
162 if (flags & FNM_CASEFOLD)
163 c2 = tolower((unsigned char)c2);
164
165 if ((unsigned char)c <= (unsigned char)test &&
166 (unsigned char)test <= (unsigned char)c2)
167 ok = 1;
168 } else if (c == test)
169 ok = 1;
170 }
171 return (ok == negate ? NULL : pattern);
172 }
173