/src/lighttpd1.4/src/burl.c
Line | Count | Source |
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 | 58.8M | (((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 | | * detect invalid UTF-8 byte and byte in overlong encoding of 7-bit ASCII |
48 | | * (but does not detect other invalid/overlong multibyte encoding sequences) */ |
49 | 58.5M | #define li_utf8_invalid_byte(b) light_utf8_invalid_byte(b) |
50 | | |
51 | | |
52 | | static int burl_is_unreserved (const int c) |
53 | 19.8k | { |
54 | 19.8k | return (light_isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~'); |
55 | 19.8k | } |
56 | | |
57 | | |
58 | | __attribute_cold__ |
59 | | static int burl_normalize_basic_unreserved_fix (buffer *b, buffer *t, int i, int qs) |
60 | 596 | { |
61 | 596 | int j = i; |
62 | 596 | const int used = (int)buffer_clen(b); |
63 | 596 | const unsigned char * const s = (unsigned char *)b->ptr; |
64 | 596 | unsigned char * const p = |
65 | 596 | (unsigned char *)buffer_string_prepare_copy(t,i+(used-i)*3+1); |
66 | 596 | unsigned int n1, n2; |
67 | 596 | int invalid_utf8 = 0; |
68 | 596 | memcpy(p, s, (size_t)i); |
69 | 26.5M | for (; i < used; ++i, ++j) { |
70 | 26.5M | if (!encoded_chars_http_uri_reqd[s[i]]) { |
71 | 967k | p[j] = s[i]; |
72 | 967k | if (__builtin_expect( (s[i] == '?'), 0) && -1 == qs) qs = j; |
73 | 967k | } |
74 | 25.5M | else if (s[i]=='%' && li_cton(s[i+1], n1) && li_cton(s[i+2], n2)) { |
75 | 17.6k | const unsigned int x = (n1 << 4) | n2; |
76 | 17.6k | if (burl_is_unreserved(x)) { |
77 | 1.58k | p[j] = x; |
78 | 1.58k | } |
79 | 16.0k | else { |
80 | 16.0k | p[j] = '%'; |
81 | 16.0k | p[++j] = hex_chars_uc[n1]; /*(s[i+1] & 0xdf)*/ |
82 | 16.0k | p[++j] = hex_chars_uc[n2]; /*(s[i+2] & 0xdf)*/ |
83 | 16.0k | invalid_utf8 |= li_utf8_invalid_byte(x); |
84 | 16.0k | } |
85 | 17.6k | i+=2; |
86 | 17.6k | } |
87 | 25.5M | else if (s[i] == '#') break; /* ignore fragment */ |
88 | 25.5M | else { |
89 | 25.5M | p[j] = '%'; |
90 | 25.5M | p[++j] = hex_chars_uc[(s[i] >> 4) & 0xF]; |
91 | 25.5M | p[++j] = hex_chars_uc[s[i] & 0xF]; |
92 | 25.5M | invalid_utf8 |= li_utf8_invalid_byte(s[i]); |
93 | 25.5M | } |
94 | 26.5M | } |
95 | 596 | buffer_copy_string_len(b, (char *)p, (size_t)j); |
96 | 596 | return !invalid_utf8 ? qs : -2; |
97 | 596 | } |
98 | | |
99 | | |
100 | | static int burl_normalize_basic_unreserved (buffer *b, buffer *t) |
101 | 780 | { |
102 | 780 | const unsigned char * const s = (unsigned char *)b->ptr; |
103 | 780 | const int used = (int)buffer_clen(b); |
104 | 780 | unsigned int n1, n2, x; |
105 | 780 | int qs = -1; |
106 | 780 | int invalid_utf8 = 0; |
107 | | |
108 | 613k | for (int i = 0; i < used; ++i) { |
109 | 613k | if (!encoded_chars_http_uri_reqd[s[i]]) { |
110 | 610k | if (__builtin_expect( (s[i] == '?'), 0) && -1 == qs) qs = i; |
111 | 610k | } |
112 | 2.73k | else if (s[i]=='%' && li_cton(s[i+1], n1) && li_cton(s[i+2], n2) |
113 | 2.19k | && !burl_is_unreserved((x = (n1 << 4) | n2))) { |
114 | 2.13k | invalid_utf8 |= li_utf8_invalid_byte(x); |
115 | 2.13k | if (s[i+1] >= 'a') b->ptr[i+1] &= 0xdf; /* uppercase hex */ |
116 | 2.13k | if (s[i+2] >= 'a') b->ptr[i+2] &= 0xdf; /* uppercase hex */ |
117 | 2.13k | i+=2; |
118 | 2.13k | } |
119 | 597 | else if (s[i] == '#') { /* ignore fragment */ |
120 | 1 | buffer_truncate(b, (size_t)i); |
121 | 1 | break; |
122 | 1 | } |
123 | 596 | else { |
124 | 596 | qs = burl_normalize_basic_unreserved_fix(b, t, i, qs); |
125 | 596 | break; |
126 | 596 | } |
127 | 613k | } |
128 | | |
129 | 780 | return !invalid_utf8 ? qs : -2; |
130 | 780 | } |
131 | | |
132 | | |
133 | | __attribute_cold__ |
134 | | static int burl_normalize_basic_required_fix (buffer *b, buffer *t, int i, int qs) |
135 | 750 | { |
136 | 750 | int j = i; |
137 | 750 | const int used = (int)buffer_clen(b); |
138 | 750 | const unsigned char * const s = (unsigned char *)b->ptr; |
139 | 750 | unsigned char * const p = |
140 | 750 | (unsigned char *)buffer_string_prepare_copy(t,i+(used-i)*3+1); |
141 | 750 | unsigned int n1, n2; |
142 | 750 | int invalid_utf8 = 0; |
143 | 750 | memcpy(p, s, (size_t)i); |
144 | 34.7M | for (; i < used; ++i, ++j) { |
145 | 34.7M | if (!encoded_chars_http_uri_reqd[s[i]]) { |
146 | 1.63M | p[j] = s[i]; |
147 | 1.63M | if (__builtin_expect( (s[i] == '?'), 0) && -1 == qs) qs = j; |
148 | 1.63M | } |
149 | 33.0M | else if (s[i]=='%' && li_cton(s[i+1], n1) && li_cton(s[i+2], n2)) { |
150 | 68.6k | const unsigned int x = (n1 << 4) | n2; |
151 | 68.6k | if (!encoded_chars_http_uri_reqd[x] |
152 | 59.6k | && (qs < 0 |
153 | 59.6k | ? (x != '/' && x != '?') |
154 | 59.6k | : (x != '&' && x != '=' && x != ';' && x != '+'))) { |
155 | 39.7k | p[j] = x; |
156 | 39.7k | } |
157 | 28.9k | else { |
158 | 28.9k | p[j] = '%'; |
159 | 28.9k | p[++j] = hex_chars_uc[n1]; /*(s[i+1] & 0xdf)*/ |
160 | 28.9k | p[++j] = hex_chars_uc[n2]; /*(s[i+2] & 0xdf)*/ |
161 | 28.9k | invalid_utf8 |= li_utf8_invalid_byte(x); |
162 | 28.9k | } |
163 | 68.6k | i+=2; |
164 | 68.6k | } |
165 | 33.0M | else if (s[i] == '#') break; /* ignore fragment */ |
166 | 33.0M | else { |
167 | 33.0M | p[j] = '%'; |
168 | 33.0M | p[++j] = hex_chars_uc[(s[i] >> 4) & 0xF]; |
169 | 33.0M | p[++j] = hex_chars_uc[s[i] & 0xF]; |
170 | 33.0M | invalid_utf8 |= li_utf8_invalid_byte(s[i]); |
171 | 33.0M | } |
172 | 34.7M | } |
173 | 750 | buffer_copy_string_len(b, (char *)p, (size_t)j); |
174 | 750 | return !invalid_utf8 ? qs : -2; |
175 | 750 | } |
176 | | |
177 | | |
178 | | static int burl_normalize_basic_required (buffer *b, buffer *t) |
179 | 959 | { |
180 | 959 | const unsigned char * const s = (unsigned char *)b->ptr; |
181 | 959 | const int used = (int)buffer_clen(b); |
182 | 959 | unsigned int n1, n2, x; |
183 | 959 | int qs = -1; |
184 | 959 | int invalid_utf8 = 0; |
185 | | |
186 | 358k | for (int i = 0; i < used; ++i) { |
187 | 357k | if (!encoded_chars_http_uri_reqd[s[i]]) { |
188 | 347k | if (__builtin_expect( (s[i] == '?'), 0) && -1 == qs) qs = i; |
189 | 347k | } |
190 | 10.1k | else if (s[i]=='%' && li_cton(s[i+1], n1) && li_cton(s[i+2], n2) |
191 | 9.43k | && (encoded_chars_http_uri_reqd[(x = (n1 << 4) | n2)] |
192 | 4.64k | || (qs < 0 |
193 | 4.64k | ? (x == '/' || x == '?') |
194 | 9.35k | : (x == '&' || x == '=' || x == ';' || x == '+')))) { |
195 | 9.35k | invalid_utf8 |= li_utf8_invalid_byte(x); |
196 | 9.35k | if (s[i+1] >= 'a') b->ptr[i+1] &= 0xdf; /* uppercase hex */ |
197 | 9.35k | if (s[i+2] >= 'a') b->ptr[i+2] &= 0xdf; /* uppercase hex */ |
198 | 9.35k | i+=2; |
199 | 9.35k | } |
200 | 751 | else if (s[i] == '#') { /* ignore fragment */ |
201 | 1 | buffer_truncate(b, (size_t)i); |
202 | 1 | break; |
203 | 1 | } |
204 | 750 | else { |
205 | 750 | qs = burl_normalize_basic_required_fix(b, t, i, qs); |
206 | 750 | break; |
207 | 750 | } |
208 | 357k | } |
209 | | |
210 | 959 | return !invalid_utf8 ? qs : -2; |
211 | 959 | } |
212 | | |
213 | | |
214 | | static int burl_contains_ctrls (const buffer *b) |
215 | 341 | { |
216 | 341 | const char * const s = b->ptr; |
217 | 341 | const int used = (int)buffer_clen(b); |
218 | 11.3M | for (int i = 0; i < used; ++i) { |
219 | 11.3M | if (s[i] == '%' && (s[i+1] < '2' || (s[i+1] == '7' && s[i+2] == 'F'))) |
220 | 97 | return 1; |
221 | 11.3M | } |
222 | 244 | return 0; |
223 | 341 | } |
224 | | |
225 | | |
226 | | __attribute_cold__ |
227 | | static void burl_normalize_qs20_to_plus_fix (buffer *b, int i) |
228 | 137 | { |
229 | 137 | char * const s = b->ptr; |
230 | 137 | const int used = (int)buffer_clen(b); |
231 | 137 | int j = i; |
232 | 14.4M | for (; i < used; ++i, ++j) { |
233 | 14.4M | s[j] = s[i]; |
234 | 14.4M | if (s[i] == '%' && s[i+1] == '2' && s[i+2] == '0') { |
235 | 2.41k | s[j] = '+'; |
236 | 2.41k | i+=2; |
237 | 2.41k | } |
238 | 14.4M | } |
239 | 137 | buffer_truncate(b, j); |
240 | 137 | } |
241 | | |
242 | | |
243 | | static void burl_normalize_qs20_to_plus (buffer *b, int qs) |
244 | 447 | { |
245 | 447 | const char * const s = b->ptr; |
246 | 447 | const int used = qs < 0 ? 0 : (int)buffer_clen(b); |
247 | 447 | int i; |
248 | 447 | if (qs < 0) return; |
249 | 8.09M | for (i = qs+1; i < used; ++i) { |
250 | 8.09M | if (s[i] == '%' && s[i+1] == '2' && s[i+2] == '0') break; |
251 | 8.09M | } |
252 | 447 | if (i != used) burl_normalize_qs20_to_plus_fix(b, i); |
253 | 447 | } |
254 | | |
255 | | |
256 | | __attribute_cold__ |
257 | | static int burl_normalize_2F_to_slash_fix (buffer *b, int qs, int i) |
258 | 158 | { |
259 | 158 | char * const s = b->ptr; |
260 | 158 | const int blen = (int)buffer_clen(b); |
261 | 158 | const int used = qs < 0 ? blen : qs; |
262 | 158 | int j = i; |
263 | 8.77M | for (; i < used; ++i, ++j) { |
264 | 8.77M | s[j] = s[i]; |
265 | 8.77M | if (s[i] == '%' && s[i+1] == '2' && s[i+2] == 'F') { |
266 | 1.22k | s[j] = '/'; |
267 | 1.22k | i+=2; |
268 | 1.22k | } |
269 | 8.77M | } |
270 | 158 | if (qs >= 0) { |
271 | 69 | const int qslen = blen - qs; |
272 | 69 | memmove(s+j, s+qs, (size_t)qslen); |
273 | 69 | qs = j; |
274 | 69 | j += qslen; |
275 | 69 | } |
276 | 158 | buffer_truncate(b, j); |
277 | 158 | return qs; |
278 | 158 | } |
279 | | |
280 | | |
281 | | static int burl_normalize_2F_to_slash (buffer *b, int qs, int flags) |
282 | 1.27k | { |
283 | | /*("%2F" must already have been uppercased during normalization)*/ |
284 | 1.27k | const char * const s = b->ptr; |
285 | 1.27k | const int used = qs < 0 ? (int)buffer_clen(b) : qs; |
286 | 77.2M | for (int i = 0; i < used; ++i) { |
287 | 77.2M | if (s[i] == '%' && s[i+1] == '2' && s[i+2] == 'F') { |
288 | 178 | return !(flags & HTTP_PARSEOPT_URL_NORMALIZE_PATH_2F_REJECT) |
289 | 178 | ? burl_normalize_2F_to_slash_fix(b, qs, i) /* _DECODE */ |
290 | 178 | : -2; /*(flags & HTTP_PARSEOPT_URL_NORMALIZE_PATH_2F_REJECT)*/ |
291 | 178 | } |
292 | 77.2M | } |
293 | 1.09k | return qs; |
294 | 1.27k | } |
295 | | |
296 | | |
297 | | static int burl_normalize_path (buffer *b, buffer *t, int qs, int flags) |
298 | 1.20k | { |
299 | 1.20k | const unsigned char * const s = (unsigned char *)b->ptr; |
300 | 1.20k | const int used = (int)buffer_clen(b); |
301 | 1.20k | int path_simplify = 0; |
302 | 6.19k | for (int i = 0, len = qs < 0 ? used : qs; i < len; ++i) { |
303 | 5.38k | if (s[i] == '.' && (s[i+1] != '.' || ++i) |
304 | 3.00k | && (s[i+1] == '/' || s[i+1] == '?' || s[i+1] == '\0')) { |
305 | 232 | path_simplify = 1; |
306 | 232 | break; |
307 | 232 | } |
308 | 68.2M | while (i < len && s[i] != '/') ++i; |
309 | 5.15k | if (s[i] == '/' && s[i+1] == '/') { /*(s[len] != '/')*/ |
310 | 169 | path_simplify = 1; |
311 | 169 | break; |
312 | 169 | } |
313 | 5.15k | } |
314 | | |
315 | 1.20k | if (path_simplify) { |
316 | 401 | if (flags & HTTP_PARSEOPT_URL_NORMALIZE_PATH_DOTSEG_REJECT) return -2; |
317 | 390 | if (qs >= 0) { |
318 | 145 | buffer_copy_string_len(t, b->ptr+qs, used - qs); |
319 | 145 | buffer_truncate(b, qs); |
320 | 145 | } |
321 | | |
322 | 390 | buffer_path_simplify(b); |
323 | | |
324 | 390 | if (qs >= 0) { |
325 | 145 | qs = (int)buffer_clen(b); |
326 | 145 | buffer_append_string_len(b, BUF_PTR_LEN(t)); |
327 | 145 | } |
328 | 390 | } |
329 | | |
330 | 1.19k | return qs; |
331 | 1.20k | } |
332 | | |
333 | | |
334 | | __attribute_cold__ |
335 | | __attribute_noinline__ |
336 | | __attribute_pure__ |
337 | 421 | static int burl_scan_qmark (const buffer * const b) { |
338 | 421 | const char * const qmark = strchr(b->ptr, '?'); |
339 | 421 | return qmark ? (int)(qmark - b->ptr) : -1; |
340 | 421 | } |
341 | | |
342 | | |
343 | | int burl_normalize (buffer *b, buffer *t, int flags) |
344 | 1.73k | { |
345 | 1.73k | int qs; |
346 | | |
347 | | #if defined(_WIN32) || defined(__CYGWIN__) |
348 | | /* Windows and Cygwin treat '\\' as '/' if '\\' is present in path; |
349 | | * convert to '/' for consistency before percent-encoding |
350 | | * normalization which will convert '\\' to "%5C" in the URL. |
351 | | * (Clients still should not be sending '\\' unencoded in requests.) */ |
352 | | if (flags & HTTP_PARSEOPT_URL_NORMALIZE_PATH_BACKSLASH_TRANS) { |
353 | | for (char *p = b->ptr; *p != '?' && *p != '\0'; ++p) { |
354 | | if (*p == '\\') *p = '/'; |
355 | | if (p[0] == '%' && p[1] == '5' && (p[2] | 0x20) == 'c') { |
356 | | p[1] = '2'; |
357 | | p[2] = 'F'; |
358 | | p += 2; |
359 | | } |
360 | | } |
361 | | } |
362 | | #endif |
363 | | |
364 | 1.73k | qs = (flags & HTTP_PARSEOPT_URL_NORMALIZE_REQUIRED) |
365 | 1.73k | ? burl_normalize_basic_required(b, t) |
366 | 1.73k | : burl_normalize_basic_unreserved(b, t); |
367 | 1.73k | if (-2 == qs) { |
368 | 551 | if (flags & HTTP_PARSEOPT_URL_NORMALIZE_INVALID_UTF8_REJECT) return -2; |
369 | 421 | qs = burl_scan_qmark(b); |
370 | 421 | } |
371 | | |
372 | 1.60k | if (flags & HTTP_PARSEOPT_URL_NORMALIZE_CTRLS_REJECT) { |
373 | 341 | if (burl_contains_ctrls(b)) return -2; |
374 | 341 | } |
375 | | |
376 | 1.51k | if (flags & (HTTP_PARSEOPT_URL_NORMALIZE_PATH_2F_DECODE |
377 | 1.51k | |HTTP_PARSEOPT_URL_NORMALIZE_PATH_2F_REJECT)) { |
378 | 1.27k | qs = burl_normalize_2F_to_slash(b, qs, flags); |
379 | 1.27k | if (-2 == qs) return -2; |
380 | 1.27k | } |
381 | | |
382 | 1.49k | if (flags & (HTTP_PARSEOPT_URL_NORMALIZE_PATH_DOTSEG_REMOVE |
383 | 1.49k | |HTTP_PARSEOPT_URL_NORMALIZE_PATH_DOTSEG_REJECT)) { |
384 | 1.20k | qs = burl_normalize_path(b, t, qs, flags); |
385 | 1.20k | if (-2 == qs) return -2; |
386 | 1.20k | } |
387 | | |
388 | 1.48k | if (flags & HTTP_PARSEOPT_URL_NORMALIZE_QUERY_20_PLUS) { |
389 | 924 | if (qs >= 0) burl_normalize_qs20_to_plus(b, qs); |
390 | 924 | } |
391 | | |
392 | 1.48k | return qs; |
393 | 1.49k | } |
394 | | |
395 | | |
396 | | static void burl_append_encode_nde (buffer * const b, const char * const str, const size_t len) |
397 | 0 | { |
398 | | /* percent-encodes everything except unreserved - . 0-9 A-Z _ a-z ~ |
399 | | * unless already percent-encoded (does not double-encode) */ |
400 | | /* Note: not checking for invalid UTF-8 */ |
401 | 0 | char * const p = buffer_string_prepare_append(b, len*3); |
402 | 0 | unsigned int n1, n2; |
403 | 0 | int j = 0; |
404 | 0 | for (unsigned int i = 0; i < len; ++i, ++j) { |
405 | 0 | if (str[i]=='%' && li_cton(str[i+1], n1) && li_cton(str[i+2], n2)) { |
406 | 0 | const unsigned int x = (n1 << 4) | n2; |
407 | 0 | if (burl_is_unreserved((int)x)) { |
408 | 0 | p[j] = (char)x; |
409 | 0 | } |
410 | 0 | else { /* leave UTF-8, control chars, and required chars encoded */ |
411 | 0 | p[j] = '%'; |
412 | 0 | p[++j] = str[i+1]; |
413 | 0 | p[++j] = str[i+2]; |
414 | 0 | } |
415 | 0 | i+=2; |
416 | 0 | } |
417 | 0 | else if (burl_is_unreserved(str[i])) { |
418 | 0 | p[j] = str[i]; |
419 | 0 | } |
420 | 0 | else { |
421 | 0 | p[j] = '%'; |
422 | 0 | p[++j] = hex_chars_uc[(str[i] >> 4) & 0xF]; |
423 | 0 | p[++j] = hex_chars_uc[str[i] & 0xF]; |
424 | 0 | } |
425 | 0 | } |
426 | 0 | buffer_commit(b, j); |
427 | 0 | } |
428 | | |
429 | | |
430 | | static void burl_append_encode_psnde (buffer * const b, const char * const str, const size_t len) |
431 | 0 | { |
432 | | /* percent-encodes everything except unreserved - . 0-9 A-Z _ a-z ~ plus / |
433 | | * unless already percent-encoded (does not double-encode) */ |
434 | | /* Note: not checking for invalid UTF-8 */ |
435 | 0 | char * const p = buffer_string_prepare_append(b, len*3); |
436 | 0 | unsigned int n1, n2; |
437 | 0 | int j = 0; |
438 | 0 | for (unsigned int i = 0; i < len; ++i, ++j) { |
439 | 0 | if (str[i]=='%' && li_cton(str[i+1], n1) && li_cton(str[i+2], n2)) { |
440 | 0 | const unsigned int x = (n1 << 4) | n2; |
441 | 0 | if (burl_is_unreserved((int)x)) { |
442 | 0 | p[j] = (char)x; |
443 | 0 | } |
444 | 0 | else { /* leave UTF-8, control chars, and required chars encoded */ |
445 | 0 | p[j] = '%'; |
446 | 0 | p[++j] = str[i+1]; |
447 | 0 | p[++j] = str[i+2]; |
448 | 0 | } |
449 | 0 | i+=2; |
450 | 0 | } |
451 | 0 | else if (burl_is_unreserved(str[i]) || str[i] == '/') { |
452 | 0 | p[j] = str[i]; |
453 | 0 | } |
454 | 0 | else { |
455 | 0 | p[j] = '%'; |
456 | 0 | p[++j] = hex_chars_uc[(str[i] >> 4) & 0xF]; |
457 | 0 | p[++j] = hex_chars_uc[str[i] & 0xF]; |
458 | 0 | } |
459 | 0 | } |
460 | 0 | buffer_commit(b, j); |
461 | 0 | } |
462 | | |
463 | | |
464 | | static void burl_append_encode_all (buffer * const b, const char * const str, const size_t len) |
465 | 0 | { |
466 | | /* percent-encodes everything except unreserved - . 0-9 A-Z _ a-z ~ |
467 | | * Note: double-encodes any existing '%') */ |
468 | | /* Note: not checking for invalid UTF-8 */ |
469 | 0 | char * const p = buffer_string_prepare_append(b, len*3); |
470 | 0 | int j = 0; |
471 | 0 | for (unsigned int i = 0; i < len; ++i, ++j) { |
472 | 0 | if (burl_is_unreserved(str[i])) { |
473 | 0 | p[j] = str[i]; |
474 | 0 | } |
475 | 0 | else { |
476 | 0 | p[j] = '%'; |
477 | 0 | p[++j] = hex_chars_uc[(str[i] >> 4) & 0xF]; |
478 | 0 | p[++j] = hex_chars_uc[str[i] & 0xF]; |
479 | 0 | } |
480 | 0 | } |
481 | 0 | buffer_commit(b, j); |
482 | 0 | } |
483 | | |
484 | | |
485 | | static void burl_offset_tolower (buffer * const b, const size_t off) |
486 | 0 | { |
487 | | /*(skips over all percent-encodings, including encoding of alpha chars)*/ |
488 | 0 | for (char *p = b->ptr+off; p[0]; ++p) { |
489 | 0 | if (light_isupper(p[0])) p[0] |= 0x20; |
490 | 0 | else if (p[0]=='%' && light_isxdigit(p[1]) && light_isxdigit(p[2])) |
491 | 0 | p+=2; |
492 | 0 | } |
493 | 0 | } |
494 | | |
495 | | |
496 | | static void burl_offset_toupper (buffer * const b, const size_t off) |
497 | 0 | { |
498 | | /*(skips over all percent-encodings, including encoding of alpha chars)*/ |
499 | 0 | for (char *p = b->ptr+off; p[0]; ++p) { |
500 | 0 | if (light_islower(p[0])) p[0] &= 0xdf; |
501 | 0 | else if (p[0]=='%' && light_isxdigit(p[1]) && light_isxdigit(p[2])) |
502 | 0 | p+=2; |
503 | 0 | } |
504 | 0 | } |
505 | | |
506 | | |
507 | | void burl_append (buffer * const b, const char * const str, const size_t len, const int flags) |
508 | 0 | { |
509 | 0 | size_t off = 0; |
510 | |
|
511 | 0 | if (0 == len) return; |
512 | | |
513 | 0 | if (0 == flags) { |
514 | 0 | buffer_append_string_len(b, str, len); |
515 | 0 | return; |
516 | 0 | } |
517 | | |
518 | 0 | if (flags & (BURL_TOUPPER|BURL_TOLOWER)) off = buffer_clen(b); |
519 | |
|
520 | 0 | if (flags & BURL_ENCODE_NONE) { |
521 | 0 | buffer_append_string_len(b, str, len); |
522 | 0 | } |
523 | 0 | else if (flags & BURL_ENCODE_ALL) { |
524 | 0 | burl_append_encode_all(b, str, len); |
525 | 0 | } |
526 | 0 | else if (flags & BURL_ENCODE_NDE) { |
527 | 0 | burl_append_encode_nde(b, str, len); |
528 | 0 | } |
529 | 0 | else if (flags & BURL_ENCODE_PSNDE) { |
530 | 0 | burl_append_encode_psnde(b, str, len); |
531 | 0 | } |
532 | 0 | else if (flags & BURL_ENCODE_B64U) { |
533 | 0 | const unsigned char *s = (const unsigned char *)str; |
534 | 0 | buffer_append_base64_encode_no_padding(b, s, len, BASE64_URL); |
535 | 0 | } |
536 | 0 | else if (flags & BURL_DECODE_B64U) { |
537 | 0 | buffer_append_base64_decode(b, str, len, BASE64_URL); |
538 | 0 | } |
539 | | |
540 | | /* note: not normalizing str, which could come from arbitrary header, |
541 | | * so it is possible that alpha chars are percent-encoded upper/lowercase */ |
542 | 0 | if (flags & (BURL_TOLOWER|BURL_TOUPPER)) { |
543 | 0 | (flags & BURL_TOLOWER) |
544 | 0 | ? burl_offset_tolower(b, off) /*(flags & BURL_TOLOWER)*/ |
545 | 0 | : burl_offset_toupper(b, off); /*(flags & BURL_TOUPPER)*/ |
546 | 0 | } |
547 | 0 | } |