Line | Count | Source |
1 | | /*************************************************************************** |
2 | | * _ _ ____ _ |
3 | | * Project ___| | | | _ \| | |
4 | | * / __| | | | |_) | | |
5 | | * | (__| |_| | _ <| |___ |
6 | | * \___|\___/|_| \_\_____| |
7 | | * |
8 | | * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. |
9 | | * |
10 | | * This software is licensed as described in the file COPYING, which |
11 | | * you should have received as part of this distribution. The terms |
12 | | * are also available at https://curl.se/docs/copyright.html. |
13 | | * |
14 | | * You may opt to use, copy, modify, merge, publish, distribute and/or sell |
15 | | * copies of the Software, and permit persons to whom the Software is |
16 | | * furnished to do so, under the terms of the COPYING file. |
17 | | * |
18 | | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY |
19 | | * KIND, either express or implied. |
20 | | * |
21 | | * SPDX-License-Identifier: curl |
22 | | * |
23 | | ***************************************************************************/ |
24 | | /* Escape and unescape URL encoding in strings. The functions return a new |
25 | | * allocated string or NULL if an error occurred. */ |
26 | | #include "curl_setup.h" |
27 | | |
28 | | struct Curl_easy; |
29 | | |
30 | | #include "urldata.h" |
31 | | #include "escape.h" |
32 | | #include "curlx/strparse.h" |
33 | | #include "curl_printf.h" |
34 | | |
35 | | /* for ABI-compatibility with previous versions */ |
36 | | char *curl_escape(const char *string, int length) |
37 | 2.48k | { |
38 | 2.48k | return curl_easy_escape(NULL, string, length); |
39 | 2.48k | } |
40 | | |
41 | | /* for ABI-compatibility with previous versions */ |
42 | | char *curl_unescape(const char *string, int length) |
43 | 0 | { |
44 | 0 | return curl_easy_unescape(NULL, string, length, NULL); |
45 | 0 | } |
46 | | |
47 | | #define NOPE 0xff |
48 | | #define _OK_ 0x1f /* octet not needing encoding but not a hexadecimal |
49 | | character */ |
50 | | |
51 | | static const unsigned char hextable[256] = { |
52 | | NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 00 */ |
53 | | NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 08 */ |
54 | | NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 10 */ |
55 | | NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 18 */ |
56 | | NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 20 */ |
57 | | NOPE, NOPE, NOPE, NOPE, NOPE, _OK_, _OK_, NOPE, /* 28 */ |
58 | | 0, 1, 2, 3, 4, 5, 6, 7, /* 30 */ |
59 | | 8, 9, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 38 */ |
60 | | NOPE, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, _OK_, /* 40 */ |
61 | | _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, /* 48 */ |
62 | | _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, /* 50 */ |
63 | | _OK_, _OK_, _OK_, NOPE, NOPE, NOPE, NOPE, _OK_, /* 58 */ |
64 | | NOPE, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, _OK_, /* 60 */ |
65 | | _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, /* 68 */ |
66 | | _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, _OK_, /* 70 */ |
67 | | _OK_, _OK_, _OK_, NOPE, NOPE, NOPE, _OK_, NOPE, /* 78 */ |
68 | | NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, /* 80 - not ASCII */ |
69 | | NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, |
70 | | NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, |
71 | | NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, |
72 | | NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, |
73 | | NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, |
74 | | NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, |
75 | | NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, |
76 | | NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, |
77 | | NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, |
78 | | NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, |
79 | | NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, |
80 | | NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, |
81 | | NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, |
82 | | NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, |
83 | | NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE, NOPE |
84 | | }; |
85 | | |
86 | | static const char hexpairs[][2] = { |
87 | | {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, |
88 | | {'0', '4'}, {'0', '5'}, {'0', '6'}, {'0', '7'}, |
89 | | {'0', '8'}, {'0', '9'}, {'0', 'A'}, {'0', 'B'}, |
90 | | {'0', 'C'}, {'0', 'D'}, {'0', 'E'}, {'0', 'F'}, |
91 | | {'1', '0'}, {'1', '1'}, {'1', '2'}, {'1', '3'}, |
92 | | {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'}, |
93 | | {'1', '8'}, {'1', '9'}, {'1', 'A'}, {'1', 'B'}, |
94 | | {'1', 'C'}, {'1', 'D'}, {'1', 'E'}, {'1', 'F'}, |
95 | | {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, |
96 | | {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, |
97 | | {'2', '8'}, {'2', '9'}, {'2', 'A'}, {'2', 'B'}, |
98 | | {'2', 'C'}, {'2', 'D'}, {'2', 'E'}, {'2', 'F'}, |
99 | | {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, |
100 | | {'3', '4'}, {'3', '5'}, {'3', '6'}, {'3', '7'}, |
101 | | {'3', '8'}, {'3', '9'}, {'3', 'A'}, {'3', 'B'}, |
102 | | {'3', 'C'}, {'3', 'D'}, {'3', 'E'}, {'3', 'F'}, |
103 | | {'4', '0'}, {'4', '1'}, {'4', '2'}, {'4', '3'}, |
104 | | {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'}, |
105 | | {'4', '8'}, {'4', '9'}, {'4', 'A'}, {'4', 'B'}, |
106 | | {'4', 'C'}, {'4', 'D'}, {'4', 'E'}, {'4', 'F'}, |
107 | | {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, |
108 | | {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, |
109 | | {'5', '8'}, {'5', '9'}, {'5', 'A'}, {'5', 'B'}, |
110 | | {'5', 'C'}, {'5', 'D'}, {'5', 'E'}, {'5', 'F'}, |
111 | | {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, |
112 | | {'6', '4'}, {'6', '5'}, {'6', '6'}, {'6', '7'}, |
113 | | {'6', '8'}, {'6', '9'}, {'6', 'A'}, {'6', 'B'}, |
114 | | {'6', 'C'}, {'6', 'D'}, {'6', 'E'}, {'6', 'F'}, |
115 | | {'7', '0'}, {'7', '1'}, {'7', '2'}, {'7', '3'}, |
116 | | {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'}, |
117 | | {'7', '8'}, {'7', '9'}, {'7', 'A'}, {'7', 'B'}, |
118 | | {'7', 'C'}, {'7', 'D'}, {'7', 'E'}, {'7', 'F'}, |
119 | | {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'}, |
120 | | {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, |
121 | | {'8', '8'}, {'8', '9'}, {'8', 'A'}, {'8', 'B'}, |
122 | | {'8', 'C'}, {'8', 'D'}, {'8', 'E'}, {'8', 'F'}, |
123 | | {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, |
124 | | {'9', '4'}, {'9', '5'}, {'9', '6'}, {'9', '7'}, |
125 | | {'9', '8'}, {'9', '9'}, {'9', 'A'}, {'9', 'B'}, |
126 | | {'9', 'C'}, {'9', 'D'}, {'9', 'E'}, {'9', 'F'}, |
127 | | {'A', '0'}, {'A', '1'}, {'A', '2'}, {'A', '3'}, |
128 | | {'A', '4'}, {'A', '5'}, {'A', '6'}, {'A', '7'}, |
129 | | {'A', '8'}, {'A', '9'}, {'A', 'A'}, {'A', 'B'}, |
130 | | {'A', 'C'}, {'A', 'D'}, {'A', 'E'}, {'A', 'F'}, |
131 | | {'B', '0'}, {'B', '1'}, {'B', '2'}, {'B', '3'}, |
132 | | {'B', '4'}, {'B', '5'}, {'B', '6'}, {'B', '7'}, |
133 | | {'B', '8'}, {'B', '9'}, {'B', 'A'}, {'B', 'B'}, |
134 | | {'B', 'C'}, {'B', 'D'}, {'B', 'E'}, {'B', 'F'}, |
135 | | {'C', '0'}, {'C', '1'}, {'C', '2'}, {'C', '3'}, |
136 | | {'C', '4'}, {'C', '5'}, {'C', '6'}, {'C', '7'}, |
137 | | {'C', '8'}, {'C', '9'}, {'C', 'A'}, {'C', 'B'}, |
138 | | {'C', 'C'}, {'C', 'D'}, {'C', 'E'}, {'C', 'F'}, |
139 | | {'D', '0'}, {'D', '1'}, {'D', '2'}, {'D', '3'}, |
140 | | {'D', '4'}, {'D', '5'}, {'D', '6'}, {'D', '7'}, |
141 | | {'D', '8'}, {'D', '9'}, {'D', 'A'}, {'D', 'B'}, |
142 | | {'D', 'C'}, {'D', 'D'}, {'D', 'E'}, {'D', 'F'}, |
143 | | {'E', '0'}, {'E', '1'}, {'E', '2'}, {'E', '3'}, |
144 | | {'E', '4'}, {'E', '5'}, {'E', '6'}, {'E', '7'}, |
145 | | {'E', '8'}, {'E', '9'}, {'E', 'A'}, {'E', 'B'}, |
146 | | {'E', 'C'}, {'E', 'D'}, {'E', 'E'}, {'E', 'F'}, |
147 | | {'F', '0'}, {'F', '1'}, {'F', '2'}, {'F', '3'}, |
148 | | {'F', '4'}, {'F', '5'}, {'F', '6'}, {'F', '7'}, |
149 | | {'F', '8'}, {'F', '9'}, {'F', 'A'}, {'F', 'B'}, |
150 | | {'F', 'C'}, {'F', 'D'}, {'F', 'E'}, {'F', 'F'} |
151 | | }; |
152 | | |
153 | | /* Escapes for URL the given unescaped string of given length. |
154 | | * 'data' is ignored since 7.82.0. |
155 | | */ |
156 | | char *curl_easy_escape(CURL *curl, const char *string, int length) |
157 | 2.53k | { |
158 | 2.53k | size_t len; |
159 | 2.53k | const char *f = string; |
160 | 2.53k | size_t nappend = 0; |
161 | 2.53k | char *target; |
162 | 2.53k | char *encoded; |
163 | 2.53k | (void)curl; |
164 | | |
165 | 2.53k | if(!string || (length < 0)) |
166 | 0 | return NULL; |
167 | | |
168 | 2.53k | len = (length ? (size_t)length : strlen(string)); |
169 | 2.53k | if(!len) |
170 | 2.22k | return curlx_strdup(""); |
171 | | |
172 | 311 | if(len > SIZE_MAX / 16) |
173 | 0 | return NULL; |
174 | | |
175 | 311 | encoded = target = curlx_malloc((len * 3) + 1); |
176 | 311 | if(!target) |
177 | 1 | return NULL; |
178 | | |
179 | 23.4k | while(len--) { |
180 | 23.1k | uint8_t in = (uint8_t)*string++; |
181 | | |
182 | 23.1k | if(!(hextable[in] & 0x80)) |
183 | | /* append this */ |
184 | 2.37k | nappend++; |
185 | 20.7k | else { |
186 | 20.7k | if(nappend) { |
187 | 536 | memcpy(target, f, nappend); |
188 | 536 | target += nappend; |
189 | 536 | nappend = 0; |
190 | 536 | } |
191 | 20.7k | f = string; |
192 | | /* encode it */ |
193 | 20.7k | target[0] = '%'; |
194 | 20.7k | memcpy(target + 1, hexpairs[in], 2); |
195 | 20.7k | target += 3; |
196 | 20.7k | } |
197 | 23.1k | } |
198 | 310 | if(nappend) { |
199 | 144 | memcpy(target, f, nappend); |
200 | 144 | target += nappend; |
201 | 144 | } |
202 | 310 | *target = '\0'; /* null-terminate */ |
203 | | |
204 | 310 | return encoded; |
205 | 311 | } |
206 | | |
207 | | /* |
208 | | * Curl_urldecode() URL decodes the given string. |
209 | | * |
210 | | * Returns a pointer to a malloced string in *ostring with length given in |
211 | | * *olen. If length == 0, the length is assumed to be strlen(string). |
212 | | * |
213 | | * ctrl options: |
214 | | * - REJECT_NADA: accept everything |
215 | | * - REJECT_CTRL: rejects control characters (byte codes lower than 32) in |
216 | | * the data |
217 | | * - REJECT_ZERO: rejects decoded zero bytes |
218 | | * |
219 | | * The values for the enum starts at 2, to make the assert detect legacy |
220 | | * invokes that used TRUE/FALSE (0 and 1). |
221 | | */ |
222 | | |
223 | | CURLcode Curl_urldecode(const char *string, size_t length, |
224 | | char **ostring, size_t *olen, |
225 | | enum urlreject ctrl) |
226 | 435 | { |
227 | 435 | size_t alloc; |
228 | 435 | char *ns; |
229 | 435 | uint8_t reject_limit; |
230 | | |
231 | 435 | DEBUGASSERT(string); |
232 | 435 | DEBUGASSERT(ctrl >= REJECT_NADA); /* crash on TRUE/FALSE */ |
233 | | |
234 | 435 | alloc = (length ? length : strlen(string)); |
235 | 435 | ns = curlx_malloc(alloc + 1); |
236 | | |
237 | 435 | if(!ns) |
238 | 0 | return CURLE_OUT_OF_MEMORY; |
239 | | |
240 | | /* store output string */ |
241 | 435 | *ostring = ns; |
242 | | |
243 | 435 | reject_limit = (ctrl == REJECT_CTRL) ? 0x20 : |
244 | 435 | (ctrl == REJECT_ZERO) ? 1 : 0; |
245 | | |
246 | 2.14k | while(alloc) { |
247 | 1.76k | uint8_t in; |
248 | | |
249 | 1.76k | if(*string == '%') { |
250 | 1.06k | if(alloc > 2) { |
251 | 949 | uint8_t h1 = hextable[(uint8_t)string[1]]; |
252 | 949 | uint8_t h2 = hextable[(uint8_t)string[2]]; |
253 | 949 | if(!((h1 | h2) & 0xf0)) { |
254 | 282 | in = (uint8_t)((h1 << 4) | h2); |
255 | 282 | string += 3; |
256 | 282 | alloc -= 3; |
257 | 282 | if(in < reject_limit) |
258 | 22 | goto error; |
259 | 260 | *ns++ = (char)in; |
260 | 260 | continue; |
261 | 282 | } |
262 | 949 | } |
263 | 780 | in = '%'; |
264 | 780 | string++; |
265 | 780 | alloc--; |
266 | 780 | *ns++ = (char)in; |
267 | 780 | continue; |
268 | 1.06k | } |
269 | 702 | else { |
270 | 702 | const char *p = memchr(string + 1, '%', alloc - 1); |
271 | 702 | size_t n = p ? (size_t)(p - string) : alloc; |
272 | | |
273 | 702 | if(reject_limit) { |
274 | 702 | if(reject_limit == 1) { |
275 | 0 | if(memchr(string, 0, n)) |
276 | 0 | goto error; |
277 | 0 | } |
278 | 702 | else { |
279 | 702 | size_t i; |
280 | 3.61k | for(i = 0; i < n; i++) { |
281 | 2.94k | if((uint8_t)string[i] < 0x20) |
282 | 32 | goto error; |
283 | 2.94k | } |
284 | 702 | } |
285 | 702 | } |
286 | | |
287 | 670 | memcpy(ns, string, n); |
288 | 670 | ns += n; |
289 | 670 | string += n; |
290 | 670 | alloc -= n; |
291 | 670 | } |
292 | 1.76k | } |
293 | 381 | *ns = 0; /* terminate it */ |
294 | | |
295 | 381 | if(olen) |
296 | | /* store output size */ |
297 | 381 | *olen = (size_t)(ns - *ostring); |
298 | | |
299 | 381 | return CURLE_OK; |
300 | 54 | error: |
301 | 54 | curlx_safefree(*ostring); |
302 | 54 | return CURLE_URL_MALFORMAT; |
303 | 435 | } |
304 | | |
305 | | /* |
306 | | * Unescapes the given URL escaped string of given length. Returns a |
307 | | * pointer to a malloced string with length given in *olen. |
308 | | * If length == 0, the length is assumed to be strlen(string). |
309 | | * If olen == NULL, no output length is stored. |
310 | | * 'data' is ignored since 7.82.0. |
311 | | */ |
312 | | char *curl_easy_unescape(CURL *curl, const char *string, int inlength, |
313 | | int *outlength) |
314 | 0 | { |
315 | 0 | char *str = NULL; |
316 | 0 | (void)curl; |
317 | 0 | if(string && (inlength >= 0)) { |
318 | 0 | size_t inputlen = (size_t)inlength; |
319 | 0 | size_t outputlen; |
320 | 0 | CURLcode res = Curl_urldecode(string, inputlen, &str, &outputlen, |
321 | 0 | REJECT_NADA); |
322 | 0 | if(res) |
323 | 0 | return NULL; |
324 | | |
325 | 0 | if(outlength) { |
326 | 0 | if(outputlen <= (size_t)INT_MAX) |
327 | 0 | *outlength = curlx_uztosi(outputlen); |
328 | 0 | else |
329 | | /* too large to return in an int, fail! */ |
330 | 0 | curlx_safefree(str); |
331 | 0 | } |
332 | 0 | } |
333 | 0 | return str; |
334 | 0 | } |
335 | | |
336 | | /* For operating systems/environments that use different malloc/free |
337 | | systems for the app and for this library, we provide a free that uses |
338 | | the library's memory system */ |
339 | | void curl_free(void *p) |
340 | 129k | { |
341 | 129k | curlx_free(p); |
342 | 129k | } |
343 | | |
344 | | /* |
345 | | * Curl_hexencode() |
346 | | * |
347 | | * Converts binary input to lowercase hex-encoded ASCII output. |
348 | | * null-terminated. |
349 | | */ |
350 | | void Curl_hexencode(const unsigned char *src, size_t len, /* input length */ |
351 | | unsigned char *out, size_t olen) /* output buffer size */ |
352 | 7.51k | { |
353 | 7.51k | DEBUGASSERT(src && len && (olen >= 3)); |
354 | 7.51k | if(src && len && (olen >= 3)) { |
355 | 247k | while(len-- && (olen >= 3)) { |
356 | 240k | out[0] = Curl_ldigits[*src >> 4]; |
357 | 240k | out[1] = Curl_ldigits[*src & 0x0F]; |
358 | 240k | ++src; |
359 | 240k | out += 2; |
360 | 240k | olen -= 2; |
361 | 240k | } |
362 | 7.51k | *out = 0; |
363 | 7.51k | } |
364 | 0 | else if(olen) |
365 | 0 | *out = 0; |
366 | 7.51k | } |
367 | | |
368 | | /* Curl_hexbyte |
369 | | * |
370 | | * Output a single unsigned char as a two-digit UPPERCASE hex number. |
371 | | */ |
372 | | void Curl_hexbyte(unsigned char *dest, /* must fit two bytes */ |
373 | | unsigned char val) |
374 | 49.4M | { |
375 | 49.4M | dest[0] = Curl_udigits[val >> 4]; |
376 | 49.4M | dest[1] = Curl_udigits[val & 0x0F]; |
377 | 49.4M | } |