1 /* 2 * keyvalue - PCRE matching and substitution for mod_redirect and mod_rewrite 3 * 4 * Fully-rewritten from original 5 * Copyright(c) 2018 Glenn Strauss gstrauss()gluelogic.com All rights reserved 6 * License: BSD 3-clause (same as lighttpd) 7 */ 8 #include "first.h" 9 10 #include "keyvalue.h" 11 #include "plugin_config.h" /* struct cond_match_t */ 12 #include "burl.h" 13 #include "log.h" 14 15 #include <stdlib.h> 16 #include <string.h> 17 18 #ifdef HAVE_PCRE_H 19 #include <pcre.h> 20 #ifndef PCRE_STUDY_JIT_COMPILE 21 #define PCRE_STUDY_JIT_COMPILE 0 22 #define pcre_free_study(x) pcre_free(x) 23 #endif 24 #endif 25 26 typedef struct pcre_keyvalue { 27 #ifdef HAVE_PCRE_H 28 pcre *key; 29 pcre_extra *key_extra; 30 #endif 31 buffer value; 32 } pcre_keyvalue; 33 34 pcre_keyvalue_buffer *pcre_keyvalue_buffer_init(void) { 35 pcre_keyvalue_buffer *kvb; 36 37 kvb = calloc(1, sizeof(*kvb)); 38 force_assert(NULL != kvb); 39 40 return kvb; 41 } 42 43 int pcre_keyvalue_buffer_append(log_error_st *errh, pcre_keyvalue_buffer *kvb, const buffer *key, const buffer *value, const int pcre_jit) { 44 #ifdef HAVE_PCRE_H 45 const char *errptr; 46 int erroff; 47 pcre_keyvalue *kv; 48 49 if (0 == (kvb->used & 3)) { /*(allocate in groups of 4)*/ 50 kvb->kv = realloc(kvb->kv, (kvb->used + 4) * sizeof(*kvb->kv)); 51 force_assert(NULL != kvb->kv); 52 } 53 54 kv = kvb->kv + kvb->used++; 55 kv->key_extra = NULL; 56 57 /* copy persistent config data, and elide free() in free_data below */ 58 memcpy(&kv->value, value, sizeof(buffer)); 59 /*buffer_copy_buffer(&kv->value, value);*/ 60 61 if (NULL == (kv->key = pcre_compile(key->ptr, 62 0, &errptr, &erroff, NULL))) { 63 64 log_error(errh, __FILE__, __LINE__, 65 "rexexp compilation error at %s", errptr); 66 return 0; 67 } 68 69 const int study_options = pcre_jit ? PCRE_STUDY_JIT_COMPILE : 0; 70 if (NULL == (kv->key_extra = pcre_study(kv->key, study_options, &errptr)) 71 && errptr != NULL) { 72 log_error(errh, __FILE__, __LINE__, 73 "studying regex failed: %s -> %s\n", 74 key->ptr, errptr); 75 return 0; 76 } 77 #else 78 static int logged_message = 0; 79 if (logged_message) return 1; 80 logged_message = 1; 81 log_error(errh, __FILE__, __LINE__, 82 "pcre support is missing, please install libpcre and the headers"); 83 UNUSED(kvb); 84 UNUSED(key); 85 UNUSED(value); 86 UNUSED(pcre_jit); 87 #endif 88 89 return 1; 90 } 91 92 void pcre_keyvalue_buffer_free(pcre_keyvalue_buffer *kvb) { 93 #ifdef HAVE_PCRE_H 94 for (uint32_t i = 0; i < kvb->used; ++i) { 95 pcre_keyvalue * const kv = kvb->kv+i; 96 if (kv->key) pcre_free(kv->key); 97 if (kv->key_extra) pcre_free_study(kv->key_extra); 98 /*free (kv->value.ptr);*//*(see pcre_keyvalue_buffer_append)*/ 99 } 100 101 if (kvb->kv) free(kvb->kv); 102 #endif 103 free(kvb); 104 } 105 106 #ifdef HAVE_PCRE_H 107 static void pcre_keyvalue_buffer_append_match(buffer *b, const char **list, int n, unsigned int num, int flags) { 108 if (num < (unsigned int)n) { /* n is always > 0 */ 109 burl_append(b, list[num], strlen(list[num]), flags); 110 } 111 } 112 113 static void pcre_keyvalue_buffer_append_ctxmatch(buffer *b, const pcre_keyvalue_ctx *ctx, unsigned int num, int flags) { 114 const struct cond_match_t * const cache = ctx->cache; 115 if (!cache) return; /* no enclosing match context */ 116 if ((int)num < ctx->cond_match_count) { 117 const int off = cache->matches[(num <<= 1)]; /*(num *= 2)*/ 118 const int len = cache->matches[num+1] - off; 119 burl_append(b, cache->comp_value->ptr + off, (size_t)len, flags); 120 } 121 } 122 123 static int pcre_keyvalue_buffer_subst_ext(buffer *b, const char *pattern, const char **list, int n, const pcre_keyvalue_ctx *ctx) { 124 const unsigned char *p = (unsigned char *)pattern+2;/* +2 past ${} or %{} */ 125 int flags = 0; 126 while (!light_isdigit(*p) && *p != '}' && *p != '\0') { 127 if (0) { 128 } 129 else if (p[0] == 'e' && p[1] == 's' && p[2] == 'c') { 130 p+=3; 131 if (p[0] == ':') { 132 flags |= BURL_ENCODE_ALL; 133 p+=1; 134 } 135 else if (0 == strncmp((const char *)p, "ape:", 4)) { 136 flags |= BURL_ENCODE_ALL; 137 p+=4; 138 } 139 else if (0 == strncmp((const char *)p, "nde:", 4)) { 140 flags |= BURL_ENCODE_NDE; 141 p+=4; 142 } 143 else if (0 == strncmp((const char *)p, "psnde:", 6)) { 144 flags |= BURL_ENCODE_PSNDE; 145 p+=6; 146 } 147 else { /* skip unrecognized esc... */ 148 p = (const unsigned char *)strchr((const char *)p, ':'); 149 if (NULL == p) return -1; 150 ++p; 151 } 152 } 153 else if (p[0] == 'n' && p[1] == 'o') { 154 p+=2; 155 if (0 == strncmp((const char *)p, "esc:", 4)) { 156 flags |= BURL_ENCODE_NONE; 157 p+=4; 158 } 159 else if (0 == strncmp((const char *)p, "escape:", 7)) { 160 flags |= BURL_ENCODE_NONE; 161 p+=7; 162 } 163 else { /* skip unrecognized no... */ 164 p = (const unsigned char *)strchr((const char *)p, ':'); 165 if (NULL == p) return -1; 166 ++p; 167 } 168 } 169 else if (p[0] == 't' && p[1] == 'o') { 170 p+=2; 171 if (0 == strncmp((const char *)p, "lower:", 6)) { 172 flags |= BURL_TOLOWER; 173 p+=6; 174 } 175 else if (0 == strncmp((const char *)p, "upper:", 6)) { 176 flags |= BURL_TOLOWER; 177 p+=6; 178 } 179 else { /* skip unrecognized to... */ 180 p = (const unsigned char *)strchr((const char *)p, ':'); 181 if (NULL == p) return -1; 182 ++p; 183 } 184 } 185 else if (p[0] == 'u' && p[1] == 'r' && p[2] == 'l' && p[3] == '.') { 186 const struct burl_parts_t * const burl = ctx->burl; 187 p+=4; 188 if (0 == strncmp((const char *)p, "scheme}", 7)) { 189 if (burl->scheme) 190 burl_append(b, BUF_PTR_LEN(burl->scheme), flags); 191 p+=6; 192 } 193 else if (0 == strncmp((const char *)p, "authority}", 10)) { 194 if (burl->authority) 195 burl_append(b, BUF_PTR_LEN(burl->authority), flags); 196 p+=9; 197 } 198 else if (0 == strncmp((const char *)p, "port}", 5)) { 199 buffer_append_int(b, (int)burl->port); 200 p+=4; 201 } 202 else if (0 == strncmp((const char *)p, "path}", 5)) { 203 const buffer * const target = burl->path; 204 const uint32_t len = buffer_clen(target); 205 const char * const ptr = target->ptr; 206 const char * const qmark = memchr(ptr, '?', len); 207 burl_append(b, ptr, qmark ? (uint32_t)(qmark-ptr) : len, flags); 208 p+=4; 209 } 210 else if (0 == strncmp((const char *)p, "query}", 6)) { 211 if (burl->query) 212 burl_append(b, BUF_PTR_LEN(burl->query), flags); 213 p+=5; 214 } 215 else { /* skip unrecognized url.* */ 216 p = (const unsigned char *)strchr((const char *)p, '}'); 217 if (NULL == p) return -1; 218 } 219 break; 220 } 221 else if (p[0] == 'q' && p[1] == 's' && p[2] == 'a' && p[3] == '}') { 222 const buffer *qs = ctx->burl->query; 223 if (qs && !buffer_is_unset(qs)) { 224 if (NULL != strchr(b->ptr, '?')) { 225 if (!buffer_is_blank(qs)) 226 buffer_append_string_len(b, CONST_STR_LEN("&")); 227 } 228 else { 229 buffer_append_string_len(b, CONST_STR_LEN("?")); 230 } 231 burl_append(b, BUF_PTR_LEN(qs), flags); 232 } 233 p+=3; 234 break; 235 } 236 else if (p[0] == 'e' && p[1] == 'n' && p[2] == 'c' 237 && 0 == strncmp((const char *)p+3, "b64u:", 5)) { 238 flags |= BURL_ENCODE_B64U; 239 p+=8; 240 } 241 else if (p[0] == 'd' && p[1] == 'e' && p[2] == 'c' 242 && 0 == strncmp((const char *)p+3, "b64u:", 5)) { 243 flags |= BURL_DECODE_B64U; 244 p+=8; 245 } 246 else ++p; /* skip unrecognized char */ 247 } 248 if (*p == '\0') return -1; 249 if (*p != '}') { /* light_isdigit(*p) */ 250 unsigned int num = *p - '0'; 251 ++p; 252 if (light_isdigit(*p)) num = num * 10 + (*p++ - '0'); 253 if (*p != '}') { 254 p = (const unsigned char *)strchr((const char *)p, '}'); 255 if (NULL == p) return -1; 256 } 257 if (0 == flags) flags = BURL_ENCODE_PSNDE; /* default */ 258 pattern[0] == '$' /*(else '%')*/ 259 ? pcre_keyvalue_buffer_append_match(b, list, n, num, flags) 260 : pcre_keyvalue_buffer_append_ctxmatch(b, ctx, num, flags); 261 } 262 return (int)(p + 1 - (unsigned char *)pattern - 2); 263 } 264 265 static void pcre_keyvalue_buffer_subst(buffer *b, const buffer *patternb, const char **list, int n, const pcre_keyvalue_ctx *ctx) { 266 const char *pattern = patternb->ptr; 267 const size_t pattern_len = buffer_clen(patternb); 268 size_t start = 0; 269 270 /* search for $... or %... pattern substitutions */ 271 272 buffer_clear(b); 273 274 for (size_t k = 0; k + 1 < pattern_len; ++k) { 275 if (pattern[k] == '$' || pattern[k] == '%') { 276 277 buffer_append_string_len(b, pattern + start, k - start); 278 279 if (pattern[k + 1] == '{') { 280 int num = pcre_keyvalue_buffer_subst_ext(b, pattern+k, list, n, ctx); 281 if (num < 0) return; /* error; truncate result */ 282 k += (size_t)num; 283 } else if (light_isdigit(((unsigned char *)pattern)[k + 1])) { 284 unsigned int num = (unsigned int)pattern[k + 1] - '0'; 285 pattern[k] == '$' /*(else '%')*/ 286 ? pcre_keyvalue_buffer_append_match(b, list, n, num, 0) 287 : pcre_keyvalue_buffer_append_ctxmatch(b, ctx, num, 0); 288 } else { 289 /* enable escape: "%%" => "%", "%a" => "%a", "$$" => "$" */ 290 buffer_append_string_len(b, pattern+k, pattern[k] == pattern[k+1] ? 1 : 2); 291 } 292 293 k++; 294 start = k + 1; 295 } 296 } 297 298 buffer_append_string_len(b, pattern + start, pattern_len - start); 299 } 300 301 handler_t pcre_keyvalue_buffer_process(const pcre_keyvalue_buffer *kvb, pcre_keyvalue_ctx *ctx, const buffer *input, buffer *result) { 302 for (int i = 0, used = (int)kvb->used; i < used; ++i) { 303 const pcre_keyvalue * const kv = kvb->kv+i; 304 #define N 20 305 int ovec[N * 3]; 306 #undef N 307 int n = pcre_exec(kv->key, kv->key_extra, BUF_PTR_LEN(input), 308 0, 0, ovec, sizeof(ovec)/sizeof(int)); 309 if (n < 0) { 310 if (n != PCRE_ERROR_NOMATCH) { 311 return HANDLER_ERROR; 312 } 313 } 314 else if (buffer_is_blank(&kv->value)) { 315 /* short-circuit if blank replacement pattern 316 * (do not attempt to match against remaining kvb rules) */ 317 ctx->m = i; 318 return HANDLER_GO_ON; 319 } 320 else { /* it matched */ 321 const char **list; 322 ctx->m = i; 323 pcre_get_substring_list(input->ptr, ovec, n, &list); 324 pcre_keyvalue_buffer_subst(result, &kv->value, list, n, ctx); 325 pcre_free(list); 326 return HANDLER_FINISHED; 327 } 328 } 329 330 return HANDLER_GO_ON; 331 } 332 #else 333 handler_t pcre_keyvalue_buffer_process(const pcre_keyvalue_buffer *kvb, pcre_keyvalue_ctx *ctx, const buffer *input, buffer *result) { 334 UNUSED(kvb); 335 UNUSED(ctx); 336 UNUSED(input); 337 UNUSED(result); 338 return HANDLER_GO_ON; 339 } 340 #endif 341 342 343 /* modified from burl_normalize_basic() to handle %% extra encoding layer */ 344 345 /* c (char) and n (nibble) MUST be unsigned integer types */ 346 #define li_cton(c,n) \ 347 (((n) = (c) - '0') <= 9 || (((n) = ((c)&0xdf) - 'A') <= 5 ? ((n) += 10) : 0)) 348 349 static void pcre_keyvalue_burl_percent_toupper (buffer *b) 350 { 351 const unsigned char * const s = (unsigned char *)b->ptr; 352 const int used = (int)buffer_clen(b); 353 unsigned int n1, n2; 354 for (int i = 0; i < used; ++i) { 355 if (s[i]=='%' && li_cton(s[i+1],n1) && li_cton(s[i+2],n2)) { 356 if (s[i+1] >= 'a') b->ptr[i+1] &= 0xdf; /* uppercase hex */ 357 if (s[i+2] >= 'a') b->ptr[i+2] &= 0xdf; /* uppercase hex */ 358 i+=2; 359 } 360 } 361 } 362 363 static void pcre_keyvalue_burl_percent_percent_toupper (buffer *b) 364 { 365 const unsigned char * const s = (unsigned char *)b->ptr; 366 const int used = (int)buffer_clen(b); 367 unsigned int n1, n2; 368 for (int i = 0; i < used; ++i) { 369 if (s[i] == '%' && s[i+1]=='%' 370 && li_cton(s[i+2],n1) && li_cton(s[i+3],n2)) { 371 if (s[i+2] >= 'a') b->ptr[i+2] &= 0xdf; /* uppercase hex */ 372 if (s[i+3] >= 'a') b->ptr[i+3] &= 0xdf; /* uppercase hex */ 373 i+=3; 374 } 375 } 376 } 377 378 static const char hex_chars_uc[] = "0123456789ABCDEF"; 379 380 static void pcre_keyvalue_burl_percent_high_UTF8 (buffer *b, buffer *t) 381 { 382 const unsigned char * const s = (unsigned char *)b->ptr; 383 unsigned char *p; 384 const int used = (int)buffer_clen(b); 385 unsigned int count = 0, j = 0; 386 for (int i = 0; i < used; ++i) { 387 if (s[i] > 0x7F) ++count; 388 } 389 if (0 == count) return; 390 391 p = (unsigned char *)buffer_string_prepare_copy(t, used+(count*2)); 392 for (int i = 0; i < used; ++i, ++j) { 393 if (s[i] <= 0x7F) 394 p[j] = s[i]; 395 else { 396 p[j] = '%'; 397 p[++j] = hex_chars_uc[(s[i] >> 4) & 0xF]; 398 p[++j] = hex_chars_uc[s[i] & 0xF]; 399 } 400 } 401 buffer_copy_string_len(b, (char *)p, (size_t)j); 402 } 403 404 static void pcre_keyvalue_burl_percent_percent_high_UTF8 (buffer *b, buffer *t) 405 { 406 const unsigned char * const s = (unsigned char *)b->ptr; 407 unsigned char *p; 408 const int used = (int)buffer_clen(b); 409 unsigned int count = 0, j = 0; 410 for (int i = 0; i < used; ++i) { 411 if (s[i] > 0x7F) ++count; 412 } 413 if (0 == count) return; 414 415 p = (unsigned char *)buffer_string_prepare_copy(t, used+(count*3)); 416 for (int i = 0; i < used; ++i, ++j) { 417 if (s[i] <= 0x7F) 418 p[j] = s[i]; 419 else { 420 p[j] = '%'; 421 p[++j] = '%'; 422 p[++j] = hex_chars_uc[(s[i] >> 4) & 0xF]; 423 p[++j] = hex_chars_uc[s[i] & 0xF]; 424 } 425 } 426 buffer_copy_string_len(b, (char *)p, (size_t)j); 427 } 428 429 /* Basic normalization of regex and regex replacement to mirror some of 430 * the normalizations performed on request URI (for better compatibility). 431 * Note: not currently attempting to replace unnecessary percent-encoding 432 * (would need to know if regex was intended to match url-path or 433 * query-string or both, and then would have to regex-escape if those 434 * chars where special regex chars such as . * + ? ( ) [ ] | and more) 435 * Not attempting to percent-encode chars which should be encoded, again 436 * since regex might target url-path, query-string, or both, and we would 437 * have to avoid percent-encoding special regex chars. 438 * Also not attempting to detect unnecessarily regex-escape in, e.g. %\x\x 439 * Preserve improper %-encoded sequences which are not %XX (using hex chars) 440 * Intentionally not performing path simplification (e.g. ./ ../) 441 * If regex-specific normalizations begin to be made to k here, 442 * must revisit callers, e.g. one configfile.c use on non-regex string. 443 * "%%" (percent_percent) is used in regex replacement strings since 444 * otherwise "%n" is used to indicate regex backreference where n is number. 445 */ 446 447 void pcre_keyvalue_burl_normalize_key (buffer *k, buffer *t) 448 { 449 pcre_keyvalue_burl_percent_toupper(k); 450 pcre_keyvalue_burl_percent_high_UTF8(k, t); 451 } 452 453 void pcre_keyvalue_burl_normalize_value (buffer *v, buffer *t) 454 { 455 pcre_keyvalue_burl_percent_percent_toupper(v); 456 pcre_keyvalue_burl_percent_percent_high_UTF8(v, t); 457 } 458