Coverage Report

Created: 2025-11-16 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}