1 /**
2  * WinPR: Windows Portable Runtime
3  * File Functions
4  *
5  * Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #endif
23 
24 #include <winpr/crt.h>
25 #include <winpr/handle.h>
26 
27 #include <winpr/file.h>
28 
29 #ifdef HAVE_UNISTD_H
30 #include <unistd.h>
31 #endif
32 
33 #ifdef HAVE_FCNTL_H
34 #include <fcntl.h>
35 #endif
36 
37 #include "../log.h"
38 #define TAG WINPR_TAG("file")
39 
40 /**
41  * File System Behavior in the Microsoft Windows Environment:
42  * http://download.microsoft.com/download/4/3/8/43889780-8d45-4b2e-9d3a-c696a890309f/File%20System%20Behavior%20Overview.pdf
43  */
44 
FilePatternFindNextWildcardA(LPCSTR lpPattern,DWORD * pFlags)45 LPSTR FilePatternFindNextWildcardA(LPCSTR lpPattern, DWORD* pFlags)
46 {
47 	LPSTR lpWildcard;
48 	*pFlags = 0;
49 	lpWildcard = strpbrk(lpPattern, "*?~");
50 
51 	if (lpWildcard)
52 	{
53 		if (*lpWildcard == '*')
54 		{
55 			*pFlags = WILDCARD_STAR;
56 			return lpWildcard;
57 		}
58 		else if (*lpWildcard == '?')
59 		{
60 			*pFlags = WILDCARD_QM;
61 			return lpWildcard;
62 		}
63 		else if (*lpWildcard == '~')
64 		{
65 			if (lpWildcard[1] == '*')
66 			{
67 				*pFlags = WILDCARD_DOS_STAR;
68 				return lpWildcard;
69 			}
70 			else if (lpWildcard[1] == '?')
71 			{
72 				*pFlags = WILDCARD_DOS_QM;
73 				return lpWildcard;
74 			}
75 			else if (lpWildcard[1] == '.')
76 			{
77 				*pFlags = WILDCARD_DOS_DOT;
78 				return lpWildcard;
79 			}
80 		}
81 	}
82 
83 	return NULL;
84 }
85 
FilePatternMatchSubExpressionA(LPCSTR lpFileName,size_t cchFileName,LPCSTR lpX,size_t cchX,LPCSTR lpY,size_t cchY,LPCSTR lpWildcard,LPSTR * ppMatchEnd)86 static BOOL FilePatternMatchSubExpressionA(LPCSTR lpFileName, size_t cchFileName, LPCSTR lpX,
87                                            size_t cchX, LPCSTR lpY, size_t cchY, LPCSTR lpWildcard,
88                                            LPSTR* ppMatchEnd)
89 {
90 	LPSTR lpMatch;
91 
92 	if (!lpFileName)
93 		return FALSE;
94 
95 	if (*lpWildcard == '*')
96 	{
97 		/*
98 		 *                            S
99 		 *                         <-----<
100 		 *                      X  |     |  e       Y
101 		 * X * Y ==        (0)----->-(1)->-----(2)-----(3)
102 		 */
103 
104 		/*
105 		 * State 0: match 'X'
106 		 */
107 		if (_strnicmp(lpFileName, lpX, cchX) != 0)
108 			return FALSE;
109 
110 		/*
111 		 * State 1: match 'S' or 'e'
112 		 *
113 		 * We use 'e' to transition to state 2
114 		 */
115 
116 		/**
117 		 * State 2: match Y
118 		 */
119 
120 		if (cchY != 0)
121 		{
122 			/* TODO: case insensitive character search */
123 			lpMatch = strchr(&lpFileName[cchX], *lpY);
124 
125 			if (!lpMatch)
126 				return FALSE;
127 
128 			if (_strnicmp(lpMatch, lpY, cchY) != 0)
129 				return FALSE;
130 		}
131 		else
132 		{
133 			lpMatch = (LPSTR)&lpFileName[cchFileName];
134 		}
135 
136 		/**
137 		 * State 3: final state
138 		 */
139 		*ppMatchEnd = (LPSTR)&lpMatch[cchY];
140 		return TRUE;
141 	}
142 	else if (*lpWildcard == '?')
143 	{
144 		/**
145 		 *                     X     S     Y
146 		 * X ? Y ==        (0)---(1)---(2)---(3)
147 		 */
148 
149 		/*
150 		 * State 0: match 'X'
151 		 */
152 		if (cchFileName < cchX)
153 			return FALSE;
154 
155 		if (_strnicmp(lpFileName, lpX, cchX) != 0)
156 			return FALSE;
157 
158 		/*
159 		 * State 1: match 'S'
160 		 */
161 
162 		/**
163 		 * State 2: match Y
164 		 */
165 
166 		if (cchY != 0)
167 		{
168 			/* TODO: case insensitive character search */
169 			lpMatch = strchr(&lpFileName[cchX + 1], *lpY);
170 
171 			if (!lpMatch)
172 				return FALSE;
173 
174 			if (_strnicmp(lpMatch, lpY, cchY) != 0)
175 				return FALSE;
176 		}
177 		else
178 		{
179 			if ((cchX + 1) > cchFileName)
180 				return FALSE;
181 
182 			lpMatch = (LPSTR)&lpFileName[cchX + 1];
183 		}
184 
185 		/**
186 		 * State 3: final state
187 		 */
188 		*ppMatchEnd = (LPSTR)&lpMatch[cchY];
189 		return TRUE;
190 	}
191 	else if (*lpWildcard == '~')
192 	{
193 		WLog_ERR(TAG, "warning: unimplemented '~' pattern match");
194 		return TRUE;
195 	}
196 
197 	return FALSE;
198 }
199 
FilePatternMatchA(LPCSTR lpFileName,LPCSTR lpPattern)200 BOOL FilePatternMatchA(LPCSTR lpFileName, LPCSTR lpPattern)
201 {
202 	BOOL match;
203 	LPSTR lpTail;
204 	size_t cchTail;
205 	size_t cchPattern;
206 	size_t cchFileName;
207 	DWORD dwFlags;
208 	DWORD dwNextFlags;
209 	LPSTR lpWildcard;
210 	LPSTR lpNextWildcard;
211 
212 	/**
213 	 * Wild Card Matching
214 	 *
215 	 * '*'	matches 0 or more characters
216 	 * '?'	matches exactly one character
217 	 *
218 	 * '~*'	DOS_STAR - matches 0 or more characters until encountering and matching final '.'
219 	 *
220 	 * '~?'	DOS_QM - matches any single character, or upon encountering a period or end of name
221 	 *               string, advances the expresssion to the end of the set of contiguous DOS_QMs.
222 	 *
223 	 * '~.'	DOS_DOT - matches either a '.' or zero characters beyond name string.
224 	 */
225 
226 	if (!lpPattern)
227 		return FALSE;
228 
229 	if (!lpFileName)
230 		return FALSE;
231 
232 	cchPattern = strlen(lpPattern);
233 	cchFileName = strlen(lpFileName);
234 
235 	/**
236 	 * First and foremost the file system starts off name matching with the expression “*”.
237 	 * If the expression contains a single wild card character ‘*’ all matches are satisfied
238 	 * immediately. This is the most common wild card character used in Windows and expression
239 	 * evaluation is optimized by looking for this character first.
240 	 */
241 
242 	if ((lpPattern[0] == '*') && (cchPattern == 1))
243 		return TRUE;
244 
245 	/**
246 	 * Subsequently evaluation of the “*X” expression is performed. This is a case where
247 	 * the expression starts off with a wild card character and contains some non-wild card
248 	 * characters towards the tail end of the name. This is evaluated by making sure the
249 	 * expression starts off with the character ‘*’ and does not contain any wildcards in
250 	 * the latter part of the expression. The tail part of the expression beyond the first
251 	 * character ‘*’ is matched against the file name at the end uppercasing each character
252 	 * if necessary during the comparison.
253 	 */
254 
255 	if (lpPattern[0] == '*')
256 	{
257 		lpTail = (LPSTR)&lpPattern[1];
258 		cchTail = strlen(lpTail);
259 
260 		if (!FilePatternFindNextWildcardA(lpTail, &dwFlags))
261 		{
262 			/* tail contains no wildcards */
263 			if (cchFileName < cchTail)
264 				return FALSE;
265 
266 			if (_stricmp(&lpFileName[cchFileName - cchTail], lpTail) == 0)
267 				return TRUE;
268 
269 			return FALSE;
270 		}
271 	}
272 
273 	/**
274 	 * The remaining expressions are evaluated in a non deterministic
275 	 * finite order as listed below, where:
276 	 *
277 	 * 'S' is any single character
278 	 * 'S-.' is any single character except the final '.'
279 	 * 'e' is a null character transition
280 	 * 'EOF' is the end of the name string
281 	 *
282 	 *                            S
283 	 *                         <-----<
284 	 *                      X  |     |  e       Y
285 	 * X * Y ==        (0)----->-(1)->-----(2)-----(3)
286 	 *
287 	 *
288 	 *                           S-.
289 	 *                         <-----<
290 	 *                      X  |     |  e       Y
291 	 * X ~* Y ==       (0)----->-(1)->-----(2)-----(3)
292 	 *
293 	 *
294 	 *                     X     S     S     Y
295 	 * X ?? Y ==       (0)---(1)---(2)---(3)---(4)
296 	 *
297 	 *
298 	 *                     X     S-.     S-.     Y
299 	 * X ~?~? ==      (0)---(1)-----(2)-----(3)---(4)
300 	 *                        |       |_______|
301 	 *                        |            ^  |
302 	 *                        |_______________|
303 	 *                            ^EOF of .^
304 	 *
305 	 */
306 	lpWildcard = FilePatternFindNextWildcardA(lpPattern, &dwFlags);
307 
308 	if (lpWildcard)
309 	{
310 		LPSTR lpX;
311 		LPSTR lpY;
312 		size_t cchX;
313 		size_t cchY;
314 		LPSTR lpMatchEnd = NULL;
315 		LPSTR lpSubPattern;
316 		size_t cchSubPattern;
317 		LPSTR lpSubFileName;
318 		size_t cchSubFileName;
319 		size_t cchWildcard;
320 		size_t cchNextWildcard;
321 		cchSubPattern = cchPattern;
322 		lpSubPattern = (LPSTR)lpPattern;
323 		cchSubFileName = cchFileName;
324 		lpSubFileName = (LPSTR)lpFileName;
325 		cchWildcard = ((dwFlags & WILDCARD_DOS) ? 2 : 1);
326 		lpNextWildcard = FilePatternFindNextWildcardA(&lpWildcard[cchWildcard], &dwNextFlags);
327 
328 		if (!lpNextWildcard)
329 		{
330 			lpX = (LPSTR)lpSubPattern;
331 			cchX = (lpWildcard - lpSubPattern);
332 			lpY = (LPSTR)&lpSubPattern[cchX + cchWildcard];
333 			cchY = (cchSubPattern - (lpY - lpSubPattern));
334 			match = FilePatternMatchSubExpressionA(lpSubFileName, cchSubFileName, lpX, cchX, lpY,
335 			                                       cchY, lpWildcard, &lpMatchEnd);
336 			return match;
337 		}
338 		else
339 		{
340 			while (lpNextWildcard)
341 			{
342 				cchSubFileName = cchFileName - (lpSubFileName - lpFileName);
343 				cchNextWildcard = ((dwNextFlags & WILDCARD_DOS) ? 2 : 1);
344 				lpX = (LPSTR)lpSubPattern;
345 				cchX = (lpWildcard - lpSubPattern);
346 				lpY = (LPSTR)&lpSubPattern[cchX + cchWildcard];
347 				cchY = (lpNextWildcard - lpWildcard) - cchWildcard;
348 				match = FilePatternMatchSubExpressionA(lpSubFileName, cchSubFileName, lpX, cchX,
349 				                                       lpY, cchY, lpWildcard, &lpMatchEnd);
350 
351 				if (!match)
352 					return FALSE;
353 
354 				lpSubFileName = lpMatchEnd;
355 				cchWildcard = cchNextWildcard;
356 				lpWildcard = lpNextWildcard;
357 				dwFlags = dwNextFlags;
358 				lpNextWildcard =
359 				    FilePatternFindNextWildcardA(&lpWildcard[cchWildcard], &dwNextFlags);
360 			}
361 
362 			return TRUE;
363 		}
364 	}
365 	else
366 	{
367 		/* no wildcard characters */
368 		if (_stricmp(lpFileName, lpPattern) == 0)
369 			return TRUE;
370 	}
371 
372 	return FALSE;
373 }
374