1 | /* |
2 | Copyright (c) 2012, Broadcom Europe Ltd |
3 | All rights reserved. |
4 | |
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the following conditions are met: |
7 | * Redistributions of source code must retain the above copyright |
8 | notice, this list of conditions and the following disclaimer. |
9 | * Redistributions in binary form must reproduce the above copyright |
10 | notice, this list of conditions and the following disclaimer in the |
11 | documentation and/or other materials provided with the distribution. |
12 | * Neither the name of the copyright holder nor the |
13 | names of its contributors may be used to endorse or promote products |
14 | derived from this software without specific prior written permission. |
15 | |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY |
20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
26 | */ |
27 | |
28 | #include <ctype.h> |
29 | #include <string.h> |
30 | #include <stdlib.h> |
31 | |
32 | #include "containers/core/containers_uri.h" |
33 | |
34 | /*****************************************************************************/ |
35 | /* Internal types and definitions */ |
36 | /*****************************************************************************/ |
37 | |
38 | typedef struct VC_URI_QUERY_T |
39 | { |
40 | char *name; |
41 | char *value; |
42 | } VC_URI_QUERY_T; |
43 | |
44 | struct VC_URI_PARTS_T |
45 | { |
46 | char *scheme; /**< Unescaped scheme */ |
47 | char *userinfo; /**< Unescaped userinfo */ |
48 | char *host; /**< Unescaped host name/IP address */ |
49 | char *port; /**< Unescaped port */ |
50 | char *path; /**< Unescaped path */ |
51 | char *path_extension; /**< Unescaped path extension */ |
52 | char *fragment; /**< Unescaped fragment */ |
53 | VC_URI_QUERY_T *queries; /**< Array of queries */ |
54 | uint32_t num_queries; /**< Number of queries in array */ |
55 | }; |
56 | |
57 | typedef const uint32_t *RESERVED_CHARS_TABLE_T; |
58 | |
59 | /** Reserved character table for scheme component |
60 | * Controls, space, !"#$%&'()*,/:;<=>?@[\]^`{|} and 0x7F and above reserved. */ |
61 | static uint32_t scheme_reserved_chars[8] = { |
62 | 0xFFFFFFFF, 0xFC0097FF, 0x78000001, 0xB8000001, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF |
63 | }; |
64 | |
65 | /** Reserved character table for userinfo component |
66 | * Controls, space, "#%/<>?@[\]^`{|} and 0x7F and above reserved. */ |
67 | static uint32_t userinfo_reserved_chars[8] = { |
68 | 0xFFFFFFFF, 0xD000802D, 0x78000001, 0xB8000001, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF |
69 | }; |
70 | |
71 | /** Reserved character table for host component |
72 | * Controls, space, "#%/<>?@\^`{|} and 0x7F and above reserved. */ |
73 | static uint32_t host_reserved_chars[8] = { |
74 | 0xFFFFFFFF, 0xD000802D, 0x50000001, 0xB8000001, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF |
75 | }; |
76 | |
77 | /** Reserved character table for port component |
78 | * Controls, space, !"#$%&'()*+,/:;<=>?@[\]^`{|} and 0x7F and above reserved. */ |
79 | static uint32_t port_reserved_chars[8] = { |
80 | 0xFFFFFFFF, 0xFC009FFF, 0x78000001, 0xB8000001, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF |
81 | }; |
82 | |
83 | /** Reserved character table for path component |
84 | * Controls, space, "#%<>?[\]^`{|} and 0x7F and above reserved. */ |
85 | static uint32_t path_reserved_chars[8] = { |
86 | 0xFFFFFFFF, 0xD000002D, 0x78000000, 0xB8000001, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF |
87 | }; |
88 | |
89 | /** Reserved character table for query component |
90 | * Controls, space, "#%<>[\]^`{|} and 0x7F and above reserved. */ |
91 | static uint32_t query_reserved_chars[8] = { |
92 | 0xFFFFFFFF, 0x5000002D, 0x78000000, 0xB8000001, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF |
93 | }; |
94 | |
95 | /** Reserved character table for fragment component |
96 | * Controls, space, "#%<>[\]^`{|} and 0x7F and above reserved. */ |
97 | static uint32_t fragment_reserved_chars[8] = { |
98 | 0xFFFFFFFF, 0x5000002D, 0x78000000, 0xB8000001, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF |
99 | }; |
100 | |
101 | #define URI_RESERVED(C, TABLE) (!!((TABLE)[(unsigned char)(C) >> 5] & (1 << ((C) & 0x1F)))) |
102 | |
103 | #define SCHEME_DELIMITERS ":/?#" |
104 | #define NETWORK_DELIMITERS "@/?#" |
105 | #define HOST_PORT_DELIMITERS "/?#" |
106 | #define PATH_DELIMITERS "?#" |
107 | #define QUERY_DELIMITERS "#" |
108 | |
109 | /*****************************************************************************/ |
110 | /* Internal functions */ |
111 | /*****************************************************************************/ |
112 | |
113 | static char to_hex(int v) |
114 | { |
115 | if (v > 9) |
116 | return 'A' + v - 10; |
117 | return '0' + v; |
118 | } |
119 | |
120 | /*****************************************************************************/ |
121 | static uint32_t from_hex(const char *str, uint32_t str_len) |
122 | { |
123 | uint32_t val = 0; |
124 | |
125 | while (str_len--) |
126 | { |
127 | char c = *str++; |
128 | if (c >= '0' && c <= '9') |
129 | c -= '0'; |
130 | else if (c >= 'A' && c <= 'F') |
131 | c -= 'A' - 10; |
132 | else if (c >= 'a' && c <= 'f') |
133 | c -= 'a' - 10; |
134 | else |
135 | c = 0; /* Illegal character (not hex) */ |
136 | val = (val << 4) + c; |
137 | } |
138 | |
139 | return val; |
140 | } |
141 | |
142 | /*****************************************************************************/ |
143 | static uint32_t escaped_length( const char *str, RESERVED_CHARS_TABLE_T reserved ) |
144 | { |
145 | uint32_t ii; |
146 | uint32_t esclen = 0; |
147 | char c; |
148 | |
149 | for (ii = strlen(str); ii > 0; ii--) |
150 | { |
151 | c = *str++; |
152 | if (URI_RESERVED(c, reserved)) |
153 | { |
154 | /* Reserved character needs escaping as %xx */ |
155 | esclen += 3; |
156 | } else { |
157 | esclen++; |
158 | } |
159 | } |
160 | |
161 | return esclen; |
162 | } |
163 | |
164 | /*****************************************************************************/ |
165 | static uint32_t escape_string( const char *str, char *escaped, |
166 | RESERVED_CHARS_TABLE_T reserved ) |
167 | { |
168 | uint32_t ii; |
169 | uint32_t esclen = 0; |
170 | |
171 | if (!str) |
172 | return 0; |
173 | |
174 | for (ii = strlen(str); ii > 0; ii--) |
175 | { |
176 | char c = *str++; |
177 | |
178 | if (URI_RESERVED(c, reserved)) |
179 | { |
180 | escaped[esclen++] = '%'; |
181 | escaped[esclen++] = to_hex((c >> 4) & 0xF); |
182 | escaped[esclen++] = to_hex(c & 0xF); |
183 | } else { |
184 | escaped[esclen++] = c; |
185 | } |
186 | } |
187 | |
188 | return esclen; |
189 | } |
190 | |
191 | /*****************************************************************************/ |
192 | static uint32_t unescaped_length( const char *str, uint32_t str_len ) |
193 | { |
194 | uint32_t ii; |
195 | uint32_t unesclen = 0; |
196 | |
197 | for (ii = 0; ii < str_len; ii++) |
198 | { |
199 | if (*str++ == '%' && (ii + 2) < str_len) |
200 | { |
201 | str += 2; /* Should be two hex values next */ |
202 | ii += 2; |
203 | } |
204 | unesclen++; |
205 | } |
206 | |
207 | return unesclen; |
208 | } |
209 | |
210 | /*****************************************************************************/ |
211 | static void unescape_string( const char *str, uint32_t str_len, char *unescaped ) |
212 | { |
213 | uint32_t ii; |
214 | |
215 | for (ii = 0; ii < str_len; ii++) |
216 | { |
217 | char c = *str++; |
218 | |
219 | if (c == '%' && (ii + 2) < str_len ) |
220 | { |
221 | c = (char)(from_hex(str, 2) & 0xFF); |
222 | str += 2; |
223 | ii += 2; |
224 | } |
225 | *unescaped++ = c; |
226 | } |
227 | |
228 | *unescaped = '\0'; |
229 | } |
230 | |
231 | /*****************************************************************************/ |
232 | static char *create_unescaped_string( const char *escstr, uint32_t esclen ) |
233 | { |
234 | char *unescstr; |
235 | |
236 | unescstr = (char *)malloc(unescaped_length(escstr, esclen) + 1); /* Allow for NUL */ |
237 | if (unescstr) |
238 | unescape_string(escstr, esclen, unescstr); |
239 | |
240 | return unescstr; |
241 | } |
242 | |
243 | /*****************************************************************************/ |
244 | static bool duplicate_string( const char *src, char **p_dst ) |
245 | { |
246 | if (*p_dst) |
247 | free(*p_dst); |
248 | |
249 | if (src) |
250 | { |
251 | size_t str_size = strlen(src) + 1; |
252 | |
253 | *p_dst = (char *)malloc(str_size); |
254 | if (!*p_dst) |
255 | return false; |
256 | |
257 | memcpy(*p_dst, src, str_size); |
258 | } else |
259 | *p_dst = NULL; |
260 | |
261 | return true; |
262 | } |
263 | |
264 | /*****************************************************************************/ |
265 | static void release_string( char **str ) |
266 | { |
267 | if (*str) |
268 | { |
269 | free(*str); |
270 | *str = NULL; |
271 | } |
272 | } |
273 | |
274 | /*****************************************************************************/ |
275 | static void to_lower_string( char *str ) |
276 | { |
277 | char c; |
278 | |
279 | while ((c = *str) != '\0') |
280 | { |
281 | if (c >= 'A' && c <= 'Z') |
282 | *str = c - 'A' + 'a'; |
283 | str++; |
284 | } |
285 | } |
286 | |
287 | /*****************************************************************************/ |
288 | static const char *vc_uri_find_delimiter(const char *str, const char *delimiters) |
289 | { |
290 | const char *ptr = str; |
291 | char c; |
292 | |
293 | while ((c = *ptr) != 0) |
294 | { |
295 | if (strchr(delimiters, c) != 0) |
296 | break; |
297 | ptr++; |
298 | } |
299 | |
300 | return ptr; |
301 | } |
302 | |
303 | /*****************************************************************************/ |
304 | static void vc_uri_set_path_extension(VC_URI_PARTS_T *p_uri) |
305 | { |
306 | char *end; |
307 | |
308 | if (!p_uri) |
309 | return; |
310 | |
311 | p_uri->path_extension = NULL; |
312 | |
313 | if (!p_uri->path) |
314 | return; |
315 | |
316 | /* Look for the magic dot */ |
317 | for (end = p_uri->path + strlen(p_uri->path); *end != '.'; end--) |
318 | if (end == p_uri->path || *end == '/' || *end == '\\') |
319 | return; |
320 | |
321 | p_uri->path_extension = end + 1; |
322 | } |
323 | |
324 | /*****************************************************************************/ |
325 | static bool parse_authority( VC_URI_PARTS_T *p_uri, const char *str, |
326 | uint32_t str_len, const char *userinfo_end ) |
327 | { |
328 | const char *marker = userinfo_end; |
329 | const char *str_end = str + str_len; |
330 | char c; |
331 | |
332 | if (marker) |
333 | { |
334 | p_uri->userinfo = create_unescaped_string(str, marker - str); |
335 | if (!p_uri->userinfo) |
336 | return false; |
337 | str = marker + 1; /* Past '@' character */ |
338 | } |
339 | |
340 | if (*str == '[') /* IPvFuture / IPv6 address */ |
341 | { |
342 | /* Find end of address marker */ |
343 | for (marker = str; marker < str_end; marker++) |
344 | { |
345 | c = *marker; |
346 | if (c == ']') |
347 | break; |
348 | } |
349 | |
350 | if (marker < str_end) |
351 | marker++; /* Found marker, move to next character */ |
352 | } else { |
353 | /* Find port value marker*/ |
354 | for (marker = str; marker < str_end; marker++) |
355 | { |
356 | c = *marker; |
357 | if (c == ':') |
358 | break; |
359 | } |
360 | } |
361 | |
362 | /* Always store the host, even if empty, to trigger the "://" form of URI */ |
363 | p_uri->host = create_unescaped_string(str, marker - str); |
364 | if (!p_uri->host) |
365 | return false; |
366 | to_lower_string(p_uri->host); /* Host names are case-insensitive */ |
367 | |
368 | if (*marker == ':') |
369 | { |
370 | str = marker + 1; |
371 | p_uri->port = create_unescaped_string(str, str_end - str); |
372 | if (!p_uri->port) |
373 | return false; |
374 | } |
375 | |
376 | return true; |
377 | } |
378 | |
379 | /*****************************************************************************/ |
380 | static bool store_query( VC_URI_PARTS_T *p_uri, const char *name_start, |
381 | const char *equals_ptr, const char *query_end) |
382 | { |
383 | uint32_t name_len, value_len; |
384 | |
385 | if (equals_ptr) |
386 | { |
387 | name_len = equals_ptr - name_start; |
388 | value_len = query_end - equals_ptr - 1; /* Don't include '=' itself */ |
389 | } else { |
390 | name_len = query_end - name_start; |
391 | value_len = 0; |
392 | } |
393 | |
394 | /* Only store something if there is a name */ |
395 | if (name_len) |
396 | { |
397 | char *name, *value = NULL; |
398 | VC_URI_QUERY_T *p_query; |
399 | |
400 | if (equals_ptr) |
401 | { |
402 | value = create_unescaped_string(equals_ptr + 1, value_len); |
403 | if (!value) |
404 | return false; |
405 | equals_ptr = query_end; |
406 | } |
407 | |
408 | name = create_unescaped_string(name_start, name_len); |
409 | if (!name) |
410 | { |
411 | if (value) |
412 | free(value); |
413 | return false; |
414 | } |
415 | |
416 | /* Store query data in URI structure */ |
417 | p_query = &p_uri->queries[ p_uri->num_queries++ ]; |
418 | p_query->name = name; |
419 | p_query->value = value; |
420 | } |
421 | |
422 | return true; |
423 | } |
424 | |
425 | /*****************************************************************************/ |
426 | static bool parse_query( VC_URI_PARTS_T *p_uri, const char *str, uint32_t str_len ) |
427 | { |
428 | uint32_t ii; |
429 | uint32_t query_count; |
430 | VC_URI_QUERY_T *queries; |
431 | const char *name_start = str; |
432 | const char *equals_ptr = NULL; |
433 | char c; |
434 | |
435 | if (!str_len) |
436 | return true; |
437 | |
438 | /* Scan for the number of query items, so array can be allocated the right size */ |
439 | query_count = 1; /* At least */ |
440 | for (ii = 0; ii < str_len; ii++) |
441 | { |
442 | c = str[ii]; |
443 | |
444 | if (c == '&' || c ==';') |
445 | query_count++; |
446 | } |
447 | |
448 | queries = (VC_URI_QUERY_T *)malloc(query_count * sizeof(VC_URI_QUERY_T)); |
449 | if (!queries) |
450 | return false; |
451 | |
452 | p_uri->queries = queries; |
453 | |
454 | /* Go back and parse the string for each query item and store in array */ |
455 | for (ii = 0; ii < str_len; ii++) |
456 | { |
457 | c = *str; |
458 | |
459 | /* Take first '=' as break between name and value */ |
460 | if (c == '=' && !equals_ptr) |
461 | equals_ptr = str; |
462 | |
463 | /* If at the end of the name or name/value pair */ |
464 | if (c == '&' || c ==';') |
465 | { |
466 | if (!store_query(p_uri, name_start, equals_ptr, str)) |
467 | return false; |
468 | |
469 | equals_ptr = NULL; |
470 | name_start = str + 1; |
471 | } |
472 | |
473 | str++; |
474 | } |
475 | |
476 | return store_query(p_uri, name_start, equals_ptr, str); |
477 | } |
478 | |
479 | /*****************************************************************************/ |
480 | static uint32_t calculate_uri_length(const VC_URI_PARTS_T *p_uri) |
481 | { |
482 | uint32_t length = 0; |
483 | uint32_t count; |
484 | |
485 | /* With no scheme, assume this is a plain path (without escaping) */ |
486 | if (!p_uri->scheme) |
487 | return p_uri->path ? strlen(p_uri->path) : 0; |
488 | |
489 | length += escaped_length(p_uri->scheme, scheme_reserved_chars); |
490 | length++; /* for the colon */ |
491 | |
492 | if (p_uri->host) |
493 | { |
494 | length += escaped_length(p_uri->host, host_reserved_chars) + 2; /* for the double slash */ |
495 | if (p_uri->userinfo) |
496 | length += escaped_length(p_uri->userinfo, userinfo_reserved_chars) + 1; /* for the '@' */ |
497 | if (p_uri->port) |
498 | length += escaped_length(p_uri->port, port_reserved_chars) + 1; /* for the ':' */ |
499 | } |
500 | |
501 | if (p_uri->path) |
502 | length += escaped_length(p_uri->path, path_reserved_chars); |
503 | |
504 | count = p_uri->num_queries; |
505 | if (count) |
506 | { |
507 | VC_URI_QUERY_T * queries = p_uri->queries; |
508 | |
509 | while (count--) |
510 | { |
511 | /* The name is preceded by either the '?' or the '&' */ |
512 | length += escaped_length(queries->name, query_reserved_chars) + 1; |
513 | |
514 | /* The value is optional, but if present will require an '=' */ |
515 | if (queries->value) |
516 | length += escaped_length(queries->value, query_reserved_chars) + 1; |
517 | queries++; |
518 | } |
519 | } |
520 | |
521 | if (p_uri->fragment) |
522 | length += escaped_length(p_uri->fragment, fragment_reserved_chars) + 1; /* for the '#' */ |
523 | |
524 | return length; |
525 | } |
526 | |
527 | /*****************************************************************************/ |
528 | static void build_uri(const VC_URI_PARTS_T *p_uri, char *buffer, size_t buffer_size) |
529 | { |
530 | uint32_t count; |
531 | |
532 | /* With no scheme, assume this is a plain path (without escaping) */ |
533 | if (!p_uri->scheme) |
534 | { |
535 | if (p_uri->path) |
536 | strncpy(buffer, p_uri->path, buffer_size); |
537 | else |
538 | buffer[0] = '\0'; |
539 | return; |
540 | } |
541 | |
542 | buffer += escape_string(p_uri->scheme, buffer, scheme_reserved_chars); |
543 | *buffer++ = ':'; |
544 | |
545 | if (p_uri->host) |
546 | { |
547 | *buffer++ = '/'; |
548 | *buffer++ = '/'; |
549 | if (p_uri->userinfo) |
550 | { |
551 | buffer += escape_string(p_uri->userinfo, buffer, userinfo_reserved_chars); |
552 | *buffer++ = '@'; |
553 | } |
554 | buffer += escape_string(p_uri->host, buffer, host_reserved_chars); |
555 | if (p_uri->port) |
556 | { |
557 | *buffer++ = ':'; |
558 | buffer += escape_string(p_uri->port, buffer, port_reserved_chars); |
559 | } |
560 | } |
561 | |
562 | if (p_uri->path) |
563 | buffer += escape_string(p_uri->path, buffer, path_reserved_chars); |
564 | |
565 | count = p_uri->num_queries; |
566 | if (count) |
567 | { |
568 | VC_URI_QUERY_T * queries = p_uri->queries; |
569 | |
570 | *buffer++ = '?'; |
571 | while (count--) |
572 | { |
573 | buffer += escape_string(queries->name, buffer, query_reserved_chars); |
574 | |
575 | if (queries->value) |
576 | { |
577 | *buffer++ = '='; |
578 | buffer += escape_string(queries->value, buffer, query_reserved_chars); |
579 | } |
580 | |
581 | /* Add separator if there is another item to add */ |
582 | if (count) |
583 | *buffer++ = '&'; |
584 | |
585 | queries++; |
586 | } |
587 | } |
588 | |
589 | if (p_uri->fragment) |
590 | { |
591 | *buffer++ = '#'; |
592 | buffer += escape_string(p_uri->fragment, buffer, fragment_reserved_chars); |
593 | } |
594 | |
595 | *buffer = '\0'; |
596 | } |
597 | |
598 | /*****************************************************************************/ |
599 | static bool vc_uri_copy_base_path( const VC_URI_PARTS_T *base_uri, |
600 | VC_URI_PARTS_T *relative_uri ) |
601 | { |
602 | const char *base_path = vc_uri_path(base_uri); |
603 | |
604 | /* No path set (or empty), copy from base */ |
605 | if (!vc_uri_set_path(relative_uri, base_path)) |
606 | return false; |
607 | |
608 | /* If relative path has no queries, copy base queries across */ |
609 | if (!vc_uri_num_queries(relative_uri)) |
610 | { |
611 | uint32_t base_queries = vc_uri_num_queries(base_uri); |
612 | const char *name, *value; |
613 | uint32_t ii; |
614 | |
615 | for (ii = 0; ii < base_queries; ii++) |
616 | { |
617 | vc_uri_query(base_uri, ii, &name, &value); |
618 | if (!vc_uri_add_query(relative_uri, name, value)) |
619 | return false; |
620 | } |
621 | } |
622 | |
623 | return true; |
624 | } |
625 | |
626 | /*****************************************************************************/ |
627 | static void vc_uri_remove_single_dot_segments( char *path_str ) |
628 | { |
629 | char *slash = path_str - 1; |
630 | |
631 | while (slash++) |
632 | { |
633 | if (*slash == '.') |
634 | { |
635 | switch (slash[1]) |
636 | { |
637 | case '/': /* Single dot segment, remove it */ |
638 | memmove(slash, slash + 2, strlen(slash + 2) + 1); |
639 | break; |
640 | case '\0': /* Trailing single dot, remove it */ |
641 | *slash = '\0'; |
642 | break; |
643 | default: /* Something else (e.g. ".." or ".foo") */ |
644 | ; /* Do nothing */ |
645 | } |
646 | } |
647 | slash = strchr(slash, '/'); |
648 | } |
649 | } |
650 | |
651 | /*****************************************************************************/ |
652 | static void vc_uri_remove_double_dot_segments( char *path_str ) |
653 | { |
654 | char *previous_segment = path_str; |
655 | char *slash; |
656 | |
657 | if (previous_segment[0] == '/') |
658 | previous_segment++; |
659 | |
660 | /* Remove strings of the form "<segment>/../" (or "<segment>/.." at the end of the path) |
661 | * as long as <segment> is not itself ".." */ |
662 | slash = strchr(previous_segment, '/'); |
663 | while (slash) |
664 | { |
665 | if (previous_segment[0] != '.' || previous_segment[1] != '.' || previous_segment[2] != '/') |
666 | { |
667 | if (slash[1] == '.' && slash[2] == '.') |
668 | { |
669 | bool previous_segment_removed = true; |
670 | |
671 | switch (slash[3]) |
672 | { |
673 | case '/': /* "/../" inside path, snip it and last segment out */ |
674 | memmove(previous_segment, slash + 4, strlen(slash + 4) + 1); |
675 | break; |
676 | case '\0': /* Trailing "/.." on path, just terminate path at last segment */ |
677 | *previous_segment = '\0'; |
678 | break; |
679 | default: /* Not a simple ".." segment, so skip over it */ |
680 | previous_segment_removed = false; |
681 | } |
682 | |
683 | if (previous_segment_removed) |
684 | { |
685 | /* The segment just removed was the first one in the path (optionally |
686 | * prefixed by a slash), so no more can be removed: stop. */ |
687 | if (previous_segment < path_str + 2) |
688 | break; |
689 | |
690 | /* Move back to slash before previous segment, or the start of the path */ |
691 | slash = previous_segment - 1; |
692 | while (--slash >= path_str && *slash != '/') |
693 | ; /* Everything done in the while */ |
694 | } |
695 | } |
696 | } |
697 | previous_segment = slash + 1; |
698 | slash = strchr(previous_segment, '/'); |
699 | } |
700 | } |
701 | |
702 | /*****************************************************************************/ |
703 | /* API functions */ |
704 | /*****************************************************************************/ |
705 | |
706 | VC_URI_PARTS_T *vc_uri_create( void ) |
707 | { |
708 | VC_URI_PARTS_T *p_uri; |
709 | |
710 | p_uri = (VC_URI_PARTS_T *)malloc(sizeof(VC_URI_PARTS_T)); |
711 | if (p_uri) |
712 | { |
713 | memset(p_uri, 0, sizeof(VC_URI_PARTS_T)); |
714 | } |
715 | |
716 | return p_uri; |
717 | } |
718 | |
719 | /*****************************************************************************/ |
720 | void vc_uri_clear( VC_URI_PARTS_T *p_uri ) |
721 | { |
722 | if (!p_uri) |
723 | return; |
724 | |
725 | release_string(&p_uri->scheme); |
726 | release_string(&p_uri->userinfo); |
727 | release_string(&p_uri->host); |
728 | release_string(&p_uri->port); |
729 | release_string(&p_uri->path); |
730 | release_string(&p_uri->fragment); |
731 | |
732 | if (p_uri->queries) |
733 | { |
734 | VC_URI_QUERY_T *queries = p_uri->queries; |
735 | uint32_t count = p_uri->num_queries; |
736 | |
737 | while (count--) |
738 | { |
739 | release_string(&queries[count].name); |
740 | release_string(&queries[count].value); |
741 | } |
742 | |
743 | free(queries); |
744 | p_uri->queries = NULL; |
745 | p_uri->num_queries = 0; |
746 | } |
747 | } |
748 | |
749 | /*****************************************************************************/ |
750 | void vc_uri_release( VC_URI_PARTS_T *p_uri ) |
751 | { |
752 | if (!p_uri) |
753 | return; |
754 | |
755 | vc_uri_clear(p_uri); |
756 | |
757 | free(p_uri); |
758 | } |
759 | |
760 | /*****************************************************************************/ |
761 | bool vc_uri_parse( VC_URI_PARTS_T *p_uri, const char *uri ) |
762 | { |
763 | const char *marker; |
764 | uint32_t len; |
765 | |
766 | if (!p_uri || !uri) |
767 | return false; |
768 | |
769 | vc_uri_clear(p_uri); |
770 | |
771 | /* URI = scheme ":" hier_part [ "?" query ] [ "#" fragment ] */ |
772 | |
773 | /* Find end of scheme, or another separator */ |
774 | marker = vc_uri_find_delimiter(uri, SCHEME_DELIMITERS); |
775 | |
776 | if (*marker == ':') |
777 | { |
778 | len = (marker - uri); |
779 | if (isalpha((int)*uri) && len == 1 && marker[1] == '\\') |
780 | { |
781 | /* Looks like a bare, absolute DOS/Windows filename with a drive letter */ |
782 | /* coverity[double_free] Pointer freed and set to NULL */ |
783 | bool ret = duplicate_string(uri, &p_uri->path); |
784 | vc_uri_set_path_extension(p_uri); |
785 | return ret; |
786 | } |
787 | |
788 | p_uri->scheme = create_unescaped_string(uri, len); |
789 | if (!p_uri->scheme) |
790 | goto error; |
791 | |
792 | to_lower_string(p_uri->scheme); /* Schemes should be handled case-insensitively */ |
793 | uri = marker + 1; |
794 | } |
795 | |
796 | if (uri[0] == '/' && uri[1] == '/') /* hier-part includes authority */ |
797 | { |
798 | const char *userinfo_end = NULL; |
799 | |
800 | /* authority = [ userinfo "@" ] host [ ":" port ] */ |
801 | uri += 2; |
802 | |
803 | marker = vc_uri_find_delimiter(uri, NETWORK_DELIMITERS); |
804 | if (*marker == '@') |
805 | { |
806 | userinfo_end = marker; |
807 | marker = vc_uri_find_delimiter(marker + 1, HOST_PORT_DELIMITERS); |
808 | } |
809 | |
810 | if (!parse_authority(p_uri, uri, marker - uri, userinfo_end)) |
811 | goto error; |
812 | uri = marker; |
813 | } |
814 | |
815 | /* path */ |
816 | marker = vc_uri_find_delimiter(uri, PATH_DELIMITERS); |
817 | len = marker - uri; |
818 | if (len) |
819 | { |
820 | p_uri->path = create_unescaped_string(uri, len); |
821 | vc_uri_set_path_extension(p_uri); |
822 | if (!p_uri->path) |
823 | goto error; |
824 | } |
825 | |
826 | /* query */ |
827 | if (*marker == '?') |
828 | { |
829 | uri = marker + 1; |
830 | marker = vc_uri_find_delimiter(uri, QUERY_DELIMITERS); |
831 | if (!parse_query(p_uri, uri, marker - uri)) |
832 | goto error; |
833 | } |
834 | |
835 | /* fragment */ |
836 | if (*marker == '#') |
837 | { |
838 | uri = marker + 1; |
839 | p_uri->fragment = create_unescaped_string(uri, strlen(uri)); |
840 | if (!p_uri->fragment) |
841 | goto error; |
842 | } |
843 | |
844 | return true; |
845 | |
846 | error: |
847 | vc_uri_clear(p_uri); |
848 | return false; |
849 | } |
850 | |
851 | /*****************************************************************************/ |
852 | uint32_t vc_uri_build( const VC_URI_PARTS_T *p_uri, char *buffer, size_t buffer_size ) |
853 | { |
854 | uint32_t required_length; |
855 | |
856 | if (!p_uri) |
857 | return 0; |
858 | |
859 | required_length = calculate_uri_length(p_uri); |
860 | if (buffer && required_length < buffer_size) /* Allow for NUL */ |
861 | build_uri(p_uri, buffer, buffer_size); |
862 | |
863 | return required_length; |
864 | } |
865 | |
866 | /*****************************************************************************/ |
867 | const char *vc_uri_scheme( const VC_URI_PARTS_T *p_uri ) |
868 | { |
869 | return p_uri ? p_uri->scheme : NULL; |
870 | } |
871 | |
872 | /*****************************************************************************/ |
873 | const char *vc_uri_userinfo( const VC_URI_PARTS_T *p_uri ) |
874 | { |
875 | return p_uri ? p_uri->userinfo : NULL; |
876 | } |
877 | |
878 | /*****************************************************************************/ |
879 | const char *vc_uri_host( const VC_URI_PARTS_T *p_uri ) |
880 | { |
881 | return p_uri ? p_uri->host : NULL; |
882 | } |
883 | |
884 | /*****************************************************************************/ |
885 | const char *vc_uri_port( const VC_URI_PARTS_T *p_uri ) |
886 | { |
887 | return p_uri ? p_uri->port : NULL; |
888 | } |
889 | |
890 | /*****************************************************************************/ |
891 | const char *vc_uri_path( const VC_URI_PARTS_T *p_uri ) |
892 | { |
893 | return p_uri ? p_uri->path : NULL; |
894 | } |
895 | |
896 | /*****************************************************************************/ |
897 | const char *vc_uri_path_extension( const VC_URI_PARTS_T *p_uri ) |
898 | { |
899 | return p_uri ? p_uri->path_extension : NULL; |
900 | } |
901 | |
902 | /*****************************************************************************/ |
903 | const char *vc_uri_fragment( const VC_URI_PARTS_T *p_uri ) |
904 | { |
905 | return p_uri ? p_uri->fragment : NULL; |
906 | } |
907 | |
908 | /*****************************************************************************/ |
909 | uint32_t vc_uri_num_queries( const VC_URI_PARTS_T *p_uri ) |
910 | { |
911 | return p_uri ? p_uri->num_queries : 0; |
912 | } |
913 | |
914 | /*****************************************************************************/ |
915 | void vc_uri_query( const VC_URI_PARTS_T *p_uri, uint32_t index, const char **p_name, const char **p_value ) |
916 | { |
917 | const char *name = NULL; |
918 | const char *value = NULL; |
919 | |
920 | if (p_uri) |
921 | { |
922 | if (index < p_uri->num_queries) |
923 | { |
924 | name = p_uri->queries[index].name; |
925 | value = p_uri->queries[index].value; |
926 | } |
927 | } |
928 | |
929 | if (p_name) |
930 | *p_name = name; |
931 | if (p_value) |
932 | *p_value = value; |
933 | } |
934 | |
935 | /*****************************************************************************/ |
936 | bool vc_uri_find_query( VC_URI_PARTS_T *p_uri, uint32_t *p_index, const char *name, const char **p_value ) |
937 | { |
938 | unsigned int i = p_index ? *p_index : 0; |
939 | |
940 | if (!p_uri) |
941 | return false; |
942 | |
943 | for (; name && i < p_uri->num_queries; i++) |
944 | { |
945 | if (!strcmp(name, p_uri->queries[i].name)) |
946 | { |
947 | if (p_value) |
948 | *p_value = p_uri->queries[i].value; |
949 | if (p_index) |
950 | *p_index = i; |
951 | return true; |
952 | } |
953 | } |
954 | |
955 | return false; |
956 | } |
957 | |
958 | /*****************************************************************************/ |
959 | bool vc_uri_set_scheme( VC_URI_PARTS_T *p_uri, const char *scheme ) |
960 | { |
961 | return p_uri ? duplicate_string(scheme, &p_uri->scheme) : false; |
962 | } |
963 | |
964 | /*****************************************************************************/ |
965 | bool vc_uri_set_userinfo( VC_URI_PARTS_T *p_uri, const char *userinfo ) |
966 | { |
967 | return p_uri ? duplicate_string(userinfo, &p_uri->userinfo) : false; |
968 | } |
969 | |
970 | /*****************************************************************************/ |
971 | bool vc_uri_set_host( VC_URI_PARTS_T *p_uri, const char *host ) |
972 | { |
973 | return p_uri ? duplicate_string(host, &p_uri->host) : false; |
974 | } |
975 | |
976 | /*****************************************************************************/ |
977 | bool vc_uri_set_port( VC_URI_PARTS_T *p_uri, const char *port ) |
978 | { |
979 | return p_uri ? duplicate_string(port, &p_uri->port) : false; |
980 | } |
981 | |
982 | /*****************************************************************************/ |
983 | bool vc_uri_set_path( VC_URI_PARTS_T *p_uri, const char *path ) |
984 | { |
985 | bool ret = p_uri ? duplicate_string(path, &p_uri->path) : false; |
986 | vc_uri_set_path_extension(p_uri); |
987 | return ret; |
988 | } |
989 | |
990 | /*****************************************************************************/ |
991 | bool vc_uri_set_fragment( VC_URI_PARTS_T *p_uri, const char *fragment ) |
992 | { |
993 | return p_uri ? duplicate_string(fragment, &p_uri->fragment) : false; |
994 | } |
995 | |
996 | /*****************************************************************************/ |
997 | bool vc_uri_add_query( VC_URI_PARTS_T *p_uri, const char *name, const char *value ) |
998 | { |
999 | VC_URI_QUERY_T *queries; |
1000 | uint32_t count; |
1001 | |
1002 | if (!p_uri || !name) |
1003 | return false; |
1004 | |
1005 | count = p_uri->num_queries; |
1006 | if (p_uri->queries) |
1007 | queries = (VC_URI_QUERY_T *)realloc(p_uri->queries, (count + 1) * sizeof(VC_URI_QUERY_T)); |
1008 | else |
1009 | queries = (VC_URI_QUERY_T *)malloc(sizeof(VC_URI_QUERY_T)); |
1010 | |
1011 | if (!queries) |
1012 | return false; |
1013 | |
1014 | /* Always store the pointer, in case it has changed, and even if we fail to copy name/value */ |
1015 | p_uri->queries = queries; |
1016 | queries[count].name = NULL; |
1017 | queries[count].value = NULL; |
1018 | |
1019 | if (duplicate_string(name, &queries[count].name)) |
1020 | { |
1021 | if (duplicate_string(value, &queries[count].value)) |
1022 | { |
1023 | /* Successful exit path */ |
1024 | p_uri->num_queries++; |
1025 | return true; |
1026 | } |
1027 | |
1028 | release_string(&queries[count].name); |
1029 | } |
1030 | |
1031 | return false; |
1032 | } |
1033 | |
1034 | /*****************************************************************************/ |
1035 | bool vc_uri_merge( const VC_URI_PARTS_T *base_uri, VC_URI_PARTS_T *relative_uri ) |
1036 | { |
1037 | bool success = true; |
1038 | const char *relative_path; |
1039 | |
1040 | /* If scheme is already set, the URI is already absolute */ |
1041 | if (relative_uri->scheme) |
1042 | return true; |
1043 | |
1044 | /* Otherwise, copy the base scheme */ |
1045 | if (!duplicate_string(base_uri->scheme, &relative_uri->scheme)) |
1046 | return false; |
1047 | |
1048 | /* If any of the network info is set, use the rest of the relative URI as-is */ |
1049 | if (relative_uri->host || relative_uri->port || relative_uri->userinfo) |
1050 | return true; |
1051 | |
1052 | /* Otherwise, copy the base network info */ |
1053 | if (!duplicate_string(base_uri->host, &relative_uri->host) || |
1054 | !duplicate_string(base_uri->port, &relative_uri->port) || |
1055 | !duplicate_string(base_uri->userinfo, &relative_uri->userinfo)) |
1056 | return false; |
1057 | |
1058 | relative_path = relative_uri->path; |
1059 | |
1060 | if (!relative_path || !*relative_path) |
1061 | { |
1062 | /* No relative path (could be queries and/or fragment), so take base path */ |
1063 | success = vc_uri_copy_base_path(base_uri, relative_uri); |
1064 | } |
1065 | else if (*relative_path != '/') |
1066 | { |
1067 | const char *base_path = base_uri->path; |
1068 | char *merged_path; |
1069 | char *slash; |
1070 | size_t len; |
1071 | |
1072 | /* Path is relative, merge in with base path */ |
1073 | if (!base_path || !*base_path) |
1074 | { |
1075 | if (relative_uri->host || relative_uri->port || relative_uri->userinfo) |
1076 | base_path = "/" ; /* Need a separator to split network info from path */ |
1077 | else |
1078 | base_path = "" ; |
1079 | } |
1080 | |
1081 | len = strlen(base_path) + strlen(relative_path) + 1; |
1082 | |
1083 | /* Allocate space for largest possible combined path */ |
1084 | merged_path = (char *)malloc(len); |
1085 | if (!merged_path) |
1086 | return false; |
1087 | |
1088 | strncpy(merged_path, base_path, len); |
1089 | |
1090 | slash = strrchr(merged_path, '/'); /* Note: reverse search */ |
1091 | if (*relative_path == ';') |
1092 | { |
1093 | char *semi; |
1094 | |
1095 | /* Relative path is just parameters, so remove any base parameters in final segment */ |
1096 | if (!slash) |
1097 | slash = merged_path; |
1098 | semi = strchr(slash, ';'); |
1099 | if (semi) |
1100 | semi[0] = '\0'; |
1101 | } else { |
1102 | /* Remove final segment */ |
1103 | if (slash) |
1104 | slash[1] = '\0'; |
1105 | else |
1106 | merged_path[0] = '\0'; |
1107 | } |
1108 | strncat(merged_path, relative_path, len - strlen(merged_path) - 1); |
1109 | |
1110 | vc_uri_remove_single_dot_segments(merged_path); |
1111 | vc_uri_remove_double_dot_segments(merged_path); |
1112 | |
1113 | success = duplicate_string(merged_path, &relative_uri->path); |
1114 | |
1115 | free(merged_path); |
1116 | } |
1117 | /* Otherwise path is absolute, which can be left as-is */ |
1118 | |
1119 | return success; |
1120 | } |
1121 | |