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