]>
Commit | Line | Data |
---|---|---|
e9c6571d | 1 | /* |
bb742ede | 2 | * Copyright (C) 2009-2011 the libgit2 contributors |
e9c6571d | 3 | * |
bb742ede VM |
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. | |
e9c6571d VM |
6 | */ |
7 | ||
8 | /* | |
9 | * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6. | |
10 | * Compares a filename or pathname to a pattern. | |
11 | */ | |
12 | ||
13 | #include <ctype.h> | |
14 | #include <stdio.h> | |
15 | #include <string.h> | |
16 | ||
17 | #include "fnmatch.h" | |
18 | ||
87d9869f | 19 | #define EOS '\0' |
e9c6571d | 20 | |
87d9869f VM |
21 | #define RANGE_MATCH 1 |
22 | #define RANGE_NOMATCH 0 | |
23 | #define RANGE_ERROR (-1) | |
e9c6571d VM |
24 | |
25 | static int rangematch(const char *, char, int, char **); | |
26 | ||
27 | int | |
28 | p_fnmatch(const char *pattern, const char *string, int flags) | |
29 | { | |
87d9869f VM |
30 | const char *stringstart; |
31 | char *newp; | |
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 | if ((string = strchr(string, '/')) == NULL) | |
72 | return (FNM_NOMATCH); | |
73 | break; | |
74 | } | |
75 | ||
76 | /* General case, use recursion. */ | |
77 | while ((test = *string) != EOS) { | |
78 | if (!p_fnmatch(pattern, string, flags & ~FNM_PERIOD)) | |
79 | return (0); | |
80 | if (test == '/' && (flags & FNM_PATHNAME)) | |
81 | break; | |
82 | ++string; | |
83 | } | |
84 | return (FNM_NOMATCH); | |
85 | case '[': | |
86 | if (*string == EOS) | |
87 | return (FNM_NOMATCH); | |
88 | if (*string == '/' && (flags & FNM_PATHNAME)) | |
89 | return (FNM_NOMATCH); | |
90 | if (*string == '.' && (flags & FNM_PERIOD) && | |
91 | (string == stringstart || | |
92 | ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) | |
93 | return (FNM_NOMATCH); | |
94 | ||
95 | switch (rangematch(pattern, *string, flags, &newp)) { | |
96 | case RANGE_ERROR: | |
97 | /* not a good range, treat as normal text */ | |
98 | goto normal; | |
99 | case RANGE_MATCH: | |
100 | pattern = newp; | |
101 | break; | |
102 | case RANGE_NOMATCH: | |
103 | return (FNM_NOMATCH); | |
104 | } | |
105 | ++string; | |
106 | break; | |
107 | case '\\': | |
108 | if (!(flags & FNM_NOESCAPE)) { | |
109 | if ((c = *pattern++) == EOS) { | |
110 | c = '\\'; | |
111 | --pattern; | |
112 | } | |
113 | } | |
114 | /* FALLTHROUGH */ | |
115 | default: | |
116 | normal: | |
117 | if (c != *string && !((flags & FNM_CASEFOLD) && | |
118 | (tolower((unsigned char)c) == | |
119 | tolower((unsigned char)*string)))) | |
120 | return (FNM_NOMATCH); | |
121 | ++string; | |
122 | break; | |
123 | } | |
124 | /* NOTREACHED */ | |
e9c6571d VM |
125 | } |
126 | ||
127 | static int | |
128 | rangematch(const char *pattern, char test, int flags, char **newp) | |
129 | { | |
87d9869f VM |
130 | int negate, ok; |
131 | char c, c2; | |
132 | ||
133 | /* | |
134 | * A bracket expression starting with an unquoted circumflex | |
135 | * character produces unspecified results (IEEE 1003.2-1992, | |
136 | * 3.13.2). This implementation treats it like '!', for | |
137 | * consistency with the regular expression syntax. | |
138 | * J.T. Conklin (conklin@ngai.kaleida.com) | |
139 | */ | |
140 | if ((negate = (*pattern == '!' || *pattern == '^')) != 0) | |
141 | ++pattern; | |
142 | ||
143 | if (flags & FNM_CASEFOLD) | |
144 | test = (char)tolower((unsigned char)test); | |
145 | ||
146 | /* | |
147 | * A right bracket shall lose its special meaning and represent | |
148 | * itself in a bracket expression if it occurs first in the list. | |
149 | * -- POSIX.2 2.8.3.2 | |
150 | */ | |
151 | ok = 0; | |
152 | c = *pattern++; | |
153 | do { | |
154 | if (c == '\\' && !(flags & FNM_NOESCAPE)) | |
155 | c = *pattern++; | |
156 | if (c == EOS) | |
157 | return (RANGE_ERROR); | |
158 | if (c == '/' && (flags & FNM_PATHNAME)) | |
159 | return (RANGE_NOMATCH); | |
160 | if ((flags & FNM_CASEFOLD)) | |
161 | c = (char)tolower((unsigned char)c); | |
162 | if (*pattern == '-' | |
163 | && (c2 = *(pattern+1)) != EOS && c2 != ']') { | |
164 | pattern += 2; | |
165 | if (c2 == '\\' && !(flags & FNM_NOESCAPE)) | |
166 | c2 = *pattern++; | |
167 | if (c2 == EOS) | |
168 | return (RANGE_ERROR); | |
169 | if (flags & FNM_CASEFOLD) | |
170 | c2 = (char)tolower((unsigned char)c2); | |
171 | if (c <= test && test <= c2) | |
172 | ok = 1; | |
173 | } else if (c == test) | |
174 | ok = 1; | |
175 | } while ((c = *pattern++) != ']'); | |
176 | ||
177 | *newp = (char *)pattern; | |
178 | return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH); | |
e9c6571d VM |
179 | } |
180 |