vmod_re2(3)

πŸ“¦
version 2.0.0+20250617 Β· 6 versions
, in ubuntu resolute, ubuntu stonking

πŸ’– NAME πŸ’–

vmod_re2 β€” it's a Varnish Module (that's the vmod_re2(3) in the corner, bestie, Library Functions Manual energy) that gives you access to the Google RE2 regular expression engine. So yes, you get to do regex in VCL, but the safe kind. Slay.


✨ SYNOPSIS ✨

import re2;

# regex object interface
new OBJECT = re2.regex(STRING pattern [, <regex options>])
BOOL <obj>.match(STRING)
STRING <obj>.backref(INT ref)
STRING <obj>.namedref(STRING name)
STRING <obj>.sub(STRING text, STRING rewrite)
STRING <obj>.suball(STRING text, STRING rewrite)
STRING <obj>.extract(STRING text, STRING rewrite)
INT <obj>.cost()

# regex function interface
BOOL re2.match(STRING pattern, STRING subject [, <regex options>])
STRING re2.backref(INT ref)
STRING re2.namedref(STRING name)
STRING re2.sub(STRING pattern, STRING text, STRING rewrite
               [, <regex options>])
STRING re2.suball(STRING pattern, STRING text, STRING rewrite
                  [, <regex options>])
STRING re2.extract(STRING pattern, STRING text, STRING rewrite
                   [, <regex options>])
INT re2.cost(STRING pattern [, <regex options>])

# set object interface
new OBJECT = re2.set([ENUM anchor] [, <regex options>])
VOID <obj>.add(STRING [, BOOL save] [, BOOL never_capture] [, STRING string]
               [, BACKEND backend] [, INT integer] [,SUB sub])
BOOL <obj>.match(STRING)
INT <obj>.nmatches()
BOOL <obj>.matched(INT)
INT <obj>.which([ENUM select])
STRING <obj>.string([INT n,] [ENUM select])
BACKEND <obj>.backend([INT n,] [ENUM select])
INT     <obj>.integer([INT n] [, ENUM select])
SUB     <obj>.subroutine([INT n] [, ENUM select])
BOOL    <obj>.check_call([INT n] [, ENUM select])
STRING <obj>.sub(STRING text, STRING rewrite [, INT n]
                 [, ENUM select])
STRING <obj>.suball(STRING text, STRING rewrite [, INT n]
                    [, ENUM select])
STRING <obj>.extract(STRING text, STRING rewrite [, INT n]
                     [, ENUM select])
BOOL <obj>.saved([ENUM {REGEX, STR, BE, INT, SUB} which] [, INT n]
                 [, ENUM select])
VOID <obj>.hdr_filter(HTTP [, BOOL])

# utility function
STRING re2.quotemeta(STRING)

# VMOD version
STRING re2.version()

🌸 DESCRIPTION 🌸

Okay so picture this: it's a Varnish Module β€” a VMOD β€” and its whole thing is giving you access to the Google RE2 regular expression engine. That's it, that's the job. ✨

Here's the tea though. Varnish VCL already ships with the PCRE library (Perl Compatible Regular Expressions) for its native regex stuff, and honestly? For tons of everyday pattern-matching in VCL it runs so efficiently. Years of PCRE happily living inside Varnish, no notes, it ate. 🫢

But. πŸ‘€ For certain kinds of patterns, the worst-case running time of the PCRE matcher is exponential in the length of the string you're matching. Why? Backtracking, bestie. It's implemented with recursive calls to the internal match() function. And in principle there is no upper bound to the possible depth of that backtracking and recursion β€” the only thing holding it back is whatever the varnishd runtime parameters pcre_match_limit and pcre_match_limit_recursion say, and matches fail if either of those limits gets hit. Stack overflow from deep backtracking has occasionally been the subject of varnishd issues. Not cute. 😭

Now RE2 is built different. RE2 differs from PCRE in that it limits the syntax of patterns so they always specify a regular language in the formally strict sense. Most notably, backreferences within a pattern are not permitted β€” so like (foo|bar)\1 (which would match foofoo and barbar, but not foobar or barfoo) is a no-go. The specification of RE2 syntax lives at the link in SEE ALSO. πŸ”₯

What that buys you: an RE2 matcher runs as a finite automaton, which guarantees linear running time in the length of the matched string. No backtracking. Therefore no risk of deep recursion or stack overflow. Iconic behavior, honestly. πŸ‘‘

Lowkey full disclosure: the relative advantages and disadvantages of RE2 versus PCRE is a broad subject, way beyond the scope of this manual. Slinks off. Check the references in SEE ALSO if you want the in-depth discussion. πŸ’…

πŸ¦‹ regex object and function interfaces

The VMOD gives you regex operations two ways: through the regex object interface and through a functional interface.

For regex objects, the pattern gets compiled at VCL initialization time, and that compiled pattern is re-used every single time its methods get invoked. So if compilation fails (because the pattern has errors), it fails at initialization time and the VCL fails to load. Also super important: the .backref() and .namedref() methods refer back to the last invocation of the .match() method for the same object. Remember that, iykyk.

The functional interface does the same set of operations, except the pattern is compiled at runtime on each invocation (and then discarded, rip). Compilation failures there get reported as errors in the Varnish log. And backref() / namedref() refer back to the last invocation of the match() function β€” for any pattern. πŸ‘€

Now, compiling a pattern at runtime on every invocation is considerably more costly than re-using a compiled pattern. So: if your pattern is fixed and known at VCL initialization, use the object interface. The functional interface is only for patterns whose contents aren't known until runtime. Periodt. πŸ’…

🌈 set object interface

set objects are a shorthand for building patterns that are an alternation β€” a group of patterns combined with | meaning "or". Like:

import re2;

sub vcl_init {
      new myset = re2.set();
      myset.add("foo");       # Pattern 1
      myset.add("bar");       # Pattern 2
      myset.add("baz");       # Pattern 3
}

So now myset.match(<string>) matches a string against the pattern foo|bar|baz. And when a match succeeds, the matcher has figured out all the patterns that matched. Grab those with .nmatches() for the number of matched patterns, and with .matched(n), which is true if the *n*th pattern matched β€” where patterns are numbered in the order they were added:

if (myset.match("foobar")) {
    std.log("Matched " + myset.nmatches() + " patterns");
    if (myset.matched(1)) {
        # Pattern /foo/ matched
        call do_foo;
    }
    if (myset.matched(2)) {
        # Pattern /bar/ matched
        call do_bar;
    }
    if (myset.matched(3)) {
        # Pattern /baz/ matched
        call do_baz;
    }
}

Why is this a glow-up over an alternation in PCRE or a whole if-elsif-elsif chain of separate matches? Because the matcher is implemented as a state machine. 🧚 That means it progresses through the string to be matched just once, following the patterns in the set that match through the state machine, or determining there's no match the moment there are no more possible paths in the state machine. So you can match a string against a large set of patterns in time proportional to the length of the string being matched. Chef's kiss.

Compare that to PCRE, which matches patterns in an alternation one after another β€” stopping after the first matching pattern, or attempting matches against all of them if nothing matches. So matching an alternation in PCRE is honestly not unlike an if-elsif-elsif sequence of individual matches, and it needs the time required for each individual match, overall in proportion with the number of patterns to match. πŸ’€

Another W for the VMOD's set object: you can associate strings or backends with the patterns you add, using the .add() method:

sub vcl_init {
      new prefix = re2.set(anchor=start);
      prefix.add("/foo", string="www.domain1.com");
      prefix.add("/bar", string="www.domain2.com");
      prefix.add("/baz", string="www.domain3.com");
      prefix.add("/quux", string="www.domain4.com");

      new appmatcher = re2.set(anchor=start);
      appmatcher.add("/foo", backend=app1);
      appmatcher.add("/bar", backend=app2);
      appmatcher.add("/baz", backend=app3);
      appmatcher.add("/quux", backend=app4);
}

After a successful match, the string or backend tied to the matching pattern is retrievable with the .string() and .backend() methods. Which means you can, for example, build a redirect response or pick the backend with code that's both efficient and compact, even when you're dealing with a huge pile of patterns:

# Use the prefix object to construct a redirect response from
# a matching request URL.
sub vcl_recv {
    if (prefix.match(req.url)) {
        # Pass the string associated with the matching pattern
        # to vcl_synth.
        return(synth(1301, prefix.string()));
    }
}

sub vcl_synth {
    # The string associated with the matching pattern is in
    # resp.reason.
    if (resp.status == 1301) {
        set resp.http.Location = "http://" + resp.reason + req.url;
        set resp.status = 301;
        set resp.reason = "Moved Permanently";
    }
}

# Use the appmatcher object to choose a backend based on the
# request URL prefix.
sub vcl_recv {
    if (appmatcher.match(req.url)) {
        set req.backend_hint = appmatcher.backend();
    }
}

πŸŽ€ regex options

Wherever a pattern gets compiled β€” in the regex and set constructors, and in functions that require compilation β€” you can specify options that affect how the pattern is interpreted or how the matcher operates. Every option has a default value, and you only need to specify options in VCL that differ from the defaults. And a little note: options specified in a set constructor apply to all of the patterns in the resulting alternation. πŸ’–

  • utf8 β€” if true, characters in a pattern match Unicode code points, and hence may match more than one byte. If false, the pattern and the strings being matched are interpreted as Latin-1 (ISO 8859-1), and a pattern character matches exactly one byte. Default is false. Heads up: this differs from the RE2 default. πŸ™ˆ
  • posix_syntax β€” if true, patterns are restricted to POSIX (egrep) syntax. Otherwise the pattern syntax resembles that of PCRE, with some deviations. The syntax specification is at the link in SEE ALSO. Default is false. Also, the options perl_classes, word_boundary and one_line are only consulted when this option is true. πŸ‘‘

(to be continued, bestie β€” there's way more coming πŸ’…)

  • longest_match β€” when there's more than one possible way to match, this makes the matcher go for the longest match it can get. If it's off, you get the first match instead. Example time: pattern a(b|bb) on string abb β€” with longest_match true you match abb and backref 1 is bb ✨. With it off you match ab and backref 1 is just b. Default is false.
  • max_mem β€” a ceiling, in bytes, on how big the compiled pattern is allowed to get. If max_mem is too small the matcher might fall back to less efficient algorithms, or the pattern might not even compile 😭. Default is the RE2 default (8MB), which should be plenty for your typical patterns.
  • literal β€” if true, the pattern is treated as a plain literal string, so no regex metacharacters (like *, +, ^ and so on) get their special powers. Default is false.
  • never_nl β€” if true, the newline character \n in a string is never matched, even if it literally shows up in the pattern. Default is false.
  • dot_nl β€” if true, the dot character . in a pattern matches everything, newline included 🫢. Otherwise . never matches newline. Default is false.
  • never_capture β€” if true, parentheses in a pattern are treated as non-capturing, and every single call to the backref and namedref methods or functions leads to VCL failure (peep ERRORS), including backref(0) after a successful match. Default is false β€” except for set objects, where never_capture is always true (and you literally cannot change it), since back references aren't a thing with sets. πŸ’…
  • case_sensitive β€” if true, matches care about case. A pattern can override this with the (?i) flag, unless posix_syntax is true. Default is true.

The next batch of options is only consulted when posix_syntax is true. If posix_syntax is false, then these features are always enabled and can't be turned off, periodt.

  • perl_classes β€” if true, the perl character classes \d, \s, \w, \D, \S and \W are allowed in a pattern. Default is false.
  • word_boundary β€” if true, the perl assertions \b and \B (word boundary and not a word boundary) are allowed. Default is false.
  • one_line β€” if true, then ^ and $ only match at the beginning and end of the string being matched, regardless of newlines. Otherwise ^ also matches right after a newline, and $ also matches right before a newline. Default is false.

🌸 new xregex = re2.regex(...) 🌸

new xregex = re2.regex(STRING pattern, BOOL utf8, BOOL posix_syntax, BOOL
    longest_match, INT max_mem, BOOL literal, BOOL never_nl, BOOL dot_nl,
    BOOL never_capture, BOOL case_sensitive, BOOL perl_classes, BOOL
    word_boundary, BOOL one_line)
new xregex = re2.regex(
   STRING pattern,
   BOOL utf8=0,
   BOOL posix_syntax=0,
   BOOL longest_match=0,
   INT max_mem=8388608,
   BOOL literal=0,
   BOOL never_nl=0,
   BOOL dot_nl=0,
   BOOL never_capture=0,
   BOOL case_sensitive=1,
   BOOL perl_classes=0,
   BOOL word_boundary=0,
   BOOL one_line=0
)

Bestie, this makes you a regex object out of pattern plus all those options (or their defaults if you don't say anything). If the pattern is invalid, then VCL will fail to load and the VCC compiler will spit out an error message πŸ’€.

Example, so you can see the vibe:

sub vcl_init {
    new domainmatcher = re2.regex("^www\.([^.]+)\.com$");
    new maxagematcher = re2.regex("max-age\s*=\s*(\d+)");

    # Group possible subdomains without capturing
    new submatcher = re2.regex("^www\.(domain1|domain2)\.com$",
                               never_capture=true);
}

πŸ‘‘ BOOL xregex.match(STRING) πŸ‘‘

Returns true if and only if the compiled regex matches the string you hand it; this is the same energy as VCL's infix operator ~.

Example:

if (myregex.match(req.http.Host)) {
   call do_on_match;
}

πŸ¦‹ STRING xregex.backref(INT ref, STRING fallback) πŸ¦‹

STRING xregex.backref(
      INT ref,
      STRING fallback="**BACKREF METHOD FAILED**"
)

Gives you the *n*th captured subexpression from the most recent successful call of the .match() method for this object in the same client or backend context β€” or a fallback string if the capture doesn't work out. Backref 0 means the entire matched string. So yeah, this behaves exactly like the \n notation in the native VCL functions regsub and regsuball, and like the $1, $2 ... variables in Perl. iykyk 🧚

Since Varnish client and backend operations run in different threads, .backref() can only refer back to a .match() call in the same thread. So a .backref() call in any of the vcl_backend_* subroutines β€” the backend context β€” refers back to a previous .match() in any of those same subroutines; and a call in any of the other VCL subroutines β€” the client context β€” refers back to a .match() in the same client context.

.backref() may return fallback after a successful match, if no captured group in the matching string corresponds to the backref number. Example: when the pattern (a|(b))c matches the string ac, there's no backref 2, because nothing matched b in the string. The default value of fallback is "**BACKREF METHOD FAILED**", but you can set your own (like the empty string).

After unsuccessful matches, .backref() invokes VCL failure (see ERRORS). .backref() always fails after a failed match, even if .match() had been called successfully before the failure. 😭

The VCL infix operators ~ and !~ do not affect this method, nor do the functions regsub or regsuball. Nor is it affected by the matches performed by any other method or function in this VMOD (like the sub(), suball() or extract() methods or functions, or the set object's .match() method).

.backref() invokes VCL failure under the following conditions, even if a previous match was successful and a substring could have been captured (see ERRORS):

  • The fallback string is undefined, for example if it's set from an unset header variable.
  • The never_capture option was set to true for this object. In this case, even .backref(0) fails after a successful match (otherwise, backref 0 always returns the full matched string).
  • ref (the backref number) is out of range, i.e. it is larger than the highest number for a capturing group in the pattern.
  • .match() was never called for this object prior to calling .backref().
  • There is insufficient workspace for the string to be returned.

Example:

if (domainmatcher.match(req.http.Host)) {
   set req.http.X-Domain = domainmatcher.backref(1);
}

🌈 STRING xregex.namedref(STRING name, STRING fallback) 🌈

STRING xregex.namedref(
      STRING name,
      STRING fallback="**NAMEDREF METHOD FAILED**"
)

Returns the captured subexpression designated by name from the most recent successful call to .match() in the current context (client or backend).

Named capturing groups are written in RE2 as: (?P<name>re). (Heads up: this syntax with P, inspired by Python, differs from the notation for named capturing groups in PCRE.) So when (?P<foo>.+)bar$ matches bazbar, then .namedref("foo") returns baz ✨

Also note that a named capturing group can be referenced as a numbered group too. So in that previous example, .backref(1) also returns baz.

fallback is returned when the named reference did not match. The default fallback is "**NAMEDREF METHOD FAILED**".

Like .backref(), .namedref() is not affected by native VCL regex operations, nor by any other matches performed by methods or functions of the VMOD, except for a prior .match() for the same object.

.namedref() invokes VCL failure (see ERRORS) if:

  • The fallback string is undefined.
  • name is undefined or the empty string.
  • The never_capture option was set to true.
  • There is no such named group.
  • .match() was not called for this object.
  • There is insufficient workspace for the string to be returned.

Example:

sub vcl_init {
      new domainmatcher = re2.regex("^www\.(?P<domain>[^.]+)\.com$");
}

sub vcl_recv {
      if (domainmatcher.match(req.http.Host)) {
         set req.http.X-Domain = domainmatcher.namedref("domain");
      }
}

πŸ’– STRING xregex.sub(STRING text, STRING rewrite, STRING fallback) πŸ’–

STRING xregex.sub(
      STRING text,
      STRING rewrite,
      STRING fallback="**SUB METHOD FAILED**"
)

If the compiled pattern for this regex object matches text, then return the result of replacing the first match in text with rewrite. Inside rewrite, \1 through \9 can be used to insert the the numbered capturing group from the pattern, and \0 to insert the entire matching text. This method corresponds to the VCL native function regsub().

fallback is returned if the pattern does not match text. The default fallback is "**SUB METHOD FAILED**".

.sub() invokes VCL failure (see ERRORS) if:

  • Any of text, rewrite or fallback are undefined.
  • There is insufficient workspace for the rewritten string.

Example:

sub vcl_init {
    new bmatcher = re2.regex("b+");
}

sub vcl_recv {
    # If Host contains "www.yabba.dabba.doo.com", then this will
    # set X-Yada to "www.yada.dabba.doo.com".
    set req.http.X-Yada = bmatcher.sub(req.http.Host, "d");
}

πŸŽ€ STRING xregex.suball(STRING text, STRING rewrite, STRING fallback) πŸŽ€

STRING xregex.suball(
      STRING text,
      STRING rewrite,
      STRING fallback="**SUBALL METHOD FAILED**"
)

Just like .sub(), except that all successive non-overlapping matches in text are replaced with rewrite. This method corresponds to VCL native regsuball().

The default fallback is "**SUBALL METHOD FAILED**". .suball() fails under the same conditions as .sub().

Since only non-overlapping matches are substituted, replacing "ana" within "banana" only results in one substitution, not two. πŸ™ˆ

Example:

sub vcl_init {
    new bmatcher = re2.regex("b+");
}

sub vcl_recv {
    # If Host contains "www.yabba.dabba.doo.com", then set X-Yada to
    # "www.yada.dada.doo.com".
    set req.http.X-Yada = bmatcher.suball(req.http.Host, "d");
}

🌸 STRING xregex.extract(STRING text, STRING rewrite, STRING fallback) 🌸

STRING xregex.extract(
      STRING text,
      STRING rewrite,
      STRING fallback="**EXTRACT METHOD FAILED**"
)

If the compiled pattern for this regex object matches text, then return rewrite with substitutions from the matching portions of text. Non-matching substrings of text are ignored. πŸ‘‘

The default fallback is "**EXTRACT METHOD FAILED**". And just like .sub() and .suball(), .extract() goes πŸ’€ if:

  • Any of text, rewrite or fallback are undefined.
  • There's not enough workspace for the rewritten string.

Example, bestie:

sub vcl_init {
    new email = re2.regex("(.*)@([^.]*)");
}

sub vcl_deliver {
    # Sets X-UUCP to "kremvax!boris"
    set resp.http.X-UUCP = email.extract("boris@kremvax.ru", "\2!\1");
}

INT xregex.cost()

Returns a numeric measurement > 0 for this regex object, straight from the RE2 library 🧚. According to the RE2 docs:

... a very approximate measure of a regexp's "cost". Larger numbers
are more expensive than smaller numbers.

The actual numbers are opaque and honestly not that deep β€” but they are meaningful relative to each other, so more complex regexen get a higher cost than less complex regexen. Super useful when you're developing and optimising your regexen, fr fr.

Example:

std.log("r1 cost=" + r1.cost() + " r_alt cost=" + r_alt.cost());

πŸ“ regex functional interface πŸ“

BOOL match(...)

BOOL match(STRING pattern, STRING subject, BOOL utf8, BOOL posix_syntax,
    BOOL longest_match, INT max_mem, BOOL literal, BOOL never_nl, BOOL
    dot_nl, BOOL never_capture, BOOL case_sensitive, BOOL perl_classes, BOOL
    word_boundary, BOOL one_line)
BOOL match(
   STRING pattern,
   STRING subject,
   BOOL utf8=0,
   BOOL posix_syntax=0,
   BOOL longest_match=0,
   INT max_mem=8388608,
   BOOL literal=0,
   BOOL never_nl=0,
   BOOL dot_nl=0,
   BOOL never_capture=0,
   BOOL case_sensitive=1,
   BOOL perl_classes=0,
   BOOL word_boundary=0,
   BOOL one_line=0
)

Same energy as the regex.match() method: returns true if pattern matches subject, except here pattern gets compiled with the given options (or the default options) every single time you call it.

If pattern won't compile, then VCL failure is invoked (go peek at ERRORS).

Example:

# Match the bereq Host header against a backend response header
if (re2.match(pattern=bereq.http.Host, subject=beresp.http.X-Host)) {
   call do_on_match;
}

STRING backref(...)

STRING backref(INT ref, STRING fallback)
STRING backref(
   INT ref,
   STRING fallback="**BACKREF FUNCTION FAILED**"
)

Returns the *n*th captured subexpression from the most recent successful match() call in the current client or backend context β€” or a fallback string if the capture didn't work out πŸ’”. The default fallback is "**BACKREF FUNCTION FAILED**".

Just like the regex.backref() method, fallback is what you get if there's no captured group matching that backref number. And no, this function is not affected by native VCL regex operations, or by any other method or function of the VMOD β€” only the match() function touches it.

It invokes VCL failure under exactly the same conditions as the corresponding method (see ERRORS):

  • fallback is undefined.
  • never_capture was true in the previous invocation of the match() function.
  • ref is out of range.
  • The match() function was never called in this context, or the previous match() call failed (returned false).
  • The pattern failed to compile for the previous match() call.
  • There's insufficient workspace for the captured subexpression.

Example:

# Match against a pattern provided in a beresp header, and capture
# subexpression 1.
if (re2.match(pattern=beresp.http.X-Pattern, bereq.http.X-Foo)) {
   set beresp.http.X-Capture = re2.backref(1);
}

STRING namedref(...)

STRING namedref(STRING name, STRING fallback)
STRING namedref(
   STRING name,
   STRING fallback="**NAMEDREF FUNCTION FAILED**"
)

Returns the captured subexpression designated by name from the most recent successful match() call in the current context, or fallback if that group didn't match. Default fallback: "**NAMEDREF FUNCTION FAILED**".

VCL failure happens under the same conditions as the corresponding method (see ERRORS):

  • fallback is undefined.
  • name is undefined or the empty string.
  • The never_capture option was set to true.
  • There is no such named group.
  • match() was not called in this context, or the previous call failed.
  • The pattern failed to compile for the previous match() call.
  • There's insufficient workspace for the captured expression.

Example:

if (re2.match(beresp.http.X-Pattern-With-Names, bereq.http.X-Foo)) {
   set beresp.http.X-Capture = re2.namedref("foo");
}

STRING sub(...)

STRING sub(STRING pattern, STRING text, STRING rewrite, STRING fallback,
    BOOL utf8, BOOL posix_syntax, BOOL longest_match, INT max_mem, BOOL
    literal, BOOL never_nl, BOOL dot_nl, BOOL never_capture, BOOL
    case_sensitive, BOOL perl_classes, BOOL word_boundary, BOOL one_line)
STRING sub(
   STRING pattern,
   STRING text,
   STRING rewrite,
   STRING fallback="**SUB FUNCTION FAILED**",
   BOOL utf8=0,
   BOOL posix_syntax=0,
   BOOL longest_match=0,
   INT max_mem=8388608,
   BOOL literal=0,
   BOOL never_nl=0,
   BOOL dot_nl=0,
   BOOL never_capture=0,
   BOOL case_sensitive=1,
   BOOL perl_classes=0,
   BOOL word_boundary=0,
   BOOL one_line=0
)

Compiles pattern with the given options, and if it matches text, returns the result of replacing the first match in text with rewrite. Just like the regex.sub() method, you can use \0 through \9 in rewrite to substitute captured groups from the pattern ✨.

fallback is what comes back if the pattern doesn't match text. Default: "**SUB FUNCTION FAILED**".

sub() invokes VCL failure (see ERRORS) if:

  • pattern cannot be compiled.
  • Any of text, rewrite or fallback are undefined.
  • There's insufficient workspace for the rewritten string.

Example:

# If the beresp header X-Sub-Letters contains "b+", and Host contains
# "www.yabba.dabba.doo.com", then set X-Yada to
# "www.yada.dabba.doo.com".
set beresp.http.X-Yada = re2.sub(beresp.http.X-Sub-Letters,
                                 bereq.http.Host, "d");

STRING suball(...)

STRING suball(STRING pattern, STRING text, STRING rewrite, STRING fallback,
    BOOL utf8, BOOL posix_syntax, BOOL longest_match, INT max_mem, BOOL
    literal, BOOL never_nl, BOOL dot_nl, BOOL never_capture, BOOL
    case_sensitive, BOOL perl_classes, BOOL word_boundary, BOOL one_line)
STRING suball(
   STRING pattern,
   STRING text,
   STRING rewrite,
   STRING fallback="**SUBALL FUNCTION FAILED**",
   BOOL utf8=0,
   BOOL posix_syntax=0,
   BOOL longest_match=0,
   INT max_mem=8388608,
   BOOL literal=0,
   BOOL never_nl=0,
   BOOL dot_nl=0,
   BOOL never_capture=0,
   BOOL case_sensitive=1,
   BOOL perl_classes=0,
   BOOL word_boundary=0,
   BOOL one_line=0
)

Same as the sub() function, except that ALL the successive non-overlapping matches in text get replaced with rewrite. Obsessed.

Default fallback: "**SUBALL FUNCTION FAILED**". And suball() fails under exactly the same conditions as sub().

Example:

# If the beresp header X-Sub-Letters contains "b+", and Host contains
# "www.yabba.dabba.doo.com", then set X-Yada to
# "www.yada.dada.doo.com".
set beresp.http.X-Yada = re2.suball(beresp.http.X-Sub-Letters,
                                    bereq.http.Host, "d");

STRING extract(...)

STRING extract(STRING pattern, STRING text, STRING rewrite, STRING fallback,
    BOOL utf8, BOOL posix_syntax, BOOL longest_match, INT max_mem, BOOL
    literal, BOOL never_nl, BOOL dot_nl, BOOL never_capture, BOOL
    case_sensitive, BOOL perl_classes, BOOL word_boundary, BOOL one_line)
STRING extract(
   STRING pattern,
   STRING text,
   STRING rewrite,
   STRING fallback="**EXTRACT FUNCTION FAILED**",
   BOOL utf8=0,
   BOOL posix_syntax=0,
   BOOL longest_match=0,
   INT max_mem=8388608,
   BOOL literal=0,
   BOOL never_nl=0,
   BOOL dot_nl=0,
   BOOL never_capture=0,
   BOOL case_sensitive=1,
   BOOL perl_classes=0,
   BOOL word_boundary=0,
   BOOL one_line=0
)

Compiles pattern with the given options, and if it matches text, returns rewrite with substitutions from the matching portions of text β€” the non-matching bits just get ignored, no thoughts head empty πŸ™ˆ.

Default fallback: "**EXTRACT FUNCTION FAILED**". And extract() fails under the same conditions as sub() and suball().

Example:

# If beresp header X-Params contains "(foo|bar)=(baz|quux)", and the
# URL contains "bar=quux", then set X-Query to "bar:quux".
set beresp.http.X-Query = re2.extract(beresp.http.X-Params, bereq.url,
                                      "\1:\2");

INT cost(...)

INT cost(STRING pattern, BOOL utf8, BOOL posix_syntax, BOOL longest_match,
    INT max_mem, BOOL literal, BOOL never_nl, BOOL dot_nl, BOOL
    never_capture, BOOL case_sensitive, BOOL perl_classes, BOOL
    word_boundary, BOOL one_line)
INT cost(
   STRING pattern,
   BOOL utf8=0,
   BOOL posix_syntax=0,
   BOOL longest_match=0,
   INT max_mem=8388608,
   BOOL literal=0,
   BOOL never_nl=0,
   BOOL dot_nl=0,
   BOOL never_capture=0,
   BOOL case_sensitive=1,
   BOOL perl_classes=0,
   BOOL word_boundary=0,
   BOOL one_line=0
)

Like the .cost() method up there πŸ‘† β€” returns a numeric measurement > 0 from the RE2 library for pattern with the given options. More complex regexen have a higher cost than less complex regexen, periodt.

Invokes VCL failure if pattern cannot be compiled (see ERRORS).

Example:

std.log("simple cost=" + re2.cost("simple")
        + " complex cost=" + re2.cost("complex{1,128}"));

new xset = re2.set(...)

new xset = re2.set(ENUM anchor, BOOL utf8, BOOL posix_syntax, BOOL
    longest_match, INT max_mem, BOOL literal, BOOL never_nl, BOOL dot_nl,
    BOOL case_sensitive, BOOL perl_classes, BOOL word_boundary, BOOL
    one_line)
new xset = re2.set(
   ENUM {none, start, both} anchor=none,
   BOOL utf8=0,
   BOOL posix_syntax=0,
   BOOL longest_match=0,
   INT max_mem=8388608,
   BOOL literal=0,
   BOOL never_nl=0,
   BOOL dot_nl=0,
   BOOL case_sensitive=1,
   BOOL perl_classes=0,
   BOOL word_boundary=0,
   BOOL one_line=0
)

Initialise a set object that represents several patterns combined by alternation β€” that's |, aka "or" πŸ’….

Optional params control how the whole composed pattern gets read. The anchor param is an enum that can be none, start or both, and none is the default. start means every pattern is matched as if it starts with ^ for start-of-text, and both means every pattern is anchored with both ^ at the beginning and $ for end-of-text at the end. none means every pattern is interpreted as a partial match (even though individual patterns inside the set can still have either ^ or $) πŸ’…

Like, for example: if a set is initialized with anchor=both, and the patterns foo and bar are added, then matches against the set match a string against ^foo$|^bar$, or equivalently ^(foo|bar)$. Obsessed with the math of it ✨

The usual regex options can be set, which then control matching against the resulting composed pattern. HOWEVER β€” the never_capture option cannot be set, and is always implicitly true, since backrefs and namedrefs are just not a thing with sets. No cap 🫢

Sets get compiled automatically when vcl_init finishes (or when the deprecated .compile() method is called). Compilation fails if any of the added patterns cannot be compiled, or if no patterns were added to the set. It may also fail if the max_mem setting is not large enough for the composed pattern. In that case, the VCL load will fail with an error message (then consider a larger value for max_mem in the set constructor). She's dramatic but she's right πŸ’€

Example:

sub vcl_init {
      # Initialize a regex set for partial matches
      # with default options
      new foo = re2.set();

      # Initialize a regex set for case insensitive matches
      # with anchors on both ends (^ and $).
      new bar = re2.set(anchor=both, case_sensitive=false);

      # Initialize a regex set using POSIX syntax, but allowing
      # Perl character classes, and anchoring at the left (^).
      new baz = re2.set(anchor=start, posix_syntax=true,
                        perl_classes=true);
}

VOID xset.add(STRING, [STRING string], [BACKEND backend], [BOOL save], [BOOL never_capture], [INT integer], [SUB sub])

VOID xset.add(
      STRING,
      [STRING string],
      [BACKEND backend],
      [BOOL save],
      [BOOL never_capture],
      [INT integer],
      [SUB sub]
)

Add the given pattern to the set, bestie. If the pattern is invalid, .add() fails, and the VCL will fail to load, with an error message describing the problem. It's giving "we tell you exactly what went wrong" energy 😭

If values for the string, backend, integer and/or sub parameters are provided, then these values can be retrieved with the .string(), .backend(), .integer() and .subroutine() methods, respectively, as described below. This makes it possible to associate data with the added pattern after it matches successfully. By default the pattern is not associated with any such value. So like β€” you can glue lil notes onto your patterns πŸ“

If save is true, then the given pattern is compiled and saved as a regex object, just as if the regex constructor described above is invoked. This object is stored internally in the set object as an independent matcher, separate from the "compound" pattern formed by the set as an alternation of the patterns added to it. By default, save is false. Two matchers, one set, it's giving main character energy πŸ‘‘

When the .match() method on the set is successful, and one of the patterns that matched is associated with a saved internal regex object, then that object may be used for subsequent method invocations such as .sub() on the set object, whose meanings are the same as documented above for regex objects. Details are described below ✨

When an internal regex object is saved (i.e. when save is true), it is compiled with the same options that were provided to the set object in the constructor. The never_capture option can also be set to false for the individual regex, even though it is implicitly set to true for the full set object (default is false). So yeah, the little one gets to bend the rule πŸ™ˆ

.add() MUST be called in vcl_init, and MAY NOT be called after .compile(). VCL failure is invoked if .add() is called in any other subroutine (see ERRORS). If it is called in vcl_init after .compile(), then the VCL load will fail with an error message. Note that .compile() is now unnecessary and deprecated. Read that again, bestie, the rule is strict πŸ”₯

When the .matched(INT) method is called after a successful match, the numbering corresponds to the order in which patterns were added. The same is true of the INT arguments that may be given for methods such as .string(), .backend() or .sub(), as described below. First added = number 1, periodt πŸ’…

Example:

sub vcl_init {
    # literal=true means that the dots are interpreted as literal
    # dots, not "match any character".
    new hostmatcher = re2.set(anchor=both, case_sensitive=false,
                              literal=true);
    hostmatcher.add("www.domain1.com");
    hostmatcher.add("www.domain2.com");
    hostmatcher.add("www.domain3.com");
}

# See the documentation of the .string() and .backend() methods
# below for uses of the parameters string and backend for .add().

VOID xset.compile()

This method is deprecated, and will be removed in a future version. .compile() may be omitted, since compilation now happens automatically when vcl_init finishes. Yeah she's retiring, don't get attached πŸŽ€

Compile the compound pattern represented by the set β€” an alternation of all patterns added by .add().

Compilation may fail for any of the reasons described for automatic compilation of set objects as described above.

.compile() MUST be called in vcl_init, and MAY NOT be called more than once for a set object. VCL failure is invoked if it is called in any other subroutine. If it is called a second time in vcl_init, the VCL load will fail. One and done, babes 🫢

BOOL xset.match(STRING)

Returns true if the given string matches the compound pattern represented by the set, i.e. if it matches any of the patterns that were added to the set. Literally the whole point 🌟

The matcher identifies all of the patterns that were added to the set and match the given string. These can be determined after a successful match using the .matched(INT) and .nmatches() methods described below.

A match may also fail (leading to VCL failure) if the internal memory limit imposed by the max_mem parameter in the constructor is exceeded. (With the default value of max_mem, this ordinarily requires very large patterns and/or a very large string to be matched.) Since about version 2017-12-01, the RE2 library reports this condition. If matches fail due to the out-of-memory condition, increase the max_mem parameter in the constructor. That's your sign to give her more room to breathe 😀

Example:

if (hostmatcher.match(req.http.Host)) {
   call do_when_a_host_matched;
}

BOOL xset.matched(INT)

Returns true after a successful match if the *n*th pattern that was added to the set is among the patterns that matched, false otherwise. The numbering of the patterns corresponds to the order in which patterns were added in vcl_init, counting from 1. Count from one, bestie, not zero 🌸

The method refers back to the most recent invocation of .match() for the same object in the same client or backend context. It always returns false, for every value of the parameter, if it is called after an unsuccessful match (.match() returned false).

.matched() invokes VCL failure (see ERRORS) if:

  • The .match() method was not called for this object in the same client or backend scope.
  • The integer parameter is out of range; that is, if it is less than 1 or greater than the number of patterns added to the set.

Example:

if (hostmatcher.match(req.http.Host)) {
    if (hostmatcher.matched(1)) {
        call do_domain1;
    }
    if (hostmatcher.matched(2)) {
        call do_domain2;
    }
    if (hostmatcher.matched(3)) {
        call do_domain3;
    }
}

INT xset.nmatches()

Returns the number of patterns that were matched by the most recent invocation of .match() for the same object in the same client or backend context. The method always returns 0 after an unsuccessful match (.match() returned false). She keeps the receipts 🧾

If .match() was not called for this object in the same client or backend scope, .nmatches() invokes VCL failure (see ERRORS).

Example:

if (myset.match(req.url)) {
    std.log("URL matched " + myset.nmatches()
            + " patterns from the set");
}

INT xset.which(ENUM {FIRST, LAST, UNIQUE} select=UNIQUE)

Returns a number indicating which pattern in a set matched in the most recent invocation of .match() in the client or backend context. The number corresponds to the order in which patterns were added to the set in vcl_init, counting from 1. Again. From 1. I'm serious πŸ’…

If exactly one pattern matched in the most recent .match() call (so that .nmatches() returns 1), and the select ENUM is set to UNIQUE, then the number for that pattern is returned. select defaults to UNIQUE, so it can be left out in this case. Easy peasy πŸ“

If more than one pattern matched in the most recent .match() call (.nmatches() > 1), then the select ENUM determines the integer that is returned. The values FIRST and LAST specify that, of the patterns that matched, the first or last one added via the .add() method is chosen, and the number for that pattern is returned. Choose your fighter πŸ‘‘

.which() invokes VCL failure (see ERRORS) if:

  • .match() was not called for the set in the current client or backend transaction, or if the previous call returned false.
  • More than one pattern in the set matched in the previous .match() call, but the select parameter is set to UNIQUE (or left out, since select defaults to UNIQUE).

Examples:

sub vcl_init {
    new myset = re2.set();
    myset.add("foo"); # Pattern 1
    myset.add("bar"); # Pattern 2
    myset.add("baz"); # Pattern 3
    myset.compile();
}

sub vcl_recv {
    if (myset.match("bar")) {
        # myset.which() returns 2.
    }
    if (myset.which("foobaz")) {
        # myset.which() fails and returns 0, with a log
        #               message indicating that 2 patterns
        #               matched.
        # myset.which(FIRST) returns 1.
        # myset.which(LAST) returns 3.
    }
    if (myset.match("quux")) {
        # ...
    }
    else {
        # myset.which() fails and returns 0, with either or
        # no value for the select ENUM, with a log message
        # indicating that the previous .match() call was
        # unsuccessful.
    }
}

STRING xset.string(INT n, ENUM select)

STRING xset.string(
      INT n=0,
      ENUM {FIRST, LAST, UNIQUE} select=UNIQUE
)

Returns the string that got attached to the *n*th pattern you added to the set β€” or, if you're feeling lazy (valid), the string for whichever pattern matched in the most recent .match() call in the same task scope (client or backend context). Basically it hands you back whatever you set as the string parameter of the .add() method back in vcl_init. πŸ’–

Now, how do we figure out which pattern you mean? The n and select params decide, and these rules hold for all the other set methods below, so pay attention bestie:

  • If n > 0 β€” grab the *n*th pattern added to the set with .add(), counting from 1. This pins down the *n*th pattern in any context, no matter whether .match() was called before, and no matter whether that earlier call returned true or false. In this case the select parameter gets completely ignored. Bye.
  • If n <= 0 β€” pick a pattern from the set that matched successfully in the most recent .match() call in the same task scope. And since n is 0 by default? Yeah, you can just leave n out entirely for this. ✨
  • If n <= 0 AND exactly one pattern in the set matched in the most recent .match() invocation (which means .nmatches() returns 1), and select is UNIQUE β€” that pattern is your winner. select defaults to UNIQUE, so when only one pattern matched, you can leave out both n and select. Slay, minimalism.
  • If n <= 0 AND more than one pattern matched in the most recent .match() call (.nmatches() > 1) β€” then the select parameter decides everything. Just like with .which(), FIRST and LAST give you the first or last matching pattern added via the .add() method.

So for whatever pattern those rules landed on, you get back the string that was set with the string parameter in the .add() call that added it. That's it, that's the tea. 🫢

.string() triggers VCL failure (peep ERRORS) if:

  • The values of n and select are invalid, specifically:
    • n is greater than the number of patterns in the set.
    • n <= 0 (or you just left it at the default), but .match() was never called earlier in the same task scope (client or backend context).
    • n <= 0, but the previous .match() call returned false.
    • n <= 0 and the select ENUM is UNIQUE (or default), but more than one pattern matched in the previous .match() call. You can dodge this one by checking for .nmatches() == 1 first. πŸ’…
  • No string was associated with the pattern picked by n and select β€” meaning you didn't set the string parameter in the .add() call that added that pattern. Dodging this one is easy: check the .saved() method (more on that below).

Examples:

# Match the request URL against a set of patterns, and generate
# a synthetic redirect response with a Location header derived
# from the string assoicated with the matching pattern.

# In the first example, exactly one pattern in the set matches.

sub vcl_init {
    # With anchor=both, we specify exact matches.
    new matcher = re2.set(anchor=both);
    matcher.add("/foo/bar", "/baz/quux");
    matcher.add("/baz/bar/foo", "/baz/quux/foo");
    matcher.add("/quux/bar/baz/foo", "/baz/quux/foo/bar");
    matcher.compile();
}

sub vcl_recv {
    if (matcher.match(req.url)) {
        # Confirm that there was exactly one match
        if (matcher.nmatches() != 1) {
            return(fail);
        }
        # Divert to vcl_synth, sending the string associated
        # with the matching pattern in the "reason" field.
        return(synth(1301, matcher.string()));
    }
}

sub vcl_synth {
    # Construct a redirect response, using the path set in
    # resp.reason.
    if (resp.status == 1301) {
        set resp.http.Location
            = "http://otherdomain.org" + resp.reason;
        set resp.status = 301;
        set resp.reason = "Moved Permanently";
        return(deliver);
    }
}

# In the second example, the patterns that may match have
# common prefixes, and more than one pattern may match. We
# add patterns to the set in a "more specific" to "less
# specific" order, and we choose the most specific pattern
# that matches, by specifying the first matching pattern in
# the set.

sub vcl_init {
    # With anchor=start, we specify matching prefixes.
    new matcher = re2.set(anchor=start);
    matcher.add("/foo/bar/baz/quux", "/baz/quux");
    matcher.add("/foo/bar/baz", "/baz/quux/foo");
    matcher.add("/foo/bar", "/baz/quux/foo/bar");
    matcher.add("/foo", "/baz");
    matcher.compile();
}

sub vcl_recv {
    if (matcher.match(req.url)) {
        # Select the first matching pattern
        return(synth(1301, matcher.string(select=FIRST)));
    }
}

# vcl_synth is implemented as shown above

BACKEND xset.backend(INT n, ENUM select)

BACKEND xset.backend(
      INT n=0,
      ENUM {FIRST, LAST, UNIQUE} select=UNIQUE
)

Returns the backend associated with the *n*th pattern added to the set β€” or with whichever pattern in the set matched in the most recent .match() call in the same task scope (client or backend context). πŸŽ€

The rules for picking a pattern out of the set, and thus its associated backend, based on n and select? Identical to what we just said for .string(). Same energy, same rules.

.backend() triggers VCL failure under the exact same conditions described for .string() above β€” n and select are invalid, or no backend was associated with the selected pattern via the .add() method (check ERRORS). πŸ’€

Example:

# Choose a backend based on the URL prefix.

# In this example, assume that backends b1 through b4
# have been defined.

sub vcl_init {
    # Use anchor=start to match prefixes.
    # The prefixes are unique, so exactly one will match.
    new matcher = re2.set(anchor=start);
    matcher.add("/foo", backend=b1);
    matcher.add("/bar", backend=b2);
    matcher.add("/baz", backend=b3);
    matcher.add("/quux", backend=b4);
    matcher.compile();
}

sub vcl_recv {
    if (matcher.match(req.url)) {
        # Confirm that there was exactly one match
        if (matcher.nmatches() != 1) {
            return(fail);
        }
        # Set the backend hint to the backend associated
        # with the matching pattern.
        set req.backend_hint = matcher.backend();
    }
}

INT xset.integer(INT n, ENUM select)

INT xset.integer(
      INT n=0,
      ENUM {FIRST, LAST, UNIQUE} select=UNIQUE
)

Returns the integer associated with the *n*th pattern added to the set β€” or with whichever pattern matched in the most recent .match() call in the same task scope. πŸ”’

Selection rules for the pattern and its integer, based on n and select? You guessed it β€” same as .string() above.

.integer() triggers VCL failure under the same error conditions described for .string() above β€” n and select are invalid, or no integer was associated with the selected pattern via the .add() method (check ERRORS).

Example:

# Generate redirect responses based on the Host header. In the
# example, subdomains are removed in the new Location, and the
# associated integer is used to set the redirect status code.

sub vcl_init {
    # No more than one pattern can match the same string. So it
    # is safe to call .integer() with default select=UNIQUE in
    # vcl_recv below (no risk of VCL failure).
    new redir = re2.set(anchor=both);
    redir.add("www\.[^.]+\.foo\.com", integer=301, string="www.foo.com");
    redir.add("www\.[^.]+\.bar\.com", integer=302, string="www.bar.com");
    redir.add("www\.[^.]+\.baz\.com", integer=303, string="www.baz.com");
    redir.add("www\.[^.]+\.quux\.com", integer=307, string="www.quux.com");
    redir.compile();
}

sub vcl_recv {
    if (redir.match(req.http.Host)) {
        # Construct a Location header that will be used in the
        # synthetic redirect response.
        set req.http.Location = "http://" + redir.string() + req.url;

        # Set the response status from the associated integer.
        return( synth(redir.integer()) );
    }
}

sub vcl_synth {
    if (resp.status >= 301 && resp.status <= 307) {
        # We come here from the synth return for the redirect
        # response. The status code was set from .integer().
        set resp.http.Location = req.http.Location;
        return(deliver);
    }
}

STRING xset.sub(STRING text, STRING rewrite, STRING fallback, INT n, ENUM select)

STRING xset.sub(
      STRING text,
      STRING rewrite,
      STRING fallback="**SUB METHOD FAILED**",
      INT n=0,
      ENUM {FIRST, LAST, UNIQUE} select=UNIQUE
)

Returns the result of the method call .sub(text, rewrite, fallback) β€” exactly as documented above for the regex interface β€” but invoked on the *n*th pattern added to the set, or on whichever pattern in the set matched in the most recent .match() call in the same task scope. πŸ¦‹

Heads up though: .sub() requires that the pattern it identifies was saved as an internal regex object β€” meaning you set save to true when you added it with the .add() method. No save, no sub, periodt.

The pattern in question is determined by n and select per the rules we already went over. If an internal regex object was saved for that pattern, then you get back the result of the .sub() method invoked on that object. Simple. ✨

.sub() triggers VCL failure (check ERRORS) if:

  • The values of n and select are invalid, per the rules given above.
  • save was false in the .add() method for the pattern identified by n and select β€” that is, there's no internal regex object saved for .sub() to have been invoked on. 😭
  • The .sub() method invoked on the regex object fails for any of the reasons described for regex.sub().

Examples:

# Generate synthethic redirect responses on URLs that match a set of
# patterns, rewriting the URL according to the matched pattern.

# In this example, we set the new URL in the redirect location to
# the path that comes after the prefix of the original req.url.
sub vcl_init {
    new matcher = re2.set(anchor=start);
    matcher.add("/foo/(.*)", save=true);
    matcher.add("/bar/(.*)", save=true);
    matcher.add("/baz/(.*)", save=true);
    matcher.compile();
}
sub vcl_recv {
    if (matcher.match(req.url)) {
        if (matcher.nmatches() != 1) {
            return(fail);
        }
        return(synth(1301));
    }
}

sub vcl_synth {
    if (resp.status == 1301) {
        # matcher.sub() rewrites the URL to the subpath after the
        # original prefix.
        set resp.http.Location
            = "http://www.otherdomain.org" + matcher.sub(req.url, "/\1");
        return(deliver);
    }
}

STRING xset.suball(STRING text, STRING rewrite, STRING fallback, INT n, ENUM select)

STRING xset.suball(
      STRING text,
      STRING rewrite,
      STRING fallback="**SUBALL METHOD FAILED**",
      INT n=0,
      ENUM {FIRST, LAST, UNIQUE} select=UNIQUE
)

Okay so this one is literally the .sub() method's louder sibling. Same energy as .sub(), except it calls .suball(text, rewrite, fallback) from the regex interface on the nth pattern you added to the set β€” or on whichever pattern matched most recently in a .match() call. ✨

.suball() is subject to the exact same conditions as the .sub() method, no cap:

  • The pattern it gets applied to is identified by n and select, according to the rules given above.
  • It fails if:
    • The pattern it identifies was not saved with .add(save=true).
    • The values of n or select are invalid.
    • The .suball() method invoked on the saved regex object fails.

Example time, the good kind πŸ“:

# In any URL that matches one of the words given below, replace all
# occurrences of the matching word with "quux" (for example to
# rewrite path components or elements of query strings).
sub vcl_init {
    new matcher = re2.set();
    matcher.add("\bfoo\b", save=true);
    matcher.add("\bbar\b", save=true);
    matcher.add("\bbaz\b", save=true);
    matcher.compile();
}

sub vcl_recv {
    if (matcher.match(req.url)) {
        if (matcher.nmatches() != 1) {
            return(fail);
        }
        set req.url = matcher.suball(req.url, "quux");
    }
}

STRING xset.extract(STRING text, STRING rewrite, STRING fallback, INT n, ENUM select)

STRING xset.extract(
      STRING text,
      STRING rewrite,
      STRING fallback="**EXTRACT METHOD FAILED**",
      INT n=0,
      ENUM {FIRST, LAST, UNIQUE} select=UNIQUE
)

Same family, third sibling. Like .sub() and .suball(), this one returns the result of calling .extract(text, rewrite, fallback) from the regex interface on the nth pattern added to the set, or the pattern that matched most recently in a .match() call. πŸ’…

.extract() is subject to the same conditions as the other rewrite methods β€” it's giving "same rules, different method":

  • The pattern it's applied to is identified by n and select, according to the rules given above.
  • It fails if:
    • The pattern it identifies was not saved with .add(save=true).
    • The values of n or select are invalid.
    • The .extract() method invoked on the saved regex object fails.

And here's the little showcase πŸŽ€:

# Rewrite any URL that matches one of the patterns in the set
# by exchanging the path components.
sub vcl_init {
    new matcher = re2.set(anchor=both);
    matcher.add("/(foo)/(bar)/", save=true);
    matcher.add("/(bar)/(baz)/", save=true);
    matcher.add("/(baz)/(quux)/", save=true);
    matcher.compile();
}

sub vcl_recv {
    if (matcher.match(req.url)) {
        if (matcher.nmatches() != 1) {
            return(fail);
        }
        set req.url = matcher.extract(req.url, "/\2/\1/");
    }
}

SUB xset.subroutine(INT n, ENUM select)

SUB xset.subroutine(
      INT n=0,
      ENUM {FIRST, LAST, UNIQUE} select=UNIQUE
)

Returns the subroutine that was set by the sub parameter, for the element of the set indicated by n and select, following the rules given above. You can then invoke that subroutine with VCL call. πŸ‘‘

Note: bestie, YOU have to make sure the subroutine can legally be invoked in the context where it's being called. Which means:
  • The subroutine may only refer to VCL elements that are legal in the invocation context. For example, if the subroutine only touches headers in req.http.*, then it can be called in vcl_recv β€” but not if it refers to any header in resp.http.*. Hit up vcl-var(7) for the spec on which VCL variables are allowed in which contexts.
  • Recursive subroutine calls are not permitted in VCL. The subroutine invocation may not appear anywhere in its own call stack.

For standard subroutine invocations with call, the VCL compiler checks these conditions and throws a compile-time error if either one is violated. That is not possible with invocations using .subroutine() β€” the error can only be determined at runtime. So it's genuinely advisable to test .subroutine() carefully before using it in production. You can use the .check_call() method described below to figure out if the subroutine call is legal. 🫢

.subroutine() invokes VCL failure (See ERRORS) if:

  • The rules for n and select indicate failure.
  • No subroutine was set with the sub parameter in .add().
  • The subroutine is invoked with call, but the call isn't legal in the invocation context, for the reasons given above.

Example:

# Due to the use of resp.http.*, this subroutine may only be invoked
# in vcl_deliver or vcl_synth, as documented in vcl-var(7). Note
# that subroutine definitions must appear before vcl_init to
# permitted for the sub parameter in .add().
sub resp_sub {
    set resp.http.Call-Me = "but only in deliver or synth";
}

sub vcl_init {
    new myset = re2.set();
    myset.add("/foo", sub=resp_sub);
    myset.add("/foo/bar", sub=some_other_sub);
    # ...
}

sub vcl_deliver {
    if (myset.match(req.url)) {
       call myset.subroutine(select=FIRST);
    }
}

BOOL xset.check_call(INT n, ENUM select)

BOOL xset.check_call(
      INT n=0,
      ENUM {FIRST, LAST, UNIQUE} select=UNIQUE
)

Returns true iff the subroutine returned by .subroutine() for the element of the set indicated by n and select may be invoked legally in the current context. The conditions for legal invocation are documented for .subroutine() above. ✨

.check_call() never invokes VCL failure β€” it just returns false under the conditions where using .subroutine() would invoke VCL failure. And when that happens, a message gets emitted to the Varnish log using the Notice tag (the exact same message that would show up with the VCL_Error tag if the subroutine were actually called).

Notice messages in the log produced by this VMOD are always prefixed with the string

vmod_re2:
πŸ’€ Not me having to report this, but the source manpage literally has build noise right here β€” two copies of System Message: WARNING/2 (vmod_re2.man.rst:, line 1748) saying Inline literal start-string without end-string. Keeping them in for completeness, because we don't skip anything around here.

Example:

# Assume that myset is declared as in the example above.
sub vcl_deliver {
    if (myset.match(req.url)) {
       if (myset.check_call(select=FIRST)) {
          call myset.subroutine(select=FIRST);
       }
       else {
          call do_if_resp_sub_is_illegal;
       }
    }
}

BOOL xset.saved(ENUM which, INT n, ENUM select)

BOOL xset.saved(
      ENUM {REGEX, STR, BE, INT, SUB} which=REGEX,
      INT n=0,
      ENUM {FIRST, LAST, UNIQUE} select=UNIQUE
)

Returns true if and only if an object of the type indicated by which was saved at initialization time for the nth pattern added to the set β€” or for the pattern indicated by select after the most recent .match() call. πŸ¦‹

In other words, .saved() returns true:

  • for which=REGEX if the individual regex was saved with .add(save=true) for the indicated pattern
  • for which=STR if a string was stored with the string parameter in .add()
  • for which=BE if a backend was stored with the backend attribute.
  • for which=INT if an integer was stored with the integer attribute.
  • for which=SUB if an integer was stored with the sub attribute.

The default value of which is REGEX.

The pattern in the set is identified by n and select according to the rules given above. .saved() invokes VCL failure if the values of n or select are invalid (see ERRORS).

Example:

sub vcl_init {
    new s = re2.set();
    s.add("1", save=true, string="1", backend=b1);
    s.add("2", save=true, string="2");
    s.add("3", save=true, backend=b3);
    s.add("4", save=true);
    s.add("5", string="5", backend=b5);
    s.add("6", string="6");
    s.add("7", backend=b7);
    s.add("8");
    s.compile();
}

# Then the following holds for this set:
# s.saved(n=1) == true        # for any value of which
# s.saved(which=REGEX, n=2) == true
# s.saved(which=STR, n=2)   == true
# s.saved(which=BE, n=2)    == false
# s.saved(which=REGEX, n=3) == true
# s.saved(which=STR, n=3)   == false
# s.saved(which=BE, n=3)    == true
# s.saved(which=REGEX, n=4) == true
# s.saved(which=STR, n=4)   == false
# s.saved(which=BE, n=4)    == false
# s.saved(which=REGEX, n=5) == false
# s.saved(which=STR, n=5)   == true
# s.saved(which=BE, n=5)    == true
# s.saved(which=REGEX, n=6) == false
# s.saved(which=STR, n=6)   == true
# s.saved(which=BE, n=6)    == false
# s.saved(which=REGEX, n=7) == false
# s.saved(which=STR, n=7)   == false
# s.saved(which=BE, n=7)    == true
# s.saved(n=8) == false       # for any value of which

if (s.match("4")) {
   # The fourth pattern has been uniquely matched.
   # So in this context: s.saved() == true
   # Since save=true was used in .add() for the 4th pattern,
   # and which=REGEX by default.
}

VOID xset.hdr_filter(HTTP, BOOL whitelist=1)

Filters the headers in the HTTP object, which may be one of req, resp, bereq, or beresp. In other words, it filters the headers in the client or backend request or response. πŸŽ€

If whitelist is true, then headers that match one of the patterns in the set are retained, and all the other headers get removed. Otherwise, headers that match a pattern in the set get removed, and all the rest are retained. By default, whitelist is true.

Example:

okay so here's the whole hdr_filter glow-up in context, bestie β€” this is the set-filtering moment in full VCL:

          sub vcl_init {
                # Header whitelist
                new white = re2.set(anchor=start);
                white.add("Foo:");
                white.add("Bar:");
                white.add("Baz: baz$");
                white.compile();

                # Header blacklist
                new black = re2.set(anchor=start);
                black.add("Chaotic:");
                black.add("Evil:");
                black.add("Wicked: wicked$");
                black.compile();
          }

          sub vcl_recv {
                # Filter the client request header with the whitelist.
                # Headers that do not match any pattern in the set are removed.
                white.hdr_filter(req);
          }

          sub vcl_deliver {
                # Filter the client response header with the blacklist.
                # Headers that match any pattern in the set are removed.
                black.hdr_filter(resp, false);
          }

✨ STRING quotemeta(STRING) ✨

Returns a copy of the argument string with all regex metacharacters escaped via backslash. When the returned string is used as a regular expression, it will exactly match the original string, regardless of any special characters. This function has a purpose similar to a \Q..\E sequence within a regex, or the literal=true setting in a regex constructor.

The function invokes VCL failure if there is insufficient workspace for the return string (see ERRORS).

Example:

# The following are always true:
re2.quotemeta("1.5-2.0?") == "1\.5\-2\.0\?"
re2.match(re2.quotemeta("1.5-2.0?"), "1.5-2.0?")

🌸 STRING version() 🌸

Return the version string for this VMOD.

Example:

std.log("Using VMOD re2 version: " + re2.version());

✨ ERRORS ✨

Functions and methods of the VMOD may invoke VCL failure under unrecoverable error conditions. The effects of VCL failure depend on the VCL subroutine in which it takes place:

  • If invoked during vcl_init, then the VCL load fails, and an error message is returned over the CLI (reported by varnishadm(1)).
  • If invoked during any other subroutine besides vcl_synth, then an error message is recorded in the log with the VCL_Error tag, further processing is aborted immediately, and a response with status 503 (Service Not Available) is returned with the reason string "VCL failed".
  • If invoked during vcl_synth, then further processing is aborted, the error message is logged with VCL_Error, and the client connection is immediately closed -- the client receives no response.

Errors that lead to VCL failure include:

  • Any regex compilation failure.
  • Out of workspace errors (see LIMITATIONS).
  • Failures reported by the RE2 library for: matches, backrefs, namedrefs, the rewrite operations (sub, suball and extract), the .cost() function or method, and the .quotemeta() function. The VMOD detects most common errors that would lead to library errors, and invokes VCL failure in such cases without calling the library. But library errors may happen under conditions such as out of memory.
  • Functions and methods that require a previous successful match when there was no prior match, or when the previous match was unsuccessful. These include backrefs, namedrefs, and the data retrieval methods for set objects.
  • Any of the following parameters are undefined, for example when set from an unset header: fallbacks; patterns for the regex functions (which are compiled at runtime); the text and rewrite parameters for rewrite operations; the name parameter for namedrefs.
  • The name parameter for namedrefs is the empty string.
  • Backref number is out of range (greater than the number of backrefs in the pattern).
  • Backref or namedref attempted when the never_capture option was set to true for the pattern.
  • For set objects:
    • Numeric index (parameter n) is out of range (greater than the number of patterns in the set).
    • Use of select=UNIQUE after more than one pattern was matched. The .nmatches() can be used to check for this condition, to avoid VCL failure -- UNIQUE will fail in .namtches() > 1.
    • Retrieval of data from a set (such as a string, backend etc) by numeric index (n) or "associatively" (after a match) when no such object was saved for the corresponding pattern. Use the .saved() and .check_call() methods to check for this.
    • Calling the subroutine returned by .subrooutine() may be illegal, if it is not permitted in the subroutine from which it is called, or if it would lead to recursive calls. Use the .check_call() method to check for this.

🌈 REQUIREMENTS 🌈

The VMOD requires Varnish since version 6.6, or the master branch. See the source repository for versions of the VMOD that are compatible with other Varnish versions.

It requires the RE2 library, and has been tested against RE2 versions since 2015-06-01 (through 2021-04-01 at the time of writing).

If the VMOD is built against versions of RE2 since 2017-12-01, it uses a version of the set match operation that reports out-of-memory conditions during a match. (Versions of RE2 since June 2019 no longer have this error, but nevertheless the different internal call is used for set matches.) In that case, the VMOD is not compatible with earlier versions of RE2. This is only a problem if the runtime version of the library differs from the version against which the VMOD was built. If you encounter this error, consider re-building the VMOD against the runtime version of RE2, or installing a newer version of RE2.


πŸ’… LIMITATIONS πŸ’…

The VMOD allocates Varnish workspace for captured groups and rewritten strings. If operations fail with "insufficient workspace" error messages in the Varnish log (with the VCL_Error tag), increase the varnishd runtime parameters workspace_client and/or workspace_backend.

The RE2 documentation states that successful matches are slowed quite a bit when they also capture substrings. There is also additional overhead from the VMOD, unless the never_capture flag is true, to manage data about captured groups in the workspace. This overhead is incurred even if there are no capturing expressions in a pattern, since it is always possible to call backref(0) to obtain the matched portion of a string.

So if you are using a pattern only to match against strings, and never to capture subexpressions, consider setting the never_capture option to true, to eliminate the extra work for both RE2 and the VMOD.


πŸ‘‘ AUTHOR πŸ‘‘

  • Geoffrey Simmons <<geoff@uplex.de>>

UPLEX Nils Goroll Systemoptimierung


πŸ¦‹ SEE ALSO πŸ¦‹


Copyright (c) 2016-2018 UPLEX Nils Goroll Systemoptimierung
All rights reserved

Author: Geoffrey Simmons <geoffrey.simmons@uplex.de>

See LICENSE

and that's the footer, bestie: vmod_re2(3) πŸ’–βœ¨ no date in sight, just that iconic source tag. periodt. πŸŽ€

πŸ“¦ where this page comes from