Starting from a feature request on users@ some time ago (see [1] if
you are interested by long threads...), I made changes in the
expressions parser.

Currently there are two types of expressions handled by httpd, string
and conditional expressions, usable in different contexts, with
different syntaxes.

While conditional expressions are quite powerful with the definition
of "word", regexps and even string expressions (what's between the
quotes), the string expressions are more limited (no "word" nor
regexps).
So the idea is to open string expressions to the "word" :)

String expressions can reference a variable with %{var}, a function
call with %{func:arg}, or a regexp back-reference with $n, but that
almost all.
I propose that they now can reference almost anything with the
%{:word:} syntax, including conditions which become "true" or "false"
in a string context.
But this is not only about evaluating conditions in strings, the
"word" also gives access to list functions and regexps that can work
on list and return a string (or a sub-list...), like split// or
join//.

For the initial users@ case, it allows things like:
  Header always set "X-SubjectAltName" \
   "expr=%{: join/PeerExtList('subjectAltName')/', '/ :}"
or:
  Header always set "X-Client-in-SubjectAltName" \
   "expr=%{: %{REMOTE_ADDR} -in \
             ( PeerExtList('subjectAltName') =~ \
               split/.*?IPAddress:([^,]+)/$1/ ) :}"

Patch attached, comments welcome...


Regards,
Yann.
Index: server/util_expr_private.h
===================================================================
--- server/util_expr_private.h	(revision 1783852)
+++ server/util_expr_private.h	(working copy)
@@ -54,9 +54,10 @@ typedef enum {
     op_REG, op_NRE,
     op_STR_EQ, op_STR_NE, op_STR_LT, op_STR_LE, op_STR_GT, op_STR_GE,
     op_Concat,
-    op_Digit, op_String, op_Regex, op_RegexBackref,
-    op_Var,
-    op_ListElement,
+    op_Digit, op_String,
+    op_Var, op_Word, op_Bool, op_Join,
+    op_Regex, op_Regsub, op_Regref,
+    op_ListElement, op_ListRegex,
     /*
      * call external functions/operators.
      * The info node contains the function pointer and some function specific
@@ -79,6 +80,15 @@ struct ap_expr_node {
     const void *node_arg2;
 };
 
+/** The stack used by scanner and parser */
+typedef struct ap_expr_parser_stack {
+    char *scan_ptr;
+    char  scan_buf[MAX_STRING_LEN];
+    char  scan_del;
+    int   scan_state;
+    struct ap_expr_parser_stack *next;
+} ap_expr_parser_stack_t;
+
 /** The context used by scanner and parser */
 typedef struct {
     /* internal state of the scanner */
@@ -86,9 +96,8 @@ typedef struct {
     int                inputlen;
     const char        *inputptr;
     void              *scanner;
-    char              *scan_ptr;
-    char               scan_buf[MAX_STRING_LEN];
-    char               scan_del;
+    ap_expr_parser_stack_t *stack;
+    ap_expr_parser_stack_t *stacks;
     int                at_start;
 
     /* pools for result and temporary usage */
@@ -119,6 +128,13 @@ void ap_expr_yyset_extra(ap_expr_parse_ctx_t *cont
 /* create a parse tree node */
 ap_expr_t *ap_expr_make(ap_expr_node_op_e op, const void *arg1,
                         const void *arg2, ap_expr_parse_ctx_t *ctx);
+ap_expr_t *ap_expr_regex_make(const char *pattern, const char *flags,
+                              const ap_expr_t *subst, int split,
+                              ap_expr_parse_ctx_t *ctx);
+ap_expr_t *ap_expr_str_word_make(const ap_expr_t *arg,
+                                 ap_expr_parse_ctx_t *ctx);
+ap_expr_t *ap_expr_str_bool_make(const ap_expr_t *arg,
+                                 ap_expr_parse_ctx_t *ctx);
 /* create parse tree node for the string-returning function 'name' */
 ap_expr_t *ap_expr_str_func_make(const char *name, const ap_expr_t *arg,
                                ap_expr_parse_ctx_t *ctx);
@@ -125,6 +141,8 @@ ap_expr_t *ap_expr_str_func_make(const char *name,
 /* create parse tree node for the list-returning function 'name' */
 ap_expr_t *ap_expr_list_func_make(const char *name, const ap_expr_t *arg,
                                 ap_expr_parse_ctx_t *ctx);
+ap_expr_t *ap_expr_list_regex_make(const ap_expr_t *lst, const ap_expr_t *re,
+                                   ap_expr_parse_ctx_t *ctx);
 /* create parse tree node for the variable 'name' */
 ap_expr_t *ap_expr_var_make(const char *name, ap_expr_parse_ctx_t *ctx);
 /* create parse tree node for the unary operator 'name' */
@@ -135,6 +153,9 @@ ap_expr_t *ap_expr_binary_op_make(const char *name
                                   const ap_expr_t *arg2,
                                   ap_expr_parse_ctx_t *ctx);
 
+/* push/pop a stack in the parser/lexer */
+void ap_expr_parser_stack_push(ap_expr_parse_ctx_t *ctx);
+void ap_expr_parser_stack_pop(ap_expr_parse_ctx_t *ctx);
 
 #endif /* __AP_EXPR_PRIVATE_H__ */
 /** @} */
Index: server/util_expr_eval.c
===================================================================
--- server/util_expr_eval.c	(revision 1783852)
+++ server/util_expr_eval.c	(working copy)
@@ -26,6 +26,7 @@
 #include "ap_provider.h"
 #include "util_expr_private.h"
 #include "util_md5.h"
+#include "util_varbuf.h"
 
 #include "apr_lib.h"
 #include "apr_fnmatch.h"
@@ -47,6 +48,8 @@ AP_IMPLEMENT_HOOK_RUN_FIRST(int, expr_lookup, (ap_
 
 #define  LOG_MARK(info)  __FILE__, __LINE__, (info)->module_index
 
+static int ap_expr_eval(ap_expr_eval_ctx_t *ctx, const ap_expr_t *node);
+
 static const char *ap_expr_eval_string_func(ap_expr_eval_ctx_t *ctx,
                                             const ap_expr_t *info,
                                             const ap_expr_t *args);
@@ -56,6 +59,19 @@ static const char *ap_expr_eval_var(ap_expr_eval_c
                                     ap_expr_var_func_t *func,
                                     const void *data);
 
+typedef struct {
+    int type, flags;
+    const ap_expr_t *subst;
+} ap_expr_regctx_t;
+
+static const char *ap_expr_regexec(const char *subject,
+                                   const ap_expr_t *reg,
+                                   apr_array_header_t *list,
+                                   ap_expr_eval_ctx_t *ctx);
+
+static apr_array_header_t *ap_expr_list_make(ap_expr_eval_ctx_t *ctx,
+                                             const ap_expr_t *node);
+
 /* define AP_EXPR_DEBUG to log the parse tree when parsing an expression */
 #ifdef AP_EXPR_DEBUG
 static void expr_dump_tree(const ap_expr_t *e, const server_rec *s,
@@ -80,6 +96,37 @@ static int inc_rec(ap_expr_eval_ctx_t *ctx)
     return 1;
 }
 
+static const char *ap_expr_list_pstrcat(apr_pool_t *p,
+                                        const apr_array_header_t *list,
+                                        const char *sep)
+{
+    if (apr_is_empty_array(list)) {
+        return NULL;
+    }
+    else if (list->nelts == 1) {
+        return APR_ARRAY_IDX(list, 0, const char*);
+    }
+    else {
+        struct ap_varbuf vb;
+        int n = list->nelts - 1, i;
+        apr_size_t slen = strlen(sep), vlen;
+        const char *val;
+
+        ap_varbuf_init(p, &vb, 0);
+        for (i = 0; i < n; ++i) {
+            val = APR_ARRAY_IDX(list, i, const char*);
+            vlen = strlen(val);
+            ap_varbuf_grow(&vb, vlen + slen + 1);
+            ap_varbuf_strmemcat(&vb, val, vlen);
+            ap_varbuf_strmemcat(&vb, sep, slen);
+        }
+        val = APR_ARRAY_IDX(list, n, const char*);
+        ap_varbuf_strmemcat(&vb, val, strlen(val));
+
+        return vb.buf;
+    }
+}
+
 static const char *ap_expr_eval_word(ap_expr_eval_ctx_t *ctx,
                                      const ap_expr_t *node)
 {
@@ -91,6 +138,12 @@ static const char *ap_expr_eval_word(ap_expr_eval_
     case op_String:
         result = node->node_arg1;
         break;
+    case op_Word:
+        result = ap_expr_eval_word(ctx, node->node_arg1);
+        break;
+    case op_Bool:
+        result = ap_expr_eval(ctx, node->node_arg1) ? "true" : "false";
+        break;
     case op_Var:
         result = ap_expr_eval_var(ctx, (ap_expr_var_func_t *)node->node_arg1,
                                   node->node_arg2);
@@ -161,7 +214,20 @@ static const char *ap_expr_eval_word(ap_expr_eval_
         result = ap_expr_eval_string_func(ctx, info, args);
         break;
     }
-    case op_RegexBackref: {
+    case op_Join: {
+        const char *sep;
+        apr_array_header_t *list = ap_expr_list_make(ctx, node->node_arg1);
+        sep = node->node_arg2 ? ap_expr_eval_word(ctx, node->node_arg2) : "";
+        result = ap_expr_list_pstrcat(ctx->p, list, sep);
+        break;
+    }
+    case op_Regsub: {
+        const ap_expr_t *reg = node->node_arg2;
+        const char *subject = ap_expr_eval_word(ctx, node->node_arg1);
+        result = ap_expr_regexec(subject, reg, NULL, ctx);
+        break;
+    }
+    case op_Regref: {
         const unsigned int *np = node->node_arg1;
         result = ap_expr_eval_re_backref(ctx, *np);
         break;
@@ -226,6 +292,170 @@ static int intstrcmp(const char *s1, const char *s
         return 1;
 }
 
+static const char *ap_expr_regexec(const char *subject,
+                                   const ap_expr_t *reg,
+                                   apr_array_header_t *list,
+                                   ap_expr_eval_ctx_t *ctx)
+{
+    struct ap_varbuf vb;
+    const char *val = subject;
+    const ap_regex_t *regex = reg->node_arg1;
+    const ap_expr_regctx_t *regctx = reg->node_arg2;
+    ap_regmatch_t *pmatch = NULL, match0;
+    apr_size_t nmatch = 0;
+    const char *str = "";
+    apr_size_t len = 0;
+    int empty = 0, rv;
+
+    ap_varbuf_init(ctx->p, &vb, 0);
+    if (ctx->re_nmatch > 0) {
+        nmatch = ctx->re_nmatch;
+        pmatch = ctx->re_pmatch;
+    }
+    else if (regctx->type != 'm') {
+        nmatch = 1;
+        pmatch = &match0;
+    }
+    do {
+        /* If previous match was empty, we can't issue the exact same one or
+         * we'd loop indefinitively.  So let's instead ask for an anchored and
+         * non-empty match (i.e. something not empty at the start of the value)
+         * and if nothing is found advance by one character below.
+         */
+        rv = ap_regexec(regex, val, nmatch, pmatch, 
+                        empty ? AP_REG_ANCHORED | AP_REG_NOTEMPTY : 0);
+        if (regctx->type == 'm') {
+            /* Simple match "m//", just return whether it matched (subject)
+             * or not (NULL) 
+             */
+            return (rv == 0) ? subject : NULL;
+        }
+        if (rv == 0) {
+            /* Substitution "s//" or split "S//" matched.
+             * s// => replace $0 with evaluated regctx->subst
+             * S// => split at $0 (keeping evaluated regctx->subst if any)
+             */
+            int pos = pmatch[0].rm_so,
+                end = pmatch[0].rm_eo;
+            AP_DEBUG_ASSERT(pos >= 0 && pos <= end);
+
+            if (regctx->subst) {
+                *ctx->re_source = val;
+                str = ap_expr_eval_word(ctx, regctx->subst);
+                len = strlen(str);
+            }
+            /* Splitting makes sense into a given list only, if NULL we fall
+             * back into returning a s// string...
+             */
+            if (list) {
+                char *tmp = apr_palloc(ctx->p, pos + len + 1);
+                memcpy(tmp, val, pos);
+                memcpy(tmp + pos, str, len);
+                tmp[pos + len] = '\0';
+                APR_ARRAY_PUSH(list, const char*) = tmp;
+            }
+            else { /* regctx->type == 's' */
+                ap_varbuf_grow(&vb, pos + len + 1);
+                ap_varbuf_strmemcat(&vb, val, pos);
+                ap_varbuf_strmemcat(&vb, str, len);
+                if (!(regctx->flags & AP_REG_MULTI)) {
+                    /* Single substitution, preserve remaining data */
+                    ap_varbuf_strmemcat(&vb, val + end, strlen(val) - end);
+                    break;
+                }
+            }
+            /* Note an empty match */
+            empty = (end == 0);
+            val += end;
+        }
+        else if (empty) {
+            /* Skip this non-matching character (or CRLF) and restart
+             * another "normal" match (possibly empty) from there.
+             */
+            if (val[0] == APR_ASCII_CR && val[1] == APR_ASCII_LF) {
+                val += 2;
+            }
+            else {
+                val++;
+            }
+            empty = 0;
+        }
+        else {
+            if (list) {
+                APR_ARRAY_PUSH(list, const char*) = val;
+            }
+            else if (vb.avail) {
+                ap_varbuf_strmemcat(&vb, val, strlen(val));
+            }
+            else {
+                return val;
+            }
+            break;
+        }
+    } while (*val);
+
+    return vb.buf;
+}
+
+static apr_array_header_t *ap_expr_list_make(ap_expr_eval_ctx_t *ctx,
+                                             const ap_expr_t *node)
+{
+    apr_array_header_t *list = NULL;
+
+    if (node->node_op == op_ListRegex) {
+        const ap_expr_t *arg = node->node_arg1;
+        const ap_expr_t *reg = node->node_arg2;
+        const ap_expr_regctx_t *regctx = reg->node_arg2;
+        const apr_array_header_t *source = ap_expr_list_make(ctx, arg);
+        int i;
+
+        list = apr_array_make(ctx->p, source->nelts, sizeof(const char*));
+        for (i = 0; i < source->nelts; ++i) {
+            const char *val = APR_ARRAY_IDX(source, i, const char*);
+            if (regctx->type == 'S') {
+                (void)ap_expr_regexec(val, reg, list, ctx);
+            }
+            else {
+                val = ap_expr_regexec(val, reg, NULL, ctx);
+                if (val) {
+                    APR_ARRAY_PUSH(list, const char*) = val;
+                }
+            }
+        }
+    }
+    else if (node->node_op == op_ListElement) {
+        int n = 0;
+        const ap_expr_t *elem;
+        for (elem = node; elem; elem = elem->node_arg2) {
+            AP_DEBUG_ASSERT(elem->node_op == op_ListElement);
+            n++;
+        }
+
+        list = apr_array_make(ctx->p, n, sizeof(const char*));
+        for (elem = node; elem; elem = elem->node_arg2) {
+            APR_ARRAY_PUSH(list, const char*) =
+                ap_expr_eval_word(ctx, elem->node_arg1);
+        }
+    }
+    else if (node->node_op == op_ListFuncCall) {
+        const ap_expr_t *info = node->node_arg1;
+        ap_expr_list_func_t *func = info->node_arg1;
+
+        AP_DEBUG_ASSERT(func != NULL);
+        AP_DEBUG_ASSERT(info->node_op == op_ListFuncInfo);
+        list = (*func)(ctx, info->node_arg2,
+                       ap_expr_eval_word(ctx, node->node_arg2));
+    }
+    else {
+        const char *subject = ap_expr_eval_word(ctx, node);
+
+        list = apr_array_make(ctx->p, 8, sizeof(const char*));
+        (void)ap_expr_regexec(subject, node->node_arg2, list, ctx);
+    }
+
+    return list;
+}
+
 static int ap_expr_eval_comp(ap_expr_eval_ctx_t *ctx, const ap_expr_t *node)
 {
     const ap_expr_t *e1 = node->node_arg1;
@@ -256,31 +486,18 @@ static int ap_expr_eval_comp(ap_expr_eval_ctx_t *c
     case op_STR_GE:
         return (strcmp(ap_expr_eval_word(ctx, e1), ap_expr_eval_word(ctx, e2)) >= 0);
     case op_IN: {
-            const char *needle = ap_expr_eval_word(ctx, e1);
-            if (e2->node_op == op_ListElement) {
-                do {
-                    const ap_expr_t *val = e2->node_arg1;
-                    AP_DEBUG_ASSERT(e2->node_op == op_ListElement);
-                    if (strcmp(needle, ap_expr_eval_word(ctx, val)) == 0)
+            int n;
+            const char *needle, *subject;
+            apr_array_header_t *haystack;
+            haystack = ap_expr_list_make(ctx, e2);
+            if (haystack) {
+                needle = ap_expr_eval_word(ctx, e1);
+                for (n = 0; n < haystack->nelts; ++n) {
+                    subject = APR_ARRAY_IDX(haystack, n, const char*);
+                    if (strcmp(needle, subject) == 0) {
                         return 1;
-                    e2 = e2->node_arg2;
-                } while (e2 != NULL);
-            }
-            else if (e2->node_op == op_ListFuncCall) {
-                const ap_expr_t *info = e2->node_arg1;
-                const ap_expr_t *arg = e2->node_arg2;
-                ap_expr_list_func_t *func = (ap_expr_list_func_t *)info->node_arg1;
-                apr_array_header_t *haystack;
-
-                AP_DEBUG_ASSERT(func != NULL);
-                AP_DEBUG_ASSERT(info->node_op == op_ListFuncInfo);
-                haystack = (*func)(ctx, info->node_arg2, ap_expr_eval_word(ctx, arg));
-                if (haystack == NULL) {
-                    return 0;
+                    }
                 }
-                if (ap_array_str_contains(haystack, needle)) {
-                    return 1;
-                }
             }
             return 0;
         }
@@ -303,10 +520,7 @@ static int ap_expr_eval_comp(ap_expr_eval_ctx_t *c
                 result = (0 == ap_regexec(regex, word, 0, NULL, 0));
             }
 
-            if (node->node_op == op_REG)
-                return result;
-            else
-                return !result;
+            return result ^ (node->node_op == op_NRE);
         }
     default:
         *ctx->err = "Internal evaluation error: Unknown comp expression node";
@@ -387,9 +601,8 @@ AP_DECLARE(const char *) ap_expr_parse(apr_pool_t
     ctx.error    = NULL;        /* generic bison error message (XXX: usually not very useful, should be axed) */
     ctx.error2   = NULL;        /* additional error message */
     ctx.flags    = info->flags;
-    ctx.scan_del    = '\0';
-    ctx.scan_buf[0] = '\0';
-    ctx.scan_ptr    = ctx.scan_buf;
+    ctx.stack    = NULL;
+    ctx.stacks   = NULL;
     ctx.lookup_fn   = lookup_fn ? lookup_fn : ap_expr_lookup_default;
     ctx.at_start    = 1;
 
@@ -450,6 +663,80 @@ ap_expr_t *ap_expr_make(ap_expr_node_op_e op, cons
     return node;
 }
 
+ap_expr_t *ap_expr_regex_make(const char *pattern, const char *flags,
+                              const ap_expr_t *subst, int split,
+                              ap_expr_parse_ctx_t *ctx)
+{
+    ap_expr_t *node = NULL;
+    ap_expr_regctx_t *regctx;
+    ap_regex_t *regex;
+
+    regctx = apr_palloc(ctx->pool, sizeof *regctx);
+    regctx->flags = 0;
+    if (flags) {
+        for (; *flags; ++flags) {
+            switch (*flags) {
+            case 'i':
+                regctx->flags |= AP_REG_ICASE;
+                break;
+            case 'm':
+                regctx->flags |= AP_REG_NEWLINE;
+                break;
+            case 's':
+                regctx->flags |= AP_REG_DOTALL;
+                break;
+            case 'g':
+                regctx->flags |= AP_REG_MULTI;
+                break;
+            }
+        }
+    }
+    regctx->subst = subst;
+    if (subst) {
+        if (split) {
+            regctx->type = 'S';
+            regctx->flags |= AP_REG_MULTI;
+        }
+        else {
+            regctx->type = 's';
+        }
+    }
+    else {
+        regctx->type = 'm';
+    }
+
+    regex = ap_pregcomp(ctx->pool, pattern, regctx->flags);
+    if (!regex) {
+        return NULL;
+    }
+
+    node = apr_palloc(ctx->pool, sizeof(ap_expr_t));
+    node->node_op   = op_Regex;
+    node->node_arg1 = regex;
+    node->node_arg2 = regctx;
+    return node;
+}
+
+ap_expr_t *ap_expr_str_word_make(const ap_expr_t *arg,
+                                 ap_expr_parse_ctx_t *ctx)
+{
+    ap_expr_t *node = apr_palloc(ctx->pool, sizeof(ap_expr_t));
+    node->node_op   = op_Word;
+    node->node_arg1 = arg;
+    node->node_arg2 = NULL;
+    return node;
+}
+
+ap_expr_t *ap_expr_str_bool_make(const ap_expr_t *arg,
+                                 ap_expr_parse_ctx_t *ctx)
+{
+    ap_expr_t *node = apr_palloc(ctx->pool, sizeof(ap_expr_t));
+    node->node_op   = op_Bool;
+    node->node_arg1 = arg;
+    node->node_arg2 = NULL;
+    return node;
+}
+
 static ap_expr_t *ap_expr_info_make(int type, const char *name,
                                   ap_expr_parse_ctx_t *ctx,
                                   const ap_expr_t *arg)
@@ -492,6 +779,16 @@ ap_expr_t *ap_expr_list_func_make(const char *name
     return ap_expr_make(op_ListFuncCall, info, arg, ctx);
 }
 
+ap_expr_t *ap_expr_list_regex_make(const ap_expr_t *arg, const ap_expr_t *reg,
+                                   ap_expr_parse_ctx_t *ctx)
+{
+    ap_expr_t *node = apr_palloc(ctx->pool, sizeof(ap_expr_t));
+    node->node_op   = op_ListRegex;
+    node->node_arg1 = arg;
+    node->node_arg2 = reg;
+    return node;
+}
+
 ap_expr_t *ap_expr_unary_op_make(const char *name, const ap_expr_t *arg,
                                ap_expr_parse_ctx_t *ctx)
 {
@@ -613,10 +910,15 @@ static void expr_dump_tree(const ap_expr_t *e, con
     case op_IN:
     case op_REG:
     case op_NRE:
+    case op_Word:
+    case op_Bool:
+    case op_Join:
+    case op_Regsub:
     case op_Concat:
     case op_StringFuncCall:
     case op_ListFuncCall:
     case op_ListElement:
+    case op_ListRegex:
         {
             char *name;
             switch (e->node_op) {
@@ -639,10 +941,15 @@ static void expr_dump_tree(const ap_expr_t *e, con
             CASE_OP(op_IN);
             CASE_OP(op_REG);
             CASE_OP(op_NRE);
+            CASE_OP(op_Word);
+            CASE_OP(op_Bool);
+            CASE_OP(op_Join);
+            CASE_OP(op_Regsub);
             CASE_OP(op_Concat);
             CASE_OP(op_StringFuncCall);
             CASE_OP(op_ListFuncCall);
             CASE_OP(op_ListElement);
+            CASE_OP(op_ListRegex);
             default:
                 ap_assert(0);
             }
@@ -688,8 +995,8 @@ static void expr_dump_tree(const ap_expr_t *e, con
         DUMP_P("op_Regex", e->node_arg1);
         break;
     /* arg1: pointer to int */
-    case op_RegexBackref:
-        DUMP_IP("op_RegexBackref", e->node_arg1);
+    case op_Regref:
+        DUMP_IP("op_Regref", e->node_arg1);
         break;
     default:
         ap_log_error(MARK, "%*sERROR: INVALID OP %d", indent, " ", e->node_op);
@@ -1804,3 +2111,32 @@ void ap_expr_init(apr_pool_t *p)
     ap_hook_post_config(ap_expr_post_config, NULL, NULL, APR_HOOK_MIDDLE);
 }
 
+void ap_expr_parser_stack_push(ap_expr_parse_ctx_t *ctx)
+{
+    ap_expr_parser_stack_t *stack;
+
+    if (ctx->stacks) {
+        stack = ctx->stacks;
+        ctx->stacks = stack->next;
+    }
+    else {
+        stack = apr_palloc(ctx->ptemp, sizeof *stack);
+    }
+    stack->scan_ptr    = stack->scan_buf;
+    stack->scan_buf[0] = '\0';
+    stack->scan_del    = '\0';
+    stack->scan_state  = 0;
+
+    stack->next = ctx->stack;
+    ctx->stack = stack;
+}
+
+void ap_expr_parser_stack_pop(ap_expr_parse_ctx_t *ctx)
+{
+    ap_expr_parser_stack_t *stack;
+
+    stack = ctx->stack;
+    ctx->stack = stack->next;
+    stack->next = ctx->stacks;
+    ctx->stacks = stack;
+}
Index: server/util_expr_parse.y
===================================================================
--- server/util_expr_parse.y	(revision 1783852)
+++ server/util_expr_parse.y	(working copy)
@@ -49,9 +49,14 @@
 %token  <cpVal> T_DIGIT
 %token  <cpVal> T_ID
 %token  <cpVal> T_STRING
-%token  <cpVal> T_REGEX
-%token  <cpVal> T_REGEX_I
-%token  <num>   T_REGEX_BACKREF
+
+%token          T_REGEX
+%token          T_REGSUB
+%token  <cpVal> T_REG_MATCH
+%token  <cpVal> T_REG_SUBST
+%token  <cpVal> T_REG_FLAGS
+%token  <num>   T_REG_REF
+
 %token  <cpVal> T_OP_UNARY
 %token  <cpVal> T_OP_BINARY
 
@@ -77,6 +82,9 @@
 %token  T_OP_STR_GE
 %token  T_OP_CONCAT
 
+%token  T_OP_SPLIT
+%token  T_OP_JOIN
+
 %token  T_OP_OR
 %token  T_OP_AND
 %token  T_OP_NOT
@@ -86,11 +94,10 @@
 %right  T_OP_NOT
 %right  T_OP_CONCAT
 
-%type   <exVal>   expr
-%type   <exVal>   comparison
+%type   <exVal>   cond
+%type   <exVal>   comp
 %type   <exVal>   strfunccall
 %type   <exVal>   lstfunccall
-%type   <exVal>   regex
 %type   <exVal>   words
 %type   <exVal>   wordlist
 %type   <exVal>   word
@@ -97,7 +104,11 @@
 %type   <exVal>   string
 %type   <exVal>   strpart
 %type   <exVal>   var
-%type   <exVal>   backref
+%type   <exVal>   regex
+%type   <exVal>   regsub
+%type   <exVal>   regsplit
+%type   <exVal>   reglist
+%type   <exVal>   regref
 
 %{
 #include "util_expr_private.h"
@@ -109,24 +120,22 @@ int ap_expr_yylex(YYSTYPE *lvalp, void *scanner);
 
 %%
 
-root      : T_EXPR_BOOL   expr           { ctx->expr = $2; }
+root      : T_EXPR_BOOL   cond           { ctx->expr = $2; }
           | T_EXPR_STRING string         { ctx->expr = $2; }
           | T_ERROR                      { YYABORT; }
           ;
 
-expr      : T_TRUE                       { $$ = ap_expr_make(op_True,        NULL, NULL, ctx); }
+cond      : T_TRUE                       { $$ = ap_expr_make(op_True,        NULL, NULL, ctx); }
           | T_FALSE                      { $$ = ap_expr_make(op_False,       NULL, NULL, ctx); }
-          | T_OP_NOT expr                { $$ = ap_expr_make(op_Not,         $2,   NULL, ctx); }
-          | expr T_OP_OR expr            { $$ = ap_expr_make(op_Or,          $1,   $3,   ctx); }
-          | expr T_OP_AND expr           { $$ = ap_expr_make(op_And,         $1,   $3,   ctx); }
-          | comparison                   { $$ = ap_expr_make(op_Comp,        $1,   NULL, ctx); }
-          | T_OP_UNARY word              { $$ = ap_expr_unary_op_make(       $1,   $2,   ctx); }
-          | word T_OP_BINARY word        { $$ = ap_expr_binary_op_make($2,   $1,   $3,   ctx); }
-          | '(' expr ')'                 { $$ = $2; }
+          | cond T_OP_OR cond            { $$ = ap_expr_make(op_Or,          $1,   $3,   ctx); }
+          | cond T_OP_AND cond           { $$ = ap_expr_make(op_And,         $1,   $3,   ctx); }
+          | T_OP_NOT cond                { $$ = ap_expr_make(op_Not,         $2,   NULL, ctx); }
+          | comp                         { $$ = ap_expr_make(op_Comp,        $1,   NULL, ctx); }
+          | '(' cond ')'                 { $$ = $2; }
           | T_ERROR                      { YYABORT; }
           ;
 
-comparison: word T_OP_EQ word            { $$ = ap_expr_make(op_EQ,      $1, $3, ctx); }
+comp      : word T_OP_EQ word            { $$ = ap_expr_make(op_EQ,      $1, $3, ctx); }
           | word T_OP_NE word            { $$ = ap_expr_make(op_NE,      $1, $3, ctx); }
           | word T_OP_LT word            { $$ = ap_expr_make(op_LT,      $1, $3, ctx); }
           | word T_OP_LE word            { $$ = ap_expr_make(op_LE,      $1, $3, ctx); }
@@ -141,10 +150,15 @@ int ap_expr_yylex(YYSTYPE *lvalp, void *scanner);
           | word T_OP_IN wordlist        { $$ = ap_expr_make(op_IN,      $1, $3, ctx); }
           | word T_OP_REG regex          { $$ = ap_expr_make(op_REG,     $1, $3, ctx); }
           | word T_OP_NRE regex          { $$ = ap_expr_make(op_NRE,     $1, $3, ctx); }
+          | word T_OP_BINARY word        { $$ = ap_expr_binary_op_make($2, $1, $3, ctx); }
+          | T_OP_UNARY word              { $$ = ap_expr_unary_op_make(     $1, $2, ctx); }
           ;
 
 wordlist  : lstfunccall                  { $$ = $1; }
           | '{' words '}'                { $$ = $2; }
+          | word     T_OP_REG regsplit   { $$ = ap_expr_list_regex_make($1, $3, ctx); }
+          | wordlist T_OP_REG reglist    { $$ = ap_expr_list_regex_make($1, $3, ctx); }
+          | '(' wordlist ')'             { $$ = $2; }
           ;
 
 words     : word                         { $$ = ap_expr_make(op_ListElement, $1, NULL, ctx); }
@@ -151,53 +165,78 @@ words     : word                         { $$ = ap
           | words ',' word               { $$ = ap_expr_make(op_ListElement, $3, $1,   ctx); }
           ;
 
-string    : string strpart               { $$ = ap_expr_make(op_Concat, $1, $2, ctx); }
-          | strpart                      { $$ = $1; }
+string    : strpart                      { $$ = $1; }
+          | string strpart               { $$ = $2 ? ap_expr_make(op_Concat, $1, $2, ctx) : $1; }
           | T_ERROR                      { YYABORT; }
           ;
 
-strpart   : T_STRING                     { $$ = ap_expr_make(op_String, $1, NULL, ctx); }
+strpart   : T_STRING                     { $$ = $1 ? ap_expr_make(op_String, $1, NULL, ctx) : NULL; }
           | var                          { $$ = $1; }
-          | backref                      { $$ = $1; }
+          | regref                       { $$ = $1; }
           ;
 
 var       : T_VAR_BEGIN T_ID T_VAR_END            { $$ = ap_expr_var_make($2, ctx); }
           | T_VAR_BEGIN T_ID ':' string T_VAR_END { $$ = ap_expr_str_func_make($2, $4, ctx); }
+          | T_VAR_BEGIN ':' word ':' T_VAR_END    { $$ = ap_expr_str_word_make($3, ctx); }
           ;
 
-word      : T_DIGIT                      { $$ = ap_expr_make(op_Digit,  $1, NULL, ctx); }
-          | word T_OP_CONCAT word        { $$ = ap_expr_make(op_Concat, $1, $3,   ctx); }
+word      : word T_OP_CONCAT word        { $$ = ap_expr_make(op_Concat, $1, $3,   ctx); }
+          | word T_OP_REG regsub         { $$ = ap_expr_make(op_Regsub, $1, $3,   ctx); }
+          | cond                         { $$ = ap_expr_str_bool_make(  $1,       ctx); }
           | var                          { $$ = $1; }
-          | backref                      { $$ = $1; }
+          | regref                       { $$ = $1; }
           | strfunccall                  { $$ = $1; }
+          | T_OP_JOIN '/' wordlist '/'      '/' { $$ = ap_expr_make(op_Join,   $3, NULL, ctx); }
+          | T_OP_JOIN '/' wordlist '/' word '/' { $$ = ap_expr_make(op_Join,   $3, $5,   ctx); }
+          | T_DIGIT                      { $$ = ap_expr_make(op_Digit,  $1, NULL, ctx); }
+          | T_STR_BEGIN T_STR_END        { $$ = ap_expr_make(op_String, "", NULL, ctx); }
           | T_STR_BEGIN string T_STR_END { $$ = $2; }
-          | T_STR_BEGIN T_STR_END        { $$ = ap_expr_make(op_String, "", NULL, ctx); }
+          | '(' word ')'                 { $$ = $2; }
           ;
-
-regex     : T_REGEX {
-                ap_regex_t *regex;
-                if ((regex = ap_pregcomp(ctx->pool, $1,
-                                         AP_REG_EXTENDED|AP_REG_NOSUB)) == NULL) {
+regex     : T_REGEX T_REG_MATCH T_REG_FLAGS {
+                ap_expr_t *e = ap_expr_regex_make($2, $3, NULL, 0, ctx);
+                if (!e) {
                     ctx->error = "Failed to compile regular expression";
                     YYERROR;
                 }
-                $$ = ap_expr_make(op_Regex, regex, NULL, ctx);
+                $$ = e;
             }
-          | T_REGEX_I {
-                ap_regex_t *regex;
-                if ((regex = ap_pregcomp(ctx->pool, $1,
-                                         AP_REG_EXTENDED|AP_REG_NOSUB|AP_REG_ICASE)) == NULL) {
+          ;
+regsub    : T_REGSUB T_REG_MATCH string T_REG_FLAGS {
+                ap_expr_t *e = ap_expr_regex_make($2, $4, $3, 0, ctx);
+                if (!e) {
                     ctx->error = "Failed to compile regular expression";
                     YYERROR;
                 }
-                $$ = ap_expr_make(op_Regex, regex, NULL, ctx);
+                $$ = e;
             }
           ;
+regsplit  : T_OP_SPLIT T_REG_MATCH string T_REG_FLAGS {
+                /* Returns a list:
+                 * <word> ~= split/://
+                 *  => split around ':'
+                 * <word> ~= split/:/\n/
+                 *  => split around ':', use '\n' instead where detected/stripped
+                 * <list> ~= split/.*?Ip Address:([^,]+)/$1/
+                 *  => split around the whole match, use $1 instead where detected/stipped
+                 */
+                ap_expr_t *e = ap_expr_regex_make($2, $4, $3, 1, ctx);
+                if (!e) {
+                    ctx->error = "Failed to compile regular expression";
+                    YYERROR;
+                }
+                $$ = e;
+            }
+          ;
+reglist   : regex     { $$ = $1; }
+          | regsub    { $$ = $1; }
+          | regsplit  { $$ = $1; }
+          ;
 
-backref     : T_REGEX_BACKREF   {
+regref      : T_REG_REF {
                 int *n = apr_palloc(ctx->pool, sizeof(int));
                 *n = $1;
-                $$ = ap_expr_make(op_RegexBackref, n, NULL, ctx);
+                $$ = ap_expr_make(op_Regref, n, NULL, ctx);
             }
             ;
 
Index: server/util_expr_scan.l
===================================================================
--- server/util_expr_scan.l	(revision 1783852)
+++ server/util_expr_scan.l	(working copy)
@@ -34,14 +34,16 @@
 %option warn
 %option noinput nounput noyy_top_state
 %option stack
+
 %x str
-%x var
-%x vararg
-%x regex regex_flags
+%x var vararg varext
+%x regex regsub regflags
+%x split join
 
 %{
 #include "util_expr_private.h"
 #include "util_expr_parse.h"
+#include "apr_lib.h" /* apr_isalnum */
 
 #undef  YY_INPUT
 #define YY_INPUT(buf,result,max_size)                       \
@@ -62,10 +64,57 @@
 
 #define PERROR(msg) do { yyextra->error2 = msg ; return T_ERROR; } while (0)
 
-#define str_ptr     (yyextra->scan_ptr)
-#define str_buf     (yyextra->scan_buf)
-#define str_del     (yyextra->scan_del)
+#if 0
+#define STACK_PUSH() do { \
+    fprintf(stderr, "YY_STACK_PUSH()\n"); \
+    ap_expr_parser_stack_push(yyextra); \
+} while (0)
+#define STACK_POP() do { \
+    fprintf(stderr, "YY_STACK_POP()\n"); \
+    ap_expr_parser_stack_pop(yyextra); \
+} while (0)
 
+#define STATE_PUSH(st, sk) do { \
+    fprintf(stderr, "YY_STATE_PUSH(%i)\n", (st)); \
+    yy_push_state((st), yyscanner); \
+    if (sk) { \
+        STACK_PUSH(); \
+    } \
+} while (0)
+#define STATE_POP(sk) do { \
+    fprintf(stderr, "YY_STATE_POP()\n"); \
+    if (sk) { \
+        STACK_POP(); \
+    } \
+    yy_pop_state(yyscanner); \
+} while (0)
+#else
+#define STACK_PUSH() do { \
+    ap_expr_parser_stack_push(yyextra); \
+} while (0)
+#define STACK_POP() do { \
+    ap_expr_parser_stack_pop(yyextra); \
+} while (0)
+
+#define STATE_PUSH(st, sk) do { \
+    yy_push_state((st), yyscanner); \
+    if (sk) { \
+        STACK_PUSH(); \
+    } \
+} while (0)
+#define STATE_POP(sk) do { \
+    if (sk) { \
+        STACK_POP(); \
+    } \
+    yy_pop_state(yyscanner); \
+} while (0)
+#endif
+
+#define str_ptr     (yyextra->stack->scan_ptr)
+#define str_buf     (yyextra->stack->scan_buf)
+#define str_del     (yyextra->stack->scan_del)
+#define str_state   (yyextra->stack->scan_state)
+
 #define STR_APPEND(c) do {                          \
         *str_ptr++ = (c);                           \
         if (str_ptr >= str_buf + sizeof(str_buf))   \
@@ -72,15 +121,22 @@
             PERROR("String too long");              \
     } while (0)
 
+#define STR_EMPTY() (str_ptr == str_buf)
+#define STR_FINAL() (*str_ptr = '\0', str_ptr = str_buf)
+
 %}
 
+SPACE     [ \t\n]
+QUOTE     ["']
+TOKEN     ([a-zA-Z][a-zA-Z0-9_]*)
+VAR_BEGIN (%\{)
+VAR_SEP   :
+VAR_END   \}
+REG_SEP   [/#$%^|?!'",;:._-]
+REG_REF   (\$[0-9])
 
 %%
 
-  char  regex_buf[MAX_STRING_LEN];
-  char *regex_ptr = NULL;
-  char  regex_del = '\0';
-
 %{
  /*
   * Set initial state for string expressions
@@ -88,7 +144,7 @@
   if (yyextra->at_start) {
     yyextra->at_start = 0;
     if (yyextra->flags & AP_EXPR_FLAG_STRING_RESULT) {
-        BEGIN(str);
+        STATE_PUSH(str, 1);
         return T_EXPR_STRING;
     }
     else {
@@ -100,7 +156,7 @@
  /*
   * Whitespaces
   */
-[ \t\n]+ { 
+<INITIAL,varext,split,join>{SPACE}+ { 
     /* NOP */
 }
 
@@ -107,267 +163,318 @@
  /*
   * strings ("..." and '...')
   */
-["'] {
-    str_ptr = str_buf;
+<INITIAL,varext,join>{QUOTE} {
+    STATE_PUSH(str, 1);
     str_del = yytext[0];
-    BEGIN(str);
     return T_STR_BEGIN;
 }
-<str>["'] {
+
+<str>{QUOTE} {
     if (yytext[0] == str_del) {
-        if (YY_START == var) {
-            PERROR("Unterminated variable in string");
-        }
-        else if (str_ptr == str_buf) {
-            BEGIN(INITIAL);
-            return T_STR_END;
-        }
-        else {
+        if (!STR_EMPTY()) {
             /* return what we have so far and scan delimiter again */
-            *str_ptr = '\0';
-            yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf);
             yyless(0);
-            str_ptr = str_buf;
+            yylval->cpVal = apr_pstrdup(yyextra->pool, STR_FINAL());
             return T_STRING;
         }
+        STATE_POP(1);
+        return T_STR_END;
     }
-    else {
-        STR_APPEND(yytext[0]);
-    }
+    STR_APPEND(yytext[0]);
 }
-<str,var,vararg>\n {
-    PERROR("Unterminated string or variable");
-}
-<var,vararg><<EOF>> {
-    PERROR("Unterminated string or variable");
-}
-<str><<EOF>> {
-    if (!(yyextra->flags & AP_EXPR_FLAG_STRING_RESULT)) {
-        PERROR("Unterminated string or variable");
-    }
-    else {
-        *str_ptr = '\0';
-        yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf);
-        str_ptr = str_buf;
-        BEGIN(INITIAL);
-        return T_STRING;
-    }
-}
 
-<str,vararg>\\[0-7]{1,3} {
-    int result;
-
-    (void)sscanf(yytext+1, "%o", &result);
-    if (result > 0xff) {
-        PERROR("Escape sequence out of bound");
-    }
-    else {
-        STR_APPEND(result);
-    }
-}
-<str,vararg>\\[0-9]+ {
-    PERROR("Bad escape sequence");
-}
-<str,vararg>\\n      { STR_APPEND('\n'); }
-<str,vararg>\\r      { STR_APPEND('\r'); }
-<str,vararg>\\t      { STR_APPEND('\t'); }
-<str,vararg>\\b      { STR_APPEND('\b'); }
-<str,vararg>\\f      { STR_APPEND('\f'); }
-<str,vararg>\\(.|\n) { STR_APPEND(yytext[1]); }
-
  /* regexp backref inside string/arg */
-<str,vararg>[$][0-9] {
-    if (str_ptr != str_buf) {
+<str,vararg,regsub>{REG_REF} {
+    if (!STR_EMPTY()) {
         /* return what we have so far and scan '$x' again */
-        *str_ptr = '\0';
-        yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf);
-        str_ptr = str_buf;
         yyless(0);
+        yylval->cpVal = apr_pstrdup(yyextra->pool, STR_FINAL());
         return T_STRING;
     }
-    else {
-        yylval->num = yytext[1] - '0';
-        return T_REGEX_BACKREF;
-    }
+    yylval->num = yytext[1] - '0';
+    return T_REG_REF;
 }
 
-<str,vararg>[^\\\n"'%}$]+ {
-    char *cp = yytext;
-    while (*cp != '\0') {
-        STR_APPEND(*cp);
-        cp++;
-    }
-}
-
  /* variable inside string/arg */
-<str,vararg>%\{ {
-    if (str_ptr != str_buf) {
+<str,vararg,regsub>{VAR_BEGIN} {
+    if (!STR_EMPTY()) {
         /* return what we have so far and scan '%{' again */
-        *str_ptr = '\0';
-        yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf);
         yyless(0);
-        str_ptr = str_buf;
+        yylval->cpVal = apr_pstrdup(yyextra->pool, STR_FINAL());
         return T_STRING;
     }
-    else {
-        yy_push_state(var, yyscanner);
-        return T_VAR_BEGIN;
-    }
+    STATE_PUSH(var, 1);
+    return T_VAR_BEGIN;
 }
 
-<vararg>[%$] {
-     STR_APPEND(yytext[0]);
+ /* Any non-octal or octal higher than 377 (decimal 255) is invalid */
+<str,vararg,regsub>\\([4-9][0-9]{2}|[8-9][0-9]{0,2}) {
+    PERROR("Bad character escape sequence");
 }
+<str,vararg,regsub>\\[0-7]{1,3} {
+    int result;
+    (void)sscanf(yytext+1, "%o", &result);
+    STR_APPEND(result);
+}
+<str,vararg,regsub>\\x[0-9A-Fa-f]{1,2} {
+    int result;
+    (void)sscanf(yytext+1, "%x", &result);
+    STR_APPEND(result);
+}
+<str,vararg,regsub>\\n      { STR_APPEND('\n'); }
+<str,vararg,regsub>\\r      { STR_APPEND('\r'); }
+<str,vararg,regsub>\\t      { STR_APPEND('\t'); }
+<str,vararg,regsub>\\b      { STR_APPEND('\b'); }
+<str,vararg,regsub>\\f      { STR_APPEND('\f'); }
+<str,vararg,regsub>\\(.|\n) { STR_APPEND(yytext[1]); }
 
-<str>[%}$] {
-     STR_APPEND(yytext[0]);
+<str,vararg,regsub>\n {
+    PERROR("Unterminated string or variable");
 }
 
-%\{ {
-    yy_push_state(var, yyscanner);
-    return T_VAR_BEGIN;
+<str>. {
+    STR_APPEND(yytext[0]);
 }
+<str><<EOF>> {
+    STATE_POP(0);
+    if (YY_START != INITIAL) {
+        PERROR("Unterminated string");
+    }
+    if (!STR_EMPTY()) {
+        yylval->cpVal = apr_pstrdup(yyextra->pool, STR_FINAL());
+    }
+    else {
+        yylval->cpVal = NULL;
+    }
+    ap_expr_parser_stack_pop(yyextra);
+    return T_STRING;
+}
 
-[$][0-9] {
+<INITIAL,varext,join>{REG_REF} {
     yylval->num = yytext[1] - '0';
-    return T_REGEX_BACKREF;
+    return T_REG_REF;
 }
 
- /*
-  * fixed name variable expansion %{XXX} and function call in %{func:arg} syntax
-  */
-<var>[a-zA-Z][a-zA-Z0-9_]* {
+<INITIAL,varext,join>{VAR_BEGIN} {
+    STATE_PUSH(var, 1);
+    return T_VAR_BEGIN;
+}
+
+<var>{TOKEN} {
+    /*
+     * fixed name variable expansion %{XXX} and function call
+     * in %{func:arg} syntax
+     */
+    str_state = 1;
     yylval->cpVal = apr_pstrdup(yyextra->pool, yytext);
     return T_ID;
 }
 
-<var>\} {
-    yy_pop_state(yyscanner);
-    return T_VAR_END;
+<var>{VAR_SEP} {
+    /* If we have a T_ID it's a vararg, otherwise a varext */
+    if (str_state) {
+        STATE_PUSH(vararg, 0);
+    }
+    else {
+        STATE_PUSH(varext, 0);
+    }
+    return yytext[0];
 }
 
-<var>: {
-    BEGIN(vararg);
+<varext>{VAR_SEP} {
+    STATE_POP(0);
     return yytext[0];
 }
 
-<var>.|\n {
+<var>{VAR_END} {
+    STATE_POP(1);
+    return T_VAR_END;
+}
+
+<var>(.|\n) {
     char *msg = apr_psprintf(yyextra->pool,
-                             "Invalid character in variable name '%c'", yytext[0]);
+                             "Invalid character in variable name '%c'",
+                             yytext[0]);
     PERROR(msg);
 }
 
-<vararg>\} {
-    if (str_ptr != str_buf) {
+<vararg>{VAR_END} {
+    yyless(0);
+    if (!STR_EMPTY()) {
         /* return what we have so far and scan '}' again */
-        *str_ptr = '\0';
-        yylval->cpVal = apr_pstrdup(yyextra->pool, str_buf);
-        str_ptr = str_buf;
-        yyless(0);
+        yylval->cpVal = apr_pstrdup(yyextra->pool, STR_FINAL());
         return T_STRING;
     }
-    else {
-        yy_pop_state(yyscanner);
-        return T_VAR_END;
-    }
+    STATE_POP(0);
 }
+<vararg>. {
+    STR_APPEND(yytext[0]);
+}
 
+<var,vararg,varext><<EOF>> {
+    PERROR("Unterminated variable");
+}
+
  /*
   * Regular Expression
   */
-"m"[/#$%^,;:_\?\|\^\-\!\.\'\"] {
-    regex_del = yytext[1];
-    regex_ptr = regex_buf;
-    BEGIN(regex);
+<INITIAL,varext>[/] {
+    STATE_PUSH(regex, 1);
+    str_state = 'm';
+    str_del = yytext[0];
+    return T_REGEX;
 }
-"/" {
-    regex_del = yytext[0];
-    regex_ptr = regex_buf;
-    BEGIN(regex);
+<INITIAL,varext>[ms]{REG_SEP} {
+    STATE_PUSH(regex, 1);
+    str_state = yytext[0];
+    str_del = yytext[1];
+    return (str_state == 'm') ? T_REGEX : T_REGSUB;
 }
-<regex>.|\n {
-    if (yytext[0] == regex_del) {
-        *regex_ptr = '\0';
-        BEGIN(regex_flags);
+<regex>(.|\n) {
+    if (yytext[0] == str_del) {
+        STATE_POP(0);
+        STATE_PUSH(regflags, 0);
+        if (str_state != 'm') {
+            STATE_PUSH(regsub, 0);
+        }
+        yylval->cpVal = apr_pstrdup(yyextra->pool, STR_FINAL());
+        return T_REG_MATCH;
     }
+    STR_APPEND(yytext[0]);
+}
+<regsub>. {
+    if (yytext[0] == str_del) {
+        /* Back to regflags */
+        STATE_POP(0);
+    }
     else {
-        *regex_ptr++ = yytext[0];
-        if (regex_ptr >= regex_buf + sizeof(regex_buf))
-            PERROR("Regexp too long");
+        STR_APPEND(yytext[0]);
     }
 }
-<regex_flags>i {
-    yylval->cpVal = apr_pstrdup(yyextra->pool, regex_buf);
-    BEGIN(INITIAL);
-    return T_REGEX_I;
+<regsub><<EOF>> {
+    PERROR("Unterminated regexp");
 }
-<regex_flags>.|\n {
-    yylval->cpVal = apr_pstrdup(yyextra->pool, regex_buf);
-    yyless(0);
-    BEGIN(INITIAL);
-    return T_REGEX;
+<regflags>. {
+    if (ap_strchr_c("ismg", yytext[0])) {
+        STR_APPEND(yytext[0]);
+    }
+    else if (apr_isalnum(yytext[0])) {
+        PERROR("Invalid regexp flag(s)");
+    }
+    else {
+        yyless(0);
+        if (!STR_EMPTY()) {
+            yylval->cpVal = apr_pstrdup(yyextra->pool, STR_FINAL());
+        }
+        else {
+            yylval->cpVal = NULL;
+            STATE_POP(1);
+        }
+        return T_REG_FLAGS;
+    }
 }
-<regex_flags><<EOF>> {
-    yylval->cpVal = apr_pstrdup(yyextra->pool, regex_buf);
-    BEGIN(INITIAL);
-    return T_REGEX;
+<regflags><<EOF>> {
+    STATE_POP(1);
+    yylval->cpVal = NULL;
+    return T_REG_FLAGS;
 }
+<regex><<EOF>> {
+    STATE_POP(1);
+    if (YY_START != INITIAL) {
+        PERROR("Unterminated regexp");
+    }
+    yylval->cpVal = NULL;
+    return T_REG_FLAGS;
+}
 
  /*
   * Operators
   */
-==?   { return T_OP_STR_EQ; }
-"!="  { return T_OP_STR_NE; }
-"<"   { return T_OP_STR_LT; }
-"<="  { return T_OP_STR_LE; }
-">"   { return T_OP_STR_GT; }
-">="  { return T_OP_STR_GE; }
-"=~"  { return T_OP_REG; }
-"!~"  { return T_OP_NRE; }
-"and" { return T_OP_AND; }
-"&&"  { return T_OP_AND; }
-"or"  { return T_OP_OR; }
-"||"  { return T_OP_OR; }
-"not" { return T_OP_NOT; }
-"!"   { return T_OP_NOT; }
-"."   { return T_OP_CONCAT; }
-"-in"  { return T_OP_IN; }
-"-eq"  { return T_OP_EQ; }
-"-ne"  { return T_OP_NE; }
-"-ge"  { return T_OP_GE; }
-"-le"  { return T_OP_LE; }
-"-gt"  { return T_OP_GT; }
-"-lt"  { return T_OP_LT; }
+<INITIAL,varext,join>==?   { return T_OP_STR_EQ; }
+<INITIAL,varext,join>"!="  { return T_OP_STR_NE; }
+<INITIAL,varext,join>"<"   { return T_OP_STR_LT; }
+<INITIAL,varext,join>"<="  { return T_OP_STR_LE; }
+<INITIAL,varext,join>">"   { return T_OP_STR_GT; }
+<INITIAL,varext,join>">="  { return T_OP_STR_GE; }
+<INITIAL,varext,join>"=~"  { return T_OP_REG; }
+<INITIAL,varext,join>"!~"  { return T_OP_NRE; }
+<INITIAL,varext,join>"and" { return T_OP_AND; }
+<INITIAL,varext,join>"&&"  { return T_OP_AND; }
+<INITIAL,varext,join>"or"  { return T_OP_OR; }
+<INITIAL,varext,join>"||"  { return T_OP_OR; }
+<INITIAL,varext,join>"not" { return T_OP_NOT; }
+<INITIAL,varext,join>"!"   { return T_OP_NOT; }
+<INITIAL,varext,join>"."   { return T_OP_CONCAT; }
+<INITIAL,varext,join>"-in"  { return T_OP_IN; }
+<INITIAL,varext,join>"-eq"  { return T_OP_EQ; }
+<INITIAL,varext,join>"-ne"  { return T_OP_NE; }
+<INITIAL,varext,join>"-ge"  { return T_OP_GE; }
+<INITIAL,varext,join>"-le"  { return T_OP_LE; }
+<INITIAL,varext,join>"-gt"  { return T_OP_GT; }
+<INITIAL,varext,join>"-lt"  { return T_OP_LT; }
 
  /* for compatibility with ssl_expr */
-"lt"  { return T_OP_LT; }
-"le"  { return T_OP_LE; }
-"gt"  { return T_OP_GT; }
-"ge"  { return T_OP_GE; }
-"ne"  { return T_OP_NE; }
-"eq"  { return T_OP_EQ; }
-"in"  { return T_OP_IN; }
+<INITIAL,varext,join>"lt"  { return T_OP_LT; }
+<INITIAL,varext,join>"le"  { return T_OP_LE; }
+<INITIAL,varext,join>"gt"  { return T_OP_GT; }
+<INITIAL,varext,join>"ge"  { return T_OP_GE; }
+<INITIAL,varext,join>"ne"  { return T_OP_NE; }
+<INITIAL,varext,join>"eq"  { return T_OP_EQ; }
+<INITIAL,varext,join>"in"  { return T_OP_IN; }
 
-"-"[a-zA-Z_] {
+<INITIAL,varext,join>"-"[a-zA-Z_][a-zA-Z_0-9]+ {
     yylval->cpVal = apr_pstrdup(yyextra->pool, yytext + 1);
-    return T_OP_UNARY;
+    return T_OP_BINARY;
 }
 
-"-"[a-zA-Z_][a-zA-Z_0-9]+ {
+<INITIAL,varext,join>"-"[a-zA-Z_] {
     yylval->cpVal = apr_pstrdup(yyextra->pool, yytext + 1);
-    return T_OP_BINARY;
+    return T_OP_UNARY;
 }
 
+ /* Split a string (or list) into a(nother) list */
+<INITIAL,varext,join>"split" {
+    STATE_PUSH(split, 0);
+    return T_OP_SPLIT;
+}
+<split>{REG_SEP} {
+    STATE_POP(0);
+    STATE_PUSH(regex, 1);
+    str_del = yytext[0];
+    str_state = 'S';
+}
+<split>. {
+    PERROR("Expecting split regular expression");
+}
+<split><<EOF>> {
+    PERROR("Unterminated split");
+}
+
+ /* Join a list into a string */
+<INITIAL,varext,join>"join"  {
+    STATE_PUSH(join, 1);
+    return T_OP_JOIN;
+}
+<join>[/] {
+    if (++str_state == 3) {
+        STATE_POP(1);
+    }
+    return yytext[0];
+}
+<join><<EOF>> {
+    PERROR("Unterminated join");
+}
+
  /*
   * Specials
   */
-"true"  { return T_TRUE; }
-"false" { return T_FALSE; }
+<INITIAL,varext,join>"true"  { return T_TRUE; }
+<INITIAL,varext,join>"false" { return T_FALSE; }
 
  /*
   * Digits
   */
--?[0-9]+ {
+<INITIAL,varext,join>-?[0-9]+ {
     yylval->cpVal = apr_pstrdup(yyextra->pool, yytext);
     return T_DIGIT;
 }
@@ -375,7 +482,7 @@
  /*
   * Identifiers
   */
-[a-zA-Z][a-zA-Z0-9_]* {
+<INITIAL,varext,join>[a-zA-Z][a-zA-Z0-9_]* {
     yylval->cpVal = apr_pstrdup(yyextra->pool, yytext);
     return T_ID;
 }
@@ -383,7 +490,7 @@
  /*
   * These are parts of the grammar and are returned as is
   */
-[(){},:] {
+<INITIAL,varext,join>[(){},] {
     return yytext[0];
 }
 
@@ -390,7 +497,7 @@
  /*
   * Anything else is an error
   */
-.|\n {
+<INITIAL,varext,join>.|\n {
     char *msg = apr_psprintf(yyextra->pool, "Parse error near '%c'", yytext[0]);
     PERROR(msg);
 }
Index: include/ap_regex.h
===================================================================
--- include/ap_regex.h	(revision 1783852)
+++ include/ap_regex.h	(working copy)
@@ -77,6 +77,9 @@ extern "C" {
 #define AP_REG_NOMEM 0x20    /* nomem in our code */
 #define AP_REG_DOTALL 0x40   /* perl's /s flag */
 
+#define AP_REG_NOTEMPTY 0x080  /* Empty match not valid */
+#define AP_REG_ANCHORED 0x100  /* Match at the first position */
+
 #define AP_REG_MATCH "MATCH_" /** suggested prefix for ap_regname */
 
 /* Error values: */
Index: server/util_pcre.c
===================================================================
--- server/util_pcre.c	(revision 1783852)
+++ server/util_pcre.c	(working copy)
@@ -189,6 +189,10 @@ AP_DECLARE(int) ap_regexec_len(const ap_regex_t *p
         options |= PCRE_NOTBOL;
     if ((eflags & AP_REG_NOTEOL) != 0)
         options |= PCRE_NOTEOL;
+    if ((eflags & AP_REG_NOTEMPTY) != 0)
+        options |= PCRE_NOTEMPTY;
+    if ((eflags & AP_REG_ANCHORED) != 0)
+        options |= PCRE_ANCHORED;
 
     ((ap_regex_t *)preg)->re_erroffset = (apr_size_t)(-1);    /* Only has meaning after compile */
 

Reply via email to