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