1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 #include "apr_strings.h"
21 #define APR_WANT_STRFUNC
22 #define APR_WANT_MEMFUNC
27 #include "http_core.h"
32 * +-------------------------------------------------------+
34 * | Debugging Utilities
36 * +-------------------------------------------------------+
41 #define TYPE_TOKEN(token, ttype) do { \
42 (token)->type = ttype; \
43 (token)->s = #ttype; \
46 #define CREATE_NODE(pool,name) do { \
47 (name) = apr_pcalloc(pool, sizeof(*(name)));
50 static void debug_printf(request_rec *r, const char *fmt, ...)
56 debug__str = apr_pvsprintf(r->pool, fmt, ap);
59 APR_BRIGADE_INSERT_TAIL(ctx->intern->debug.bb, apr_bucket_pool_create(
60 debug__str, strlen(debug__str), ctx->pool,
61 ctx->intern->debug.f->c->bucket_alloc));
65 #define DUMP__CHILD(ctx, is, node, child) if (1) { \
66 ap_parse_node_t *d__c = node->child; \
68 if (!d__c->dump_done) { \
69 if (d__c->parent != node) { \
70 debug_printf(ctx, "!!! Parse tree is not consistent !!!\n"); \
71 if (!d__c->parent) { \
72 debug_printf(ctx, "Parent of " #child " child node is " \
76 debug_printf(ctx, "Parent of " #child " child node " \
77 "points to another node (of type %s)!\n", \
78 d__c->parent->token.s); \
87 debug_printf(ctx, "%s(missing)\n", is); \
91 static void debug_dump_tree(include_ctx_t *ctx, ap_parse_node_t *root)
93 ap_parse_node_t *current;
97 debug_printf(ctx, " -- Parse Tree empty --\n\n");
101 debug_printf(ctx, " ----- Parse Tree -----\n");
106 switch (current->token.type) {
109 debug_printf(ctx, "%s%s (%s)\n", is, current->token.s,
110 current->token.value);
111 current->dump_done = 1;
112 current = current->parent;
119 if (!current->dump_done) {
120 debug_printf(ctx, "%s%s\n", is, current->token.s);
121 is = apr_pstrcat(ctx->dpool, is, " ", NULL);
122 current->dump_done = 1;
125 DUMP__CHILD(ctx, is, current, right)
127 if (!current->right || current->right->dump_done) {
128 is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4);
129 if (current->right) current->right->dump_done = 0;
130 current = current->parent;
135 if (!current->dump_done) {
136 debug_printf(ctx, "%s%s\n", is, current->token.s);
137 is = apr_pstrcat(ctx->dpool, is, " ", NULL);
138 current->dump_done = 1;
141 DUMP__CHILD(ctx, is, current, left)
142 DUMP__CHILD(ctx, is, current, right)
144 if ((!current->left || current->left->dump_done) &&
145 (!current->right || current->right->dump_done)) {
147 is = apr_pstrmemdup(ctx->dpool, is, strlen(is) - 4);
148 if (current->left) current->left->dump_done = 0;
149 if (current->right) current->right->dump_done = 0;
150 current = current->parent;
156 /* it is possible to call this function within the parser loop, to see
157 * how the tree is built. That way, we must cleanup after us to dump
158 * always the whole tree
161 if (root->left) root->left->dump_done = 0;
162 if (root->right) root->right->dump_done = 0;
164 debug_printf(ctx, " --- End Parse Tree ---\n\n");
169 #define DEBUG_INIT(ctx, filter, brigade) do { \
170 (ctx)->intern->debug.f = filter; \
171 (ctx)->intern->debug.bb = brigade; \
174 #define DEBUG_PRINTF(arg) debug_printf arg
176 #define DEBUG_DUMP_TOKEN(ctx, token) do { \
177 token_t *d__t = (token); \
179 if (d__t->type == TOKEN_STRING || d__t->type == TOKEN_RE) { \
180 DEBUG_PRINTF(((ctx), " Found: %s (%s)\n", d__t->s, d__t->value)); \
183 DEBUG_PRINTF((ctx, " Found: %s\n", d__t->s)); \
187 #define DEBUG_DUMP_EVAL(r, node) do { \
189 switch ((node)->token.type) { \
191 debug_printf((r), " Evaluate: %s (%s) -> %c\n", (node)->token.s,\
192 (node)->token.value, ((node)->value) ? '1':'0'); \
196 debug_printf((r), " Evaluate: %s (Left: %s; Right: %s) -> %c\n",\
198 (((node)->left->done) ? ((node)->left->value ?"1":"0") \
199 : "short circuited"), \
200 (((node)->right->done) ? ((node)->right->value?"1":"0") \
201 : "short circuited"), \
202 (node)->value ? '1' : '0'); \
210 if ((node)->right->token.type == TOKEN_RE) c = '/'; \
211 debug_printf((r), " Compare: %s (\"%s\" with %c%s%c) -> %c\n", \
213 (node)->left->token.value, \
214 c, (node)->right->token.value, c, \
215 (node)->value ? '1' : '0'); \
218 debug_printf((r), " Evaluate: %s -> %c\n", (node)->token.s, \
219 (node)->value ? '1' : '0'); \
224 #define DEBUG_DUMP_UNMATCHED(r, unmatched) do { \
226 DEBUG_PRINTF(((r), " Unmatched %c\n", (char)(unmatched))); \
230 #define DEBUG_DUMP_COND(ctx, text) \
231 DEBUG_PRINTF(((ctx), "**** %s cond status=\"%c\"\n", (text), \
232 ((ctx)->flags & SSI_FLAG_COND_TRUE) ? '1' : '0'))
234 #define DEBUG_DUMP_TREE(ctx, root) debug_dump_tree(ctx, root)
236 #else /* DEBUG_INCLUDE */
238 #define TYPE_TOKEN(token, ttype) (token)->type = ttype
240 #define CREATE_NODE(pool,name) do { \
241 (name) = apr_pcalloc(pool, sizeof(*(name))); \
244 #define DEBUG_INIT(ctx, f, bb)
245 #define DEBUG_PRINTF(arg)
246 #define DEBUG_DUMP_TOKEN(ctx, token)
247 #define DEBUG_DUMP_EVAL(ctx, node)
248 #define DEBUG_DUMP_UNMATCHED(ctx, unmatched)
249 #define DEBUG_DUMP_COND(ctx, text)
250 #define DEBUG_DUMP_TREE(ctx, root)
252 #endif /* !DEBUG_INCLUDE */
258 * +-------------------------------------------------------+
260 * | Conditional Expression Parser
262 * +-------------------------------------------------------+
264 static APR_INLINE int re_check(request_rec *r, const char *string,
265 const char *rexp, backref_t **reptr)
267 ap_regex_t *compiled;
268 backref_t *re = reptr ? *reptr : NULL;
270 compiled = ap_pregcomp(r->pool, rexp, AP_REG_EXTENDED);
272 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "unable to "
273 "compile pattern \"%s\"", rexp);
278 re = apr_palloc(r->pool, sizeof(*re));
284 re->source = apr_pstrdup(r->pool, string);
285 re->rexp = apr_pstrdup(r->pool, rexp);
286 re->nsub = compiled->re_nsub;
287 re->have_match = !ap_regexec(compiled, string, AP_MAX_REG_MATCH,
290 ap_pregfree(r->pool, compiled);
291 return re->have_match;
294 static int get_ptoken(apr_pool_t *pool, const char **parse, token_t *token,
307 /* Skip leading white space */
308 while (apr_isspace(**parse)) {
317 TYPE_TOKEN(token, TOKEN_STRING); /* the default type */
321 switch (*(*parse)++) {
323 TYPE_TOKEN(token, TOKEN_LBRACE);
326 TYPE_TOKEN(token, TOKEN_RBRACE);
329 if (**parse == '=') ++*parse;
330 TYPE_TOKEN(token, TOKEN_EQ);
333 if (**parse == '=') {
334 TYPE_TOKEN(token, TOKEN_NE);
338 TYPE_TOKEN(token, TOKEN_NOT);
344 /* if last token was ACCESS, this token is STRING */
345 if (previous != NULL && TOKEN_ACCESS == previous->type) {
348 TYPE_TOKEN(token, TOKEN_RE);
352 if (**parse == '|') {
353 TYPE_TOKEN(token, TOKEN_OR);
359 if (**parse == '&') {
360 TYPE_TOKEN(token, TOKEN_AND);
366 if (**parse == '=') {
367 TYPE_TOKEN(token, TOKEN_GE);
371 TYPE_TOKEN(token, TOKEN_GT);
374 if (**parse == '=') {
375 TYPE_TOKEN(token, TOKEN_LE);
379 TYPE_TOKEN(token, TOKEN_LT);
382 if (apr_isalnum(**parse) && apr_isspace((*parse)[1])) {
383 TYPE_TOKEN(token, TOKEN_ACCESS);
384 token->value = *parse;
391 /* It's a string or regex token
392 * Now search for the next token, which finishes this string
395 p = *parse = token->value = unmatched ? *parse : p;
397 for (; **parse; p = ++*parse) {
398 if (**parse == '\\') {
408 if (**parse == unmatched) {
414 else if (apr_isspace(**parse)) {
432 if ((*parse)[1] == **parse) {
446 token->value = apr_pstrdup(pool, "");
449 apr_size_t len = p - token->value - shift;
450 char *c = apr_palloc(pool, len + 1);
456 const char *e = ap_strchr_c(p, '\\');
474 /* This is what we export. We can split it in two. */
475 AP_DECLARE(ap_parse_node_t*) ap_expr_parse(apr_pool_t* pool, const char *expr,
478 ap_parse_node_t *new, *root = NULL, *current = NULL;
479 const char *error = "Invalid expression \"%s\" in file %s";
480 const char *parse = expr;
481 int was_unmatched = 0;
490 /* Create Parse Tree */
492 /* uncomment this to see how the tree a built:
494 * DEBUG_DUMP_TREE(ctx, root);
496 CREATE_NODE(pool, new);
498 was_unmatched = get_ptoken(pool, &parse, &new->token,
499 (current != NULL ? ¤t->token : NULL));
504 DEBUG_DUMP_UNMATCHED(ctx, was_unmatched);
505 DEBUG_DUMP_TOKEN(ctx, &new->token);
508 switch (new->token.type) {
513 root = current = new;
522 switch (new->token.type) {
524 switch (current->token.type) {
526 current->token.value =
527 apr_pstrcat(pool, current->token.value,
528 *current->token.value ? " " : "",
529 new->token.value, NULL);
538 new->parent = current;
539 current = current->right = new;
545 switch (current->token.type) {
548 new->parent = current;
549 current = current->right = new;
560 switch (current->token.type) {
564 current = current->parent;
567 switch (current->token.type) {
574 current = current->parent;
583 current = root = new;
587 new->left = current->right;
588 new->left->parent = new;
589 new->parent = current;
590 current = current->right = new;
604 if (current->token.type == TOKEN_STRING) {
605 current = current->parent;
610 current = root = new;
614 switch (current->token.type) {
618 new->left = current->right;
619 new->left->parent = new;
620 new->parent = current;
621 current = current->right = new;
631 while (current && current->token.type != TOKEN_LBRACE) {
632 current = current->parent;
636 TYPE_TOKEN(¤t->token, TOKEN_GROUP);
640 error = "Unmatched ')' in \"%s\" in file %s";
646 switch (current->token.type) {
654 current->right = new;
655 new->parent = current;
669 DEBUG_DUMP_TREE(ctx, root);
673 static ap_parse_node_t *ap_expr_clone_tree(apr_pool_t *pool,
674 ap_parse_node_t *pnode,
675 ap_parse_node_t *parent)
677 ap_parse_node_t *ret;
678 ret = apr_pmemdup(pool, pnode, sizeof(ap_parse_node_t));
680 ret->left = ap_expr_clone_tree(pool, pnode->left, ret);
683 ret->right = ap_expr_clone_tree(pool, pnode->right, ret);
685 ret->parent = parent;
689 #define PARSE_STRING(r,s) (string_func ? string_func((r),(s)) : (s))
690 static int expr_eval(request_rec *r, ap_parse_node_t *root,
691 int *was_error, backref_t **reptr,
692 string_func_t string_func, opt_func_t eval_func)
694 ap_parse_node_t *current = root;
695 const char *error = NULL;
696 unsigned int regex = 0;
701 /* Evaluate Parse Tree */
703 switch (current->token.type) {
705 val = PARSE_STRING(r, current->token.value);
706 current->value = !!*val;
711 if (!current->left || !current->right) {
712 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
713 "Invalid expression in file %s", r->filename);
718 if (!current->left->done) {
719 switch (current->left->token.type) {
721 lval = PARSE_STRING(r, current->left->token.value);
722 current->left->value = !!*lval;
723 DEBUG_DUMP_EVAL(ctx, current->left);
724 current->left->done = 1;
728 current = current->left;
733 /* short circuit evaluation */
734 if (!current->right->done && !regex &&
735 ((current->token.type == TOKEN_AND && !current->left->value) ||
736 (current->token.type == TOKEN_OR && current->left->value))) {
737 current->value = current->left->value;
740 if (!current->right->done) {
741 switch (current->right->token.type) {
743 rval = PARSE_STRING(r,current->right->token.value);
744 current->right->value = !!*rval;
745 DEBUG_DUMP_EVAL(r, current->right);
746 current->right->done = 1;
750 current = current->right;
755 if (current->token.type == TOKEN_AND) {
756 current->value = current->left->value &&
757 current->right->value;
760 current->value = current->left->value ||
761 current->right->value;
768 if (!current->left || !current->right ||
769 current->left->token.type != TOKEN_STRING ||
770 (current->right->token.type != TOKEN_STRING &&
771 current->right->token.type != TOKEN_RE)) {
772 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
773 "Invalid expression in file %s", r->filename);
777 lval = PARSE_STRING(r, current->left->token.value);
778 rval = PARSE_STRING(r, current->right->token.value);
780 if (current->right->token.type == TOKEN_RE) {
781 current->value = re_check(r, lval, rval, reptr);
785 current->value = !strcmp(lval, rval);
788 if (current->token.type == TOKEN_NE) {
789 current->value = !current->value;
797 if (!current->left || !current->right ||
798 current->left->token.type != TOKEN_STRING ||
799 current->right->token.type != TOKEN_STRING) {
800 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
801 "Invalid expression in file %s", r->filename);
806 lval = PARSE_STRING(r, current->left->token.value);
807 rval = PARSE_STRING(r, current->right->token.value);
809 current->value = strcmp(lval, rval);
811 switch (current->token.type) {
812 case TOKEN_GE: current->value = current->value >= 0; break;
813 case TOKEN_GT: current->value = current->value > 0; break;
814 case TOKEN_LE: current->value = current->value <= 0; break;
815 case TOKEN_LT: current->value = current->value < 0; break;
816 default: current->value = 0; break; /* should not happen */
822 if (current->right) {
823 if (!current->right->done) {
824 current = current->right;
827 current->value = current->right->value;
833 if (current->token.type == TOKEN_NOT) {
834 current->value = !current->value;
839 *was_error = eval_func(r, current, string_func);
848 error = "No operator before regex in file %s";
852 error = "Unmatched '(' in file %s";
856 error = "internal parser error in file %s";
859 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, error, r->filename);
864 DEBUG_DUMP_EVAL(r, current);
866 current = current->parent;
869 return (root ? root->value : 0);
871 AP_DECLARE(int) ap_expr_eval(request_rec *r, ap_parse_node_t *root,
872 int *was_error, backref_t **reptr,
873 string_func_t string_func, opt_func_t eval_func)
875 ap_parse_node_t *clone;
876 if (root == NULL) { /* no condition == unconditional */
879 clone = ap_expr_clone_tree(r->pool, root, NULL);
880 return expr_eval(r, clone, was_error, reptr, string_func, eval_func);
882 AP_DECLARE(int) ap_expr_evalstring(request_rec *r, const char *expr,
883 int *was_error, backref_t **reptr,
884 string_func_t string_func,
885 opt_func_t eval_func)
887 ap_parse_node_t *root = ap_expr_parse(r->pool, expr, was_error);
888 if (*was_error || !root) {
889 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
890 "Error parsing expression in %s", r->filename);
893 return expr_eval(r, root, was_error, reptr, string_func, eval_func);
897 static ap_regex_t *isvar = NULL;
898 AP_DECLARE_NONSTD(const char*) ap_expr_string(request_rec *r,
901 /* a default string evaluator: support headers and env */
902 const char *ret = str;
903 ap_regmatch_t match[3];
906 ap_assert(isvar != NULL);
907 if (ap_regexec(isvar, str, 3, match, 0) == 0) {
908 apr_table_t *table = NULL;
909 int len = match[1].rm_eo-match[1].rm_so;
910 const char *name = str+match[1].rm_so;
911 if (!strncasecmp("req", name, len)) {
912 table = r->headers_in;
914 else if (!strncasecmp("resp", name, len)) {
915 table = r->headers_out;
917 else if (!strncasecmp("env", name, len)) {
918 table = r->subprocess_env;
921 char *key = apr_pstrndup(r->pool, str+match[2].rm_so,
922 match[2].rm_eo-match[2].rm_so);
923 ret = apr_table_get(table, key);
926 else if (str[0] == '$') {
927 if (!strcasecmp(str, "$handler")) {
930 else if (!strcasecmp(str, "$content-type")) {
931 ret = r->content_type;
935 /* copy wholesale from mod_rewrite to support its %{varname} vars */
936 else if ((str[0] == '%') && (str[1] == '{')
937 && (p = ap_strchr_c(str, '}'), p != NULL)) {
941 var = apr_pstrndup(r->pool, str+2, p-str-3);
942 for (ch = var; *ch; ++ch) {
943 *ch = apr_toupper(*ch);
946 switch (strlen(var)) {
948 if (!strcmp(var, "TIME")) {
949 apr_time_exp_lt(&tm, apr_time_now());
950 ret = apr_psprintf(r->pool, "%04d%02d%02d%02d%02d%02d",
951 tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
952 tm.tm_hour, tm.tm_min, tm.tm_sec);
955 else if (!strcmp(var, "IPV6")) {
958 apr_sockaddr_t *addr = r->connection->remote_addr;
959 flag = (addr->family == AF_INET6 &&
960 !IN6_IS_ADDR_V4MAPPED((struct in6_addr *)addr->ipaddr_ptr));
962 ret = (flag ? "on" : "off");
968 if (!strcmp(var, "HTTPS")) {
969 int flag = rewrite_is_https && rewrite_is_https(r->connection);
970 return apr_pstrdup(r->pool, flag ? "on" : "off");
977 if (!strcmp(var, "TIME_DAY")) {
978 apr_time_exp_lt(&tm, apr_time_now());
979 return apr_psprintf(r->pool, "%02d", tm.tm_mday);
984 if (!strcmp(var, "TIME_SEC")) {
985 apr_time_exp_lt(&tm, apr_time_now());
986 return apr_psprintf(r->pool, "%02d", tm.tm_sec);
991 if (!strcmp(var, "TIME_MIN")) {
992 apr_time_exp_lt(&tm, apr_time_now());
993 return apr_psprintf(r->pool, "%02d", tm.tm_min);
998 if (!strcmp(var, "TIME_MON")) {
999 apr_time_exp_lt(&tm, apr_time_now());
1000 return apr_psprintf(r->pool, "%02d", tm.tm_mon+1);
1009 if (var[8] == 'Y' && !strcmp(var, "TIME_WDAY")) {
1010 apr_time_exp_lt(&tm, apr_time_now());
1011 return apr_psprintf(r->pool, "%d", tm.tm_wday);
1013 else if (!strcmp(var, "TIME_YEAR")) {
1014 apr_time_exp_lt(&tm, apr_time_now());
1015 return apr_psprintf(r->pool, "%04d", tm.tm_year+1900);
1020 if (!strcmp(var, "IS_SUBREQ")) {
1021 ret = (r->main ? "true" : "false");
1026 if (!strcmp(var, "PATH_INFO")) {
1032 if (!strcmp(var, "AUTH_TYPE")) {
1033 ret = r->ap_auth_type;
1038 if (!strcmp(var, "HTTP_HOST")) {
1039 ret = apr_table_get(r->headers_in, "Host");
1044 if (!strcmp(var, "TIME_HOUR")) {
1045 apr_time_exp_lt(&tm, apr_time_now());
1046 return apr_psprintf(r->pool, "%02d", tm.tm_hour);
1055 if (!strcmp(var, "SERVER_NAME")) {
1056 ret = ap_get_server_name(r);
1061 if (*var == 'R' && !strcmp(var, "REMOTE_ADDR")) {
1062 ret = r->connection->remote_ip;
1064 else if (!strcmp(var, "SERVER_ADDR")) {
1065 ret = r->connection->local_ip;
1070 if (*var == 'H' && !strcmp(var, "HTTP_ACCEPT")) {
1071 ret = apr_table_get(r->headers_in, "Accept");
1073 else if (!strcmp(var, "THE_REQUEST")) {
1074 ret = r->the_request;
1079 if (!strcmp(var, "API_VERSION")) {
1080 return apr_psprintf(r->pool, "%d:%d",
1081 MODULE_MAGIC_NUMBER_MAJOR,
1082 MODULE_MAGIC_NUMBER_MINOR);
1087 if (!strcmp(var, "HTTP_COOKIE")) {
1088 ret = apr_table_get(r->headers_in, "Cookie");
1093 if (*var == 'S' && !strcmp(var, "SERVER_PORT")) {
1094 return apr_psprintf(r->pool, "%u", ap_get_server_port(r));
1096 else if (var[7] == 'H' && !strcmp(var, "REMOTE_HOST")) {
1097 ret = ap_get_remote_host(r->connection,r->per_dir_config,
1100 else if (!strcmp(var, "REMOTE_PORT")) {
1101 return apr_itoa(r->pool, r->connection->remote_addr->port);
1106 if (*var == 'R' && !strcmp(var, "REMOTE_USER")) {
1109 else if (!strcmp(var, "SCRIPT_USER")) {
1111 if (r->finfo.valid & APR_FINFO_USER) {
1112 apr_uid_name_get((char **)&ret, r->finfo.user,
1119 if (!strcmp(var, "REQUEST_URI")) {
1129 if (!strcmp(var, "SCRIPT_GROUP")) {
1131 if (r->finfo.valid & APR_FINFO_GROUP) {
1132 apr_gid_name_get((char **)&ret, r->finfo.group,
1139 if (!strcmp(var, "REMOTE_IDENT")) {
1140 ret = ap_get_remote_logname(r);
1145 if (!strcmp(var, "HTTP_REFERER")) {
1146 ret = apr_table_get(r->headers_in, "Referer");
1151 if (!strcmp(var, "QUERY_STRING")) {
1157 if (!strcmp(var, "SERVER_ADMIN")) {
1158 ret = r->server->server_admin;
1165 if (!strcmp(var, "DOCUMENT_ROOT")) {
1166 ret = ap_document_root(r);
1171 if (*var == 'H' && !strcmp(var, "HTTP_FORWARDED")) {
1172 ret = apr_table_get(r->headers_in, "Forwarded");
1174 else if (!strcmp(var, "REQUEST_METHOD")) {
1182 if (!strcmp(var, "HTTP_USER_AGENT")) {
1183 ret = apr_table_get(r->headers_in, "User-Agent");
1188 if (!strcmp(var, "SCRIPT_FILENAME")) {
1189 ret = r->filename; /* same as request_filename (16) */
1194 if (!strcmp(var, "SERVER_PROTOCOL")) {
1200 if (!strcmp(var, "SERVER_SOFTWARE")) {
1201 ret = ap_get_server_banner();
1208 if (!strcmp(var, "REQUEST_FILENAME")) {
1209 ret = r->filename; /* same as script_filename (15) */
1214 if (!strcmp(var, "HTTP_PROXY_CONNECTION")) {
1215 ret = apr_table_get(r->headers_in, "Proxy-Connection");
1221 /* TODO: provide a hook so modules can interpret other patterns */
1222 /* OhBugger, where's the regexp for backreferences ? */
1226 return ret; /* default - literal string as-is */
1228 static apr_status_t ap_expr_term(void *expr)
1236 AP_DECLARE(apr_status_t) ap_expr_init(apr_pool_t *pool)
1238 static ap_regex_t var;
1241 if (ap_regcomp(isvar, "\\$([A-Za-z0-9]+)\\{([^\\}]+)\\}", 0)) {
1245 apr_pool_cleanup_register(pool, isvar, ap_expr_term,
1246 apr_pool_cleanup_null);
1249 return isvar ? APR_SUCCESS : APR_EGENERAL;