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 #ifndef _FNMATCH_H_
9 #define _FNMATCH_H_
10
11 /*
12 * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6.
13 * Compares a filename or pathname to a pattern.
14 */
15
16 #include <ctype.h>
17 #include <string.h>
18 #include <stdio.h>
19
20 #define FNM_NOMATCH 1
21
22 #define FNM_NOESCAPE 0x01
23 #define FNM_PATHNAME 0x02
24 #define FNM_PERIOD 0x04
25 #define FNM_LEADING_DIR 0x08
26 #define FNM_CASEFOLD 0x10
27 #define FNM_PREFIX_DIRS 0x20
28
29 #define FNM_EOS '\0'
30
31 static inline const char *
fnm_rangematch(const char * pattern,char test,int flags)32 fnm_rangematch(const char *pattern, char test, int flags)
33 {
34 int negate, ok;
35 char c, c2;
36
37 /*
38 * A bracket expression starting with an unquoted circumflex
39 * character produces unspecified results (IEEE 1003.2-1992,
40 * 3.13.2). This implementation treats it like '!', for
41 * consistency with the regular expression syntax.
42 * J.T. Conklin (conklin@ngai.kaleida.com)
43 */
44 negate = (*pattern == '!' || *pattern == '^');
45 if (negate)
46 ++pattern;
47
48 if (flags & FNM_CASEFOLD)
49 test = tolower((unsigned char)test);
50
51 for (ok = 0; (c = *pattern++) != ']';) {
52 if (c == '\\' && !(flags & FNM_NOESCAPE))
53 c = *pattern++;
54 if (c == FNM_EOS)
55 return (NULL);
56
57 if (flags & FNM_CASEFOLD)
58 c = tolower((unsigned char)c);
59
60 c2 = *(pattern + 1);
61 if (*pattern == '-' && c2 != FNM_EOS && c2 != ']') {
62 pattern += 2;
63 if (c2 == '\\' && !(flags & FNM_NOESCAPE))
64 c2 = *pattern++;
65 if (c2 == FNM_EOS)
66 return (NULL);
67
68 if (flags & FNM_CASEFOLD)
69 c2 = tolower((unsigned char)c2);
70
71 if ((unsigned char)c <= (unsigned char)test &&
72 (unsigned char)test <= (unsigned char)c2)
73 ok = 1;
74 } else if (c == test)
75 ok = 1;
76 }
77 return (ok == negate ? NULL : pattern);
78 }
79
80 /**
81 * This function is used for searching a given string source
82 * with the given regular expression pattern.
83 *
84 * @param pattern
85 * regular expression notation describing the pattern to match
86 *
87 * @param string
88 * source string to search for the pattern
89 *
90 * @param flag
91 * containing information about the pattern
92 *
93 * @return
94 * if the pattern is found then return 0 or else FNM_NOMATCH
95 */
96 static inline int
fnmatch(const char * pattern,const char * string,int flags)97 fnmatch(const char *pattern, const char *string, int flags)
98 {
99 const char *stringstart;
100 char c, test;
101
102 for (stringstart = string;;)
103 switch (c = *pattern++) {
104 case FNM_EOS:
105 if ((flags & FNM_LEADING_DIR) && *string == '/')
106 return (0);
107 return (*string == FNM_EOS ? 0 : FNM_NOMATCH);
108 case '?':
109 if (*string == FNM_EOS)
110 return (FNM_NOMATCH);
111 if (*string == '/' && (flags & FNM_PATHNAME))
112 return (FNM_NOMATCH);
113 if (*string == '.' && (flags & FNM_PERIOD) &&
114 (string == stringstart ||
115 ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
116 return (FNM_NOMATCH);
117 ++string;
118 break;
119 case '*':
120 c = *pattern;
121 /* Collapse multiple stars. */
122 while (c == '*')
123 c = *++pattern;
124
125 if (*string == '.' && (flags & FNM_PERIOD) &&
126 (string == stringstart ||
127 ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
128 return (FNM_NOMATCH);
129
130 /* Optimize for pattern with * at end or before /. */
131 if (c == FNM_EOS)
132 if (flags & FNM_PATHNAME)
133 return ((flags & FNM_LEADING_DIR) ||
134 strchr(string, '/') == NULL ?
135 0 : FNM_NOMATCH);
136 else
137 return (0);
138 else if (c == '/' && flags & FNM_PATHNAME) {
139 string = strchr(string, '/');
140 if (string == NULL)
141 return (FNM_NOMATCH);
142 break;
143 }
144
145 /* General case, use recursion. */
146 while ((test = *string) != FNM_EOS) {
147 if (!fnmatch(pattern, string,
148 flags & ~FNM_PERIOD))
149 return (0);
150 if (test == '/' && flags & FNM_PATHNAME)
151 break;
152 ++string;
153 }
154 return (FNM_NOMATCH);
155 case '[':
156 if (*string == FNM_EOS)
157 return (FNM_NOMATCH);
158 if (*string == '/' && flags & FNM_PATHNAME)
159 return (FNM_NOMATCH);
160 pattern = fnm_rangematch(pattern, *string, flags);
161 if (pattern == NULL)
162 return (FNM_NOMATCH);
163 ++string;
164 break;
165 case '\\':
166 if (!(flags & FNM_NOESCAPE)) {
167 c = *pattern++;
168 if (c == FNM_EOS) {
169 c = '\\';
170 --pattern;
171 }
172 }
173 /* FALLTHROUGH */
174 default:
175 if (c == *string)
176 ;
177 else if ((flags & FNM_CASEFOLD) &&
178 (tolower((unsigned char)c) ==
179 tolower((unsigned char)*string)))
180 ;
181 else if ((flags & FNM_PREFIX_DIRS) && *string == FNM_EOS &&
182 ((c == '/' && string != stringstart) ||
183 (string == stringstart+1 && *stringstart == '/')))
184 return (0);
185 else
186 return (FNM_NOMATCH);
187 string++;
188 break;
189 }
190 /* NOTREACHED */
191 }
192
193 #endif /* _FNMATCH_H_ */
194