| /* Licensed to the Apache Software Foundation (ASF) under one or more | |
| * contributor license agreements. See the NOTICE file distributed with | |
| * this work for additional information regarding copyright ownership. | |
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |
| * (the "License"); you may not use this file except in compliance with | |
| * the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include "apr.h" | |
| #include "apr_lib.h" | |
| #include "apr_pools.h" | |
| #include "apr_strings.h" | |
| #include "ap_config.h" | |
| #include "ap_regex.h" | |
| #include "httpd.h" | |
| static apr_status_t rxplus_cleanup(void *preg) | |
| { | |
| ap_regfree((ap_regex_t *) preg); | |
| return APR_SUCCESS; | |
| } | |
| AP_DECLARE(ap_rxplus_t*) ap_rxplus_compile(apr_pool_t *pool, | |
| const char *pattern) | |
| { | |
| /* perl style patterns | |
| * add support for more as and when wanted | |
| * substitute: s/rx/subs/ | |
| * match: m/rx/ or just /rx/ | |
| */ | |
| /* allow any nonalnum delimiter as first or second char. | |
| * If we ever use this with non-string pattern we'll need an extra check | |
| */ | |
| const char *endp = 0; | |
| const char *str = pattern; | |
| const char *rxstr; | |
| ap_rxplus_t *ret = apr_pcalloc(pool, sizeof(ap_rxplus_t)); | |
| char delim = 0; | |
| enum { SUBSTITUTE = 's', MATCH = 'm'} action = MATCH; | |
| if (!apr_isalnum(pattern[0])) { | |
| delim = *str++; | |
| } | |
| else if (pattern[0] == 's' && !apr_isalnum(pattern[1])) { | |
| action = SUBSTITUTE; | |
| delim = pattern[1]; | |
| str += 2; | |
| } | |
| else if (pattern[0] == 'm' && !apr_isalnum(pattern[1])) { | |
| delim = pattern[1]; | |
| str += 2; | |
| } | |
| /* TODO: support perl's after/before */ | |
| /* FIXME: fix these simplminded delims */ | |
| /* we think there's a delimiter. Allow for it not to be if unmatched */ | |
| if (delim) { | |
| endp = ap_strchr_c(str, delim); | |
| } | |
| if (!endp) { /* there's no delim or flags */ | |
| if (ap_regcomp(&ret->rx, pattern, 0) == 0) { | |
| apr_pool_cleanup_register(pool, &ret->rx, rxplus_cleanup, | |
| apr_pool_cleanup_null); | |
| return ret; | |
| } | |
| else { | |
| return NULL; | |
| } | |
| } | |
| /* We have a delimiter. Use it to extract the regexp */ | |
| rxstr = apr_pstrmemdup(pool, str, endp-str); | |
| /* If it's a substitution, we need the replacement string | |
| * TODO: possible future enhancement - support other parsing | |
| * in the replacement string. | |
| */ | |
| if (action == SUBSTITUTE) { | |
| str = endp+1; | |
| if (!*str || (endp = ap_strchr_c(str, delim), !endp)) { | |
| /* missing replacement string is an error */ | |
| return NULL; | |
| } | |
| ret->subs = apr_pstrmemdup(pool, str, endp-str); | |
| } | |
| /* anything after the current delimiter is flags */ | |
| ret->flags = ap_regcomp_get_default_cflags() & AP_REG_DOLLAR_ENDONLY; | |
| while (*++endp) { | |
| switch (*endp) { | |
| case 'i': ret->flags |= AP_REG_ICASE; break; | |
| case 'm': ret->flags |= AP_REG_NEWLINE; break; | |
| case 'n': ret->flags |= AP_REG_NOMEM; break; | |
| case 'g': ret->flags |= AP_REG_MULTI; break; | |
| case 's': ret->flags |= AP_REG_DOTALL; break; | |
| case '^': ret->flags |= AP_REG_NOTBOL; break; | |
| case '$': ret->flags |= AP_REG_NOTEOL; break; | |
| default: break; /* we should probably be stricter here */ | |
| } | |
| } | |
| if (ap_regcomp(&ret->rx, rxstr, AP_REG_NO_DEFAULT | ret->flags) == 0) { | |
| apr_pool_cleanup_register(pool, &ret->rx, rxplus_cleanup, | |
| apr_pool_cleanup_null); | |
| } | |
| else { | |
| return NULL; | |
| } | |
| if (!(ret->flags & AP_REG_NOMEM)) { | |
| /* count size of memory required, starting at 1 for the whole-match | |
| * Simpleminded should be fine 'cos regcomp already checked syntax | |
| */ | |
| ret->nmatch = 1; | |
| while (*rxstr) { | |
| switch (*rxstr++) { | |
| case '\\': /* next char is escaped - skip it */ | |
| if (*rxstr != 0) { | |
| ++rxstr; | |
| } | |
| break; | |
| case '(': /* unescaped bracket implies memory */ | |
| ++ret->nmatch; | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| ret->pmatch = apr_palloc(pool, ret->nmatch*sizeof(ap_regmatch_t)); | |
| } | |
| return ret; | |
| } | |
| AP_DECLARE(int) ap_rxplus_exec(apr_pool_t *pool, ap_rxplus_t *rx, | |
| const char *pattern, char **newpattern) | |
| { | |
| int ret = 1; | |
| int startl, oldl, newl, diffsz; | |
| const char *remainder; | |
| char *subs; | |
| /* snrf process_regexp from mod_headers */ | |
| if (ap_regexec(&rx->rx, pattern, rx->nmatch, rx->pmatch, rx->flags) != 0) { | |
| rx->match = NULL; | |
| return 0; /* no match, nothing to do */ | |
| } | |
| rx->match = pattern; | |
| if (rx->subs) { | |
| *newpattern = ap_pregsub(pool, rx->subs, pattern, | |
| rx->nmatch, rx->pmatch); | |
| if (!*newpattern) { | |
| return 0; /* FIXME - should we do more to handle error? */ | |
| } | |
| startl = rx->pmatch[0].rm_so; | |
| oldl = rx->pmatch[0].rm_eo - startl; | |
| newl = strlen(*newpattern); | |
| diffsz = newl - oldl; | |
| remainder = pattern + startl + oldl; | |
| if (rx->flags & AP_REG_MULTI) { | |
| /* recurse to do any further matches */ | |
| ret += ap_rxplus_exec(pool, rx, remainder, &subs); | |
| if (ret > 1) { | |
| /* a further substitution happened */ | |
| diffsz += strlen(subs) - strlen(remainder); | |
| remainder = subs; | |
| } | |
| } | |
| subs = apr_palloc(pool, strlen(pattern) + 1 + diffsz); | |
| memcpy(subs, pattern, startl); | |
| memcpy(subs+startl, *newpattern, newl); | |
| strcpy(subs+startl+newl, remainder); | |
| *newpattern = subs; | |
| } | |
| return ret; | |
| } | |
| #ifdef DOXYGEN | |
| AP_DECLARE(int) ap_rxplus_nmatch(ap_rxplus_t *rx) | |
| { | |
| return (rx->match != NULL) ? rx->nmatch : 0; | |
| } | |
| #endif | |
| /* If this blows up on you, see the notes in the header/apidoc | |
| * rx->match is a pointer and it's your responsibility to ensure | |
| * it hasn't gone out-of-scope since the last ap_rxplus_exec | |
| */ | |
| AP_DECLARE(void) ap_rxplus_match(ap_rxplus_t *rx, int n, int *len, | |
| const char **match) | |
| { | |
| if (n >= 0 && n < ap_rxplus_nmatch(rx)) { | |
| *match = rx->match + rx->pmatch[n].rm_so; | |
| *len = rx->pmatch[n].rm_eo - rx->pmatch[n].rm_so; | |
| } | |
| else { | |
| *len = -1; | |
| *match = NULL; | |
| } | |
| } | |
| AP_DECLARE(char*) ap_rxplus_pmatch(apr_pool_t *pool, ap_rxplus_t *rx, int n) | |
| { | |
| int len; | |
| const char *match; | |
| ap_rxplus_match(rx, n, &len, &match); | |
| return apr_pstrndup(pool, match, len); | |
| } |