1/*
2** Do shell-style pattern matching for ?, \, [], and * characters.
3** It is 8bit clean.
4**
5** Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986.
6** Rich $alz is now <rsalz@bbn.com>.
7**
8** Modified by Wayne Davison to special-case '/' matching, to make '**'
9** work differently than '*', and to fix the character-class code.
10*/
11
12#include "cache.h"
13#include "wildmatch.h"
14
15typedef unsigned char uchar;
16
17/* What character marks an inverted character class? */
18#define NEGATE_CLASS '!'
19#define NEGATE_CLASS2 '^'
20
21#define FALSE 0
22#define TRUE 1
23
24#define CC_EQ(class, len, litmatch) ((len) == sizeof (litmatch)-1 \
25 && *(class) == *(litmatch) \
26 && strncmp((char*)class, litmatch, len) == 0)
27
28#if defined STDC_HEADERS || !defined isascii
29# define ISASCII(c) 1
30#else
31# define ISASCII(c) isascii(c)
32#endif
33
34#ifdef isblank
35# define ISBLANK(c) (ISASCII(c) && isblank(c))
36#else
37# define ISBLANK(c) ((c) == ' ' || (c) == '\t')
38#endif
39
40#ifdef isgraph
41# define ISGRAPH(c) (ISASCII(c) && isgraph(c))
42#else
43# define ISGRAPH(c) (ISASCII(c) && isprint(c) && !isspace(c))
44#endif
45
46#define ISPRINT(c) (ISASCII(c) && isprint(c))
47#define ISDIGIT(c) (ISASCII(c) && isdigit(c))
48#define ISALNUM(c) (ISASCII(c) && isalnum(c))
49#define ISALPHA(c) (ISASCII(c) && isalpha(c))
50#define ISCNTRL(c) (ISASCII(c) && iscntrl(c))
51#define ISLOWER(c) (ISASCII(c) && islower(c))
52#define ISPUNCT(c) (ISASCII(c) && ispunct(c))
53#define ISSPACE(c) (ISASCII(c) && isspace(c))
54#define ISUPPER(c) (ISASCII(c) && isupper(c))
55#define ISXDIGIT(c) (ISASCII(c) && isxdigit(c))
56
57/* Match pattern "p" against "text" */
58static int dowild(const uchar *p, const uchar *text, int force_lower_case)
59{
60 uchar p_ch;
61
62 for ( ; (p_ch = *p) != '\0'; text++, p++) {
63 int matched, special;
64 uchar t_ch, prev_ch;
65 if ((t_ch = *text) == '\0' && p_ch != '*')
66 return ABORT_ALL;
67 if (force_lower_case && ISUPPER(t_ch))
68 t_ch = tolower(t_ch);
69 if (force_lower_case && ISUPPER(p_ch))
70 p_ch = tolower(p_ch);
71 switch (p_ch) {
72 case '\\':
73 /* Literal match with following character. Note that the test
74 * in "default" handles the p[1] == '\0' failure case. */
75 p_ch = *++p;
76 /* FALLTHROUGH */
77 default:
78 if (t_ch != p_ch)
79 return NOMATCH;
80 continue;
81 case '?':
82 /* Match anything but '/'. */
83 if (t_ch == '/')
84 return NOMATCH;
85 continue;
86 case '*':
87 if (*++p == '*') {
88 const uchar *prev_p = p - 2;
89 while (*++p == '*') {}
90 if ((prev_p == text || *prev_p == '/') ||
91 (*p == '\0' || *p == '/' ||
92 (p[0] == '\\' && p[1] == '/'))) {
93 special = TRUE;
94 } else
95 return ABORT_MALFORMED;
96 } else
97 special = FALSE;
98 if (*p == '\0') {
99 /* Trailing "**" matches everything. Trailing "*" matches
100 * only if there are no more slash characters. */
101 if (!special) {
102 if (strchr((char*)text, '/') != NULL)
103 return NOMATCH;
104 }
105 return MATCH;
106 }
107 while (1) {
108 if (t_ch == '\0')
109 break;
110 if ((matched = dowild(p, text, force_lower_case)) != NOMATCH) {
111 if (!special || matched != ABORT_TO_STARSTAR)
112 return matched;
113 } else if (!special && t_ch == '/')
114 return ABORT_TO_STARSTAR;
115 t_ch = *++text;
116 }
117 return ABORT_ALL;
118 case '[':
119 p_ch = *++p;
120#ifdef NEGATE_CLASS2
121 if (p_ch == NEGATE_CLASS2)
122 p_ch = NEGATE_CLASS;
123#endif
124 /* Assign literal TRUE/FALSE because of "matched" comparison. */
125 special = p_ch == NEGATE_CLASS? TRUE : FALSE;
126 if (special) {
127 /* Inverted character class. */
128 p_ch = *++p;
129 }
130 prev_ch = 0;
131 matched = FALSE;
132 do {
133 if (!p_ch)
134 return ABORT_ALL;
135 if (p_ch == '\\') {
136 p_ch = *++p;
137 if (!p_ch)
138 return ABORT_ALL;
139 if (t_ch == p_ch)
140 matched = TRUE;
141 } else if (p_ch == '-' && prev_ch && p[1] && p[1] != ']') {
142 p_ch = *++p;
143 if (p_ch == '\\') {
144 p_ch = *++p;
145 if (!p_ch)
146 return ABORT_ALL;
147 }
148 if (t_ch <= p_ch && t_ch >= prev_ch)
149 matched = TRUE;
150 p_ch = 0; /* This makes "prev_ch" get set to 0. */
151 } else if (p_ch == '[' && p[1] == ':') {
152 const uchar *s;
153 int i;
154 for (s = p += 2; (p_ch = *p) && p_ch != ']'; p++) {} /*SHARED ITERATOR*/
155 if (!p_ch)
156 return ABORT_ALL;
157 i = p - s - 1;
158 if (i < 0 || p[-1] != ':') {
159 /* Didn't find ":]", so treat like a normal set. */
160 p = s - 2;
161 p_ch = '[';
162 if (t_ch == p_ch)
163 matched = TRUE;
164 continue;
165 }
166 if (CC_EQ(s,i, "alnum")) {
167 if (ISALNUM(t_ch))
168 matched = TRUE;
169 } else if (CC_EQ(s,i, "alpha")) {
170 if (ISALPHA(t_ch))
171 matched = TRUE;
172 } else if (CC_EQ(s,i, "blank")) {
173 if (ISBLANK(t_ch))
174 matched = TRUE;
175 } else if (CC_EQ(s,i, "cntrl")) {
176 if (ISCNTRL(t_ch))
177 matched = TRUE;
178 } else if (CC_EQ(s,i, "digit")) {
179 if (ISDIGIT(t_ch))
180 matched = TRUE;
181 } else if (CC_EQ(s,i, "graph")) {
182 if (ISGRAPH(t_ch))
183 matched = TRUE;
184 } else if (CC_EQ(s,i, "lower")) {
185 if (ISLOWER(t_ch))
186 matched = TRUE;
187 } else if (CC_EQ(s,i, "print")) {
188 if (ISPRINT(t_ch))
189 matched = TRUE;
190 } else if (CC_EQ(s,i, "punct")) {
191 if (ISPUNCT(t_ch))
192 matched = TRUE;
193 } else if (CC_EQ(s,i, "space")) {
194 if (ISSPACE(t_ch))
195 matched = TRUE;
196 } else if (CC_EQ(s,i, "upper")) {
197 if (ISUPPER(t_ch))
198 matched = TRUE;
199 } else if (CC_EQ(s,i, "xdigit")) {
200 if (ISXDIGIT(t_ch))
201 matched = TRUE;
202 } else /* malformed [:class:] string */
203 return ABORT_ALL;
204 p_ch = 0; /* This makes "prev_ch" get set to 0. */
205 } else if (t_ch == p_ch)
206 matched = TRUE;
207 } while (prev_ch = p_ch, (p_ch = *++p) != ']');
208 if (matched == special || t_ch == '/')
209 return NOMATCH;
210 continue;
211 }
212 }
213
214 return *text ? NOMATCH : MATCH;
215}
216
217/* Match the "pattern" against the "text" string. */
218int wildmatch(const char *pattern, const char *text, int flags)
219{
220 return dowild((const uchar*)pattern, (const uchar*)text,
221 flags & FNM_CASEFOLD ? 1 :0);
222}