/src/samba/lib/util/ms_fnmatch.c
Line | Count | Source |
1 | | /* |
2 | | Unix SMB/CIFS implementation. |
3 | | filename matching routine |
4 | | Copyright (C) Andrew Tridgell 1992-2004 |
5 | | |
6 | | This program is free software; you can redistribute it and/or modify |
7 | | it under the terms of the GNU General Public License as published by |
8 | | the Free Software Foundation; either version 3 of the License, or |
9 | | (at your option) any later version. |
10 | | |
11 | | This program is distributed in the hope that it will be useful, |
12 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | GNU General Public License for more details. |
15 | | |
16 | | You should have received a copy of the GNU General Public License |
17 | | along with this program. If not, see <http://www.gnu.org/licenses/>. |
18 | | */ |
19 | | |
20 | | /* |
21 | | This module was originally based on fnmatch.c copyright by the Free |
22 | | Software Foundation. It bears little (if any) resemblance to that |
23 | | code now |
24 | | */ |
25 | | |
26 | | /** |
27 | | * @file |
28 | | * @brief MS-style Filename matching |
29 | | */ |
30 | | |
31 | | #include "replace.h" |
32 | | #include "lib/util/samba_util.h" |
33 | | #include "libcli/smb/smb_constants.h" |
34 | | |
35 | | static int null_match(const char *p) |
36 | 0 | { |
37 | 0 | for (;*p;p++) { |
38 | 0 | if (*p != '*' && |
39 | 0 | *p != '<' && |
40 | 0 | *p != '"' && |
41 | 0 | *p != '>') return -1; |
42 | 0 | } |
43 | 0 | return 0; |
44 | 0 | } |
45 | | |
46 | | /* |
47 | | the max_n structure is purely for efficiency, it doesn't contribute |
48 | | to the matching algorithm except by ensuring that the algorithm does |
49 | | not grow exponentially |
50 | | */ |
51 | | struct max_n { |
52 | | const char *predot; |
53 | | const char *postdot; |
54 | | }; |
55 | | |
56 | | |
57 | | /* |
58 | | p and n are the pattern and string being matched. The max_n array is |
59 | | an optimisation only. The ldot pointer is NULL if the string does |
60 | | not contain a '.', otherwise it points at the last dot in 'n'. |
61 | | */ |
62 | | static int ms_fnmatch_core(const char *p, const char *n, |
63 | | struct max_n *max_n, const char *ldot, |
64 | | bool is_case_sensitive) |
65 | 0 | { |
66 | 0 | codepoint_t c, c2; |
67 | 0 | int i; |
68 | 0 | size_t size, size_n; |
69 | |
|
70 | 0 | while ((c = next_codepoint(p, &size))) { |
71 | 0 | p += size; |
72 | |
|
73 | 0 | switch (c) { |
74 | 0 | case '*': |
75 | | /* a '*' matches zero or more characters of any type */ |
76 | 0 | if (max_n != NULL && max_n->predot && |
77 | 0 | max_n->predot <= n) { |
78 | 0 | return null_match(p); |
79 | 0 | } |
80 | 0 | for (i=0; n[i]; i += size_n) { |
81 | 0 | next_codepoint(n+i, &size_n); |
82 | 0 | if (ms_fnmatch_core(p, n+i, max_n+1, ldot, is_case_sensitive) == 0) { |
83 | 0 | return 0; |
84 | 0 | } |
85 | 0 | } |
86 | 0 | if (max_n != NULL && (!max_n->predot || |
87 | 0 | max_n->predot > n)) { |
88 | 0 | max_n->predot = n; |
89 | 0 | } |
90 | 0 | return null_match(p); |
91 | | |
92 | 0 | case '<': |
93 | | /* a '<' matches zero or more characters of |
94 | | any type, but stops matching at the last |
95 | | '.' in the string. */ |
96 | 0 | if (max_n != NULL && max_n->predot && |
97 | 0 | max_n->predot <= n) { |
98 | 0 | return null_match(p); |
99 | 0 | } |
100 | 0 | if (max_n != NULL && max_n->postdot && |
101 | 0 | max_n->postdot <= n && n <= ldot) { |
102 | 0 | return -1; |
103 | 0 | } |
104 | 0 | for (i=0; n[i]; i += size_n) { |
105 | 0 | next_codepoint(n+i, &size_n); |
106 | 0 | if (ms_fnmatch_core(p, n+i, max_n+1, ldot, is_case_sensitive) == 0) return 0; |
107 | 0 | if (n+i == ldot) { |
108 | 0 | if (ms_fnmatch_core(p, n+i+size_n, max_n+1, ldot, is_case_sensitive) == 0) return 0; |
109 | 0 | if (max_n != NULL) { |
110 | 0 | if (!max_n->postdot || |
111 | 0 | max_n->postdot > n) { |
112 | 0 | max_n->postdot = n; |
113 | 0 | } |
114 | 0 | } |
115 | 0 | return -1; |
116 | 0 | } |
117 | 0 | } |
118 | 0 | if (max_n != NULL && (!max_n->predot || |
119 | 0 | max_n->predot > n)) { |
120 | 0 | max_n->predot = n; |
121 | 0 | } |
122 | 0 | return null_match(p); |
123 | | |
124 | 0 | case '?': |
125 | | /* a '?' matches any single character */ |
126 | 0 | if (! *n) { |
127 | 0 | return -1; |
128 | 0 | } |
129 | 0 | next_codepoint(n, &size_n); |
130 | 0 | n += size_n; |
131 | 0 | break; |
132 | | |
133 | 0 | case '>': |
134 | | /* a '?' matches any single character, but |
135 | | treats '.' specially */ |
136 | 0 | if (n[0] == '.') { |
137 | 0 | if (! n[1] && null_match(p) == 0) { |
138 | 0 | return 0; |
139 | 0 | } |
140 | 0 | break; |
141 | 0 | } |
142 | 0 | if (! *n) return null_match(p); |
143 | 0 | next_codepoint(n, &size_n); |
144 | 0 | n += size_n; |
145 | 0 | break; |
146 | | |
147 | 0 | case '"': |
148 | | /* a bit like a soft '.' */ |
149 | 0 | if (*n == 0 && null_match(p) == 0) { |
150 | 0 | return 0; |
151 | 0 | } |
152 | 0 | if (*n != '.') return -1; |
153 | 0 | next_codepoint(n, &size_n); |
154 | 0 | n += size_n; |
155 | 0 | break; |
156 | | |
157 | 0 | default: |
158 | 0 | c2 = next_codepoint(n, &size_n); |
159 | 0 | if (c != c2) { |
160 | 0 | if (is_case_sensitive) { |
161 | 0 | return -1; |
162 | 0 | } |
163 | 0 | if (codepoint_cmpi(c, c2) != 0) { |
164 | 0 | return -1; |
165 | 0 | } |
166 | 0 | } |
167 | 0 | n += size_n; |
168 | 0 | break; |
169 | 0 | } |
170 | 0 | } |
171 | | |
172 | 0 | if (! *n) { |
173 | 0 | return 0; |
174 | 0 | } |
175 | | |
176 | 0 | return -1; |
177 | 0 | } |
178 | | |
179 | | int ms_fnmatch_protocol(const char *pattern, const char *string, int protocol, |
180 | | bool is_case_sensitive) |
181 | 0 | { |
182 | 0 | int ret = -1; |
183 | 0 | size_t count, i; |
184 | |
|
185 | 0 | if (strcmp(string, "..") == 0) { |
186 | 0 | string = "."; |
187 | 0 | } |
188 | |
|
189 | 0 | if (strpbrk(pattern, "<>*?\"") == NULL) { |
190 | | /* this is not just an optimisation - it is essential |
191 | | for LANMAN1 correctness */ |
192 | 0 | return strcasecmp_m(pattern, string); |
193 | 0 | } |
194 | | |
195 | 0 | if (protocol <= PROTOCOL_LANMAN2) { |
196 | 0 | char *p = talloc_strdup(NULL, pattern); |
197 | 0 | if (p == NULL) { |
198 | 0 | return -1; |
199 | 0 | } |
200 | | /* |
201 | | for older negotiated protocols it is possible to |
202 | | translate the pattern to produce a "new style" |
203 | | pattern that exactly matches w2k behaviour |
204 | | */ |
205 | 0 | for (i=0;p[i];i++) { |
206 | 0 | if (p[i] == '?') { |
207 | 0 | p[i] = '>'; |
208 | 0 | } else if (p[i] == '.' && |
209 | 0 | (p[i+1] == '?' || |
210 | 0 | p[i+1] == '*' || |
211 | 0 | p[i+1] == 0)) { |
212 | 0 | p[i] = '"'; |
213 | 0 | } else if (p[i] == '*' && |
214 | 0 | p[i+1] == '.') { |
215 | 0 | p[i] = '<'; |
216 | 0 | } |
217 | 0 | } |
218 | 0 | ret = ms_fnmatch_protocol(p, string, PROTOCOL_NT1, |
219 | 0 | is_case_sensitive); |
220 | 0 | talloc_free(p); |
221 | 0 | return ret; |
222 | 0 | } |
223 | | |
224 | 0 | for (count=i=0;pattern[i];i++) { |
225 | 0 | if (pattern[i] == '*' || pattern[i] == '<') count++; |
226 | 0 | } |
227 | | |
228 | | /* If the pattern includes '*' or '<' */ |
229 | 0 | if (count >= 1) { |
230 | 0 | struct max_n max_n[count]; |
231 | |
|
232 | 0 | memset(max_n, 0, sizeof(struct max_n) * count); |
233 | |
|
234 | 0 | ret = ms_fnmatch_core(pattern, string, max_n, strrchr(string, '.'), |
235 | 0 | is_case_sensitive); |
236 | 0 | } else { |
237 | 0 | ret = ms_fnmatch_core(pattern, string, NULL, strrchr(string, '.'), |
238 | 0 | is_case_sensitive); |
239 | 0 | } |
240 | |
|
241 | 0 | return ret; |
242 | 0 | } |
243 | | |
244 | | |
245 | | /** a generic fnmatch function - uses for non-CIFS pattern matching */ |
246 | | int gen_fnmatch(const char *pattern, const char *string) |
247 | 0 | { |
248 | | return ms_fnmatch_protocol(pattern, string, PROTOCOL_NT1, false); |
249 | 0 | } |