Coverage Report

Created: 2025-07-01 06:46

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