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