Coverage Report

Created: 2025-11-24 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib/wildcard-match.c
Line
Count
Source
1
/*
2
 * This code would not have been possible without the prior work and
3
 * suggestions of various sourced.  Special thanks to Robey for
4
 * all his time/help tracking down bugs and his ever-helpful advice.
5
 *
6
 * 04/09:  Fixed the "*\*" against "*a" bug (caused an endless loop)
7
 *
8
 *   Chris Fuller  (aka Fred1@IRC & Fwitz@IRC)
9
 *     crf@cfox.bchs.uh.edu
10
 *
11
 * I hereby release this code into the public domain
12
 *
13
 */
14
15
#include "lib.h"
16
#include "str.h"
17
#include "wildcard-match.h"
18
19
#include <ctype.h>
20
21
0
#define WILDS '*'  /* matches 0 or more characters (including spaces) */
22
0
#define WILDQ '?'  /* matches exactly one character */
23
0
#define WILDE '\\' /* escapes one wildcard */
24
25
0
#define NOMATCH 0
26
0
#define MATCH (match+sofar)
27
28
static bool is_escaped(const char *p, const char *start)
29
0
{
30
0
  bool is_escaped = FALSE;
31
0
  while (p > start && p[-1] == WILDE) {
32
0
    is_escaped = !is_escaped;
33
0
    p--;
34
0
  }
35
0
  return is_escaped;
36
0
}
37
38
static int
39
wildcard_match_int(const char *data, const char *mask, bool icase, bool escaped)
40
0
{
41
0
  const char *ma = mask, *na = data, *lsm = NULL, *lsn = NULL;
42
0
  int match = 1;
43
0
  int sofar = 0;
44
45
0
  if (na[0] == '\0') {
46
    /* empty string can match only "*" wildcard(s) */
47
0
    while (ma[0] == '*') ma++;
48
0
    return ma[0] == '\0' ? MATCH : NOMATCH;
49
0
  }
50
  /* find the end of each string */
51
0
  while (*(mask++) != '\0');
52
0
  mask-=2;
53
0
  while (*(data++) != '\0');
54
0
  data-=2;
55
56
0
  while (data >= na) {
57
    /* If the mask runs out of chars before the string, fall back on
58
     * a wildcard or fail. */
59
0
    if (mask < ma) {
60
0
      if (lsm != NULL) {
61
0
        data = --lsn;
62
0
        mask = lsm;
63
0
        if (data < na)
64
0
          lsm = NULL;
65
0
        sofar = 0;
66
0
      }
67
0
      else
68
0
        return NOMATCH;
69
0
    }
70
71
0
    switch (*mask) {
72
0
    case WILDE:
73
0
      if (escaped && is_escaped(mask, ma)) {
74
0
  if (*mask != *data)
75
0
    goto nomatch;
76
0
  mask -= 2;
77
0
  data--;
78
0
  sofar++;
79
0
  continue;
80
0
      }
81
0
      break;
82
0
    case WILDS:                /* Matches anything */
83
0
      if (escaped && is_escaped(mask, ma)) {
84
0
  if (*mask != *data)
85
0
    goto nomatch;
86
0
  mask -= 2;
87
0
  data--;
88
0
  sofar++;
89
0
  continue;
90
0
      }
91
0
      do
92
0
  mask--;                    /* Zap redundant wilds */
93
0
      while ((mask >= ma) && (*mask == WILDS) &&
94
0
       (!escaped || !is_escaped(mask, ma)));
95
0
      lsm = mask;
96
0
      lsn = data;
97
0
      match += sofar;
98
0
      sofar = 0;                /* Update fallback pos */
99
0
      if (mask < ma)
100
0
  return MATCH;
101
0
      continue;                 /* Next char, please */
102
0
    case WILDQ:
103
0
      if (escaped && is_escaped(mask, ma)) {
104
0
  if (*mask != *data)
105
0
    goto nomatch;
106
0
  mask -= 2;
107
0
  data--;
108
0
  sofar++;
109
0
  continue;
110
0
      }
111
0
      mask--;
112
0
      data--;
113
0
      continue;                 /* '?' always matches */
114
0
    }
115
0
    if (icase ? (i_toupper(*mask) == i_toupper(*data)) :
116
0
  (*mask == *data)) {     /* If matching char */
117
0
      mask--;
118
0
      data--;
119
0
      sofar++;                  /* Tally the match */
120
0
      continue;                 /* Next char, please */
121
0
    }
122
0
nomatch:
123
0
    if (lsm != NULL) {          /* To to fallback on '*' */
124
0
      data = --lsn;
125
0
      mask = lsm;
126
0
      if (data < na)
127
0
        lsm = NULL;                /* Rewind to saved pos */
128
0
      sofar = 0;
129
0
      continue;                 /* Next char, please */
130
0
    }
131
0
    return NOMATCH;             /* No fallback=No match */
132
0
  }
133
0
  while ((mask >= ma) && (*mask == WILDS) &&
134
0
   (!escaped || !is_escaped(mask, ma)))
135
0
    mask--;                        /* Zap leftover %s & *s */
136
0
  return (mask >= ma) ? NOMATCH : MATCH;   /* Start of both = match */
137
0
}
138
139
bool wildcard_match(const char *data, const char *mask)
140
0
{
141
0
  return wildcard_match_int(data, mask, FALSE, FALSE) != 0;
142
0
}
143
144
bool wildcard_match_icase(const char *data, const char *mask)
145
0
{
146
0
  return wildcard_match_int(data, mask, TRUE, FALSE) != 0;
147
0
}
148
149
bool wildcard_match_escaped(const char *data, const char *mask)
150
0
{
151
0
  return wildcard_match_int(data, mask, FALSE, TRUE) != 0;
152
0
}
153
154
bool wildcard_match_escaped_icase(const char *data, const char *mask)
155
0
{
156
0
  return wildcard_match_int(data, mask, TRUE, TRUE) != 0;
157
0
}
158
159
bool wildcard_is_escaped_literal(const char *mask)
160
0
{
161
0
  const char *p = mask;
162
163
0
  while ((p = strpbrk(p, "*?\\")) != NULL) {
164
0
    if (*p != '\\')
165
0
      return FALSE;
166
0
    if (p[1] == '\0')
167
0
      break;
168
0
    p += 2;
169
0
  }
170
0
  return TRUE;
171
0
}
172
173
const char *wildcard_str_escape(const char *str)
174
0
{
175
0
  const char *p = strpbrk(str, "*?\\\"'");
176
0
  if (p == NULL)
177
0
    return str;
178
179
0
  string_t *esc = t_str_new((p - str) + strlen(p) + 8);
180
0
  do {
181
0
    str_append_data(esc, str, p - str);
182
0
    str_append_c(esc, '\\');
183
0
    str_append_c(esc, *p);
184
185
0
    str = p + 1;
186
0
    p = strpbrk(str, "*?\\\"'");
187
0
  } while (p != NULL);
188
0
  str_append(esc, str);
189
0
  return str_c(esc);
190
0
}