Coverage Report

Created: 2026-07-16 07:09

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