xref: /lighttpd1.4/src/keyvalue.c (revision 94edf7e8)
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