Coverage Report

Created: 2026-10-23 06:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/trafficserver/include/tsutil/Regex.h
Line
Count
Source
1
/** @file
2
3
  A brief file description
4
5
  @section license License
6
7
  Licensed to the Apache Software Foundation (ASF) under one
8
  or more contributor license agreements.  See the NOTICE file
9
  distributed with this work for additional information
10
  regarding copyright ownership.  The ASF licenses this file
11
  to you under the Apache License, Version 2.0 (the
12
  "License"); you may not use this file except in compliance
13
  with the License.  You may obtain a copy of the License at
14
15
      http://www.apache.org/licenses/LICENSE-2.0
16
17
  Unless required by applicable law or agreed to in writing, software
18
  distributed under the License is distributed on an "AS IS" BASIS,
19
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
  See the License for the specific language governing permissions and
21
  limitations under the License.
22
 */
23
24
#pragma once
25
26
#include <cstdint>
27
#include <memory>
28
#include <string_view>
29
#include <string>
30
#include <vector>
31
32
/// @brief Match flags for regular expression evaluation.
33
///
34
/// @internal These values are copied from pcre2.h, to avoid having to include it.  The values are checked (with
35
/// static_assert) in Regex.cc against PCRE2 named constants, in case they change in future PCRE2 releases.
36
enum REFlags {
37
  RE_CASE_INSENSITIVE = 0x00000008u, ///< Ignore case (by default, matches are case sensitive).
38
  RE_UNANCHORED       = 0x00000400u, ///< Unanchored (@a DFA defaults to anchored).
39
  RE_ANCHORED         = 0x80000000u, ///< Anchored (@a Regex defaults to unanchored).
40
  RE_ENDANCHORED      = 0x20000000u, ///< Anchored at the subject end; with RE_ANCHORED, requires a whole-subject match.
41
  RE_NOTEMPTY         = 0x00000004u, ///< Not empty (by default, matches may match empty string).
42
  /// Require the match to consume the entire subject. When set, a successful pcre2 match
43
  /// that does not span [0, subject.size()) is reported as @c RE_ERROR_NOMATCH. Implemented
44
  /// as a post-match length check so JIT remains eligible on all supported PCRE2 versions.
45
  RE_FULL_MATCH = 0x10000000u,
46
};
47
48
/// @brief Error codes returned by regular expression operations.
49
///
50
/// @internal As with REFlags, these values are copied from pcre2.h, to avoid having to include it.
51
enum REErrors {
52
  RE_ERROR_NOMATCH = -1, ///< No match found.
53
  RE_ERROR_NULL    = -51 ///< NULL code or subject was passed.
54
};
55
56
/// @brief Wrapper for PCRE2 match data.
57
class RegexMatches
58
{
59
  friend class Regex;
60
61
public:
62
  /** Construct a new RegexMatches object.
63
   *
64
   * @param size The number of matches to allocate space for.
65
   */
66
  RegexMatches(uint32_t size = DEFAULT_MATCHES);
67
  ~RegexMatches();
68
69
  /** Get the match at the given index.
70
   *
71
   * @return The match at the given index, or an empty view.
72
   */
73
  std::string_view operator[](size_t index) const;
74
  /** Get the ovector pointer for the capture groups.  Don't use this unless you know what you are doing.
75
   *
76
   * @return ovector pointer.
77
   */
78
  size_t *get_ovector_pointer();
79
  int32_t size() const;
80
81
private:
82
  constexpr static uint32_t DEFAULT_MATCHES = 10;
83
  static void              *malloc(size_t size, void *caller);
84
  static void               free(void *p, void *caller);
85
  std::string_view          _subject;
86
  char    _buffer[24 + 96 + 28 * DEFAULT_MATCHES]; // 24 bytes for the general context, 96 bytes overhead, 28 bytes per match.
87
  size_t  _buffer_bytes_used = 0;
88
  int32_t _size              = 0;
89
90
  /// @internal This effectively wraps a void* so that we can avoid requiring the pcre2.h include for the user of the Regex
91
  /// API (see Regex.cc).
92
  struct _MatchData;
93
  class _MatchDataPtr
94
  {
95
    friend struct _MatchData;
96
97
  private:
98
    void *_ptr = nullptr;
99
  };
100
  _MatchDataPtr _match_data;
101
};
102
103
/// @brief Wrapper for PCRE2 match context
104
///
105
/// @internal This instance is not tied to any Regex and can be used with one of the Regex::exec overloads.
106
class RegexMatchContext
107
{
108
  friend class Regex;
109
110
public:
111
  /** Construct a new RegexMatchContext object.
112
   */
113
  RegexMatchContext();
114
  ~RegexMatchContext();
115
116
  /// uses pcre2_match_context_copy for a deep copy.
117
  RegexMatchContext(RegexMatchContext const &orig);
118
  RegexMatchContext &operator=(RegexMatchContext const &orig);
119
120
  RegexMatchContext(RegexMatchContext &&)            = default;
121
  RegexMatchContext &operator=(RegexMatchContext &&) = default;
122
123
  /** Limits the amount of backtracking that can take place.
124
   * Any regex exec call that fails will return PCRE2_ERROR_MATCHLIMIT(-47)
125
   */
126
  void set_match_limit(uint32_t limit);
127
128
private:
129
  /// @internal This wraps a void* so to avoid requiring a pcre2 include.
130
  struct _MatchContext;
131
  struct _MatchContextPtr {
132
    void *_ptr = nullptr;
133
  };
134
135
  _MatchContextPtr _match_context;
136
};
137
138
/// @brief Wrapper for PCRE2 regular expression.
139
class Regex
140
{
141
public:
142
0
  Regex() = default;
143
  /** Deep copy constructor.
144
   *
145
   * Creates a new Regex object with a deep copy of the compiled pattern.
146
   * Uses pcre2_code_copy() to duplicate the compiled pattern without
147
   * requiring the original pattern string, then compiles the copy for the
148
   * just-in-time engine, which pcre2_code_copy() cannot carry over.
149
   *
150
   * @param other The Regex object to copy from.
151
   */
152
  Regex(Regex const &other);
153
  /** Deep copy assignment operator.
154
   *
155
   * Replaces the current compiled pattern with a deep copy of the other's pattern.
156
   *
157
   * @param other The Regex object to copy from.
158
   * @return Reference to this object.
159
   */
160
  Regex &operator=(Regex const &other);
161
  Regex(Regex &&that) noexcept;
162
  Regex &operator=(Regex &&other);
163
  ~Regex();
164
165
  /** Compile the @a pattern into a regular expression.
166
   *
167
   * @param pattern Source pattern for regular expression.
168
   * @param flags Compilation flags.
169
   * @return @a true if compiled successfully, @a false otherwise.
170
   *
171
   * @a flags should be the bitwise @c or of @c REFlags values.
172
   *
173
   * On failure any previously compiled pattern is left in place and remains usable.
174
   */
175
  bool compile(std::string_view pattern, uint32_t flags = 0);
176
177
  /** Compile the @a pattern into a regular expression that drives a policy decision.
178
   *
179
   * @param pattern Source pattern for regular expression.
180
   * @param flags Compilation flags.
181
   * @param policy_gate Operator-facing label identifying this pattern as a policy gate.
182
   * @return @a true if compiled successfully, @a false otherwise.
183
   *
184
   * @a flags should be the bitwise @c or of @c REFlags values.
185
   *
186
   * A non-empty @a policy_gate marks @a pattern as operator-authored input that
187
   * drives a policy decision, subjecting it to @c policy_gate_anchoring. The label
188
   * names the site in diagnostics, e.g. @c "url_sig excl_regex".
189
   *
190
   * @internal This is a distinct overload rather than a defaulted third parameter
191
   * on the two-argument form. A default argument is supplied at the call site, so
192
   * appending one changes the mangled name and every plugin already built against
193
   * the old header fails to load with an undefined symbol for @c Regex::compile.
194
   *
195
   * @internal Dropping @a policy_gate at a call site selects the two-argument
196
   * overload and compiles clean, leaving that site neither anchored nor reported.
197
   * The two-argument form has to stay reachable for the plugins above, so this is
198
   * covered by tests at the gate sites rather than by the signature.
199
   */
200
  bool compile(std::string_view pattern, uint32_t flags, std::string_view policy_gate);
201
202
  /** Compile the @a pattern into a regular expression.
203
   *
204
   * @param pattern Source pattern for regular expression.
205
   * @param error String to receive error message.
206
   * @param erroffset Pointer to integer to receive error offset.
207
   * @param flags Compilation flags.
208
   * @return @a true if compiled successfully, @a false otherwise.
209
   *
210
   * @a flags should be the bitwise @c or of @c REFlags values.
211
   *
212
   * On failure any previously compiled pattern is left in place and remains usable.
213
   */
214
  bool compile(std::string_view pattern, std::string &error, int &erroffset, unsigned flags = 0);
215
216
  /** Compile the @a pattern into a regular expression that drives a policy decision.
217
   *
218
   * @param pattern Source pattern for regular expression.
219
   * @param error String to receive error message.
220
   * @param erroffset Pointer to integer to receive error offset.
221
   * @param flags Compilation flags.
222
   * @param policy_gate Operator-facing label identifying this pattern as a policy gate.
223
   * @return @a true if compiled successfully, @a false otherwise.
224
   *
225
   * @a flags should be the bitwise @c or of @c REFlags values.
226
   *
227
   * @internal A distinct overload for the same ABI reason as the two-argument form.
228
   */
229
  bool compile(std::string_view pattern, std::string &error, int &erroffset, unsigned flags, std::string_view policy_gate);
230
231
  /** Process-wide anchoring policy for policy-gate patterns.
232
   *
233
   * Non-zero anchors gate patterns to the whole subject. Zero (the default) leaves
234
   * matching unchanged and warns instead. Read at compile time, so a change takes
235
   * effect as configurations are reloaded.
236
   *
237
   * @internal tsutil cannot depend on librecords, so the owning process links this
238
   * with @c RecEstablishStaticConfigInt32. Plugins share the variable, so they need
239
   * no plumbing of their own.
240
   */
241
  static int32_t policy_gate_anchoring;
242
243
  /** What a policy-gate report is telling the operator. */
244
  enum class PolicyGateReport {
245
    UNENFORCED, ///< The policy is off and this pattern is not required to match the whole subject.
246
    ENFORCED,   ///< The policy anchored this pattern to the whole subject.
247
  };
248
249
  /** Handler invoked when a policy-gate pattern is compiled.
250
   *
251
   * @a message is a complete sentence. @a kind selects the severity: @c UNENFORCED is
252
   * a warning, @c ENFORCED is informational, since under enforcement the pattern is
253
   * safe and the operator only needs a record that the policy touched it. Null (the
254
   * default) stays silent.
255
   *
256
   * @internal A function pointer because tsutil must not reference the diagnostics
257
   * implementation: a @c Regex.cc to @c DbgCtl.cc edge pulls that object into every
258
   * target linking @c libtsutil.a, colliding with the stubs some plugin tests
259
   * provide and with the ODR constraint in @c plugins/esi/lib/CMakeLists.txt.
260
   */
261
  using PolicyGateReportHandler = void (*)(PolicyGateReport kind, std::string_view message);
262
263
  static PolicyGateReportHandler policy_gate_report_handler;
264
265
  /** Execute the regular expression.
266
   *
267
   * @param subject String to match against.
268
   * @return @c true if the pattern matched, @a false if not.
269
   *
270
   * It is safe to call this method concurrently on the same instance of @a this.
271
   */
272
  bool exec(std::string_view subject) const;
273
274
  /** Execute the regular expression.
275
   *
276
   * @param subject String to match against.
277
   * @param flags Match flags (e.g., RE_NOTEMPTY).
278
   * @return @c true if the pattern matched, @a false if not.
279
   *
280
   * It is safe to call this method concurrently on the same instance of @a this.
281
   */
282
  bool exec(std::string_view subject, uint32_t flags) const;
283
284
  /** Execute the regular expression.
285
   *
286
   * @param subject String to match against.
287
   * @param matches Place to store the capture groups.
288
   * @return @c The number of capture groups. < 0 if an error occurred. 0 if the number of Matches is too small.
289
   *
290
   * It is safe to call this method concurrently on the same instance of @a this.
291
   *
292
   * Each capture group takes 3 elements of @a ovector, therefore @a ovecsize must
293
   * be a multiple of 3 and at least three times the number of desired capture groups.
294
   */
295
  int exec(std::string_view subject, RegexMatches &matches) const;
296
297
  /** Execute the regular expression.
298
   *
299
   * @param subject String to match against.
300
   * @param matches Place to store the capture groups.
301
   * @param flags Match flags (e.g., RE_NOTEMPTY).
302
   * @param optional context Match context (set matching limits).
303
   * @return @c The number of capture groups. < 0 if an error occurred. 0 if the number of Matches is too small.
304
   *
305
   * It is safe to call this method concurrently on the same instance of @a this.
306
   *
307
   * Each capture group takes 3 elements of @a ovector, therefore @a ovecsize must
308
   * be a multiple of 3 and at least three times the number of desired capture groups.
309
   */
310
  int exec(std::string_view subject, RegexMatches &matches, uint32_t flags,
311
           RegexMatchContext const *const matchContext = nullptr) const;
312
313
  /** Error string for exec failure.
314
   *
315
   * @param int return code from exec call.
316
   */
317
  static std::string get_error_string(int rc);
318
319
  /// @return The number of capture groups in the compiled pattern, -1 for fail.
320
  int32_t get_capture_count() const;
321
322
  /// @return number of highest back references, -1 for fail.
323
  int32_t get_backref_max() const;
324
325
  /// @return Is the compiled pattern empty?
326
  bool empty() const;
327
328
private:
329
  /// @internal This effectively wraps a void* so that we can avoid requiring the pcre2.h include for the user of the Regex
330
  /// API (see Regex.cc).
331
  struct _Code;
332
  class _CodePtr
333
  {
334
    friend struct _Code;
335
336
  private:
337
    void *_ptr = nullptr;
338
  };
339
  _CodePtr _code;
340
};
341
342
/** Deterministic Finite state Automata container.
343
 *
344
 * This contains a set of patterns (which may be of size 1) and matches if any of the patterns
345
 * match.
346
 */
347
class DFA
348
{
349
public:
350
  DFA() = default;
351
  ~DFA();
352
353
  /// @return The number of patterns successfully compiled.
354
  int32_t compile(const std::string_view pattern, unsigned flags = 0);
355
  /// @return The number of patterns successfully compiled.
356
  int32_t compile(const std::string_view *const patterns, int npatterns, unsigned flags = 0);
357
  /// @return The number of patterns successfully compiled.
358
  int32_t compile(const char *const *patterns, int npatterns, unsigned flags = 0);
359
360
  /** Match @a str against the internal patterns.
361
   *
362
   * @param str String to match.
363
   * @return Index of the matched pattern, -1 if no match.
364
   */
365
  int32_t match(std::string_view str) const;
366
367
private:
368
  struct Pattern {
369
0
    Pattern(Regex &&rxp, std::string &&s) : _re(std::move(rxp)), _p(std::move(s)) {}
370
    Regex       _re; ///< The compile pattern.
371
    std::string _p;  ///< The original pattern.
372
  };
373
374
  /** Compile @a pattern and add it to the pattern set.
375
   *
376
   * @param pattern Regular expression to compile.
377
   * @param flags Regular expression compilation flags.
378
   * @return @c true if @a pattern was successfully compiled, @c false if not.
379
   */
380
  bool build(std::string_view pattern, unsigned flags = 0);
381
382
  std::vector<Pattern> _patterns;
383
  bool                 _full_match = false; ///< Apply RE_FULL_MATCH on every match() call.
384
};