xref: /lighttpd1.4/src/burl.c (revision 9570cc2f)
1 /*
2  * burl - buffer URL normalization
3  *
4  * Copyright(c) 2018 Glenn Strauss gstrauss()gluelogic.com  All rights reserved
5  * License: BSD 3-clause (same as lighttpd)
6  */
7 #include "first.h"
8 #include "burl.h"
9 
10 #include <string.h>
11 
12 #include "buffer.h"
13 #include "base64.h"
14 
15 static const char hex_chars_uc[] = "0123456789ABCDEF";
16 
17 /* everything except: ! $ & ' ( ) * + , - . / 0-9 : ; = ? @ A-Z _ a-z ~ */
18 static const char encoded_chars_http_uri_reqd[] = {
19   /*
20   0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
21   */
22   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /*  00 -  0F control chars */
23   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /*  10 -  1F */
24   1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  /*  20 -  2F space " # % */
25   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0,  /*  30 -  3F < > */
26   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  /*  40 -  4F */
27   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0,  /*  50 -  5F [ \ ] ^ */
28   1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  /*  60 -  6F ` */
29   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1,  /*  70 -  7F { | } DEL */
30   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /*  80 -  8F */
31   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /*  90 -  9F */
32   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /*  A0 -  AF */
33   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /*  B0 -  BF */
34   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /*  C0 -  CF */
35   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /*  D0 -  DF */
36   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /*  E0 -  EF */
37   1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /*  F0 -  FF */
38 };
39 
40 
41 /* c (char) and n (nibble) MUST be unsigned integer types */
42 #define li_cton(c,n) \
43   (((n) = (c) - '0') <= 9 || (((n) = ((c)&0xdf) - 'A') <= 5 ? ((n) += 10) : 0))
44 
45 /* b (byte) MUST be unsigned integer type
46  * https://en.wikipedia.org/wiki/UTF-8
47  * reject overlong encodings of 7-byte ASCII and invalid UTF-8
48  * (but does not detect other overlong multi-byte encodings) */
49 #define li_utf8_invalid_byte(b) ((b) >= 0xF5 || ((b)|0x1) == 0xC1)
50 
51 
52 static int burl_is_unreserved (const int c)
53 {
54     return (light_isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~');
55 }
56 
57 
58 static int burl_normalize_basic_unreserved_fix (buffer *b, buffer *t, int i, int qs)
59 {
60     int j = i;
61     const int used = (int)buffer_string_length(b);
62     const unsigned char * const s = (unsigned char *)b->ptr;
63     unsigned char * const p =
64       (unsigned char *)buffer_string_prepare_copy(t,i+(used-i)*3+1);
65     unsigned int n1, n2;
66     memcpy(p, s, (size_t)i);
67     for (; i < used; ++i, ++j) {
68         if (!encoded_chars_http_uri_reqd[s[i]]) {
69             if (s[i] == '?' && -1 == qs) qs = j;
70             p[j] = s[i];
71         }
72         else if (s[i]=='%' && li_cton(s[i+1], n1) && li_cton(s[i+2], n2)) {
73             const unsigned int x = (n1 << 4) | n2;
74             if (burl_is_unreserved(x)) {
75                 p[j] = x;
76             }
77             else {
78                 p[j]   = '%';
79                 p[++j] = hex_chars_uc[n1]; /*(s[i+1] & 0xdf)*/
80                 p[++j] = hex_chars_uc[n2]; /*(s[i+2] & 0xdf)*/
81                 if (li_utf8_invalid_byte(x)) qs = -2;
82             }
83             i+=2;
84         }
85         else if (s[i] == '#') break; /* ignore fragment */
86         else {
87             p[j]   = '%';
88             p[++j] = hex_chars_uc[(s[i] >> 4) & 0xF];
89             p[++j] = hex_chars_uc[s[i] & 0xF];
90             if (li_utf8_invalid_byte(s[i])) qs = -2;
91         }
92     }
93     buffer_commit(t, (size_t)j);
94     buffer_copy_buffer(b, t);
95     return qs;
96 }
97 
98 
99 static int burl_normalize_basic_unreserved (buffer *b, buffer *t)
100 {
101     const unsigned char * const s = (unsigned char *)b->ptr;
102     const int used = (int)buffer_string_length(b);
103     unsigned int n1, n2, x;
104     int qs = -1;
105 
106     for (int i = 0; i < used; ++i) {
107         if (!encoded_chars_http_uri_reqd[s[i]]) {
108             if (s[i] == '?' && -1 == qs) qs = i;
109         }
110         else if (s[i]=='%' && li_cton(s[i+1], n1) && li_cton(s[i+2], n2)
111                  && !burl_is_unreserved((x = (n1 << 4) | n2))) {
112             if (li_utf8_invalid_byte(x)) qs = -2;
113             if (s[i+1] >= 'a') b->ptr[i+1] &= 0xdf; /* uppercase hex */
114             if (s[i+2] >= 'a') b->ptr[i+2] &= 0xdf; /* uppercase hex */
115             i+=2;
116         }
117         else if (s[i] == '#') { /* ignore fragment */
118             buffer_string_set_length(b, (size_t)i);
119             break;
120         }
121         else {
122             qs = burl_normalize_basic_unreserved_fix(b, t, i, qs);
123             break;
124         }
125     }
126 
127     return qs;
128 }
129 
130 
131 static int burl_normalize_basic_required_fix (buffer *b, buffer *t, int i, int qs)
132 {
133     int j = i;
134     const int used = (int)buffer_string_length(b);
135     const unsigned char * const s = (unsigned char *)b->ptr;
136     unsigned char * const p =
137       (unsigned char *)buffer_string_prepare_copy(t,i+(used-i)*3+1);
138     unsigned int n1, n2;
139     memcpy(p, s, (size_t)i);
140     for (; i < used; ++i, ++j) {
141         if (!encoded_chars_http_uri_reqd[s[i]]) {
142             if (s[i] == '?' && -1 == qs) qs = j;
143             p[j] = s[i];
144         }
145         else if (s[i]=='%' && li_cton(s[i+1], n1) && li_cton(s[i+2], n2)) {
146             const unsigned int x = (n1 << 4) | n2;
147             if (!encoded_chars_http_uri_reqd[x]
148                 && (qs < 0
149                     ? (x != '/' && x != '?')
150                     : (x != '&' && x != '=' && x != ';' && x != '+'))) {
151                 p[j] = x;
152             }
153             else {
154                 p[j]   = '%';
155                 p[++j] = hex_chars_uc[n1]; /*(s[i+1] & 0xdf)*/
156                 p[++j] = hex_chars_uc[n2]; /*(s[i+2] & 0xdf)*/
157                 if (li_utf8_invalid_byte(x)) qs = -2;
158             }
159             i+=2;
160         }
161         else if (s[i] == '#') break; /* ignore fragment */
162         else {
163             p[j]   = '%';
164             p[++j] = hex_chars_uc[(s[i] >> 4) & 0xF];
165             p[++j] = hex_chars_uc[s[i] & 0xF];
166             if (li_utf8_invalid_byte(s[i])) qs = -2;
167         }
168     }
169     buffer_commit(t, (size_t)j);
170     buffer_copy_buffer(b, t);
171     return qs;
172 }
173 
174 
175 static int burl_normalize_basic_required (buffer *b, buffer *t)
176 {
177     const unsigned char * const s = (unsigned char *)b->ptr;
178     const int used = (int)buffer_string_length(b);
179     unsigned int n1, n2, x;
180     int qs = -1;
181 
182     for (int i = 0; i < used; ++i) {
183         if (!encoded_chars_http_uri_reqd[s[i]]) {
184             if (s[i] == '?' && -1 == qs) qs = i;
185         }
186         else if (s[i]=='%' && li_cton(s[i+1], n1) && li_cton(s[i+2], n2)
187                  && (encoded_chars_http_uri_reqd[(x = (n1 << 4) | n2)]
188                      || (qs < 0
189                          ? (x == '/' || x == '?')
190                          : (x == '&' || x == '=' || x == ';' || x == '+')))) {
191             if (li_utf8_invalid_byte(x)) qs = -2;
192             if (s[i+1] >= 'a') b->ptr[i+1] &= 0xdf; /* uppercase hex */
193             if (s[i+2] >= 'a') b->ptr[i+2] &= 0xdf; /* uppercase hex */
194             i+=2;
195         }
196         else if (s[i] == '#') { /* ignore fragment */
197             buffer_string_set_length(b, (size_t)i);
198             break;
199         }
200         else {
201             qs = burl_normalize_basic_required_fix(b, t, i, qs);
202             break;
203         }
204     }
205 
206     return qs;
207 }
208 
209 
210 static int burl_contains_ctrls (const buffer *b)
211 {
212     const char * const s = b->ptr;
213     const int used = (int)buffer_string_length(b);
214     for (int i = 0; i < used; ++i) {
215         if (s[i] == '%' && (s[i+1] < '2' || (s[i+1] == '7' && s[i+2] == 'F')))
216             return 1;
217     }
218     return 0;
219 }
220 
221 
222 static void burl_normalize_qs20_to_plus_fix (buffer *b, int i)
223 {
224     char * const s = b->ptr;
225     const int used = (int)buffer_string_length(b);
226     int j = i;
227     for (; i < used; ++i, ++j) {
228         s[j] = s[i];
229         if (s[i] == '%' && s[i+1] == '2' && s[i+2] == '0') {
230             s[j] = '+';
231             i+=2;
232         }
233     }
234     buffer_string_set_length(b, j);
235 }
236 
237 
238 static void burl_normalize_qs20_to_plus (buffer *b, int qs)
239 {
240     const char * const s = b->ptr;
241     const int used = qs < 0 ? 0 : (int)buffer_string_length(b);
242     int i;
243     if (qs < 0) return;
244     for (i = qs+1; i < used; ++i) {
245         if (s[i] == '%' && s[i+1] == '2' && s[i+2] == '0') break;
246     }
247     if (i != used) burl_normalize_qs20_to_plus_fix(b, i);
248 }
249 
250 
251 static int burl_normalize_2F_to_slash_fix (buffer *b, int qs, int i)
252 {
253     char * const s = b->ptr;
254     const int blen = (int)buffer_string_length(b);
255     const int used = qs < 0 ? blen : qs;
256     int j = i;
257     for (; i < used; ++i, ++j) {
258         s[j] = s[i];
259         if (s[i] == '%' && s[i+1] == '2' && s[i+2] == 'F') {
260             s[j] = '/';
261             i+=2;
262         }
263     }
264     if (qs >= 0) {
265         const int qslen = blen - qs;
266         memmove(s+j, s+qs, (size_t)qslen);
267         qs = j;
268         j += qslen;
269     }
270     buffer_string_set_length(b, j);
271     return qs;
272 }
273 
274 
275 static int burl_normalize_2F_to_slash (buffer *b, int qs, int flags)
276 {
277     /*("%2F" must already have been uppercased during normalization)*/
278     const char * const s = b->ptr;
279     const int used = qs < 0 ? (int)buffer_string_length(b) : qs;
280     for (int i = 0; i < used; ++i) {
281         if (s[i] == '%' && s[i+1] == '2' && s[i+2] == 'F') {
282             return (flags & HTTP_PARSEOPT_URL_NORMALIZE_PATH_2F_DECODE)
283               ? burl_normalize_2F_to_slash_fix(b, qs, i)
284               : -2; /*(flags & HTTP_PARSEOPT_URL_NORMALIZE_PATH_2F_REJECT)*/
285         }
286     }
287     return qs;
288 }
289 
290 
291 static int burl_normalize_path (buffer *b, buffer *t, int qs, int flags)
292 {
293     const unsigned char * const s = (unsigned char *)b->ptr;
294     const int used = (int)buffer_string_length(b);
295     int path_simplify = 0;
296     for (int i = 0, len = qs < 0 ? used : qs; i < len; ++i) {
297         if (s[i] == '.' && (s[i+1] != '.' || ++i)
298             && (s[i+1] == '/' || s[i+1] == '?' || s[i+1] == '\0')) {
299             path_simplify = 1;
300             break;
301         }
302         while (i < len && s[i] != '/') ++i;
303         if (s[i] == '/' && s[i+1] == '/') { /*(s[len] != '/')*/
304             path_simplify = 1;
305             break;
306         }
307     }
308 
309     if (path_simplify) {
310         if (flags & HTTP_PARSEOPT_URL_NORMALIZE_PATH_DOTSEG_REJECT) return -2;
311         if (qs >= 0) {
312             buffer_copy_string_len(t, b->ptr+qs, used - qs);
313             buffer_string_set_length(b, qs);
314         }
315 
316         buffer_path_simplify(b, b);
317 
318         if (qs >= 0) {
319             qs = (int)buffer_string_length(b);
320             buffer_append_string_len(b, CONST_BUF_LEN(t));
321         }
322     }
323 
324     return qs;
325 }
326 
327 
328 int burl_normalize (buffer *b, buffer *t, int flags)
329 {
330     int qs;
331 
332   #if defined(__WIN32) || defined(__CYGWIN__)
333     /* Windows and Cygwin treat '\\' as '/' if '\\' is present in path;
334      * convert to '/' for consistency before percent-encoding
335      * normalization which will convert '\\' to "%5C" in the URL.
336      * (Clients still should not be sending '\\' unencoded in requests.) */
337     if (flags & HTTP_PARSEOPT_URL_NORMALIZE_PATH_BACKSLASH_TRANS) {
338         for (char *p = b->ptr; *p != '?' && *p != '\0'; ++p) {
339             if (*p == '\\') *p = '/';
340         }
341     }
342   #endif
343 
344     qs = (flags & HTTP_PARSEOPT_URL_NORMALIZE_REQUIRED)
345       ? burl_normalize_basic_required(b, t)
346       : burl_normalize_basic_unreserved(b, t);
347     if (-2 == qs) return -2;
348 
349     if (flags & HTTP_PARSEOPT_URL_NORMALIZE_CTRLS_REJECT) {
350         if (burl_contains_ctrls(b)) return -2;
351     }
352 
353     if (flags & (HTTP_PARSEOPT_URL_NORMALIZE_PATH_2F_DECODE
354                 |HTTP_PARSEOPT_URL_NORMALIZE_PATH_2F_REJECT)) {
355         qs = burl_normalize_2F_to_slash(b, qs, flags);
356         if (-2 == qs) return -2;
357     }
358 
359     if (flags & (HTTP_PARSEOPT_URL_NORMALIZE_PATH_DOTSEG_REMOVE
360                 |HTTP_PARSEOPT_URL_NORMALIZE_PATH_DOTSEG_REJECT)) {
361         qs = burl_normalize_path(b, t, qs, flags);
362         if (-2 == qs) return -2;
363     }
364 
365     if (flags & HTTP_PARSEOPT_URL_NORMALIZE_QUERY_20_PLUS) {
366         if (qs >= 0) burl_normalize_qs20_to_plus(b, qs);
367     }
368 
369     return qs;
370 }
371 
372 
373 static void burl_append_encode_nde (buffer * const b, const char * const str, const size_t len)
374 {
375     /* percent-encodes everything except unreserved  - . 0-9 A-Z _ a-z ~
376      * unless already percent-encoded (does not double-encode) */
377     /* Note: not checking for invalid UTF-8 */
378     char * const p = buffer_string_prepare_append(b, len*3);
379     unsigned int n1, n2;
380     int j = 0;
381     for (unsigned int i = 0; i < len; ++i, ++j) {
382         if (str[i]=='%' && li_cton(str[i+1], n1) && li_cton(str[i+2], n2)) {
383             const unsigned int x = (n1 << 4) | n2;
384             if (burl_is_unreserved((int)x)) {
385                 p[j] = (char)x;
386             }
387             else { /* leave UTF-8, control chars, and required chars encoded */
388                 p[j]   = '%';
389                 p[++j] = str[i+1];
390                 p[++j] = str[i+2];
391             }
392             i+=2;
393         }
394         else if (burl_is_unreserved(str[i])) {
395             p[j] = str[i];
396         }
397         else {
398             p[j]   = '%';
399             p[++j] = hex_chars_uc[(str[i] >> 4) & 0xF];
400             p[++j] = hex_chars_uc[str[i] & 0xF];
401         }
402     }
403     buffer_commit(b, j);
404 }
405 
406 
407 static void burl_append_encode_psnde (buffer * const b, const char * const str, const size_t len)
408 {
409     /* percent-encodes everything except unreserved  - . 0-9 A-Z _ a-z ~ plus /
410      * unless already percent-encoded (does not double-encode) */
411     /* Note: not checking for invalid UTF-8 */
412     char * const p = buffer_string_prepare_append(b, len*3);
413     unsigned int n1, n2;
414     int j = 0;
415     for (unsigned int i = 0; i < len; ++i, ++j) {
416         if (str[i]=='%' && li_cton(str[i+1], n1) && li_cton(str[i+2], n2)) {
417             const unsigned int x = (n1 << 4) | n2;
418             if (burl_is_unreserved((int)x)) {
419                 p[j] = (char)x;
420             }
421             else { /* leave UTF-8, control chars, and required chars encoded */
422                 p[j]   = '%';
423                 p[++j] = str[i+1];
424                 p[++j] = str[i+2];
425             }
426             i+=2;
427         }
428         else if (burl_is_unreserved(str[i]) || str[i] == '/') {
429             p[j] = str[i];
430         }
431         else {
432             p[j]   = '%';
433             p[++j] = hex_chars_uc[(str[i] >> 4) & 0xF];
434             p[++j] = hex_chars_uc[str[i] & 0xF];
435         }
436     }
437     buffer_commit(b, j);
438 }
439 
440 
441 static void burl_append_encode_all (buffer * const b, const char * const str, const size_t len)
442 {
443     /* percent-encodes everything except unreserved  - . 0-9 A-Z _ a-z ~
444      * Note: double-encodes any existing '%') */
445     /* Note: not checking for invalid UTF-8 */
446     char * const p = buffer_string_prepare_append(b, len*3);
447     int j = 0;
448     for (unsigned int i = 0; i < len; ++i, ++j) {
449         if (burl_is_unreserved(str[i])) {
450             p[j] = str[i];
451         }
452         else {
453             p[j]   = '%';
454             p[++j] = hex_chars_uc[(str[i] >> 4) & 0xF];
455             p[++j] = hex_chars_uc[str[i] & 0xF];
456         }
457     }
458     buffer_commit(b, j);
459 }
460 
461 
462 static void burl_offset_tolower (buffer * const b, const size_t off)
463 {
464     /*(skips over all percent-encodings, including encoding of alpha chars)*/
465     for (char *p = b->ptr+off; p[0]; ++p) {
466         if (p[0] >= 'A' && p[0] <= 'Z') p[0] |= 0x20;
467         else if (p[0]=='%' && light_isxdigit(p[1]) && light_isxdigit(p[2]))
468             p+=2;
469     }
470 }
471 
472 
473 static void burl_offset_toupper (buffer * const b, const size_t off)
474 {
475     /*(skips over all percent-encodings, including encoding of alpha chars)*/
476     for (char *p = b->ptr+off; p[0]; ++p) {
477         if (p[0] >= 'a' && p[0] <= 'z') p[0] &= 0xdf;
478         else if (p[0]=='%' && light_isxdigit(p[1]) && light_isxdigit(p[2]))
479             p+=2;
480     }
481 }
482 
483 
484 void burl_append (buffer * const b, const char * const str, const size_t len, const int flags)
485 {
486     size_t off = 0;
487 
488     if (0 == len) return;
489 
490     if (0 == flags) {
491         buffer_append_string_len(b, str, len);
492         return;
493     }
494 
495     if (flags & (BURL_TOUPPER|BURL_TOLOWER)) off = buffer_string_length(b);
496 
497     if (flags & BURL_ENCODE_NONE) {
498         buffer_append_string_len(b, str, len);
499     }
500     else if (flags & BURL_ENCODE_ALL) {
501         burl_append_encode_all(b, str, len);
502     }
503     else if (flags & BURL_ENCODE_NDE) {
504         burl_append_encode_nde(b, str, len);
505     }
506     else if (flags & BURL_ENCODE_PSNDE) {
507         burl_append_encode_psnde(b, str, len);
508     }
509     else if (flags & BURL_ENCODE_B64U) {
510         const unsigned char *s = (const unsigned char *)str;
511         buffer_append_base64_encode_no_padding(b, s, len, BASE64_URL);
512     }
513     else if (flags & BURL_DECODE_B64U) {
514         buffer_append_base64_decode(b, str, len, BASE64_URL);
515     }
516 
517     /* note: not normalizing str, which could come from arbitrary header,
518      * so it is possible that alpha chars are percent-encoded upper/lowercase */
519     if (flags & (BURL_TOLOWER|BURL_TOUPPER)) {
520         (flags & BURL_TOLOWER)
521           ? burl_offset_tolower(b, off)  /*(flags & BURL_TOLOWER)*/
522           : burl_offset_toupper(b, off); /*(flags & BURL_TOUPPER)*/
523     }
524 }
525