about summary refs log tree commit diff
path: root/src/regex/fnmatch.c
blob: c0856f991939655b48ab0336090ee785b6c5713e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#include <fnmatch.h>
#include <wctype.h>
#include <string.h>
#include <wchar.h>
#include <stdlib.h>
#include <limits.h>

static int next(const char **s)
{
	wchar_t c;
	int l = mbtowc(&c, *s, MB_LEN_MAX);
	/* hack to allow literal matches of invalid byte sequences */
	if (l < 0) return (unsigned char)*(*s)++ - 0x100;
	*s += l;
	return c;
}

#define BRACKET_ERROR  -0x100
#define BRACKET_NOCHAR -0x101

static int bracket_next(const char **s)
{
	int c;
	int type;
	if (**s == '[') {
		type = *(*s+1);
		if (type == '.' || type == '=') {
			*s += 2;
			c = next(s);
			if (c <= 0) return BRACKET_ERROR;
			if (**s == type && *(*s+1) == ']') {
				*s += 2;
				return c;
			}
			for (; **s && (**s != type || *(*s+1) != ']'); (*s)++);
			if (!**s) return BRACKET_ERROR;
			*s += 2;
			return BRACKET_NOCHAR;
		}
	}
	c = next(s);
	if (c <= 0) return BRACKET_ERROR;
	return c;
}

#define __FNM_CONT 0x8000

int fnmatch(const char *p, const char *s, int flags)
{
	int c, d, k;
	int not;
	int match;
	int first;
	int no_slash = (flags & FNM_PATHNAME) ? '/' : 0;
	int no_period = (flags & FNM_PERIOD) && !(flags & __FNM_CONT) ? '.' : 0x100;

	flags |= __FNM_CONT;

	while ((c = *p++)) {
		switch (c) {
		case '?':
			k = next(&s);
			if (!k || k == no_period || k == no_slash)
				return FNM_NOMATCH;
			break;
		case '\\':
			if (!(flags & FNM_NOESCAPE)) {
				c = *p++;
				goto literal;
			}
			if (*s++ != c) return FNM_NOMATCH;
			break;
		case '*':
			for (; *p == '*'; p++);
			if (*p && !*s) return FNM_NOMATCH;
			if (*s == no_period)
				return FNM_NOMATCH;
			if (!*p && (!no_slash || !strchr(s, no_slash)))
				return 0;
			for (; *s; s++)
				if (!fnmatch(p, s, flags))
					return 0;
				else if (*s == no_slash)
					break;
			return FNM_NOMATCH;
		case '[':
			not = (*p == '!' || *p == '^');
			if (not) p++;
			k = next(&s);
			if (!k || k == no_slash || k == no_period)
				return FNM_NOMATCH;
			match = 0;
			first = 1;
			for (;;) {
				if (!*p) return FNM_NOMATCH;
				if (*p == ']' && !first) break;
				first = 0;
				if (*p == '[' && *(p+1) == ':') {
					const char *z;
					p += 2;
					for (z=p; *z && (*z != ':' || *(z+1) != ']'); z++);
					if (!*z || z-p > 32) { /* FIXME: symbolic const? */
						return FNM_NOMATCH;
					} else {
						char class[33];
						memcpy(class, p, z-p);
						class[z-p] = 0;
						if (iswctype(k, wctype(class)))
							match = 1;
					}
					p = z+2;
					continue;
				}
				c = bracket_next(&p);
				if (c == BRACKET_ERROR)
					return FNM_NOMATCH;
				if (c == BRACKET_NOCHAR)
					continue;
				if (*p == '-' && *(p+1) != ']') {
					p++;
					d = bracket_next(&p);
					if (d == BRACKET_ERROR)
						return FNM_NOMATCH;
					if (d == BRACKET_NOCHAR)
						continue;
					if (k >= c && k <= d)
						match = 1;
					continue;
				}
				if (k == c) match = 1;
			}
			p++;
			if (not == match)
				return FNM_NOMATCH;
			break;
		default:
		literal:
			if (*s++ != c)
				return FNM_NOMATCH;
			if (c == no_slash && (flags & FNM_PERIOD)) {
				no_period = '.';
				continue;
			}
			break;
		}
		no_period = 0x100;
	}
	if (*s) return FNM_NOMATCH;
	return 0;
}