commit 6847767b7e2f466df854e262604e7ac367da8d54
Author: Sergey Poznyakoff <gray@gnu.org>
Date:   Sun Aug 18 13:47:49 2024 +0300
Forwarded: not-needed

    Rewrite matching support.
    
    Instead of being fixed at compile time, the regex flavour to use can
    be selected in configuration file.  Two mechanisms are provided:
    the RegexType statement sets the regex type globally, for all statements
    that follow it, and special matching options -posix, -pcre (or -perl),
    change it for the given statement only.
    
    New matching flag is implemented: -contain.  This flag enables substring
    match.
    
    Instead of being translated to regular expressions, the -exact, -beg,
    and -end flags are implemented as special matchers.  Translation to
    POSIX regex is needed only in case of "Host" statement, which implies
    prefixing the pattern with "Host:[[:space:]]*".
    
    * configure.ac (COND_REGEX): Remove variable.
    * src/pound.h: Introduce "generic pattern" concept.
    (GENPAT): New data type.
    (genpat_compile): Rename to genpat_compile.
    (genpat_match,genpat_free,genpat_error,genpat_nsub): New protos.
    * src/config.c: New global statement: RegexType.
    New matching flags: -posix, -pcre, -perl, -contain.
    * src/regex_std.c: Remove.
    * src/genpat.c: New source.
    * src/regex_pcre.c: Rewrite as a genpat module.
    * src/regex_pcre2.c: Likewise.
    * tests/atlocal.in (PCRE_AVAILABLE): New variable, defined depending on
    the value of COND_PCRE and COND_PCRE2 conditionals.
    * tests/testsuite.at (PT_PREREQ_PCRE): New macro.
    Include pcre.at and regextype.at.
    * tests/Makefile.am: Add new files.
    * tests/pcre.at: New test.
    * tests/regextype.at: New test.
    * tests/host.at: Test regexp translation depending on the flags given
    (host_prefix_regex function).
    * tests/poundharness.pl (preproc): Treat argument to "Host -end" as
    "exact".
    * tests/url.at: Test the -contain flag.

diff --git a/NEWS b/NEWS
index 79b30ed..b5a60bc 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,4 @@
-Pound -- history of user-visible changes. 2024-08-14
+Pound -- history of user-visible changes. 2024-08-18
 See the end of file for copying conditions.
 
 Pound is a continuation of the software originally developed by
@@ -9,11 +9,6 @@ Please send pound bug reports to <gray@gnu.org>
 
 Version 4.12.90 (git)
 
-* New configuration statement: LogTag
-
-Sets a string to tag syslog messages with.  By default, same as the
-name used to start the program.
-
 * Support for pcre and pcre2 rewritten
 
 The pcre2posix (or pcreposix) layer is no longer used.  Instead, pound
@@ -21,6 +16,35 @@ uses the native API of the corresponding library.  This provides for
 additional speed-up and (in case of pcre2) avoids coredumps under high
 load.
 
+In contrast to previous versions, PCRE is not used by default.  You
+need to explicitly enable its use by adding the following statement
+somewhere near the beginning of your pound.cfg file:
+
+  RegexType pcre
+
+This statement sets the default regex flavour to use in all header
+matching statements below it.  It remains in effect until next
+RegexType statement or end of file is enountered, whichever occurs
+first.
+
+You can also request the use of PCRE in each such statement
+individually, even if POSIX regex is used by default.  For that, use
+the -pcre option, e.g.:
+
+  Host -pcre -icase "(?<!www\\.)example.org"
+
+* New matching option: -contain
+
+The -contain option enables substring match. E.g. the following will
+match if URL contains the word "user" (case-insensitive):
+
+  URL -contain -icase "user"
+
+* New configuration statement: LogTag
+
+Sets a string to tag syslog messages with.  By default, same as the
+name used to start the program.
+
 * Bugfixes
 
 ** Fix infinite recursion on reporting an out of memory condition.
diff --git a/README.md b/README.md
index 021f70e..7bbd48f 100644
--- a/README.md
+++ b/README.md
@@ -124,8 +124,8 @@ configuration options:
 
   By default, its presence is determined automatically; `libpcre2`
   is preferred over `libpcre`.  To force compiling with the older
-  `libpcre`, use `--enable-pcre=1`.  To disable the use of `pcre`
-  and stick with POSIX regular expressions, use `--disable-pcre`.
+  `libpcre`, use `--enable-pcre=1`.  To disable `pcre` support,
+  use `--disable-pcre`.
 
 * `--enable-pthread-cancel-probe` or `--disable-pthread-cancel-probe`
 
diff --git a/configure.ac b/configure.ac
index d5c568f..111b7c7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -161,7 +161,6 @@ AC_DEFINE_UNQUOTED([SET_DH_AUTO],[$SET_DH_AUTO],
   [Define to 1 of *set_dh_auto macros are available])
 
 PND_PCRE
-AM_CONDITIONAL([COND_REGEX], [test $status_pcre = no])
 AM_CONDITIONAL([COND_PCRE], [test $status_pcre = 1])
 AM_CONDITIONAL([COND_PCRE2], [test $status_pcre = 2])
 
@@ -224,7 +223,7 @@ Pound configuration parameters:
 Buffer size ................................... $bufsize
 Owner user .................................... $owner_user
 Owner group ................................... $owner_group
-Regular expressions ........................... $status_pcre
+Regular expressions ........................... POSIX$status_pcre
 Memory allocator .............................. $memory_allocator
 Early pthread_cancel probe .................... $status_pthread_cancel_probe
 *******************************************************************
@@ -235,9 +234,9 @@ EOF
 owner_user=$I_OWNER
 owner_group=$I_GRP
 if test $status_pcre != no; then
-  status_pcre=pcre$status_pcre
+  status_pcre=", PCRE$status_pcre"
 else
-  status_pcre="POSIX"
+  status_pcre=""
 fi
 memory_allocator=$memory_allocator
 if test "$early_pthread_cancel_probe" = 1; then
diff --git a/doc/pound.8 b/doc/pound.8
index acda940..cec8704 100644
--- a/doc/pound.8
+++ b/doc/pound.8
@@ -14,7 +14,7 @@
 .\"
 .\" You should have received a copy of the GNU General Public License
 .\" along with pound.  If not, see <http://www.gnu.org/licenses/>.
-.TH POUND 8 "June 26, 2024" "pound" "System Manager's Manual"
+.TH POUND 8 "August 18, 2024" "pound" "System Manager's Manual"
 .SH NAME
 pound \- HTTP/HTTPS reverse-proxy and load-balancer
 .SH SYNOPSIS
@@ -732,6 +732,20 @@ privileges of another user after startup (at least one of \fBUser\fR or
 \fBGroup\fR are set in the configuration file) and the file is stored in
 a directory whose permissions forbid write access for that user.
 .RE
+.TP
+\fBRegexType posix\fR | \fBpcre\fR | \fBperl\fR
+Sets the type of regular expressions to use in request matching
+statements.
+.B posix
+selects POSIX extended regular expressions and
+.B pcre
+or
+.B perl
+select Perl-compatible regular expressions.  The latter requires
+compile-time support.  The selected regular expression type remains in
+effect until next 
+.B RegexType
+statement or end of the configuration file, whichever occurs first.
 .SS Control socket
 .B Pound
 can be instructed to listen on a UNIX socket for management requests,
@@ -1395,23 +1409,20 @@ The \fIoptions\fR argument in the above directives can be used to
 select the comparison method.  It consists of zero or more option
 flags from the following list:
 .TP
-.B \-re
-Use regular expression matching.
-.TP
-.B \-exact
-Use exact string matching.
-.TP
 .B \-beg
 Exact match at the beginning of string (prefix match).
 .TP
-.B \-end
-Exact match at the end of string (suffix match).
-.TP
 .B \-case
 Case-sensitive comparison.
 .TP
-.B \-icase
-Case-insensitive comparison.
+.B \-contain
+Match substring.
+.TP
+.B \-end
+Exact match at the end of string (suffix match).
+.TP
+.B \-exact
+Exact string match.
 .TP
 .B \-file
 Treat \fIpattern\fR as the name of a file to read patterns from.  If
@@ -1420,6 +1431,25 @@ directory\fR (see the discussion of the \fBIncludeDir\fR directory
 above).  Patterns are read from the file line by line.  Leading and
 trailing whitespace is removed.  Empty
 lines and comments (lines starting with \fB#\fR) are ignored.
+.TP
+.B \-icase
+Case-insensitive comparison.
+.TP
+.BR \-pcre " or " \-perl
+Use Perl-compatible regular expression (requires compilation-time
+support).  This overrides global
+.B RegexType
+settings.
+.TP
+.B \-posix
+Use POSIX extended regular expression.  This overrides global
+.B RegexType
+settings.
+.TP
+.B \-re
+Use regular expression matching, as set by the
+.B RegexType
+statement.
 .PP
 For example, the following will match any request whose \fBHost\fR
 header begins with "www." (case-insensitive):
@@ -2096,23 +2126,39 @@ expression.  The \fIoptions\fR argument can be used to change this
 behavior.  It consists of zero or more option flags from the following list:
 .RS
 .TP
-.B \-re
-Use regular expression matching.
-.TP
-.B \-exact
-Use exact string matching.
-.TP
 .B \-beg
 Exact match at the beginning of string (prefix match).
 .TP
+.B \-case
+Case-sensitive comparison.
+.TP
+.B \-contain
+Match substring.
+.TP
 .B \-end
 Exact match at the end of string (suffix match).
 .TP
-.B \-case
-Case-sensitive comparison.
+.B \-exact
+Use exact string matching.
 .TP
 .B \-icase
 Case-insensitive comparison.
+.TP
+.BR \-pcre " or " \-perl
+Use Perl-compatible regular expression (requires compilation-time
+support).  This overrides global
+.B RegexType
+settings.
+.TP
+.B \-posix
+Use POSIX extended regular expression.  This overrides global
+.B RegexType
+settings.
+.TP
+.B \-re
+Use regular expression matching, as set by the
+.B RegexType
+statement.
 .RE
 .PP
 The \fIvalue\fR argument in the above directives is subject to
diff --git a/doc/pound.texi b/doc/pound.texi
index a2f46e7..6592d70 100644
--- a/doc/pound.texi
+++ b/doc/pound.texi
@@ -426,10 +426,13 @@ option, as in:
 Host -re ".*\\.example\\.com"
 @end example
 
-Whenever we speak about regular expression we mean POSIX extended
+@cindex regular expressions, POSIX
+@cindex POSIX regular expressions
+Whenever we speak about regular expression we usually mean POSIX extended
 regular expressions (@pxref{Extended
 regexps, POSIX extended regular expressions, POSIX extended regular expressions,
-sed, GNU sed}).
+sed, GNU sed}).  However, other regex types can also be used.  This is
+covered in @ref{Regular Expressions}.
 
 Notice the use of double backslashes in the above example.  The
 backslash before each dot is needed to match it literally, while another one
@@ -586,6 +589,45 @@ Service
 End
 @end example
 
+@node Regular Expressions
+@subsection Regular Expressions
+
+@cindex regular expressions, PCRE
+@cindex regular expressions, Perl-compatible
+@cindex Perl-compatible regular expressions
+Request matching directives use POSIX extended regular expressions by
+default.  If @command{pound} was compiled with @code{PCRE} or
+@code{PCRE2} library, @dfn{Perl-compatible regular expressions} can be
+used instead.  This can be done either globally or individually for a
+given directive.
+
+@kwindex RegexType
+To change regular expression type globally, use the following
+directive:
+
+@example
+RegexType pcre
+@end example
+
+It affects all request matching directives that appear after it in the
+configuration file, until next @code{RegexType} directive or end of
+file, whichever occurs first.  To change back to POSIX regular
+expressions, use @code{posix} argument:
+
+@example
+RegexType posix
+@end example
+
+Argument to the @code{RegexType} directive is case-insensitive.
+
+Regular expression type can also be selected individually for a
+directive, using @option{-posix} or @option{-pcre} flags.  For
+example:
+
+@example
+Host -pcre -icase "(?<!www\\.)example.org"
+@end example
+
 @node ACL
 @subsection ACL
   Access control lists, or @dfn{ACLs}, are special request matching
@@ -2015,14 +2057,8 @@ character.  Otherwise, it expands to empty string.
 may appear anywhere at the global scope of the configuration file,
 although it is customary for them to be at its start.
 
-@deffn {Global directive} User "@var{user_name}"
-Configures the user @command{pound} will run as.
-@end deffn
-
-@deffn {Global directive} Group "@var{group_name}"
-Sets the group @command{pound} will run as.  If not set, the primary
-group of the user (as set by the @code{User} directive) will be used.
-@end deffn
+@node Runtime directives
+@subsection Runtime directives
 
 @deffn {Global directive} Daemon @var{bool}
 @anchor{Daemon}
@@ -2037,6 +2073,35 @@ This setting can be overridden by the @option{-F} and @option{-e}
 command line options.
 @end deffn
 
+@deffn {Global directive} Group "@var{group_name}"
+Sets the group @command{pound} will run as.  If not set, the primary
+group of the user (as set by the @code{User} directive) will be used.
+@end deffn
+
+@deffn {Global directive} PIDFile "@var{filename}"
+@anchor{PIDFile}
+Sets the name of the file where to store program PID.  This can be
+also be set from command line, using @option{-p} command line option
+(@pxref{Usage}).
+
+Notice the following:
+
+@enumerate 1
+@item
+When running with a supervisor, this file holds PID of the supervisor
+process.  Otherwise, it holds PID of the main @comment{pound} process.
+This means it is always suitable for signalling the program using the
+traditional @command{kill `cat filename`} technique.
+
+@item
+Before shutting down, @command{pound} removes this file.  However,
+it may fail to do so if it switches to privileges of another user
+after startup (at least one of @code{User} or @code{Group} are set in
+the configuration file) and the file is stored in a directory whose
+permissions forbid write access for that user.
+@end enumerate
+@end deffn
+
 @deffn {Global directive} Supervisor @var{bool}
 When running in daemon mode, start a @dfn{supervisor} process.  This
 process, in turn, will start main @command{pound} process and will
@@ -2057,6 +2122,75 @@ user databases supplied with the @code{BasicAuth} statements
 (@pxref{Authentication}).
 @end deffn
 
+@deffn {Global directive} User "@var{user_name}"
+Configures the user @command{pound} will run as.
+@end deffn
+
+@node Worker Settings
+@subsection Worker Settings
+
+@deffn {Global directive} WorkerMinCount @var{n}
+Sets minimum number of worker threads that must always be running.
+The default is 5.  @xref{Worker model}.
+@end deffn
+
+@deffn {Global directive} WorkerMaxCount @var{n}
+Sets maximum number of worker threads.  The default is 128.
+@xref{Worker model}.
+@end deffn
+
+@deffn {Global directive} WorkerIdleTimeout @var{n}
+Sets idle timeout for a worker thread, in seconds.  Default is 30 seconds.
+@xref{Worker model}.
+@end deffn
+
+@deffn {Global directive} Threads @var{n}
+This statement, retained for backward compatibility with previous
+versions of pound, is equivalent to:
+
+@example
+WorkerMinCount @var{n}
+WorkerMaxCount @var{n}
+@end example
+@end deffn
+
+@node Proxy Tuning
+@subsection Proxy Tuning Directives
+
+@deffn {Global directive} BackendStats @var{bool}
+@anchor{BackendStats}
+Whether to enable backend statistics collection.  Backend statistics
+consists of the following values:
+
+@enumerate 1
+@item Total number of requests processed by this backend.
+@item Average time per request.
+@item Standard deviation of the average time per request.
+@end enumerate
+
+If enabled, these values are made available via @command{poundctl}
+(@pxref{poundctl list}) and telemetry output (@pxref{Metrics}).
+@end deffn
+
+@deffn {Global directive} Balancer @var{algo}
+Sets the request balancing algorithm to use.  Allowed values for
+@var{algo} are:
+
+@table @asis
+@item random
+Use weighted random balancing algorithm.
+
+@item iwrr
+Use interleaved weighted round robin balancing.
+@end table
+
+@xref{Balancer}, for a detailed discussion of these algorithms.
+
+The @code{Balancer} statement in global scope applies to all @code{Service}
+definitions in the file that don't contain @code{Balancer} definitions
+of their own.
+@end deffn
+
 @deffn {Global directive} HeaderOption @var{opt} ...
 Sets default header addition options.  One or more arguments are
 allowed, each being one of:
@@ -2107,48 +2241,36 @@ This setting can be overridden for a particular listener using the
 @code{HeadOption} within it.
 @end deffn
 
-@deffn {Global directive} Balancer @var{algo}
-Sets the request balancing algorithm to use.  Allowed values for
-@var{algo} are:
-
-@table @asis
-@item random
-Use weighted random balancing algorithm.
-
-@item iwrr
-Use interleaved weighted round robin balancing.
-@end table
-
-@xref{Balancer}, for a detailed discussion of these algorithms.
+@node SSL Settings
+@subsection SSL Settings
 
-The @code{Balancer} statement in global scope applies to all @code{Service}
-definitions in the file that don't contain @code{Balancer} definitions
-of their own.
+@deffn {Global directive} SSLEngine "@var{name}"
+Use an OpenSSL hardware acceleration card called @var{name}.  Available
+only if OpenSSL-engine is installed on your system.
 @end deffn
 
-@deffn {Global directive} WorkerMinCount @var{n}
-Sets minimum number of worker threads that must always be running.
-The default is 5.  @xref{Worker model}.
+@deffn {Global directive} ECDHcurve "@var{name}"
+Use the named curve for elliptical curve encryption.
 @end deffn
 
-@deffn {Global directive} WorkerMaxCount @var{n}
-Sets maximum number of worker threads.  The default is 128.
-@xref{Worker model}.
-@end deffn
+@node Regexp Settings
+@subsection Regular Expression Settings
 
-@deffn {Global directive} WorkerIdleTimeout @var{n}
-Sets idle timeout for a worker thread, in seconds.  Default is 30 seconds.
-@xref{Worker model}.
-@end deffn
+@deffn {Global directive} RegexType @var{type}
+Sets the type of regular expressions to use in request matching
+statements.  Allowed values for @var{type} are: @code{posix} and
+@code{pcre} (or @code{perl}), case-insensitive.  The latter requires
+compilation time support.
 
-@deffn {Global directive} Threads @var{n}
-This statement, retained for backward compatibility with previous
-versions of pound, is equivalent to:
+The selected regular expression type remains in effect for all request
+matching directives that follow this statement, until next
+@code{RegexType} statement or end of the configuration file, whichever
+occurs first.
 
-@example
-WorkerMinCount @var{n}
-WorkerMaxCount @var{n}
-@end example
+Regular expression type can be set individually for a directive, using
+the @code{-pcre} or @code{-posix} option (@pxref{conditional-option}).
+
+@xref{Regular Expressions}, for a detailed discussion.
 @end deffn
 
 @deffn {Global directive} IgnoreCase @var{bool}
@@ -2163,16 +2285,9 @@ Please, use the @option{-icase} option to the matching directive
 instead (@pxref{conditional-option}).
 @end deffn
 
-@deffn {Global directive} SSLEngine "@var{name}"
-Use an OpenSSL hardware acceleration card called @var{name}.  Available
-only if OpenSSL-engine is installed on your system.
-@end deffn
-
-@deffn {Global directive} ECDHcurve "@var{name}"
-Use the named curve for elliptical curve encryption.
-@end deffn
+@node ACL definition
+@subsection ACL Definition
 
-@anchor{ACL definition}
 @deffn {Global directive} ACL "@var{name}"
 Define a @dfn{named access control list}.  An @dfn{ACL} is a
 list of network addresses in CIDR notation, one address per line,
@@ -2193,45 +2308,6 @@ to services from certain IP addresses only.  @xref{ACL}, for a
 detailed discussion of this.
 @end deffn
 
-@deffn {Global directive} PIDFile "@var{filename}"
-@anchor{PIDFile}
-Sets the name of the file where to store program PID.  This can be
-also be set from command line, using @option{-p} command line option
-(@pxref{Usage}).
-
-Notice the following:
-
-@enumerate 1
-@item
-When running with a supervisor, this file holds PID of the supervisor
-process.  Otherwise, it holds PID of the main @comment{pound} process.
-This means it is always suitable for signalling the program using the
-traditional @command{kill `cat filename`} technique.
-
-@item
-Before shutting down, @command{pound} removes this file.  However,
-it may fail to do so if it switches to privileges of another user
-after startup (at least one of @code{User} or @code{Group} are set in
-the configuration file) and the file is stored in a directory whose
-permissions forbid write access for that user.
-@end enumerate
-@end deffn
-
-@deffn {Global directive} BackendStats @var{bool}
-@anchor{BackendStats}
-Whether to enable backend statistics collection.  Backend statistics
-consists of the following values:
-
-@enumerate 1
-@item Total number of requests processed by this backend.
-@item Average time per request.
-@item Standard deviation of the average time per request.
-@end enumerate
-
-If enabled, these values are made available via @command{poundctl}
-(@pxref{poundctl list}) and telemetry output (@pxref{Metrics}).
-@end deffn
-
 @node File inclusion
 @section File inclusion
 
@@ -2788,31 +2864,59 @@ of zero or more option flags from the following list:
 @multitable @columnfractions .30 .70
 @headitem Flag @tab Meaning
 
-@kwindex -re, DeleteHeader option
-@cindex regular expression match, @code{DeleteHeader}
-@item @code{-re} @tab Use regular expression match.
-
-@kwindex -exact, DeleteHeader option
-@cindex exact match, @code{DeleteHeader}
-@item @code{-exact} @tab Use exact string match.
-
 @kwindex -beg, DeleteHeader option
 @cindex prefix match, @code{DeleteHeader}
 @item @code{-beg} @tab Exact match at the beginning of string (prefix match).
 
+@kwindex -case, DeleteHeader option
+@cindex case insensitive match, @code{DeleteHeader}
+@item @code{-case} @tab Case-sensitive comparison.
+
+@kwindex -contain, DeleteHeader option
+@cindex substring match, @code{DeleteHeader}
+@item @code{-contain} @tab Delete each header where "@var{pattern}" is
+a substring.
+
 @kwindex -end, DeleteHeader option
 @cindex suffix match, @code{DeleteHeader}
 @item @code{-end} @tab Exact match at the end of string (suffix match).
 
-@kwindex -case, DeleteHeader option
-@cindex case insensitive match, @code{DeleteHeader}
-@item @code{-case} @tab Case-sensitive comparison.
+@kwindex -exact, DeleteHeader option
+@cindex exact match, @code{DeleteHeader}
+@item @code{-exact} @tab Use exact string match.
 
 @kwindex -icase, DeleteHeader option
 @cindex case sensitive match, @code{DeleteHeader}
 @item @code{-icase} @tab Case-insensitive comparison.
+
+@kwindex -pcre, DeleteHeader option
+@cindex Perl-compatible regular expression match, @code{DeleteHeader}
+@cindex PCRE match, @code{DeleteHeader}
+@item @code{-pcre} @tab Use Perl-compatible regular expression.
+@pxref{Regular Expressions}.
+
+@kwindex -perl, DeleteHeader option
+@cindex Perl-compatible regular expression match, @code{DeleteHeader}
+@cindex PCRE match, @code{DeleteHeader}
+@item @code{-perl} @tab Same as @code{-pcre}.
+
+@kwindex -posix, DeleteHeader option
+@cindex posix regular expression match, @code{DeleteHeader}
+@item @code{-posix} @tab Use POSIX extended regular expression.
+@pxref{Regular Expressions}.
+
+@kwindex -re, DeleteHeader option
+@cindex regular expression match, @code{DeleteHeader}
+@item @code{-re} @tab Use regular expression match.  This assumes the
+default regular expression type, as set by the @code{RegexType}
+directive (@pxref{Regular Expressions}).
 @end multitable
 @end float
+
+The following options are mutually exclusive: @code{-beg},
+@code{-contain}, @code{-end}, @code{-exact}, @code{-pcre} (@code{-perl}),
+@code{-posix}, @code{-re}.  If more than one of these are used, the
+last one takes effect.
 @end deffn
 
 @deffn {ListenerHTTP directive} HeaderRemove "@var{pattern}"
@@ -2963,29 +3067,26 @@ zero or more flags from the following table:
 @multitable @columnfractions .30 .70
 @headitem Flag @tab Meaning
 
-@kwindex -re, header matching flag
-@cindex regular expression match, headers
-@item @code{-re} @tab Use regular expression match.
-
-@kwindex -exact, header matching flag
-@cindex exact match, headers
-@item @code{-exact} @tab Use exact string match.
-
 @kwindex -beg, header matching flag
 @cindex prefix match, headers
 @item @code{-beg} @tab Exact match at the beginning of string (prefix match).
 
-@kwindex -end, header matching flag
-@cindex suffix match, headers
-@item @code{-end} @tab Exact match at the end of string (suffix match).
-
 @kwindex -case, header matching flag
 @cindex case insensitive match, headers
 @item @code{-case} @tab Case-sensitive comparison.
 
-@kwindex -icase, header matching flag
-@cindex case sensitive match, headers
-@item @code{-icase} @tab Case-insensitive comparison.
+@kwindex -contain, header matching flag
+@cindex substring match, headers
+@item @code{-contain} @tab Match if @var{pattern} is a substring of
+the original value.
+
+@kwindex -end, header matching flag
+@cindex suffix match, headers
+@item @code{-end} @tab Exact match at the end of string (suffix match).
+
+@kwindex -exact, header matching flag
+@cindex exact match, headers
+@item @code{-exact} @tab Use exact string match.
 
 @kwindex -file, header matching flag
 @cindex file lookup, headers
@@ -2996,9 +3097,40 @@ will be looked up in the @ref{include directory}.  Patterns are read
 from the file line by line.  Leading and trailing whitespace is
 removed.  Empty lines and comments (lines starting with @code{#}) are
 ignored.
+
+@kwindex -icase, header matching flag
+@cindex case sensitive match, headers
+@item @code{-icase} @tab Case-insensitive comparison.
+
+@kwindex -pcre, header matching flag
+@cindex Perl-compatible regular expression match, headers
+@cindex PCRE match, headers
+@item @code{-pcre} @tab Use Perl-compatible regular expression.
+@pxref{Regular Expressions}.
+
+@kwindex -perl, header matching flag
+@cindex Perl-compatible regular expression match, headers
+@cindex PCRE match, headers
+@item @code{-perl} @tab Same as @code{-pcre}.
+
+@kwindex -posix, header matching flag
+@cindex posix regular expression match, headers
+@item @code{-posix} @tab Use POSIX extended regular expression.
+@pxref{Regular Expressions}.
+
+@kwindex -re, header matching flag
+@cindex regular expression match, headers
+@item @code{-re} @tab Use regular expression match.  This assumes the
+default regular expression type, as set by the @code{RegexType}
+directive (@pxref{Regular Expressions}).
 @end multitable
 @end float
 
+The following options are mutually exclusive: @code{-beg}, @code{-contain},
+@code{-end}, @code{-exact}, @code{-pcre} (@code{-perl}),
+@code{-posix}, @code{-re}.  If more than one of these are used, the
+last one takes effect.
+
 @kwindex Not
 Placing the keyword @code{Not} before a header matching directive reverts its
 meaning.  For example, the following will match any request whose URL
diff --git a/src/Makefile.am b/src/Makefile.am
index 68c8b2b..b3f975f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -28,15 +28,13 @@ sbin_PROGRAMS=pound
 pound_SOURCES=\
  bauth.c\
  config.c\
+ genpat.c\
  http.c\
  log.c\
  metrics.c\
  pound.c\
  svc.c
 
-if COND_REGEX
-  pound_SOURCES += regex_std.c
-endif
 if COND_PCRE
   pound_SOURCES += regex_pcre.c
 endif
diff --git a/src/config.c b/src/config.c
index 0dfa55d..641ba33 100644
--- a/src/config.c
+++ b/src/config.c
@@ -309,11 +309,11 @@ conf_error_at_locus_point (struct locus_point const *loc, char const *fmt, ...)
 }
 
 static void
-regcomp_error_at_locus_range (struct locus_range const *loc, POUND_REGEX rx,
+regcomp_error_at_locus_range (struct locus_range const *loc, GENPAT rx,
 			      char const *expr)
 {
   size_t off;
-  char const *errmsg = regex_error (rx, &off);
+  char const *errmsg = genpat_error (rx, &off);
 
   if (off)
     conf_error_at_locus_range (loc, "%s at byte %zu", errmsg, off);
@@ -1224,6 +1224,7 @@ typedef struct
   unsigned ws_to;
   unsigned be_connto;
   unsigned ignore_case;
+  int re_type;
   int header_options;
   BALANCER balancer;
   NAMED_BACKEND_TABLE named_backend_table;
@@ -2513,35 +2514,37 @@ stringbuf_escape_regex (struct stringbuf *sb, char const *p)
     }
 }
 
-enum match_mode
-  {
-    MATCH_EXACT,
-    MATCH_RE,
-    MATCH_BEG,
-    MATCH_END,
-    MATCH__MAX
-  };
-
 static int
-parse_match_mode (int *mode, int *re_flags, int *from_file)
+parse_match_mode (int dfl_re_type, int *gp_type, int *sp_flags, int *from_file)
 {
   struct token *tok;
 
   enum
   {
-    MATCH_ICASE = MATCH__MAX,
+    MATCH_RE,
+    MATCH_EXACT,
+    MATCH_BEG,
+    MATCH_END,
+    MATCH_CONTAIN,
+    MATCH_ICASE,
     MATCH_CASE,
-    MATCH_FILE
+    MATCH_FILE,
+    MATCH_POSIX,
+    MATCH_PCRE,
   };
 
   static struct kwtab optab[] = {
-    { "-re",    MATCH_RE },
-    { "-exact", MATCH_EXACT },
-    { "-beg",   MATCH_BEG },
-    { "-end",   MATCH_END },
-    { "-icase", MATCH_ICASE },
-    { "-case",  MATCH_CASE },
-    { "-file",  MATCH_FILE },
+    { "-re",      MATCH_RE },
+    { "-exact",   MATCH_EXACT },
+    { "-beg",     MATCH_BEG },
+    { "-end",     MATCH_END },
+    { "-contain", MATCH_CONTAIN },
+    { "-icase",   MATCH_ICASE },
+    { "-case",    MATCH_CASE },
+    { "-file",    MATCH_FILE },
+    { "-posix",   MATCH_POSIX },
+    { "-pcre",    MATCH_PCRE },
+    { "-perl",    MATCH_PCRE },
     { NULL }
   };
 
@@ -2567,11 +2570,11 @@ parse_match_mode (int *mode, int *re_flags, int *from_file)
       switch (n)
 	{
 	case MATCH_CASE:
-	  *re_flags &= ~POUND_REGEX_ICASE;
+	  *sp_flags &= ~GENPAT_ICASE;
 	  break;
 
 	case MATCH_ICASE:
-	  *re_flags |= POUND_REGEX_ICASE;
+	  *sp_flags |= GENPAT_ICASE;
 	  break;
 
 	case MATCH_FILE:
@@ -2584,8 +2587,38 @@ parse_match_mode (int *mode, int *re_flags, int *from_file)
 	    }
 	  break;
 
-	default:
-	  *mode = n;
+	case MATCH_RE:
+	  *gp_type = dfl_re_type;
+	  break;
+
+	case MATCH_POSIX:
+	  *gp_type = GENPAT_POSIX;
+	  break;
+
+	case MATCH_EXACT:
+	  *gp_type = GENPAT_EXACT;
+	  break;
+
+	case MATCH_BEG:
+	  *gp_type = GENPAT_PREFIX;
+	  break;
+
+	case MATCH_END:
+	  *gp_type = GENPAT_SUFFIX;
+	  break;
+
+	case MATCH_CONTAIN:
+	  *gp_type = GENPAT_CONTAIN;
+	  break;
+
+	case MATCH_PCRE:
+#ifdef HAVE_LIBPCRE
+	  *gp_type = GENPAT_PCRE;
+#else
+	  conf_error ("%s", "pound compiled without PCRE");
+	  return PARSER_FAIL;
+#endif
+	  break;
 	}
     }
   putback_tkn (tok);
@@ -2593,42 +2626,46 @@ parse_match_mode (int *mode, int *re_flags, int *from_file)
 }
 
 static char *
-build_regex (struct stringbuf *sb, int mode, char const *expr, char const *pfx)
+host_prefix_regex (struct stringbuf *sb, int *gp_type, char const *expr)
 {
-  switch (mode)
+  stringbuf_add_char (sb, '^');
+  stringbuf_add_string (sb, "Host:");
+  switch (*gp_type)
     {
-    case MATCH_EXACT:
-      stringbuf_add_char (sb, '^');
-      if (pfx)
-	stringbuf_add_string (sb, pfx);
-      stringbuf_escape_regex (sb, expr);
-      stringbuf_add_char (sb, '$');
+    case GENPAT_POSIX:
+      stringbuf_add_string (sb, "[[:space:]]*");
+      if (expr[0] == '^')
+	expr++;
+      stringbuf_add_string (sb, expr);
       break;
 
-    case MATCH_RE:
-      if (pfx)
-	{
-	  stringbuf_add_string (sb, pfx);
-	  if (expr[0] == '^')
-	    expr++;
-	}
+    case GENPAT_PCRE:
+      stringbuf_add_string (sb, "\\s*");
+      if (expr[0] == '^')
+	expr++;
       stringbuf_add_string (sb, expr);
       break;
 
-    case MATCH_BEG:
-      stringbuf_add_char (sb, '^');
-      if (pfx)
-	stringbuf_add_string (sb, pfx);
+    case GENPAT_EXACT:
+    case GENPAT_PREFIX:
+      stringbuf_add_string (sb, "[[:space:]]*");
       stringbuf_escape_regex (sb, expr);
-      stringbuf_add_string (sb, ".*");
+      *gp_type = GENPAT_POSIX;
       break;
 
-    case MATCH_END:
+    case GENPAT_SUFFIX:
+      stringbuf_add_string (sb, "[[:space:]]*");
       stringbuf_add_string (sb, ".*");
-      if (pfx)
-	stringbuf_add_string (sb, pfx);
       stringbuf_escape_regex (sb, expr);
       stringbuf_add_char (sb, '$');
+      *gp_type = GENPAT_POSIX;
+      break;
+
+    case GENPAT_CONTAIN:
+      stringbuf_add_string (sb, "[[:space:]]*");
+      stringbuf_add_string (sb, ".*");
+      stringbuf_escape_regex (sb, expr);
+      *gp_type = GENPAT_POSIX;
       break;
 
     default:
@@ -2638,28 +2675,22 @@ build_regex (struct stringbuf *sb, int mode, char const *expr, char const *pfx)
 }
 
 static int
-parse_regex_compat (POUND_REGEX *regex, int flags)
+parse_regex_compat (GENPAT *regex, int dfl_re_type, int gp_type, int flags)
 {
   struct token *tok;
-  int mode = MATCH_RE;
-  char *p;
   int rc;
-  struct stringbuf sb;
 
-  if (parse_match_mode (&mode, &flags, NULL))
+  if (parse_match_mode (dfl_re_type, &gp_type, &flags, NULL))
     return PARSER_FAIL;
 
   if ((tok = gettkn_expect (T_STRING)) == NULL)
     return PARSER_FAIL;
 
-  xstringbuf_init (&sb);
-  p = build_regex (&sb, mode, tok->str, NULL);
-  rc = regex_compile (regex, p, flags);
-  stringbuf_free (&sb);
+  rc = genpat_compile (regex, gp_type, tok->str, flags);
   if (rc)
     {
       conf_regcomp_error (rc, *regex, NULL);
-      regex_free (*regex);
+      genpat_free (*regex);
       return PARSER_FAIL;
     }
 
@@ -2691,18 +2722,19 @@ string_ref_free (STRING_REF *ref)
 }
 
 static int
-parse_cond_matcher_0 (SERVICE_COND *top_cond, enum service_cond_type type,
-		      int mode, int flags, char const *string)
+parse_cond_matcher_0 (SERVICE_COND *top_cond,
+		      enum service_cond_type type,
+		      int dfl_re_type,
+		      int gp_type, int flags, char const *string)
 {
   struct token *tok;
   int rc;
   struct stringbuf sb;
   SERVICE_COND *cond;
-  static char const host_pfx[] = "Host:[[:space:]]*";
   int from_file;
   char *expr;
 
-  if (parse_match_mode (&mode, &flags, &from_file))
+  if (parse_match_mode (dfl_re_type, &gp_type, &flags, &from_file))
     return PARSER_FAIL;
 
   if ((tok = gettkn_expect (T_STRING)) == NULL)
@@ -2749,14 +2781,20 @@ parse_cond_matcher_0 (SERVICE_COND *top_cond, enum service_cond_type type,
 	    continue;
 	  p[len] = 0;
 
-	  stringbuf_reset (&sb);
-	  expr = build_regex (&sb, mode, p, type == COND_HOST ? host_pfx : NULL);
+	  if (type == COND_HOST)
+	    {
+	      stringbuf_reset (&sb);
+	      expr = host_prefix_regex (&sb, &gp_type, p);
+	    }
+	  else
+	    expr = p;
+
 	  hc = service_cond_append (cond, type);
-	  rc = regex_compile (&hc->re, expr, flags);
+	  rc = genpat_compile (&hc->re, gp_type, expr, flags);
 	  if (rc)
 	    {
 	      conf_regcomp_error (rc, hc->re, NULL);
-	      // FIXME: regex_free (hc->re);
+	      // FIXME: genpat_free (hc->re);
 	      return PARSER_FAIL;
 	    }
 	  switch (type)
@@ -2777,12 +2815,15 @@ parse_cond_matcher_0 (SERVICE_COND *top_cond, enum service_cond_type type,
   else
     {
       cond = service_cond_append (top_cond, type);
-      expr = build_regex (&sb, mode, tok->str, type == COND_HOST ? host_pfx : NULL);
-      rc = regex_compile (&cond->re, expr, flags);
+      if (type == COND_HOST)
+	expr = host_prefix_regex (&sb, &gp_type, tok->str);
+      else
+	expr = tok->str;
+      rc = genpat_compile (&cond->re, gp_type, expr, flags);
       if (rc)
 	{
 	  conf_regcomp_error (rc, cond->re, NULL);
-	  // FIXME: regex_free (cond->re);
+	  // FIXME: genpat_free (cond->re);
 	  return PARSER_FAIL;
 	}
       switch (type)
@@ -2803,8 +2844,10 @@ parse_cond_matcher_0 (SERVICE_COND *top_cond, enum service_cond_type type,
 }
 
 static int
-parse_cond_matcher (SERVICE_COND *top_cond, enum service_cond_type type,
-		    int mode, int flags, char const *string)
+parse_cond_matcher (SERVICE_COND *top_cond,
+		    enum service_cond_type type,
+		    int dfl_re_type,
+		    int gp_type, int flags, char const *string)
 {
   int rc;
   char *string_copy;
@@ -2812,7 +2855,8 @@ parse_cond_matcher (SERVICE_COND *top_cond, enum service_cond_type type,
     string_copy = xstrdup (string);
   else
     string_copy = NULL;
-  rc = parse_cond_matcher_0 (top_cond, type, mode, flags, string_copy);
+  rc = parse_cond_matcher_0 (top_cond, type, dfl_re_type, gp_type, flags,
+			     string_copy);
   free (string_copy);
   return rc;
 }
@@ -2828,8 +2872,9 @@ static int
 parse_cond_url_matcher (void *call_data, void *section_data)
 {
   POUND_DEFAULTS *dfl = section_data;
-  return parse_cond_matcher (call_data, COND_URL, MATCH_RE,
-			     (dfl->ignore_case ? POUND_REGEX_ICASE : 0),
+  return parse_cond_matcher (call_data, COND_URL, dfl->re_type,
+			     dfl->re_type,
+			     (dfl->ignore_case ? GENPAT_ICASE : 0),
 			     NULL);
 }
 
@@ -2837,8 +2882,9 @@ static int
 parse_cond_path_matcher (void *call_data, void *section_data)
 {
   POUND_DEFAULTS *dfl = section_data;
-  return parse_cond_matcher (call_data, COND_PATH, MATCH_RE,
-			     (dfl->ignore_case ? POUND_REGEX_ICASE : 0),
+  return parse_cond_matcher (call_data, COND_PATH, dfl->re_type,
+			     dfl->re_type,
+			     (dfl->ignore_case ? GENPAT_ICASE : 0),
 			     NULL);
 }
 
@@ -2846,8 +2892,9 @@ static int
 parse_cond_query_matcher (void *call_data, void *section_data)
 {
   POUND_DEFAULTS *dfl = section_data;
-  return parse_cond_matcher (call_data, COND_QUERY, MATCH_RE,
-			     (dfl->ignore_case ? POUND_REGEX_ICASE : 0),
+  return parse_cond_matcher (call_data, COND_QUERY, dfl->re_type,
+			     dfl->re_type,
+			     (dfl->ignore_case ? GENPAT_ICASE : 0),
 			     NULL);
 }
 
@@ -2856,7 +2903,7 @@ parse_cond_query_param_matcher (void *call_data, void *section_data)
 {
   SERVICE_COND *top_cond = call_data;
   POUND_DEFAULTS *dfl = section_data;
-  int flags = (dfl->ignore_case ? POUND_REGEX_ICASE : 0);
+  int flags = (dfl->ignore_case ? GENPAT_ICASE : 0);
   struct token *tok;
   char *string;
   int rc;
@@ -2864,8 +2911,9 @@ parse_cond_query_param_matcher (void *call_data, void *section_data)
   if ((tok = gettkn_expect (T_STRING)) == NULL)
     return PARSER_FAIL;
   string = xstrdup (tok->str);
-  rc = parse_cond_matcher (top_cond, COND_QUERY_PARAM, MATCH_RE, flags,
-			   string);
+  rc = parse_cond_matcher (top_cond,
+			   COND_QUERY_PARAM, dfl->re_type,
+			   dfl->re_type, flags, string);
   free (string);
   return rc;
 }
@@ -2875,7 +2923,7 @@ parse_cond_string_matcher (void *call_data, void *section_data)
 {
   SERVICE_COND *top_cond = call_data;
   POUND_DEFAULTS *dfl = section_data;
-  int flags = (dfl->ignore_case ? POUND_REGEX_ICASE : 0);
+  int flags = (dfl->ignore_case ? GENPAT_ICASE : 0);
   struct token *tok;
   char *string;
   int rc;
@@ -2883,7 +2931,8 @@ parse_cond_string_matcher (void *call_data, void *section_data)
   if ((tok = gettkn_expect (T_STRING)) == NULL)
     return PARSER_FAIL;
   string = xstrdup (tok->str);
-  rc = parse_cond_matcher (top_cond, COND_STRING_MATCH, MATCH_RE, flags,
+  rc = parse_cond_matcher (top_cond,
+			   COND_STRING_MATCH, dfl->re_type, dfl->re_type, flags,
 			   string);
   free (string);
   return rc;
@@ -2892,26 +2941,29 @@ parse_cond_string_matcher (void *call_data, void *section_data)
 static int
 parse_cond_hdr_matcher (void *call_data, void *section_data)
 {
-  return parse_cond_matcher (call_data, COND_HDR, MATCH_RE,
-			     POUND_REGEX_MULTILINE | POUND_REGEX_ICASE,
+  POUND_DEFAULTS *dfl = section_data;
+  return parse_cond_matcher (call_data, COND_HDR, dfl->re_type, dfl->re_type,
+			     GENPAT_MULTILINE | GENPAT_ICASE,
 			     NULL);
 }
 
 static int
 parse_cond_head_deny_matcher (void *call_data, void *section_data)
 {
+  POUND_DEFAULTS *dfl = section_data;
   SERVICE_COND *cond = service_cond_append (call_data, COND_BOOL);
   cond->bool.op = BOOL_NOT;
-  return parse_cond_matcher (cond, COND_HDR, MATCH_RE,
-			     POUND_REGEX_MULTILINE | POUND_REGEX_ICASE,
+  return parse_cond_matcher (cond, COND_HDR, dfl->re_type, dfl->re_type,
+			     GENPAT_MULTILINE | GENPAT_ICASE,
 			     NULL);
 }
 
 static int
 parse_cond_host (void *call_data, void *section_data)
 {
-  return parse_cond_matcher (call_data, COND_HOST, MATCH_EXACT,
-			     POUND_REGEX_ICASE, NULL);
+  POUND_DEFAULTS *dfl = section_data;
+  return parse_cond_matcher (call_data, COND_HOST, dfl->re_type,
+			     GENPAT_EXACT, GENPAT_ICASE, NULL);
 }
 
 static int
@@ -2981,7 +3033,7 @@ parse_redirect_backend (void *call_data, void *section_data)
   be->v.redirect.status = code;
   be->v.redirect.url = xstrdup (tok->str);
 
-  if (regex_exec (LOCATION, be->v.redirect.url, 4, matches))
+  if (genpat_match (LOCATION, be->v.redirect.url, 4, matches))
     {
       conf_error ("%s", "Redirect bad URL");
       return PARSER_FAIL;
@@ -3452,8 +3504,8 @@ parse_delete_header (void *call_data, void *section_data)
   POUND_DEFAULTS *dfl = section_data;
 
   XZALLOC (op->v.hdrdel);
-  return parse_regex_compat (&op->v.hdrdel->pat,
-			     (dfl->ignore_case ? POUND_REGEX_ICASE : 0));
+  return parse_regex_compat (&op->v.hdrdel->pat, dfl->re_type, dfl->re_type,
+			     (dfl->ignore_case ? GENPAT_ICASE : 0));
 }
 
 static int
@@ -3716,11 +3768,12 @@ SETFN_SVC_DECL (delete_header)
 static int
 parse_header_remove (void *call_data, void *section_data)
 {
+  POUND_DEFAULTS *dfl = section_data;
   REWRITE_RULE *rule = rewrite_rule_last_uncond (call_data);
   REWRITE_OP *op = rewrite_op_alloc (&rule->ophead, REWRITE_HDR_DEL);
   XZALLOC (op->v.hdrdel);
-  return parse_regex_compat (&op->v.hdrdel->pat,
-			     POUND_REGEX_ICASE | POUND_REGEX_MULTILINE);
+  return parse_regex_compat (&op->v.hdrdel->pat, dfl->re_type, dfl->re_type,
+			     GENPAT_ICASE | GENPAT_MULTILINE);
 }
 
 static int
@@ -4051,7 +4104,7 @@ parse_acme (void *call_data, void *section_data)
   struct token *tok;
   struct stat st;
   int rc;
-  static char re_acme[] = "^/\\.well-known/acme-challenge/(.+)";
+  static char sp_acme[] = "^/\\.well-known/acme-challenge/(.+)";
   int fd;
   struct locus_range range;
 
@@ -4083,7 +4136,7 @@ parse_acme (void *call_data, void *section_data)
 
   /* Create a URL matcher */
   cond = service_cond_append (&svc->cond, COND_URL);
-  rc = regex_compile (&cond->re, re_acme, 0);
+  rc = genpat_compile (&cond->re, GENPAT_POSIX, sp_acme, 0);
   if (rc)
     {
       conf_regcomp_error (rc, cond->re, NULL);
@@ -4129,8 +4182,6 @@ listener_parse_checkurl (void *call_data, void *section_data)
 {
   LISTENER *lst = call_data;
   POUND_DEFAULTS *dfl = section_data;
-  struct token *tok;
-  int rc;
 
   if (lst->url_pat)
     {
@@ -4138,18 +4189,8 @@ listener_parse_checkurl (void *call_data, void *section_data)
       return PARSER_FAIL;
     }
 
-  if ((tok = gettkn_expect (T_STRING)) == NULL)
-    return PARSER_FAIL;
-
-  rc = regex_compile (&lst->url_pat, tok->str,
-		      (dfl->ignore_case ? POUND_REGEX_ICASE : 0));
-  if (rc)
-    {
-      conf_regcomp_error (rc, lst->url_pat, NULL);
-      return PARSER_FAIL;
-    }
-
-  return PARSER_OK;
+  return parse_regex_compat (&lst->url_pat, dfl->re_type, dfl->re_type,
+			     (dfl->ignore_case ? GENPAT_ICASE : 0));
 }
 
 static int
@@ -5661,6 +5702,33 @@ parse_combine_headers (void *call_data, void *section_data)
     }
   return PARSER_OK;
 }
+
+static struct kwtab regex_type_table[] = {
+  { "posix", GENPAT_POSIX },
+#ifdef HAVE_LIBPCRE
+  { "pcre",  GENPAT_PCRE },
+  { "perl",  GENPAT_PCRE },
+#endif
+  { NULL }
+};
+
+static int
+assign_regex_type (void *call_data, void *section_data)
+{
+  int *gp_type = call_data;
+  struct token *tok;
+  int n;
+
+  if ((tok = gettkn_expect (T_IDENT)) == NULL)
+    return PARSER_FAIL;
+  if (kw_to_tok (regex_type_table, tok->str, 1, &n) != 0)
+    {
+      conf_error ("%s", "unsupported regex type");
+      return PARSER_FAIL;
+    }
+  *gp_type = n;
+  return PARSER_OK;
+}
 
 static PARSER_TABLE top_level_parsetab[] = {
   {
@@ -5839,6 +5907,11 @@ static PARSER_TABLE top_level_parsetab[] = {
     .name = "CombineHeaders",
     .parser = parse_combine_headers
   },
+  {
+    .name = "RegexType",
+    .parser = assign_regex_type,
+    .off = offsetof (POUND_DEFAULTS, re_type),
+  },
 
   /* Backward compatibility. */
   {
@@ -5997,6 +6070,7 @@ parse_config_file (char const *file, int nosyslog)
     .ws_to = 600,
     .be_connto = 15,
     .ignore_case = 0,
+    .re_type = GENPAT_POSIX,
     .header_options = HDROPT_FORWARDED_HEADERS | HDROPT_SSL_HEADERS,
     .balancer = BALANCER_RANDOM
   };
@@ -6157,13 +6231,11 @@ struct string_value pound_settings[] = {
   { "Include directory",   STRING_CONSTANT, { .s_const = SYSCONFDIR } },
   { "PID file",   STRING_CONSTANT,  { .s_const = POUND_PID } },
   { "Buffer size",STRING_INT, { .s_int = MAXBUF } },
-  { "Regex flavor", STRING_CONSTANT, { .s_const =
-#if !defined(HAVE_LIBPCRE)
-				       "POSIX"
-#elif HAVE_LIBPCRE == 1
-				       "pcre"
+  { "Regex types", STRING_CONSTANT, { .s_const = "POSIX"
+#if HAVE_LIBPCRE == 1
+				       ", PCRE"
 #elif HAVE_LIBPCRE == 2
-				       "pcre2"
+				       ", PCRE2"
 #endif
     }
   },
diff --git a/src/extern.h b/src/extern.h
index d4d2011..2258c7e 100644
--- a/src/extern.h
+++ b/src/extern.h
@@ -25,7 +25,7 @@ extern int print_log;           /* print log messages to stdout/stderr during
 				   startup */
 extern int enable_backend_stats;
 
-extern POUND_REGEX HEADER,	/* Allowed header */
+extern GENPAT HEADER,	/* Allowed header */
   CONN_UPGRD,			/* upgrade in connection header */
   LOCATION;			/* the host we are redirected to */
 
diff --git a/src/genpat.c b/src/genpat.c
new file mode 100644
index 0000000..61b81d7
--- /dev/null
+++ b/src/genpat.c
@@ -0,0 +1,530 @@
+/*
+ * Pound - the reverse-proxy load-balancer
+ * Copyright (C) 2023-2024 Sergey Poznyakoff
+ *
+ * Pound is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Pound is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with pound.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "pound.h"
+#include <regex.h>
+#include <string.h>
+#include <assert.h>
+
+static struct genpat_defn const *genpat_vtab[] = {
+  [GENPAT_POSIX]   = &posix_genpat_defn,
+  [GENPAT_PCRE]    = PCRE_REGEX_DEFN,
+  [GENPAT_PREFIX]  = &prefix_genpat_defn,
+  [GENPAT_SUFFIX]  = &suffix_genpat_defn,
+  [GENPAT_CONTAIN] = &contain_genpat_defn,
+  [GENPAT_EXACT]   = &exact_genpat_defn
+};
+#define GENPAT_MAX (sizeof (genpat_vtab) / sizeof (genpat_vtab[0]))
+
+
+int
+genpat_compile (GENPAT *retval, int pat_type, const char *pattern, int pflags)
+{
+  GENPAT gp;
+  struct genpat_defn const *vtab;
+  int rc;
+
+  assert (pat_type >= 0 && pat_type < GENPAT_MAX);
+  if ((vtab = genpat_vtab[pat_type]) == NULL)
+    {
+      *retval = NULL;
+      errno = ENOSYS;
+      return -1;
+    }
+
+  XZALLOC (gp);
+  gp->vtab = vtab;
+  *retval = gp;
+  if ((rc = vtab->gp_init (gp)) != 0)
+    return rc;
+  return vtab->gp_compile (gp->data, pattern, pflags);
+}
+
+#define GENPAT_ASSERT(p) \
+  assert (p != NULL); \
+  assert (p->vtab != NULL);
+
+char const *
+genpat_error (GENPAT p, size_t *off)
+{
+  GENPAT_ASSERT (p);
+  return p->vtab->gp_error (p->data, off);
+}
+
+int
+genpat_match (GENPAT p, const char *subj, size_t n, POUND_REGMATCH *prm)
+{
+  GENPAT_ASSERT (p);
+  return p->vtab->gp_exec (p->data, subj, n, prm);
+}
+
+size_t
+genpat_nsub (GENPAT p)
+{
+  GENPAT_ASSERT (p);
+  return p->vtab->gp_nsub (p->data);
+}
+
+void
+genpat_free (GENPAT p)
+{
+  GENPAT_ASSERT (p);
+  p->vtab->gp_free (p->data);
+  free (p);
+}
+
+struct posix_pattern
+{
+  regex_t re;
+  char *errmsg;
+};
+
+static int
+posix_pattern_init (GENPAT p)
+{
+  p->data = xzalloc (sizeof (struct posix_pattern));
+  return 0;
+}
+
+static int
+posix_pattern_compile (void *gp_data, const char *pattern, int pflags)
+{
+  struct posix_pattern *pat = gp_data;
+  int flags = REG_EXTENDED;
+  int rc;
+
+  if (pflags & GENPAT_ICASE)
+    flags |= REG_ICASE;
+  if (pflags & GENPAT_MULTILINE)
+    flags |= REG_NEWLINE;
+
+  if ((rc = regcomp (&pat->re, pattern, flags)) != 0)
+    {
+      char errbuf[128];
+      regerror (rc, &pat->re, errbuf, sizeof (errbuf));
+      pat->errmsg = xstrdup (errbuf);
+    }
+  return rc;
+}
+
+static char const *
+posix_pattern_error (void *gp_data, size_t *off)
+{
+  struct posix_pattern *pat = gp_data;
+  *off = 0;
+  return pat->errmsg;
+}
+
+static int
+posix_pattern_exec (void *gp_data, const char *subj, size_t n, POUND_REGMATCH *prm)
+{
+  struct posix_pattern *pat = gp_data;
+  int rc;
+  regmatch_t *rm = NULL;
+
+  if (n > 0)
+    {
+      rm = calloc (n, sizeof (rm[0]));
+      if (rm == NULL)
+	return -1;
+      if (n > pat->re.re_nsub + 1)
+	n = pat->re.re_nsub + 1;
+    }
+
+  if ((rc = regexec (&pat->re, subj, n, rm, 0)) == 0)
+    {
+      size_t i;
+      for (i = 0; i < n; i++)
+	{
+	  prm[i].rm_so = rm[i].rm_so;
+	  prm[i].rm_eo = rm[i].rm_eo;
+	}
+    }
+  free (rm);
+  return rc == REG_NOMATCH;
+}
+
+static size_t
+posix_pattern_nsub (void *gp_data)
+{
+  struct posix_pattern *pat = gp_data;
+  return pat->re.re_nsub + 1;
+}
+
+static void
+posix_pattern_free (void *gp_data)
+{
+  if (gp_data)
+    {
+      struct posix_pattern *pat = gp_data;
+      regfree (&pat->re);
+      free (pat->errmsg);
+      free (pat);
+    }
+}
+
+struct genpat_defn posix_genpat_defn =
+  {
+    .gp_init = posix_pattern_init,
+    .gp_compile = posix_pattern_compile,
+    .gp_error = posix_pattern_error,
+    .gp_exec = posix_pattern_exec,
+    .gp_nsub = posix_pattern_nsub,
+    .gp_free = posix_pattern_free
+  };
+
+/* Substring matches */
+struct substr_pattern
+{
+  char *pattern;      /* Substring. */
+  size_t len;         /* Length of the pattern. */
+  int ci;             /* Is it case-insensitive. */
+};
+
+static int
+substr_init (GENPAT p)
+{
+  p->data = xzalloc (sizeof (struct substr_pattern));
+  return 0;
+}
+
+static void
+substr_free (void *gp_data)
+{
+  if (gp_data)
+    {
+      struct substr_pattern *pat = gp_data;
+      free (pat->pattern);
+      free (pat);
+    }
+}
+
+static int
+substr_compile (void *gp_data, const char *pattern, int pflags)
+{
+  struct substr_pattern *pat = gp_data;
+  pat->ci = pflags & GENPAT_ICASE;
+  //FIXME: error out on GENPAT_MULTILINE
+  pat->pattern = xstrdup (pattern);
+  pat->len = strlen (pat->pattern);
+  return 0;
+}
+
+static char const *
+substr_error (void *gp_data, size_t *off)
+{
+  *off = 0;
+  return "no error";
+}
+
+static size_t
+substr_num_submatch (void *gp_data)
+{
+  return 0;
+}
+
+/*
+ * Implementations of the substring matching.
+ */
+
+/* Exact match. */
+static int
+exact_exec (void *gp_data, const char *subj, size_t n, POUND_REGMATCH *prm)
+{
+  struct substr_pattern *pat = gp_data;
+  size_t slen = strlen (subj);
+
+  if (slen != pat->len)
+    return 1;
+  return (pat->ci ? strcasecmp : strcmp) (subj, pat->pattern);
+}
+
+/* Prefix match (-beg). */
+static int
+prefix_exec (void *gp_data, const char *subj, size_t n, POUND_REGMATCH *prm)
+{
+  struct substr_pattern *pat = gp_data;
+  size_t slen = strlen (subj);
+
+  if (slen < pat->len)
+    return 1;
+  return (pat->ci ? strncasecmp : strncmp) (subj, pat->pattern, pat->len);
+}
+
+/* Suffix match (-end). */
+static int
+suffix_exec (void *gp_data, const char *subj, size_t n, POUND_REGMATCH *prm)
+{
+  struct substr_pattern *pat = gp_data;
+  size_t slen = strlen (subj);
+
+  if (slen < pat->len)
+    return 1;
+  return (pat->ci ? strncasecmp : strncmp) (subj + slen - pat->len, pat->pattern, pat->len);
+}
+
+/* "Regex" definitions for the above. */
+struct genpat_defn prefix_genpat_defn =
+  {
+    .gp_init = substr_init,
+    .gp_compile = substr_compile,
+    .gp_error = substr_error,
+    .gp_exec = prefix_exec,
+    .gp_nsub = substr_num_submatch,
+    .gp_free = substr_free
+  };
+
+struct genpat_defn suffix_genpat_defn =
+  {
+    .gp_init = substr_init,
+    .gp_compile = substr_compile,
+    .gp_error = substr_error,
+    .gp_exec = suffix_exec,
+    .gp_nsub = substr_num_submatch,
+    .gp_free = substr_free
+  };
+
+struct genpat_defn exact_genpat_defn =
+  {
+    .gp_init = substr_init,
+    .gp_compile = substr_compile,
+    .gp_error = substr_error,
+    .gp_exec = exact_exec,
+    .gp_nsub = substr_num_submatch,
+    .gp_free = substr_free
+  };
+
+/*
+ * Substring match (-contain).
+ * Implements Boyer–Moore string search algorithm.
+ */
+
+/* Upper-case equivalents of ASCII symbols. */
+static unsigned char casemap[] = {
+  '\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007',
+  '\010', '\011', '\012', '\013', '\014', '\015', '\016', '\017',
+  '\020', '\021', '\022', '\023', '\024', '\025', '\026', '\027',
+  '\030', '\031', '\032', '\033', '\034', '\035', '\036', '\037',
+     ' ',    '!',    '"',    '#',    '$',    '%',    '&',   '\'',
+     '(',    ')',    '*',    '+',    ',',    '-',    '.',    '/',
+     '0',    '1',    '2',    '3',    '4',    '5',    '6',    '7',
+     '8',    '9',    ':',    ';',    '<',    '=',    '>',    '?',
+     '@',    'A',    'B',    'C',    'D',    'E',    'F',    'G',
+     'H',    'I',    'J',    'K',    'L',    'M',    'N',    'O',
+     'P',    'Q',    'R',    'S',    'T',    'U',    'V',    'W',
+     'X',    'Y',    'Z',    '[',   '\\',    ']',    '^',    '_',
+     '`',    'A',    'B',    'C',    'D',    'E',    'F',    'G',
+     'H',    'I',    'J',    'K',    'L',    'M',    'N',    'O',
+     'P',    'Q',    'R',    'S',    'T',    'U',    'V',    'W',
+     'X',    'Y',    'Z',    '{',    '|',    '}',    '~', '\177',
+  '\200', '\201', '\202', '\203', '\204', '\205', '\206', '\207',
+  '\210', '\211', '\212', '\213', '\214', '\215', '\216', '\217',
+  '\220', '\221', '\222', '\223', '\224', '\225', '\226', '\227',
+  '\230', '\231', '\232', '\233', '\234', '\235', '\236', '\237',
+  '\240', '\241', '\242', '\243', '\244', '\245', '\246', '\247',
+  '\250', '\251', '\252', '\253', '\254', '\255', '\256', '\257',
+  '\260', '\261', '\262', '\263', '\264', '\265', '\266', '\267',
+  '\270', '\271', '\272', '\273', '\274', '\275', '\276', '\277',
+  '\300', '\301', '\302', '\303', '\304', '\305', '\306', '\307',
+  '\310', '\311', '\312', '\313', '\314', '\315', '\316', '\317',
+  '\320', '\321', '\322', '\323', '\324', '\325', '\326', '\327',
+  '\330', '\331', '\332', '\333', '\334', '\335', '\336', '\337',
+  '\340', '\341', '\342', '\343', '\344', '\345', '\346', '\347',
+  '\350', '\351', '\352', '\353', '\354', '\355', '\356', '\357',
+  '\360', '\361', '\362', '\363', '\364', '\365', '\366', '\367',
+  '\370', '\371', '\372', '\373', '\374', '\375', '\376', '\377'
+};
+
+struct strstr_pattern
+{
+  unsigned char *pattern;          /* Search pattern. */
+  size_t patlen;                   /* Length of the pattern. */
+  int ci;                          /* Case-insensitivity flag. */
+  int bad_char_delta[UCHAR_MAX];   /* Table of shifts for bad character rule. */
+  int *good_pfx_delta;             /* Table of shifts for good prefix rule. */
+};
+
+#define UC(sp,c) ((sp)->ci ? casemap[(unsigned char)c] : (unsigned char) c)
+
+/*
+ * Compute shift amounts for bad character rule.
+ * Each table element bad_char_delta[C] contains the distance between the last
+ * character in the pattern and the rightmost occurrence of character C
+ * in the pattern.
+ *
+ * If C doesn't occur in the pattern, the value is pattern length - 1.
+ *
+ * The table will be indexed by characters from the subject string, therefore
+ * case mapping is applied to its indices.
+ */
+static void
+fill_bad_char_delta (struct strstr_pattern *sp)
+{
+  int i;
+
+  for (i = 0; i < UCHAR_MAX; i++)
+    sp->bad_char_delta[UC (sp, i)] = sp->patlen - 1;
+  for (i = 0; i < sp->patlen; i++)
+    sp->bad_char_delta[UC (sp, sp->pattern[i])] = sp->patlen - 1 - i;
+}
+
+/*
+ * Return true if text starting at str[pos] is the prefix of str,
+ * i.e. str[pos..len] == str[0..len-pos].
+ * For case-insensitive patterns, this uses the fact that the pattern
+ * has already been converted to upper-case.
+ */
+static int
+is_prefix (unsigned char *str, int len, int pos)
+{
+  return memcmp (str, str + pos, len - pos) != 0;
+}
+
+/*
+ * Return the biggest possible N, such that
+ *   str[pos..pos+N] == str[len-N..len]
+ */
+static int
+longest_suffix_length (unsigned char *str, int len, int pos)
+{
+  int i;
+
+  for (i = 0; i < pos; i++)
+    if (str[pos - i] != str[len - 1 - i])
+      break;
+
+  return i;
+}
+
+/*
+ * Compute shift amounts for good suffix rule.
+ *
+ * The Good suffix rule applies if a substring in subject string matches
+ * a suffix of pattern P[i,patlen-1] and this substring is the largest
+ * such substring for the given alignment.
+ * 
+ * There are two cases:
+ * 
+ * 1. The substring occurs elsewhere in pattern.  In this case the algorithm
+ * selects the rightmost occurrence of the substring such that it does not
+ * form the suffix of the pattern and the character to the left of it is not
+ * the same as the character to the left of the found substring.
+ *
+ * 2. The substring does not occur elsewhere in pattern.  In that case,
+ * shift the left end of the pattern to the right past the left end of
+ * the found substring, so that a prefix of the shifted pattern is also
+ * a suffix of the substring.  If such shift is not possible, then shift
+ * pattern patlen characters to the right.
+ */
+static void
+fill_good_pfx_delta (struct strstr_pattern *sp)
+{
+  int i;
+  int j = 1;
+
+  sp->good_pfx_delta = xcalloc (sp->patlen, sizeof sp->good_pfx_delta[0]);
+
+  for (i = sp->patlen - 1; i >= 0; i--)
+    {
+      if (is_prefix (sp->pattern, sp->patlen, i + 1))
+	j = i + 1;
+      sp->good_pfx_delta[i] = j + sp->patlen - 1 - i;
+    }
+
+  for (i = 0; i < sp->patlen - 1; i++)
+    {
+      int suflen = longest_suffix_length (sp->pattern, sp->patlen, i);
+      if (sp->pattern[i - suflen] != sp->pattern[sp->patlen - 1 - suflen])
+	sp->good_pfx_delta[sp->patlen - 1 - suflen] = sp->patlen - 1 - i + suflen;
+    }
+}
+
+int
+strstr_init (GENPAT p)
+{
+  p->data = xzalloc (sizeof (struct strstr_pattern));
+  return 0;
+}
+
+static void
+strstr_free (void *gp_data)
+{
+  if (gp_data)
+    {
+      struct strstr_pattern *sp = gp_data;
+      free (sp->pattern);
+      free (sp->good_pfx_delta);
+      free (sp);
+    }
+}
+
+int
+strstr_compile (void *gp_data, const char *pattern, int pflags)
+{
+  struct strstr_pattern *sp = gp_data;
+  int i;
+
+  sp->ci = pflags & GENPAT_ICASE;
+  sp->patlen = strlen (pattern);
+  sp->pattern = xmalloc (sp->patlen + 1);
+  for (i = 0; i < sp->patlen; i++)
+    sp->pattern[i] = UC (sp, pattern[i]);
+  sp->pattern[i] = 0;
+
+  fill_bad_char_delta (sp);
+  fill_good_pfx_delta (sp);
+  return 0;
+};
+
+#define max(a,b) ((a)>(b)?(a):(b))
+
+int
+strstr_exec (void *gp_data, const char *subj, size_t n, POUND_REGMATCH *prm)
+{
+  struct strstr_pattern *sp = gp_data;
+  int i;
+  int subjlen;
+
+  if (sp->patlen == 0)
+    return 0;
+
+  subjlen = strlen (subj);
+  i = sp->patlen - 1;
+  while (i < subjlen)
+    {
+      int j;
+
+      for (j = sp->patlen - 1; j >= 0 && UC (sp, subj[i]) == sp->pattern[j]; i--, j--)
+	;
+
+      if (j < 0)
+	return 0;
+
+      i += max (sp->bad_char_delta[UC (sp, subj[i])], sp->good_pfx_delta[j]);
+    }
+  return 1;
+}
+
+struct genpat_defn contain_genpat_defn =
+  {
+    .gp_init = strstr_init,
+    .gp_compile = strstr_compile,
+    .gp_error = substr_error,
+    .gp_exec = strstr_exec,
+    .gp_nsub = substr_num_submatch,
+    .gp_free = strstr_free
+  };
diff --git a/src/http.c b/src/http.c
index 3e3bbcf..2d2444c 100644
--- a/src/http.c
+++ b/src/http.c
@@ -243,9 +243,9 @@ isws (int c)
 }
 
 static int
-submatch_realloc (struct submatch *sm, POUND_REGEX re)
+submatch_realloc (struct submatch *sm, GENPAT re)
 {
-  size_t n = regex_num_submatch (re);
+  size_t n = genpat_nsub (re);
   if (n > sm->matchmax)
     {
       POUND_REGMATCH *p = realloc (sm->matchv, n * sizeof (p[0]));
@@ -302,7 +302,7 @@ submatch_queue_push (struct submatch_queue *smq)
 }
 
 static int
-submatch_exec (POUND_REGEX re, char const *subject, struct submatch *sm)
+submatch_exec (GENPAT re, char const *subject, struct submatch *sm)
 {
   int res;
 
@@ -312,7 +312,7 @@ submatch_exec (POUND_REGEX re, char const *subject, struct submatch *sm)
       lognomem ();
       return 0;
     }
-  res = regex_exec (re, subject, sm->matchn, sm->matchv) == 0;
+  res = genpat_match (re, subject, sm->matchn, sm->matchv) == 0;
   if (res)
     {
       if ((sm->subject = strdup (subject)) == NULL)
@@ -1889,7 +1889,7 @@ qualify_header (struct http_header *hdr)
   };
   int i;
 
-  if (regex_exec (HEADER, hdr->header, 4, matches) == 0)
+  if (genpat_match (HEADER, hdr->header, 4, matches) == 0)
     {
       hdr->name_start = matches[1].rm_so;
       hdr->name_end = matches[1].rm_eo;
@@ -2139,7 +2139,7 @@ http_header_list_filter (HTTP_HEADER_LIST *head, MATCHER *m)
 
   DLIST_FOREACH_SAFE (hdr, tmp, head, link)
     {
-      if (regex_exec (m->pat, hdr->header, 0, NULL) == 0)
+      if (genpat_match (m->pat, hdr->header, 0, NULL) == 0)
 	{
 	  http_header_list_remove (head, hdr);
 	}
@@ -2961,7 +2961,7 @@ parse_http_request (struct http_request *req, int group)
 }
 
 static int
-match_headers (HTTP_HEADER_LIST *headers, POUND_REGEX re,
+match_headers (HTTP_HEADER_LIST *headers, GENPAT re,
 	       struct submatch *sm)
 {
   struct http_header *hdr;
@@ -3695,7 +3695,7 @@ backend_response (POUND_HTTP *phttp)
 	      /*
 	       * Connection: upgrade
 	       */
-	      else if (regex_exec (CONN_UPGRD, val, 0, NULL) == 0)
+	      else if (genpat_match (CONN_UPGRD, val, 0, NULL) == 0)
 		phttp->ws_state |= WSS_RESP_HEADER_CONNECTION_UPGRADE;
 	      break;
 
@@ -4134,7 +4134,7 @@ send_to_backend (POUND_HTTP *phttp, int chunked, CONTENT_LENGTH content_length)
       if ((val = http_header_get_value (hdr)) == NULL)
 	return HTTP_STATUS_INTERNAL_SERVER_ERROR;
 
-      if (regex_exec (LOCATION, val, 4, matches))
+      if (genpat_match (LOCATION, val, 4, matches))
 	{
 	  logmsg (LOG_NOTICE, "(%"PRItid") can't parse Destination %s",
 		  POUND_TID (), val);
@@ -4600,7 +4600,7 @@ do_http (POUND_HTTP *phttp)
 	phttp->ws_state |= WSS_REQ_GET;
 
       if (phttp->lstn->url_pat &&
-	  regex_exec (phttp->lstn->url_pat, phttp->request.url, 0, NULL))
+	  genpat_match (phttp->lstn->url_pat, phttp->request.url, 0, NULL))
 	{
 	  log_error (phttp, HTTP_STATUS_NOT_IMPLEMENTED, 0,
 		     "bad URL \"%s\"", phttp->request.url);
@@ -4625,7 +4625,7 @@ do_http (POUND_HTTP *phttp)
 	      /*
 	       * Connection: upgrade
 	       */
-	      else if (regex_exec (CONN_UPGRD, val, 0, NULL) == 0)
+	      else if (genpat_match (CONN_UPGRD, val, 0, NULL) == 0)
 		phttp->ws_state |= WSS_REQ_HEADER_CONNECTION_UPGRADE;
 	      break;
 
diff --git a/src/pound.c b/src/pound.c
index 4d46b86..4686f56 100644
--- a/src/pound.c
+++ b/src/pound.c
@@ -43,7 +43,7 @@ LISTENER_HEAD listeners = SLIST_HEAD_INITIALIZER (listeners);
 				/* all available listeners */
 int n_listeners;                /* Number of listeners */
 
-POUND_REGEX HEADER, 	        /* Allowed header */
+GENPAT HEADER, 	        /* Allowed header */
   CONN_UPGRD,			/* upgrade in connection header */
   LOCATION;			/* the host we are redirected to */
 
@@ -987,12 +987,15 @@ main (const int argc, char **argv)
   CRYPTO_set_locking_callback (l_lock);
 
   /* prepare regular expressions */
-  if (regex_compile (&HEADER, "^([a-z0-9!#$%&'*+.^_`|~-]+):[ \t]*(.*)[ \t]*$",
-		     POUND_REGEX_ICASE | POUND_REGEX_MULTILINE)
-      || regex_compile (&CONN_UPGRD, "(^|[ \t,])upgrade([ \t,]|$)",
-		  POUND_REGEX_ICASE | POUND_REGEX_MULTILINE)
-      || regex_compile (&LOCATION, "(http|https)://([^/]+)(.*)",
-		  POUND_REGEX_ICASE | POUND_REGEX_MULTILINE))
+  if (genpat_compile (&HEADER, GENPAT_POSIX,
+		     "^([a-z0-9!#$%&'*+.^_`|~-]+):[ \t]*(.*)[ \t]*$",
+		     GENPAT_ICASE | GENPAT_MULTILINE)
+      || genpat_compile (&CONN_UPGRD, GENPAT_POSIX,
+			"(^|[ \t,])upgrade([ \t,]|$)",
+			GENPAT_ICASE | GENPAT_MULTILINE)
+      || genpat_compile (&LOCATION,  GENPAT_POSIX,
+			"(http|https)://([^/]+)(.*)",
+			GENPAT_ICASE | GENPAT_MULTILINE))
     abend ("bad essential Regex");
 
 #ifndef SOL_TCP
diff --git a/src/pound.h b/src/pound.h
index da4f3f9..484da2a 100644
--- a/src/pound.h
+++ b/src/pound.h
@@ -91,22 +91,61 @@
 # include <openssl/engine.h>
 #endif
 
-typedef struct pound_regex *POUND_REGEX;
+struct genpat
+{
+  struct genpat_defn const *vtab;
+  void *data;
+};
+
+typedef struct genpat *GENPAT;
 
 typedef struct {
   int rm_so;
   int rm_eo;
 } POUND_REGMATCH;
 
-#define POUND_REGEX_DEFAULT   0
-#define POUND_REGEX_ICASE     0x1
-#define POUND_REGEX_MULTILINE 0x2
+#define GENPAT_DEFAULT   0
+#define GENPAT_ICASE     0x1
+#define GENPAT_MULTILINE 0x2
+
+int genpat_compile (GENPAT *, int, const char *, int);
+int genpat_match (GENPAT, const char *, size_t, POUND_REGMATCH *);
+void genpat_free (GENPAT);
+char const *genpat_error (GENPAT, size_t *);
+size_t genpat_nsub (GENPAT);
+
+enum
+  {
+    GENPAT_POSIX,
+    GENPAT_PCRE,
+    GENPAT_PREFIX,
+    GENPAT_SUFFIX,
+    GENPAT_CONTAIN,
+    GENPAT_EXACT,
+  };
+
+struct genpat_defn
+{
+  int (*gp_init) (GENPAT);
+  int (*gp_compile) (void *, const char *, int);
+  char const *(*gp_error) (void *, size_t *);
+  int (*gp_exec) (void *, const char *, size_t, POUND_REGMATCH *);
+  size_t (*gp_nsub) (void *);
+  void (*gp_free) (void *);
+};
 
-int regex_compile (POUND_REGEX *, const char *, int);
-int regex_exec (POUND_REGEX, const char *, size_t, POUND_REGMATCH *);
-void regex_free (POUND_REGEX);
-char const *regex_error (POUND_REGEX pre, size_t *off);
-size_t regex_num_submatch (POUND_REGEX pre);
+extern struct genpat_defn posix_genpat_defn;
+extern struct genpat_defn prefix_genpat_defn;
+extern struct genpat_defn suffix_genpat_defn;
+extern struct genpat_defn contain_genpat_defn;
+extern struct genpat_defn exact_genpat_defn;
+
+#ifdef HAVE_LIBPCRE
+extern struct genpat_defn pcre_genpat_defn;
+# define PCRE_REGEX_DEFN &pcre_genpat_defn
+#else
+# define PCRE_REGEX_DEFN NULL
+#endif
 
 #ifdef  HAVE_LONG_LONG_INT
 typedef long long CONTENT_LENGTH;
@@ -394,7 +433,7 @@ int acl_match (ACL *acl, struct sockaddr *sa);
 /* matcher chain */
 typedef struct _matcher
 {
-  POUND_REGEX pat;		/* pattern to match the request/header against */
+  GENPAT pat;		/* pattern to match the request/header against */
   SLIST_ENTRY (_matcher) next;
 } MATCHER;
 
@@ -556,7 +595,7 @@ typedef struct string_ref
 struct string_match
 {
   STRING_REF *string;
-  POUND_REGEX re;
+  GENPAT re;
 };
 
 struct user_pass
@@ -583,7 +622,7 @@ typedef struct _service_cond
   union
   {
     ACL *acl;
-    POUND_REGEX re;
+    GENPAT re;
     struct bool_service_cond bool;
     struct _service_cond *cond;
     struct string_match sm;  /* COND_QUERY_PARAM and COND_STRING_MATCH */
@@ -736,7 +775,7 @@ typedef struct _listener
   REWRITE_RULE_HEAD rewrite[2];
   int verb;			/* allowed HTTP verb group */
   unsigned to;			/* client time-out */
-  POUND_REGEX url_pat;		/* pattern to match the request URL against */
+  GENPAT url_pat;	/* pattern to match the request URL against */
   char *http_err[HTTP_STATUS_MAX];	/* error messages */
   CONTENT_LENGTH max_req_size;	/* max. request size */
   unsigned max_uri_length;      /* max. URI length */
diff --git a/src/regex_pcre.c b/src/regex_pcre.c
index 563e068..18cf2c1 100644
--- a/src/regex_pcre.c
+++ b/src/regex_pcre.c
@@ -24,7 +24,7 @@
 #  error "You have libpcre, but the header files are missing. Use --disable-pcre"
 #endif
 
-struct pound_regex
+struct pcre_pattern
 {
   pcre *pcre;
   size_t nsub;
@@ -32,20 +32,25 @@ struct pound_regex
   int erroff;
 };
 
-int
-regex_compile (POUND_REGEX *retval, const char *pattern, int pflags)
+static int
+pcre_pattern_init (GENPAT strpat)
 {
-  struct pound_regex *pre;
+  strpat->data = xzalloc (sizeof (struct pcre_pattern));
+  return 0;
+}
+
+static int
+pcre_pattern_compile (void *gp_data, const char *pattern, int pflags)
+{
+  struct pcre_pattern *pre = gp_data;
   int flags = 0;
   int nsub;
 
-  if (pflags & POUND_REGEX_ICASE)
+  if (pflags & GENPAT_ICASE)
     flags |= PCRE_CASELESS;
-  if (pflags & POUND_REGEX_MULTILINE)
+  if (pflags & GENPAT_MULTILINE)
     flags |= PCRE_MULTILINE;
 
-  XZALLOC (pre);
-  *retval = pre;
   pre->pcre = pcre_compile (pattern, flags, &pre->errmsg, &pre->erroff, 0);
 
   if (pre->pcre == NULL)
@@ -60,16 +65,18 @@ regex_compile (POUND_REGEX *retval, const char *pattern, int pflags)
   return 0;
 }
 
-char const *
-regex_error (POUND_REGEX pre, size_t *off)
+static char const *
+pcre_pattern_error (void *gp_data, size_t *off)
 {
+  struct pcre_pattern *pre = gp_data;
   *off = pre->erroff;
   return pre->errmsg;
 }
 
-int
-regex_exec (POUND_REGEX pre, const char *subj, size_t n, POUND_REGMATCH *prm)
+static int
+pcre_pattern_exec (void *gp_data, const char *subj, size_t n, POUND_REGMATCH *prm)
 {
+  struct pcre_pattern *pre = gp_data;
   int rc;
   int ovsize;
   int *ovector;
@@ -99,18 +106,30 @@ regex_exec (POUND_REGEX pre, const char *subj, size_t n, POUND_REGMATCH *prm)
   return rc < 0;
 }
 
-size_t
-regex_num_submatch (POUND_REGEX pre)
+static size_t
+pcre_pattern_nsub (void *gp_data)
 {
+  struct pcre_pattern *pre = gp_data;
   return pre->nsub;
 }
 
-void
-regex_free (POUND_REGEX pre)
+static void
+pcre_pattern_free (void *gp_data)
 {
-  if (pre)
+  if (gp_data)
     {
+      struct pcre_pattern *pre = gp_data;
       pcre_free (pre->pcre);
       free (pre);
     }
 }
+
+struct genpat_defn pcre_genpat_defn =
+  {
+    .gp_init = pcre_pattern_init,
+    .gp_compile = pcre_pattern_compile,
+    .gp_error = pcre_pattern_error,
+    .gp_exec = pcre_pattern_exec,
+    .gp_nsub = pcre_pattern_nsub,
+    .gp_free = pcre_pattern_free
+  };
diff --git a/src/regex_pcre2.c b/src/regex_pcre2.c
index 3c3500d..2395d63 100644
--- a/src/regex_pcre2.c
+++ b/src/regex_pcre2.c
@@ -1,8 +1,25 @@
+/*
+ * Pound - the reverse-proxy load-balancer
+ * Copyright (C) 2023-2024 Sergey Poznyakoff
+ *
+ * Pound is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Pound is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with pound.  If not, see <http://www.gnu.org/licenses/>.
+ */
 #include "pound.h"
 #define PCRE2_CODE_UNIT_WIDTH 8
 #include <pcre2.h>
 
-struct pound_regex
+struct pcre2_pattern
 {
   pcre2_code *code;
   size_t nsub;
@@ -10,20 +27,25 @@ struct pound_regex
   size_t erroff;
 };
 
-int
-regex_compile (POUND_REGEX *retval, const char *pattern, int pflags)
+static int
+pcre2_pattern_init (GENPAT pat)
 {
-  struct pound_regex *pre;
+  pat->data = xzalloc (sizeof (struct pcre2_pattern));
+  return 0;
+}
+
+static int
+pcre2_pattern_compile (void *sp_data, const char *pattern, int pflags)
+{
+  struct pcre2_pattern *pre = sp_data;
   int flags = 0;
   int error_code;
 
-  if (pflags & POUND_REGEX_ICASE)
+  if (pflags & GENPAT_ICASE)
     flags |= PCRE2_CASELESS;
-  if (pflags & POUND_REGEX_MULTILINE)
+  if (pflags & GENPAT_MULTILINE)
     flags |= PCRE2_MULTILINE;
 
-  XZALLOC (pre);
-  *retval = pre;
   pre->code = pcre2_compile ((PCRE2_SPTR8) pattern, strlen (pattern), flags,
 			     &error_code, &pre->erroff, NULL);
   if (pre->code == NULL)
@@ -59,33 +81,37 @@ regex_compile (POUND_REGEX *retval, const char *pattern, int pflags)
   return 0;
 }
 
-void
-regex_free (POUND_REGEX pre)
+static void
+pcre2_pattern_free (void *sp_data)
 {
-  if (pre)
+  if (sp_data)
     {
+      struct pcre2_pattern *pre = sp_data;
       pcre2_code_free (pre->code);
       free (pre->errmsg);
       free (pre);
     }
 }
 
-char const *
-regex_error (POUND_REGEX pre, size_t *off)
+static char const *
+pcre2_pattern_error (void *sp_data, size_t *off)
 {
+  struct pcre2_pattern *pre = sp_data;
   *off = pre->erroff;
   return pre->errmsg;
 }
 
-size_t
-regex_num_submatch (POUND_REGEX pre)
+static size_t
+pcre2_pattern_nsub (void *sp_data)
 {
+  struct pcre2_pattern *pre = sp_data;
   return pre->nsub;
 }
 
-int
-regex_exec (POUND_REGEX pre, const char *subj, size_t n, POUND_REGMATCH *prm)
+static int
+pcre2_pattern_exec (void *sp_data, const char *subj, size_t n, POUND_REGMATCH *prm)
 {
+  struct pcre2_pattern *pre = sp_data;
   int rc;
   PCRE2_SIZE *ovector;
   size_t i, j;
@@ -115,3 +141,13 @@ regex_exec (POUND_REGEX pre, const char *subj, size_t n, POUND_REGMATCH *prm)
   pcre2_match_data_free (md);
   return 0;
 }
+
+struct genpat_defn pcre_genpat_defn =
+  {
+    .gp_init = pcre2_pattern_init,
+    .gp_compile = pcre2_pattern_compile,
+    .gp_error = pcre2_pattern_error,
+    .gp_exec = pcre2_pattern_exec,
+    .gp_nsub = pcre2_pattern_nsub,
+    .gp_free = pcre2_pattern_free
+  };
diff --git a/src/regex_std.c b/src/regex_std.c
deleted file mode 100644
index dc96ede..0000000
--- a/src/regex_std.c
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Pound - the reverse-proxy load-balancer
- * Copyright (C) 2023-2024 Sergey Poznyakoff
- *
- * Pound is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * Pound is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with pound.  If not, see <http://www.gnu.org/licenses/>.
- */
-#include "pound.h"
-#include <regex.h>
-
-struct pound_regex
-{
-  regex_t re;
-  char *errmsg;
-};
-
-int
-regex_compile (POUND_REGEX *retval, const char *pattern, int pflags)
-{
-  struct pound_regex *pre;
-  int flags = REG_EXTENDED;
-  int rc;
-
-  if (pflags & POUND_REGEX_ICASE)
-    flags |= REG_ICASE;
-  if (pflags & POUND_REGEX_MULTILINE)
-    flags |= REG_NEWLINE;
-  
-  XZALLOC (pre);
-  *retval = pre;
-  if ((rc = regcomp (&pre->re, pattern, flags)) != 0)
-    {
-      char errbuf[128];
-      regerror (rc, &pre->re, errbuf, sizeof (errbuf));
-      pre->errmsg = xstrdup (errbuf);
-    }
-  return rc;
-}
-
-char const *
-regex_error (POUND_REGEX pre, size_t *off)
-{
-  *off = 0;
-  return pre->errmsg;
-}
-
-int
-regex_exec (POUND_REGEX pre, const char *subj, size_t n, POUND_REGMATCH *prm)
-{
-  int rc;
-  regmatch_t *rm = NULL;
-
-  if (n > 0)
-    {
-      rm = calloc (n, sizeof (rm[0]));
-      if (rm == NULL)
-	return -1;
-      if (n > pre->re.re_nsub + 1)
-	n = pre->re.re_nsub + 1;
-    }
-
-  if ((rc = regexec (&pre->re, subj, n, rm, 0)) == 0)
-    {
-      size_t i;
-      for (i = 0; i < n; i++)
-	{
-	  prm[i].rm_so = rm[i].rm_so;
-	  prm[i].rm_eo = rm[i].rm_eo;
-	}
-    }
-  free (rm);
-  return rc == REG_NOMATCH;
-}
-
-size_t
-regex_num_submatch (POUND_REGEX pre)
-{
-  return pre->re.re_nsub + 1;
-}
-
-void
-regex_free (POUND_REGEX pre)
-{
-  if (pre)
-    {
-      regfree (&pre->re);
-      free (pre->errmsg);
-      free (pre);
-    }
-}
diff --git a/src/svc.c b/src/svc.c
index 4f56c53..29736bd 100644
--- a/src/svc.c
+++ b/src/svc.c
@@ -1008,7 +1008,7 @@ need_rewrite (const char *location, const char *v_host,
     return 0;
 
   /* split the location into its fields */
-  if (regex_exec (LOCATION, location, 4, matches))
+  if (genpat_match (LOCATION, location, 4, matches))
     return 0;
   proto = location + matches[1].rm_so;
 
diff --git a/tests/Makefile.am b/tests/Makefile.am
index b36dad1..35cac50 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -105,9 +105,11 @@ TESTSUITE_AT = \
  optssl.at\
  or.at\
  path.at\
+ pcre.at\
  prio.at\
  query.at\
  queryparam.at\
+ regextype.at\
  reqacc.at\
  redirect.at\
  resprw.at\
diff --git a/tests/atlocal.in b/tests/atlocal.in
index 7d48222..d7ae4fa 100644
--- a/tests/atlocal.in
+++ b/tests/atlocal.in
@@ -6,3 +6,5 @@ HARNESS="@abs_srcdir@/poundharness.pl"
 # working without internet connection.
 PERL_HTTP_TINY_IPV4_ONLY=1
 export PERL_HTTP_TINY_IPV4_ONLY
+@COND_PCRE_TRUE@PCRE_AVAILABLE=1
+@COND_PCRE2_TRUE@PCRE_AVAILABLE=1
diff --git a/tests/host.at b/tests/host.at
index a42feff..5cc55e4 100644
--- a/tests/host.at
+++ b/tests/host.at
@@ -30,6 +30,20 @@ PT_CHECK([ListenHTTP
 			Port
 		End
 	End
+	Service
+		Host -beg "WWW."
+		Backend
+			Address
+			Port
+		End
+	End
+	Service
+		Host -end ".NET"
+		Backend
+			Address
+			Port
+		End
+	End
 End
 ],
 [GET /echo/foo
@@ -47,5 +61,21 @@ end
 200
 x-backend-number: 1
 end
+
+GET /echo/foo
+Host: www.example.com
+end
+
+200
+x-backend-number: 2
+end
+
+GET /echo/foo
+Host: example.net
+end
+
+200
+x-backend-number: 3
+end
 ])
 AT_CLEANUP
diff --git a/tests/pcre.at b/tests/pcre.at
new file mode 100644
index 0000000..e565185
--- /dev/null
+++ b/tests/pcre.at
@@ -0,0 +1,103 @@
+AT_SETUP([PCRE support])
+AT_KEYWORDS([config cfg regexptype regexp pcre])
+AT_CHECK([PT_PREREQ_PCRE])
+PT_CHECK([RegexType pcre
+ListenHTTP
+	Service
+		Header "Host:\\s*(?<!www\\.)example.org"
+		Backend
+			Address
+			Port
+		End
+	End
+	Service
+		Backend
+			Address
+			Port
+		End
+	End
+End
+],
+[GET /echo/foo
+Host: example.org
+end
+
+200
+x-backend-number: 0
+end
+
+GET /echo/foo
+Host: www.example.org
+end
+
+200
+x-backend-number: 1
+end
+])
+
+PT_CHECK([ListenHTTP
+	Service
+		Header -pcre "Host:\\s*(?<!www\\.)example.org"
+		Backend
+			Address
+			Port
+		End
+	End
+	Service
+		Backend
+			Address
+			Port
+		End
+	End
+End
+],
+[GET /echo/foo
+Host: example.org
+end
+
+200
+x-backend-number: 0
+end
+
+GET /echo/foo
+Host: www.example.org
+end
+
+200
+x-backend-number: 1
+end
+])
+
+PT_CHECK([ListenHTTP
+	Service
+		Header -pcre -icase "Host:\\s*(?<!www\\.)example.org"
+		Backend
+			Address
+			Port
+		End
+	End
+	Service
+		Backend
+			Address
+			Port
+		End
+	End
+End
+],
+[GET /echo/foo
+Host: EXAMPLE.ORG
+end
+
+200
+x-backend-number: 0
+end
+
+GET /echo/foo
+Host: WWW.EXAMPLE.ORG
+end
+
+200
+x-backend-number: 1
+end
+])
+AT_CLEANUP
diff --git a/tests/poundharness.pl b/tests/poundharness.pl
index a7e9a54..750ca54 100644
--- a/tests/poundharness.pl
+++ b/tests/poundharness.pl
@@ -293,7 +293,7 @@ EOT
 		    my $file;
 		    while ($hostline =~ m/^\s*(-\S+)(.*)/) {
 			push @opts, $1;
-			if ($1 eq '-re' || $1 eq '-beg' || $1 eq '-end') {
+			if ($1 eq '-re' || $1 eq '-beg') {
 			    $exact = 0;
 			} elsif ($1 eq '-file') {
 			    $file = 1;
diff --git a/tests/regextype.at b/tests/regextype.at
new file mode 100644
index 0000000..448b26e
--- /dev/null
+++ b/tests/regextype.at
@@ -0,0 +1,28 @@
+# This file is part of pound testsuite. -*- autotest -*-
+# Copyright (C) 2024 Sergey Poznyakoff
+#
+# Pound is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Pound is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with pound.  If not, see <http://www.gnu.org/licenses/>.
+AT_SETUP([RegexpType statement])
+AT_KEYWORDS([config cfg regexptype regexp])
+PT_CONF([RegexType posix
+])
+PT_CONF([RegexType nosuchre
+],
+[1],
+[],
+[pound: pound.cfg:1.11-18: unsupported regex type
+])
+
+AT_CLEANUP
+
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 31937bd..c0faf7c 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -23,6 +23,7 @@ _CONF
 AT_CHECK([pound -c -Wno-dns -Wno-include-dir -f pound.cfg],m4_shift($@))])
 
 m4_define([PT_PREREQ_PERL],[perl -v >/dev/null 2>&1 || exit 77])
+m4_define([PT_PREREQ_PCRE],[test "$PCRE_AVAILABLE" = "1" || exit 77])
 
 m4_pushdef([HARNESS_OPTIONS])
 
@@ -107,6 +108,10 @@ m4_include([rwchain.at])
 AT_BANNER([Response Rewriting])
 m4_include([resprw.at])
 
+AT_BANNER([Regexp Types])
+m4_include([regextype.at])
+m4_include([pcre.at])
+
 AT_BANNER([Sessions])
 m4_include([sessip.at])
 m4_include([sessauth.at])
diff --git a/tests/url.at b/tests/url.at
index a1398ab..8c269d9 100644
--- a/tests/url.at
+++ b/tests/url.at
@@ -66,6 +66,20 @@ PT_CHECK([ListenHTTP
 		End
 	End
 	Service "7"
+		Url -contain "substr"
+		Backend
+			Address
+			Port
+		End
+	End
+	Service "8"
+		Url -contain -icase "substr"
+		Backend
+			Address
+			Port
+		End
+	End
+	Service "9"
 		Backend
 			Address
 			Port
@@ -128,6 +142,20 @@ end
 200
 x-backend-number: 6
 end
+
+GET /echo/match_substring
+end
+
+200
+x-backend-number: 7
+end
+
+GET /echo/match_sUBString
+end
+
+200
+x-backend-number: 8
+end
 ])
 
 AT_CLEANUP
