Coverage Report

Created: 2026-10-15 06:26

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
   * Windows keeps the MS-DOS meaning of "*.*": every name, also the ones
246
   * without a dot. Servers use it to list directories (MS-RDPEFS clients
247
   * receive it in IRP_MN_QUERY_DIRECTORY), so match it the same way.
248
   */
249
0
  if ((cchPattern == 3) && (strcmp(lpPattern, "*.*") == 0))
250
0
    return TRUE;
251
252
  /**
253
   * Subsequently evaluation of the “*X” expression is performed. This is a case where
254
   * the expression starts off with a wild card character and contains some non-wild card
255
   * characters towards the tail end of the name. This is evaluated by making sure the
256
   * expression starts off with the character ‘*’ and does not contain any wildcards in
257
   * the latter part of the expression. The tail part of the expression beyond the first
258
   * character ‘*’ is matched against the file name at the end uppercasing each character
259
   * if necessary during the comparison.
260
   */
261
262
0
  if (lpPattern[0] == '*')
263
0
  {
264
0
    lpTail = &lpPattern[1];
265
0
    cchTail = strlen(lpTail);
266
267
0
    if (!FilePatternFindNextWildcardA(lpTail, &dwFlags))
268
0
    {
269
      /* tail contains no wildcards */
270
0
      if (cchFileName < cchTail)
271
0
        return FALSE;
272
273
0
      if (_stricmp(&lpFileName[cchFileName - cchTail], lpTail) == 0)
274
0
        return TRUE;
275
276
0
      return FALSE;
277
0
    }
278
0
  }
279
280
  /**
281
   * The remaining expressions are evaluated in a non deterministic
282
   * finite order as listed below, where:
283
   *
284
   * 'S' is any single character
285
   * 'S-.' is any single character except the final '.'
286
   * 'e' is a null character transition
287
   * 'EOF' is the end of the name string
288
   *
289
   *                            S
290
   *                         <-----<
291
   *                      X  |     |  e       Y
292
   * X * Y ==        (0)----->-(1)->-----(2)-----(3)
293
   *
294
   *
295
   *                           S-.
296
   *                         <-----<
297
   *                      X  |     |  e       Y
298
   * X ~* Y ==       (0)----->-(1)->-----(2)-----(3)
299
   *
300
   *
301
   *                     X     S     S     Y
302
   * X ?? Y ==       (0)---(1)---(2)---(3)---(4)
303
   *
304
   *
305
   *                     X     S-.     S-.     Y
306
   * X ~?~? ==      (0)---(1)-----(2)-----(3)---(4)
307
   *                        |       |_______|
308
   *                        |            ^  |
309
   *                        |_______________|
310
   *                            ^EOF of .^
311
   *
312
   */
313
0
  LPCSTR lpWildcard = FilePatternFindNextWildcardA(lpPattern, &dwFlags);
314
315
0
  if (lpWildcard)
316
0
  {
317
0
    LPCSTR lpMatchEnd = nullptr;
318
0
    size_t cchNextWildcard = 0;
319
0
    const size_t cchSubPattern = cchPattern;
320
0
    LPCSTR lpSubPattern = lpPattern;
321
0
    size_t cchSubFileName = cchFileName;
322
0
    LPCSTR lpSubFileName = lpFileName;
323
0
    size_t cchWildcard = ((dwFlags & WILDCARD_DOS) ? 2 : 1);
324
0
    LPCSTR lpNextWildcard =
325
0
        FilePatternFindNextWildcardA(&lpWildcard[cchWildcard], &dwNextFlags);
326
327
0
    if (!lpNextWildcard)
328
0
    {
329
0
      LPCSTR lpX = lpSubPattern;
330
331
0
      if (lpWildcard < lpSubPattern)
332
0
        return FALSE;
333
0
      const size_t cchX = WINPR_ASSERTING_INT_CAST(size_t, (lpWildcard - lpSubPattern));
334
0
      LPCSTR lpY = &lpSubPattern[cchX + cchWildcard];
335
336
0
      if (lpY < lpSubPattern)
337
0
        return FALSE;
338
0
      const size_t lpYSSubPattern = WINPR_ASSERTING_INT_CAST(size_t, (lpY - lpSubPattern));
339
340
0
      if (cchSubPattern < lpYSSubPattern)
341
0
        return FALSE;
342
0
      const size_t cchY = cchSubPattern - lpYSSubPattern;
343
344
0
      return FilePatternMatchSubExpressionA(lpSubFileName, cchSubFileName, lpX, cchX, lpY,
345
0
                                            cchY, lpWildcard, &lpMatchEnd);
346
0
    }
347
0
    else
348
0
    {
349
0
      while (lpNextWildcard)
350
0
      {
351
0
        if (lpSubFileName < lpFileName)
352
0
          return FALSE;
353
354
0
        cchSubFileName =
355
0
            cchFileName - WINPR_ASSERTING_INT_CAST(size_t, (lpSubFileName - lpFileName));
356
0
        cchNextWildcard = ((dwNextFlags & WILDCARD_DOS) ? 2 : 1);
357
0
        LPCSTR lpX = lpSubPattern;
358
359
0
        if (lpWildcard < lpSubPattern)
360
0
          return FALSE;
361
0
        const size_t cchX = WINPR_ASSERTING_INT_CAST(size_t, (lpWildcard - lpSubPattern));
362
0
        LPCSTR lpY = &lpSubPattern[cchX + cchWildcard];
363
364
0
        if (lpNextWildcard < lpWildcard)
365
0
          return FALSE;
366
367
0
        const size_t diff = WINPR_ASSERTING_INT_CAST(size_t, (lpNextWildcard - lpWildcard));
368
0
        if (diff < cchWildcard)
369
0
          return FALSE;
370
371
0
        const size_t cchY = diff - cchWildcard;
372
0
        match = FilePatternMatchSubExpressionA(lpSubFileName, cchSubFileName, lpX, cchX,
373
0
                                               lpY, cchY, lpWildcard, &lpMatchEnd);
374
375
0
        if (!match)
376
0
          return FALSE;
377
378
0
        lpSubFileName = lpMatchEnd;
379
0
        cchWildcard = cchNextWildcard;
380
0
        lpWildcard = lpNextWildcard;
381
0
        dwFlags = dwNextFlags;
382
0
        lpNextWildcard =
383
0
            FilePatternFindNextWildcardA(&lpWildcard[cchWildcard], &dwNextFlags);
384
0
      }
385
386
0
      return TRUE;
387
0
    }
388
0
  }
389
0
  else
390
0
  {
391
    /* no wildcard characters */
392
0
    if (_stricmp(lpFileName, lpPattern) == 0)
393
0
      return TRUE;
394
0
  }
395
396
0
  return FALSE;
397
0
}