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, 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, 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 p+=4; 187 if (0 == strncmp((const char *)p, "scheme}", 7)) { 188 burl_append(b, CONST_BUF_LEN(ctx->burl->scheme), flags); 189 p+=6; 190 } 191 else if (0 == strncmp((const char *)p, "authority}", 10)) { 192 burl_append(b, CONST_BUF_LEN(ctx->burl->authority), flags); 193 p+=9; 194 } 195 else if (0 == strncmp((const char *)p, "port}", 5)) { 196 buffer_append_int(b, (int)ctx->burl->port); 197 p+=4; 198 } 199 else if (0 == strncmp((const char *)p, "path}", 5)) { 200 const buffer * const target = ctx->burl->path; 201 const uint32_t len = buffer_string_length(target); 202 const char * const ptr = target->ptr; 203 const char * const qmark = memchr(ptr, '?', len); 204 burl_append(b, ptr, qmark ? (uint32_t)(qmark-ptr) : len, flags); 205 p+=4; 206 } 207 else if (0 == strncmp((const char *)p, "query}", 6)) { 208 burl_append(b, CONST_BUF_LEN(ctx->burl->query), flags); 209 p+=5; 210 } 211 else { /* skip unrecognized url.* */ 212 p = (const unsigned char *)strchr((const char *)p, '}'); 213 if (NULL == p) return -1; 214 } 215 break; 216 } 217 else if (p[0] == 'q' && p[1] == 's' && p[2] == 'a' && p[3] == '}') { 218 const buffer *qs = ctx->burl->query; 219 if (!buffer_is_empty(qs)) { 220 if (NULL != strchr(b->ptr, '?')) { 221 if (!buffer_string_is_empty(qs)) 222 buffer_append_string_len(b, CONST_STR_LEN("&")); 223 } 224 else { 225 buffer_append_string_len(b, CONST_STR_LEN("?")); 226 } 227 burl_append(b, CONST_BUF_LEN(qs), flags); 228 } 229 p+=3; 230 break; 231 } 232 else if (p[0] == 'e' && p[1] == 'n' && p[2] == 'c' 233 && 0 == strncmp((const char *)p+3, "b64u:", 5)) { 234 flags |= BURL_ENCODE_B64U; 235 p+=8; 236 } 237 else if (p[0] == 'd' && p[1] == 'e' && p[2] == 'c' 238 && 0 == strncmp((const char *)p+3, "b64u:", 5)) { 239 flags |= BURL_DECODE_B64U; 240 p+=8; 241 } 242 else ++p; /* skip unrecognized char */ 243 } 244 if (*p == '\0') return -1; 245 if (*p != '}') { /* light_isdigit(*p) */ 246 unsigned int num = *p - '0'; 247 ++p; 248 if (light_isdigit(*p)) num = num * 10 + (*p++ - '0'); 249 if (*p != '}') { 250 p = (const unsigned char *)strchr((const char *)p, '}'); 251 if (NULL == p) return -1; 252 } 253 if (0 == flags) flags = BURL_ENCODE_PSNDE; /* default */ 254 pattern[0] == '$' /*(else '%')*/ 255 ? pcre_keyvalue_buffer_append_match(b, list, n, num, flags) 256 : pcre_keyvalue_buffer_append_ctxmatch(b, ctx, num, flags); 257 } 258 return (int)(p + 1 - (unsigned char *)pattern - 2); 259 } 260 261 static void pcre_keyvalue_buffer_subst(buffer *b, const buffer *patternb, const char **list, int n, pcre_keyvalue_ctx *ctx) { 262 const char *pattern = patternb->ptr; 263 const size_t pattern_len = buffer_string_length(patternb); 264 size_t start = 0; 265 266 /* search for $... or %... pattern substitutions */ 267 268 buffer_clear(b); 269 270 for (size_t k = 0; k + 1 < pattern_len; ++k) { 271 if (pattern[k] == '$' || pattern[k] == '%') { 272 273 buffer_append_string_len(b, pattern + start, k - start); 274 275 if (pattern[k + 1] == '{') { 276 int num = pcre_keyvalue_buffer_subst_ext(b, pattern+k, list, n, ctx); 277 if (num < 0) return; /* error; truncate result */ 278 k += (size_t)num; 279 } else if (light_isdigit(((unsigned char *)pattern)[k + 1])) { 280 unsigned int num = (unsigned int)pattern[k + 1] - '0'; 281 pattern[k] == '$' /*(else '%')*/ 282 ? pcre_keyvalue_buffer_append_match(b, list, n, num, 0) 283 : pcre_keyvalue_buffer_append_ctxmatch(b, ctx, num, 0); 284 } else { 285 /* enable escape: "%%" => "%", "%a" => "%a", "$$" => "$" */ 286 buffer_append_string_len(b, pattern+k, pattern[k] == pattern[k+1] ? 1 : 2); 287 } 288 289 k++; 290 start = k + 1; 291 } 292 } 293 294 buffer_append_string_len(b, pattern + start, pattern_len - start); 295 } 296 297 handler_t pcre_keyvalue_buffer_process(const pcre_keyvalue_buffer *kvb, pcre_keyvalue_ctx *ctx, const buffer *input, buffer *result) { 298 for (int i = 0, used = (int)kvb->used; i < used; ++i) { 299 const pcre_keyvalue * const kv = kvb->kv+i; 300 #define N 20 301 int ovec[N * 3]; 302 #undef N 303 int n = pcre_exec(kv->key, kv->key_extra, CONST_BUF_LEN(input), 304 0, 0, ovec, sizeof(ovec)/sizeof(int)); 305 if (n < 0) { 306 if (n != PCRE_ERROR_NOMATCH) { 307 return HANDLER_ERROR; 308 } 309 } 310 else if (buffer_string_is_empty(&kv->value)) { 311 /* short-circuit if blank replacement pattern 312 * (do not attempt to match against remaining kvb rules) */ 313 ctx->m = i; 314 return HANDLER_GO_ON; 315 } 316 else { /* it matched */ 317 const char **list; 318 ctx->m = i; 319 pcre_get_substring_list(input->ptr, ovec, n, &list); 320 pcre_keyvalue_buffer_subst(result, &kv->value, list, n, ctx); 321 pcre_free(list); 322 return HANDLER_FINISHED; 323 } 324 } 325 326 return HANDLER_GO_ON; 327 } 328 #else 329 handler_t pcre_keyvalue_buffer_process(const pcre_keyvalue_buffer *kvb, pcre_keyvalue_ctx *ctx, const buffer *input, buffer *result) { 330 UNUSED(kvb); 331 UNUSED(ctx); 332 UNUSED(input); 333 UNUSED(result); 334 return HANDLER_GO_ON; 335 } 336 #endif 337 338 339 /* modified from burl_normalize_basic() to handle %% extra encoding layer */ 340 341 /* c (char) and n (nibble) MUST be unsigned integer types */ 342 #define li_cton(c,n) \ 343 (((n) = (c) - '0') <= 9 || (((n) = ((c)&0xdf) - 'A') <= 5 ? ((n) += 10) : 0)) 344 345 static void pcre_keyvalue_burl_percent_toupper (buffer *b) 346 { 347 const unsigned char * const s = (unsigned char *)b->ptr; 348 const int used = (int)buffer_string_length(b); 349 unsigned int n1, n2; 350 for (int i = 0; i < used; ++i) { 351 if (s[i]=='%' && li_cton(s[i+1],n1) && li_cton(s[i+2],n2)) { 352 if (s[i+1] >= 'a') b->ptr[i+1] &= 0xdf; /* uppercase hex */ 353 if (s[i+2] >= 'a') b->ptr[i+2] &= 0xdf; /* uppercase hex */ 354 i+=2; 355 } 356 } 357 } 358 359 static void pcre_keyvalue_burl_percent_percent_toupper (buffer *b) 360 { 361 const unsigned char * const s = (unsigned char *)b->ptr; 362 const int used = (int)buffer_string_length(b); 363 unsigned int n1, n2; 364 for (int i = 0; i < used; ++i) { 365 if (s[i] == '%' && s[i+1]=='%' 366 && li_cton(s[i+2],n1) && li_cton(s[i+3],n2)) { 367 if (s[i+2] >= 'a') b->ptr[i+2] &= 0xdf; /* uppercase hex */ 368 if (s[i+3] >= 'a') b->ptr[i+3] &= 0xdf; /* uppercase hex */ 369 i+=3; 370 } 371 } 372 } 373 374 static const char hex_chars_uc[] = "0123456789ABCDEF"; 375 376 static void pcre_keyvalue_burl_percent_high_UTF8 (buffer *b, buffer *t) 377 { 378 const unsigned char * const s = (unsigned char *)b->ptr; 379 unsigned char *p; 380 const int used = (int)buffer_string_length(b); 381 unsigned int count = 0, j = 0; 382 for (int i = 0; i < used; ++i) { 383 if (s[i] > 0x7F) ++count; 384 } 385 if (0 == count) return; 386 387 p = (unsigned char *)buffer_string_prepare_copy(t, used+(count*2)); 388 for (int i = 0; i < used; ++i, ++j) { 389 if (s[i] <= 0x7F) 390 p[j] = s[i]; 391 else { 392 p[j] = '%'; 393 p[++j] = hex_chars_uc[(s[i] >> 4) & 0xF]; 394 p[++j] = hex_chars_uc[s[i] & 0xF]; 395 } 396 } 397 buffer_copy_string_len(b, (char *)p, (size_t)j); 398 } 399 400 static void pcre_keyvalue_burl_percent_percent_high_UTF8 (buffer *b, buffer *t) 401 { 402 const unsigned char * const s = (unsigned char *)b->ptr; 403 unsigned char *p; 404 const int used = (int)buffer_string_length(b); 405 unsigned int count = 0, j = 0; 406 for (int i = 0; i < used; ++i) { 407 if (s[i] > 0x7F) ++count; 408 } 409 if (0 == count) return; 410 411 p = (unsigned char *)buffer_string_prepare_copy(t, used+(count*3)); 412 for (int i = 0; i < used; ++i, ++j) { 413 if (s[i] <= 0x7F) 414 p[j] = s[i]; 415 else { 416 p[j] = '%'; 417 p[++j] = '%'; 418 p[++j] = hex_chars_uc[(s[i] >> 4) & 0xF]; 419 p[++j] = hex_chars_uc[s[i] & 0xF]; 420 } 421 } 422 buffer_copy_string_len(b, (char *)p, (size_t)j); 423 } 424 425 /* Basic normalization of regex and regex replacement to mirror some of 426 * the normalizations performed on request URI (for better compatibility). 427 * Note: not currently attempting to replace unnecessary percent-encoding 428 * (would need to know if regex was intended to match url-path or 429 * query-string or both, and then would have to regex-escape if those 430 * chars where special regex chars such as . * + ? ( ) [ ] | and more) 431 * Not attempting to percent-encode chars which should be encoded, again 432 * since regex might target url-path, query-string, or both, and we would 433 * have to avoid percent-encoding special regex chars. 434 * Also not attempting to detect unnecessarily regex-escape in, e.g. %\x\x 435 * Preserve improper %-encoded sequences which are not %XX (using hex chars) 436 * Intentionally not performing path simplification (e.g. ./ ../) 437 * If regex-specific normalizations begin to be made to k here, 438 * must revisit callers, e.g. one configfile.c use on non-regex string. 439 * "%%" (percent_percent) is used in regex replacement strings since 440 * otherwise "%n" is used to indicate regex backreference where n is number. 441 */ 442 443 void pcre_keyvalue_burl_normalize_key (buffer *k, buffer *t) 444 { 445 pcre_keyvalue_burl_percent_toupper(k); 446 pcre_keyvalue_burl_percent_high_UTF8(k, t); 447 } 448 449 void pcre_keyvalue_burl_normalize_value (buffer *v, buffer *t) 450 { 451 pcre_keyvalue_burl_percent_percent_toupper(v); 452 pcre_keyvalue_burl_percent_percent_high_UTF8(v, t); 453 } 454