ares_uri.c (36428B)
1 /* MIT License 2 * 3 * Copyright (c) 2024 Brad house 4 * 5 * Permission is hereby granted, free of charge, to any person obtaining a copy 6 * of this software and associated documentation files (the "Software"), to deal 7 * in the Software without restriction, including without limitation the rights 8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 * copies of the Software, and to permit persons to whom the Software is 10 * furnished to do so, subject to the following conditions: 11 * 12 * The above copyright notice and this permission notice (including the next 13 * paragraph) shall be included in all copies or substantial portions of the 14 * Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 * SOFTWARE. 23 * 24 * SPDX-License-Identifier: MIT 25 */ 26 27 28 #include "ares_private.h" 29 #include "ares_uri.h" 30 #ifdef HAVE_STDINT_H 31 # include <stdint.h> 32 #endif 33 34 struct ares_uri { 35 char scheme[16]; 36 char *username; 37 char *password; 38 unsigned short port; 39 char host[256]; 40 char *path; 41 ares_htable_dict_t *query; 42 char *fragment; 43 }; 44 45 /* RFC3986 character set notes: 46 * gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" 47 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 48 * / "*" / "+" / "," / ";" / "=" 49 * reserved = gen-delims / sub-delims 50 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 51 * scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) 52 * authority = [ userinfo "@" ] host [ ":" port ] 53 * userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) 54 * NOTE: Use of the format "user:password" in the userinfo field is 55 * deprecated. Applications should not render as clear text any data 56 * after the first colon (":") character found within a userinfo 57 * subcomponent unless the data after the colon is the empty string 58 * (indicating no password). 59 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 60 * query = *( pchar / "/" / "?" ) 61 * fragment = *( pchar / "/" / "?" ) 62 * 63 * NOTE: Due to ambiguity, "+" in a query must be percent-encoded, as old 64 * URLs used that for spaces. 65 */ 66 67 68 static ares_bool_t ares_uri_chis_subdelim(char x) 69 { 70 switch (x) { 71 case '!': 72 return ARES_TRUE; 73 case '$': 74 return ARES_TRUE; 75 case '&': 76 return ARES_TRUE; 77 case '\'': 78 return ARES_TRUE; 79 case '(': 80 return ARES_TRUE; 81 case ')': 82 return ARES_TRUE; 83 case '*': 84 return ARES_TRUE; 85 case '+': 86 return ARES_TRUE; 87 case ',': 88 return ARES_TRUE; 89 case ';': 90 return ARES_TRUE; 91 case '=': 92 return ARES_TRUE; 93 default: 94 break; 95 } 96 return ARES_FALSE; 97 } 98 99 /* These don't actually appear to be referenced in any logic */ 100 #if 0 101 static ares_bool_t ares_uri_chis_gendelim(char x) 102 { 103 switch (x) { 104 case ':': 105 return ARES_TRUE; 106 case '/': 107 return ARES_TRUE; 108 case '?': 109 return ARES_TRUE; 110 case '#': 111 return ARES_TRUE; 112 case '[': 113 return ARES_TRUE; 114 case ']': 115 return ARES_TRUE; 116 case '@': 117 return ARES_TRUE; 118 default: 119 break; 120 } 121 return ARES_FALSE; 122 } 123 124 125 static ares_bool_t ares_uri_chis_reserved(char x) 126 { 127 return ares_uri_chis_gendelim(x) || ares_uri_chis_subdelim(x); 128 } 129 #endif 130 131 static ares_bool_t ares_uri_chis_unreserved(char x) 132 { 133 switch (x) { 134 case '-': 135 return ARES_TRUE; 136 case '.': 137 return ARES_TRUE; 138 case '_': 139 return ARES_TRUE; 140 case '~': 141 return ARES_TRUE; 142 default: 143 break; 144 } 145 return ares_isalpha(x) || ares_isdigit(x); 146 } 147 148 static ares_bool_t ares_uri_chis_scheme(char x) 149 { 150 switch (x) { 151 case '+': 152 return ARES_TRUE; 153 case '-': 154 return ARES_TRUE; 155 case '.': 156 return ARES_TRUE; 157 default: 158 break; 159 } 160 return ares_isalpha(x) || ares_isdigit(x); 161 } 162 163 static ares_bool_t ares_uri_chis_authority(char x) 164 { 165 /* This one here isn't well defined. We are going to include the valid 166 * characters of the subfields plus known delimiters */ 167 return ares_uri_chis_unreserved(x) || ares_uri_chis_subdelim(x) || x == '%' || 168 x == '[' || x == ']' || x == '@' || x == ':'; 169 } 170 171 static ares_bool_t ares_uri_chis_userinfo(char x) 172 { 173 /* NOTE: we don't include ':' here since we are using that as our 174 * username/password delimiter */ 175 return ares_uri_chis_unreserved(x) || ares_uri_chis_subdelim(x); 176 } 177 178 static ares_bool_t ares_uri_chis_path(char x) 179 { 180 switch (x) { 181 case ':': 182 return ARES_TRUE; 183 case '@': 184 return ARES_TRUE; 185 /* '/' isn't in the spec as a path character since its technically a 186 * delimiter but we're not splitting on '/' so we accept it as valid */ 187 case '/': 188 return ARES_TRUE; 189 default: 190 break; 191 } 192 return ares_uri_chis_unreserved(x) || ares_uri_chis_subdelim(x); 193 } 194 195 static ares_bool_t ares_uri_chis_path_enc(char x) 196 { 197 return ares_uri_chis_path(x) || x == '%'; 198 } 199 200 static ares_bool_t ares_uri_chis_query(char x) 201 { 202 switch (x) { 203 case '/': 204 return ARES_TRUE; 205 case '?': 206 return ARES_TRUE; 207 default: 208 break; 209 } 210 211 /* Exclude & and = used as delimiters, they're valid characters in the 212 * set, just not for the individual pieces */ 213 return ares_uri_chis_path(x) && x != '&' && x != '='; 214 } 215 216 static ares_bool_t ares_uri_chis_query_enc(char x) 217 { 218 return ares_uri_chis_query(x) || x == '%'; 219 } 220 221 static ares_bool_t ares_uri_chis_fragment(char x) 222 { 223 switch (x) { 224 case '/': 225 return ARES_TRUE; 226 case '?': 227 return ARES_TRUE; 228 default: 229 break; 230 } 231 return ares_uri_chis_path(x); 232 } 233 234 static ares_bool_t ares_uri_chis_fragment_enc(char x) 235 { 236 return ares_uri_chis_fragment(x) || x == '%'; 237 } 238 239 ares_uri_t *ares_uri_create(void) 240 { 241 ares_uri_t *uri = ares_malloc_zero(sizeof(*uri)); 242 243 if (uri == NULL) { 244 return NULL; 245 } 246 247 uri->query = ares_htable_dict_create(); 248 if (uri->query == NULL) { 249 ares_free(uri); 250 return NULL; 251 } 252 253 return uri; 254 } 255 256 void ares_uri_destroy(ares_uri_t *uri) 257 { 258 if (uri == NULL) { 259 return; 260 } 261 262 ares_free(uri->username); 263 ares_free(uri->password); 264 ares_free(uri->path); 265 ares_free(uri->fragment); 266 ares_htable_dict_destroy(uri->query); 267 ares_free(uri); 268 } 269 270 static ares_bool_t ares_uri_scheme_is_valid(const char *uri) 271 { 272 size_t i; 273 274 if (ares_strlen(uri) == 0) { 275 return ARES_FALSE; 276 } 277 278 if (!ares_isalpha(*uri)) { 279 return ARES_FALSE; 280 } 281 282 for (i = 0; uri[i] != 0; i++) { 283 if (!ares_uri_chis_scheme(uri[i])) { 284 return ARES_FALSE; 285 } 286 } 287 return ARES_TRUE; 288 } 289 290 static ares_bool_t ares_uri_str_isvalid(const char *str, size_t max_len, 291 ares_bool_t (*ischr)(char)) 292 { 293 size_t i; 294 295 if (str == NULL) { 296 return ARES_FALSE; 297 } 298 299 for (i = 0; i != max_len && str[i] != 0; i++) { 300 if (!ischr(str[i])) { 301 return ARES_FALSE; 302 } 303 } 304 return ARES_TRUE; 305 } 306 307 ares_status_t ares_uri_set_scheme(ares_uri_t *uri, const char *scheme) 308 { 309 if (uri == NULL) { 310 return ARES_EFORMERR; 311 } 312 313 if (!ares_uri_scheme_is_valid(scheme)) { 314 return ARES_EBADSTR; 315 } 316 317 ares_strcpy(uri->scheme, scheme, sizeof(uri->scheme)); 318 ares_str_lower(uri->scheme); 319 320 return ARES_SUCCESS; 321 } 322 323 const char *ares_uri_get_scheme(const ares_uri_t *uri) 324 { 325 if (uri == NULL) { 326 return NULL; 327 } 328 329 return uri->scheme; 330 } 331 332 static ares_status_t ares_uri_set_username_own(ares_uri_t *uri, char *username) 333 { 334 if (uri == NULL) { 335 return ARES_EFORMERR; 336 } 337 338 if (username != NULL && (!ares_str_isprint(username, ares_strlen(username)) || 339 ares_strlen(username) == 0)) { 340 return ARES_EBADSTR; 341 } 342 343 344 ares_free(uri->username); 345 uri->username = username; 346 return ARES_SUCCESS; 347 } 348 349 ares_status_t ares_uri_set_username(ares_uri_t *uri, const char *username) 350 { 351 ares_status_t status; 352 char *temp = NULL; 353 354 if (uri == NULL) { 355 return ARES_EFORMERR; 356 } 357 358 if (username != NULL) { 359 temp = ares_strdup(username); 360 if (temp == NULL) { 361 return ARES_ENOMEM; 362 } 363 } 364 365 status = ares_uri_set_username_own(uri, temp); 366 if (status != ARES_SUCCESS) { 367 ares_free(temp); 368 } 369 370 return status; 371 } 372 373 const char *ares_uri_get_username(const ares_uri_t *uri) 374 { 375 if (uri == NULL) { 376 return NULL; 377 } 378 379 return uri->username; 380 } 381 382 static ares_status_t ares_uri_set_password_own(ares_uri_t *uri, char *password) 383 { 384 if (uri == NULL) { 385 return ARES_EFORMERR; 386 } 387 388 if (password != NULL && !ares_str_isprint(password, ares_strlen(password))) { 389 return ARES_EBADSTR; 390 } 391 392 ares_free(uri->password); 393 uri->password = password; 394 return ARES_SUCCESS; 395 } 396 397 ares_status_t ares_uri_set_password(ares_uri_t *uri, const char *password) 398 { 399 ares_status_t status; 400 char *temp = NULL; 401 402 if (uri == NULL) { 403 return ARES_EFORMERR; 404 } 405 406 if (password != NULL) { 407 temp = ares_strdup(password); 408 if (temp == NULL) { 409 return ARES_ENOMEM; 410 } 411 } 412 413 status = ares_uri_set_password_own(uri, temp); 414 if (status != ARES_SUCCESS) { 415 ares_free(temp); 416 } 417 418 return status; 419 } 420 421 const char *ares_uri_get_password(const ares_uri_t *uri) 422 { 423 if (uri == NULL) { 424 return NULL; 425 } 426 427 return uri->password; 428 } 429 430 ares_status_t ares_uri_set_host(ares_uri_t *uri, const char *host) 431 { 432 struct ares_addr addr; 433 size_t addrlen; 434 char hoststr[256]; 435 char *ll_scope; 436 437 if (uri == NULL || ares_strlen(host) == 0 || 438 ares_strlen(host) >= sizeof(hoststr)) { 439 return ARES_EFORMERR; 440 } 441 442 ares_strcpy(hoststr, host, sizeof(hoststr)); 443 444 /* Look for '%' which could be a link-local scope for ipv6 addresses and 445 * parse it off */ 446 ll_scope = strchr(hoststr, '%'); 447 if (ll_scope != NULL) { 448 *ll_scope = 0; 449 ll_scope++; 450 if (!ares_str_isalnum(ll_scope)) { 451 return ARES_EBADNAME; 452 } 453 } 454 455 /* If its an IP address, normalize it */ 456 memset(&addr, 0, sizeof(addr)); 457 addr.family = AF_UNSPEC; 458 if (ares_dns_pton(hoststr, &addr, &addrlen) != NULL) { 459 char ipaddr[INET6_ADDRSTRLEN]; 460 ares_inet_ntop(addr.family, &addr.addr, ipaddr, sizeof(ipaddr)); 461 /* Only IPv6 is allowed to have a scope */ 462 if (ll_scope != NULL && addr.family != AF_INET6) { 463 return ARES_EBADNAME; 464 } 465 466 if (ll_scope != NULL) { 467 snprintf(uri->host, sizeof(uri->host), "%s%%%s", ipaddr, ll_scope); 468 } else { 469 ares_strcpy(uri->host, ipaddr, sizeof(uri->host)); 470 } 471 return ARES_SUCCESS; 472 } 473 474 /* If its a hostname, make sure its a valid charset */ 475 if (!ares_is_hostname(host)) { 476 return ARES_EBADNAME; 477 } 478 479 ares_strcpy(uri->host, host, sizeof(uri->host)); 480 return ARES_SUCCESS; 481 } 482 483 const char *ares_uri_get_host(const ares_uri_t *uri) 484 { 485 if (uri == NULL) { 486 return NULL; 487 } 488 489 return uri->host; 490 } 491 492 ares_status_t ares_uri_set_port(ares_uri_t *uri, unsigned short port) 493 { 494 if (uri == NULL) { 495 return ARES_EFORMERR; 496 } 497 uri->port = port; 498 return ARES_SUCCESS; 499 } 500 501 unsigned short ares_uri_get_port(const ares_uri_t *uri) 502 { 503 if (uri == NULL) { 504 return 0; 505 } 506 return uri->port; 507 } 508 509 /* URI spec says path normalization is a requirement */ 510 static char *ares_uri_path_normalize(const char *path) 511 { 512 ares_status_t status; 513 ares_array_t *arr = NULL; 514 ares_buf_t *outpath = NULL; 515 ares_buf_t *inpath = NULL; 516 ares_ssize_t i; 517 size_t j; 518 size_t len; 519 520 inpath = 521 ares_buf_create_const((const unsigned char *)path, ares_strlen(path)); 522 if (inpath == NULL) { 523 status = ARES_ENOMEM; 524 goto done; 525 } 526 527 outpath = ares_buf_create(); 528 if (outpath == NULL) { 529 status = ARES_ENOMEM; 530 goto done; 531 } 532 533 status = ares_buf_split_str_array(inpath, (const unsigned char *)"/", 1, 534 ARES_BUF_SPLIT_TRIM, 0, &arr); 535 if (status != ARES_SUCCESS) { 536 return NULL; 537 } 538 539 for (i = 0; i < (ares_ssize_t)ares_array_len(arr); i++) { 540 const char **strptr = ares_array_at(arr, (size_t)i); 541 const char *str = *strptr; 542 543 if (ares_streq(str, ".")) { 544 ares_array_remove_at(arr, (size_t)i); 545 i--; 546 } else if (ares_streq(str, "..")) { 547 if (i != 0) { 548 ares_array_remove_at(arr, (size_t)i - 1); 549 i--; 550 } 551 ares_array_remove_at(arr, (size_t)i); 552 i--; 553 } 554 } 555 556 status = ares_buf_append_byte(outpath, '/'); 557 if (status != ARES_SUCCESS) { 558 goto done; 559 } 560 561 len = ares_array_len(arr); 562 for (j = 0; j < len; j++) { 563 const char **strptr = ares_array_at(arr, j); 564 const char *str = *strptr; 565 status = ares_buf_append_str(outpath, str); 566 if (status != ARES_SUCCESS) { 567 goto done; 568 } 569 570 /* Path separator, but on the last entry, we need to check if it was 571 * originally terminated or not because they have different meanings */ 572 if (j != len - 1 || path[ares_strlen(path) - 1] == '/') { 573 status = ares_buf_append_byte(outpath, '/'); 574 if (status != ARES_SUCCESS) { 575 goto done; 576 } 577 } 578 } 579 580 done: 581 ares_array_destroy(arr); 582 ares_buf_destroy(inpath); 583 if (status != ARES_SUCCESS) { 584 ares_buf_destroy(outpath); 585 return NULL; 586 } 587 588 return ares_buf_finish_str(outpath, NULL); 589 } 590 591 ares_status_t ares_uri_set_path(ares_uri_t *uri, const char *path) 592 { 593 char *temp = NULL; 594 595 if (uri == NULL) { 596 return ARES_EFORMERR; 597 } 598 599 if (path != NULL && !ares_str_isprint(path, ares_strlen(path))) { 600 return ARES_EBADSTR; 601 } 602 603 if (path != NULL) { 604 temp = ares_uri_path_normalize(path); 605 if (temp == NULL) { 606 return ARES_ENOMEM; 607 } 608 } 609 610 ares_free(uri->path); 611 uri->path = temp; 612 613 return ARES_SUCCESS; 614 } 615 616 const char *ares_uri_get_path(const ares_uri_t *uri) 617 { 618 if (uri == NULL) { 619 return NULL; 620 } 621 622 return uri->path; 623 } 624 625 ares_status_t ares_uri_set_query_key(ares_uri_t *uri, const char *key, 626 const char *val) 627 { 628 if (uri == NULL || key == NULL || *key == 0) { 629 return ARES_EFORMERR; 630 } 631 632 if (!ares_str_isprint(key, ares_strlen(key)) || 633 (val != NULL && !ares_str_isprint(val, ares_strlen(val)))) { 634 return ARES_EBADSTR; 635 } 636 637 if (!ares_htable_dict_insert(uri->query, key, val)) { 638 return ARES_ENOMEM; 639 } 640 return ARES_SUCCESS; 641 } 642 643 ares_status_t ares_uri_del_query_key(ares_uri_t *uri, const char *key) 644 { 645 if (uri == NULL || key == NULL || *key == 0 || 646 !ares_str_isprint(key, ares_strlen(key))) { 647 return ARES_EFORMERR; 648 } 649 650 if (!ares_htable_dict_remove(uri->query, key)) { 651 return ARES_ENOTFOUND; 652 } 653 654 return ARES_SUCCESS; 655 } 656 657 const char *ares_uri_get_query_key(const ares_uri_t *uri, const char *key) 658 { 659 if (uri == NULL || key == NULL || *key == 0 || 660 !ares_str_isprint(key, ares_strlen(key))) { 661 return NULL; 662 } 663 664 return ares_htable_dict_get_direct(uri->query, key); 665 } 666 667 char **ares_uri_get_query_keys(const ares_uri_t *uri, size_t *num) 668 { 669 if (uri == NULL || num == NULL) { 670 return NULL; 671 } 672 673 return ares_htable_dict_keys(uri->query, num); 674 } 675 676 static ares_status_t ares_uri_set_fragment_own(ares_uri_t *uri, char *fragment) 677 { 678 if (uri == NULL) { 679 return ARES_EFORMERR; 680 } 681 682 if (fragment != NULL && !ares_str_isprint(fragment, ares_strlen(fragment))) { 683 return ARES_EBADSTR; 684 } 685 686 ares_free(uri->fragment); 687 uri->fragment = fragment; 688 return ARES_SUCCESS; 689 } 690 691 ares_status_t ares_uri_set_fragment(ares_uri_t *uri, const char *fragment) 692 { 693 ares_status_t status; 694 char *temp = NULL; 695 696 if (uri == NULL) { 697 return ARES_EFORMERR; 698 } 699 700 if (fragment != NULL) { 701 temp = ares_strdup(fragment); 702 if (temp == NULL) { 703 return ARES_ENOMEM; 704 } 705 } 706 707 status = ares_uri_set_fragment_own(uri, temp); 708 if (status != ARES_SUCCESS) { 709 ares_free(temp); 710 } 711 712 return status; 713 } 714 715 const char *ares_uri_get_fragment(const ares_uri_t *uri) 716 { 717 if (uri == NULL) { 718 return NULL; 719 } 720 return uri->fragment; 721 } 722 723 static ares_status_t ares_uri_encode_buf(ares_buf_t *buf, const char *str, 724 ares_bool_t (*ischr)(char)) 725 { 726 size_t i; 727 728 if (buf == NULL || str == NULL) { 729 return ARES_EFORMERR; 730 } 731 732 for (i = 0; str[i] != 0; i++) { 733 if (ischr(str[i])) { 734 if (ares_buf_append_byte(buf, (unsigned char)str[i]) != ARES_SUCCESS) { 735 return ARES_ENOMEM; 736 } 737 } else { 738 if (ares_buf_append_byte(buf, '%') != ARES_SUCCESS) { 739 return ARES_ENOMEM; 740 } 741 if (ares_buf_append_num_hex(buf, (size_t)str[i], 2) != ARES_SUCCESS) { 742 return ARES_ENOMEM; 743 } 744 } 745 } 746 return ARES_SUCCESS; 747 } 748 749 static ares_status_t ares_uri_write_scheme(const ares_uri_t *uri, 750 ares_buf_t *buf) 751 { 752 ares_status_t status; 753 754 status = ares_buf_append_str(buf, uri->scheme); 755 if (status != ARES_SUCCESS) { 756 return status; 757 } 758 759 status = ares_buf_append_str(buf, "://"); 760 761 return status; 762 } 763 764 static ares_status_t ares_uri_write_authority(const ares_uri_t *uri, 765 ares_buf_t *buf) 766 { 767 ares_status_t status; 768 ares_bool_t is_ipv6 = ARES_FALSE; 769 770 if (ares_strlen(uri->username)) { 771 status = ares_uri_encode_buf(buf, uri->username, ares_uri_chis_userinfo); 772 if (status != ARES_SUCCESS) { 773 return status; 774 } 775 } 776 777 if (ares_strlen(uri->password)) { 778 status = ares_buf_append_byte(buf, ':'); 779 if (status != ARES_SUCCESS) { 780 return status; 781 } 782 783 status = ares_uri_encode_buf(buf, uri->password, ares_uri_chis_userinfo); 784 if (status != ARES_SUCCESS) { 785 return status; 786 } 787 } 788 789 if (ares_strlen(uri->username) || ares_strlen(uri->password)) { 790 status = ares_buf_append_byte(buf, '@'); 791 if (status != ARES_SUCCESS) { 792 return status; 793 } 794 } 795 796 /* We need to write ipv6 addresses with [ ] */ 797 if (strchr(uri->host, '%') != NULL) { 798 /* If we have a % in the name, it must be ipv6 link local scope, so we 799 * don't need to check anything else */ 800 is_ipv6 = ARES_TRUE; 801 } else { 802 /* Parse the host to see if it is an ipv6 address */ 803 struct ares_addr addr; 804 size_t addrlen; 805 memset(&addr, 0, sizeof(addr)); 806 addr.family = AF_INET6; 807 if (ares_dns_pton(uri->host, &addr, &addrlen) != NULL) { 808 is_ipv6 = ARES_TRUE; 809 } 810 } 811 812 if (is_ipv6) { 813 status = ares_buf_append_byte(buf, '['); 814 if (status != ARES_SUCCESS) { 815 return status; 816 } 817 } 818 819 status = ares_buf_append_str(buf, uri->host); 820 if (status != ARES_SUCCESS) { 821 return status; 822 } 823 824 if (is_ipv6) { 825 status = ares_buf_append_byte(buf, ']'); 826 if (status != ARES_SUCCESS) { 827 return status; 828 } 829 } 830 831 if (uri->port > 0) { 832 status = ares_buf_append_byte(buf, ':'); 833 if (status != ARES_SUCCESS) { 834 return status; 835 } 836 status = ares_buf_append_num_dec(buf, uri->port, 0); 837 if (status != ARES_SUCCESS) { 838 return status; 839 } 840 } 841 842 return status; 843 } 844 845 static ares_status_t ares_uri_write_path(const ares_uri_t *uri, ares_buf_t *buf) 846 { 847 ares_status_t status; 848 849 if (ares_strlen(uri->path) == 0) { 850 return ARES_SUCCESS; 851 } 852 853 if (*uri->path != '/') { 854 status = ares_buf_append_byte(buf, '/'); 855 if (status != ARES_SUCCESS) { 856 return status; 857 } 858 } 859 860 status = ares_uri_encode_buf(buf, uri->path, ares_uri_chis_path); 861 if (status != ARES_SUCCESS) { 862 return status; 863 } 864 865 return ARES_SUCCESS; 866 } 867 868 static ares_status_t ares_uri_write_query(const ares_uri_t *uri, 869 ares_buf_t *buf) 870 { 871 ares_status_t status; 872 char **keys; 873 size_t num_keys = 0; 874 size_t i; 875 876 if (ares_htable_dict_num_keys(uri->query) == 0) { 877 return ARES_SUCCESS; 878 } 879 880 keys = ares_uri_get_query_keys(uri, &num_keys); 881 if (keys == NULL || num_keys == 0) { 882 return ARES_ENOMEM; 883 } 884 885 status = ares_buf_append_byte(buf, '?'); 886 if (status != ARES_SUCCESS) { 887 goto done; 888 } 889 890 for (i = 0; i < num_keys; i++) { 891 const char *val; 892 893 if (i != 0) { 894 status = ares_buf_append_byte(buf, '&'); 895 if (status != ARES_SUCCESS) { 896 goto done; 897 } 898 } 899 900 status = ares_uri_encode_buf(buf, keys[i], ares_uri_chis_query); 901 if (status != ARES_SUCCESS) { 902 goto done; 903 } 904 905 val = ares_uri_get_query_key(uri, keys[i]); 906 if (val != NULL) { 907 status = ares_buf_append_byte(buf, '='); 908 if (status != ARES_SUCCESS) { 909 goto done; 910 } 911 912 status = ares_uri_encode_buf(buf, val, ares_uri_chis_query); 913 if (status != ARES_SUCCESS) { 914 goto done; 915 } 916 } 917 } 918 919 done: 920 ares_free_array(keys, num_keys, ares_free); 921 return status; 922 } 923 924 static ares_status_t ares_uri_write_fragment(const ares_uri_t *uri, 925 ares_buf_t *buf) 926 { 927 ares_status_t status; 928 929 if (!ares_strlen(uri->fragment)) { 930 return ARES_SUCCESS; 931 } 932 933 status = ares_buf_append_byte(buf, '#'); 934 if (status != ARES_SUCCESS) { 935 return status; 936 } 937 938 status = ares_uri_encode_buf(buf, uri->fragment, ares_uri_chis_fragment); 939 if (status != ARES_SUCCESS) { 940 return status; 941 } 942 943 return ARES_SUCCESS; 944 } 945 946 ares_status_t ares_uri_write_buf(const ares_uri_t *uri, ares_buf_t *buf) 947 { 948 ares_status_t status; 949 size_t orig_len; 950 951 if (uri == NULL || buf == NULL) { 952 return ARES_EFORMERR; 953 } 954 955 if (ares_strlen(uri->scheme) == 0 || ares_strlen(uri->host) == 0) { 956 return ARES_ENODATA; 957 } 958 959 orig_len = ares_buf_len(buf); 960 961 status = ares_uri_write_scheme(uri, buf); 962 if (status != ARES_SUCCESS) { 963 goto done; 964 } 965 966 status = ares_uri_write_authority(uri, buf); 967 if (status != ARES_SUCCESS) { 968 goto done; 969 } 970 971 status = ares_uri_write_path(uri, buf); 972 if (status != ARES_SUCCESS) { 973 goto done; 974 } 975 976 status = ares_uri_write_query(uri, buf); 977 if (status != ARES_SUCCESS) { 978 goto done; 979 } 980 981 status = ares_uri_write_fragment(uri, buf); 982 if (status != ARES_SUCCESS) { 983 goto done; 984 } 985 986 done: 987 if (status != ARES_SUCCESS) { 988 ares_buf_set_length(buf, orig_len); 989 } 990 return status; 991 } 992 993 ares_status_t ares_uri_write(char **out, const ares_uri_t *uri) 994 { 995 ares_buf_t *buf; 996 ares_status_t status; 997 998 if (out == NULL || uri == NULL) { 999 return ARES_EFORMERR; 1000 } 1001 1002 *out = NULL; 1003 1004 buf = ares_buf_create(); 1005 if (buf == NULL) { 1006 return ARES_ENOMEM; 1007 } 1008 1009 status = ares_uri_write_buf(uri, buf); 1010 if (status != ARES_SUCCESS) { 1011 ares_buf_destroy(buf); 1012 return status; 1013 } 1014 1015 *out = ares_buf_finish_str(buf, NULL); 1016 return ARES_SUCCESS; 1017 } 1018 1019 #define xdigit_val(x) \ 1020 ((x >= '0' && x <= '9') \ 1021 ? (x - '0') \ 1022 : ((x >= 'A' && x <= 'F') ? (x - 'A' + 10) : (x - 'a' + 10))) 1023 1024 static ares_status_t ares_uri_decode_inplace(char *str, ares_bool_t is_query, 1025 ares_bool_t must_be_printable, 1026 size_t *out_len) 1027 { 1028 size_t i; 1029 size_t len = 0; 1030 1031 for (i = 0; str[i] != 0; i++) { 1032 if (is_query && str[i] == '+') { 1033 str[len++] = ' '; 1034 continue; 1035 } 1036 1037 if (str[i] != '%') { 1038 str[len++] = str[i]; 1039 continue; 1040 } 1041 1042 if (!ares_isxdigit(str[i + 1]) || !ares_isxdigit(str[i + 2])) { 1043 return ARES_EBADSTR; 1044 } 1045 1046 str[len] = (char)(xdigit_val(str[i + 1]) << 4 | xdigit_val(str[i + 2])); 1047 1048 if (must_be_printable && !ares_isprint(str[len])) { 1049 return ARES_EBADSTR; 1050 } 1051 1052 len++; 1053 1054 i += 2; 1055 } 1056 1057 str[len] = 0; 1058 1059 *out_len = len; 1060 return ARES_SUCCESS; 1061 } 1062 1063 static ares_status_t ares_uri_parse_scheme(ares_uri_t *uri, ares_buf_t *buf) 1064 { 1065 ares_status_t status; 1066 size_t bytes; 1067 char scheme[sizeof(uri->scheme)]; 1068 1069 ares_buf_tag(buf); 1070 1071 bytes = 1072 ares_buf_consume_until_seq(buf, (const unsigned char *)"://", 3, ARES_TRUE); 1073 if (bytes == SIZE_MAX || bytes > sizeof(uri->scheme)) { 1074 return ARES_EBADSTR; 1075 } 1076 1077 status = ares_buf_tag_fetch_string(buf, scheme, sizeof(scheme)); 1078 if (status != ARES_SUCCESS) { 1079 return status; 1080 } 1081 1082 status = ares_uri_set_scheme(uri, scheme); 1083 if (status != ARES_SUCCESS) { 1084 return status; 1085 } 1086 1087 /* Consume :// */ 1088 ares_buf_consume(buf, 3); 1089 1090 return ARES_SUCCESS; 1091 } 1092 1093 static ares_status_t ares_uri_parse_userinfo(ares_uri_t *uri, ares_buf_t *buf) 1094 { 1095 size_t userinfo_len; 1096 size_t username_len; 1097 ares_bool_t has_password = ARES_FALSE; 1098 char *temp = NULL; 1099 ares_status_t status; 1100 size_t len; 1101 1102 ares_buf_tag(buf); 1103 1104 /* Search for @, if its not found, return */ 1105 userinfo_len = ares_buf_consume_until_charset(buf, (const unsigned char *)"@", 1106 1, ARES_TRUE); 1107 1108 if (userinfo_len == SIZE_MAX) { 1109 return ARES_SUCCESS; 1110 } 1111 1112 /* Rollback since now we know there really is userinfo */ 1113 ares_buf_tag_rollback(buf); 1114 1115 /* Search for ':', if it isn't found or its past the '@' then we only have 1116 * a username and no password */ 1117 ares_buf_tag(buf); 1118 username_len = ares_buf_consume_until_charset(buf, (const unsigned char *)":", 1119 1, ARES_TRUE); 1120 if (username_len < userinfo_len) { 1121 has_password = ARES_TRUE; 1122 status = ares_buf_tag_fetch_strdup(buf, &temp); 1123 if (status != ARES_SUCCESS) { 1124 goto done; 1125 } 1126 1127 status = ares_uri_decode_inplace(temp, ARES_FALSE, ARES_TRUE, &len); 1128 if (status != ARES_SUCCESS) { 1129 goto done; 1130 } 1131 1132 status = ares_uri_set_username_own(uri, temp); 1133 if (status != ARES_SUCCESS) { 1134 goto done; 1135 } 1136 temp = NULL; 1137 1138 /* Consume : */ 1139 ares_buf_consume(buf, 1); 1140 } 1141 1142 ares_buf_tag(buf); 1143 ares_buf_consume_until_charset(buf, (const unsigned char *)"@", 1, ARES_TRUE); 1144 status = ares_buf_tag_fetch_strdup(buf, &temp); 1145 if (status != ARES_SUCCESS) { 1146 goto done; 1147 } 1148 1149 status = ares_uri_decode_inplace(temp, ARES_FALSE, ARES_TRUE, &len); 1150 if (status != ARES_SUCCESS) { 1151 goto done; 1152 } 1153 1154 if (has_password) { 1155 status = ares_uri_set_password_own(uri, temp); 1156 } else { 1157 status = ares_uri_set_username_own(uri, temp); 1158 } 1159 if (status != ARES_SUCCESS) { 1160 goto done; 1161 } 1162 temp = NULL; 1163 1164 /* Consume @ */ 1165 ares_buf_consume(buf, 1); 1166 1167 done: 1168 ares_free(temp); 1169 return status; 1170 } 1171 1172 static ares_status_t ares_uri_parse_hostport(ares_uri_t *uri, ares_buf_t *buf) 1173 { 1174 unsigned char b; 1175 char host[256]; 1176 char port[6]; 1177 size_t len; 1178 ares_status_t status; 1179 1180 status = ares_buf_peek_byte(buf, &b); 1181 if (status != ARES_SUCCESS) { 1182 return status; 1183 } 1184 1185 /* Bracketed syntax for ipv6 addresses */ 1186 if (b == '[') { 1187 ares_buf_consume(buf, 1); 1188 ares_buf_tag(buf); 1189 len = ares_buf_consume_until_charset(buf, (const unsigned char *)"]", 1, 1190 ARES_TRUE); 1191 if (len == SIZE_MAX) { 1192 return ARES_EBADSTR; 1193 } 1194 1195 status = ares_buf_tag_fetch_string(buf, host, sizeof(host)); 1196 if (status != ARES_SUCCESS) { 1197 return status; 1198 } 1199 /* Consume ']' */ 1200 ares_buf_consume(buf, 1); 1201 } else { 1202 /* Either ipv4 or hostname */ 1203 ares_buf_tag(buf); 1204 ares_buf_consume_until_charset(buf, (const unsigned char *)":", 1, 1205 ARES_FALSE); 1206 1207 status = ares_buf_tag_fetch_string(buf, host, sizeof(host)); 1208 if (status != ARES_SUCCESS) { 1209 return status; 1210 } 1211 } 1212 1213 status = ares_uri_set_host(uri, host); 1214 if (status != ARES_SUCCESS) { 1215 return status; 1216 } 1217 1218 /* No port if nothing left to consume */ 1219 if (!ares_buf_len(buf)) { 1220 return status; 1221 } 1222 1223 status = ares_buf_peek_byte(buf, &b); 1224 if (status != ARES_SUCCESS) { 1225 return status; 1226 } 1227 1228 /* Only valid extra character at this point is ':' */ 1229 if (b != ':') { 1230 return ARES_EBADSTR; 1231 } 1232 ares_buf_consume(buf, 1); 1233 1234 len = ares_buf_len(buf); 1235 if (len == 0 || len > sizeof(port) - 1) { 1236 return ARES_EBADSTR; 1237 } 1238 1239 status = ares_buf_fetch_bytes(buf, (unsigned char *)port, len); 1240 if (status != ARES_SUCCESS) { 1241 return status; 1242 } 1243 port[len] = 0; 1244 1245 if (!ares_str_isnum(port)) { 1246 return ARES_EBADSTR; 1247 } 1248 1249 status = ares_uri_set_port(uri, (unsigned short)atoi(port)); 1250 if (status != ARES_SUCCESS) { 1251 return status; 1252 } 1253 1254 return ARES_SUCCESS; 1255 } 1256 1257 static ares_status_t ares_uri_parse_authority(ares_uri_t *uri, ares_buf_t *buf) 1258 { 1259 ares_status_t status; 1260 size_t bytes; 1261 ares_buf_t *auth = NULL; 1262 const unsigned char *ptr; 1263 size_t ptr_len; 1264 1265 ares_buf_tag(buf); 1266 1267 bytes = ares_buf_consume_until_charset(buf, (const unsigned char *)"/?#", 3, 1268 ARES_FALSE); 1269 if (bytes == 0) { 1270 return ARES_EBADSTR; 1271 } 1272 1273 status = ares_buf_tag_fetch_constbuf(buf, &auth); 1274 if (status != ARES_SUCCESS) { 1275 goto done; 1276 } 1277 1278 ptr = ares_buf_peek(auth, &ptr_len); 1279 if (!ares_uri_str_isvalid((const char *)ptr, ptr_len, 1280 ares_uri_chis_authority)) { 1281 status = ARES_EBADSTR; 1282 goto done; 1283 } 1284 1285 status = ares_uri_parse_userinfo(uri, auth); 1286 if (status != ARES_SUCCESS) { 1287 goto done; 1288 } 1289 1290 status = ares_uri_parse_hostport(uri, auth); 1291 if (status != ARES_SUCCESS) { 1292 goto done; 1293 } 1294 1295 /* NOTE: the /, ?, or # is still in the buffer at this point so it can 1296 * be used to determine what parser should be called next */ 1297 1298 done: 1299 ares_buf_destroy(auth); 1300 return status; 1301 } 1302 1303 static ares_status_t ares_uri_parse_path(ares_uri_t *uri, ares_buf_t *buf) 1304 { 1305 unsigned char b; 1306 char *path = NULL; 1307 ares_status_t status; 1308 size_t len; 1309 1310 if (ares_buf_len(buf) == 0) { 1311 return ARES_SUCCESS; 1312 } 1313 1314 status = ares_buf_peek_byte(buf, &b); 1315 if (status != ARES_SUCCESS) { 1316 return status; 1317 } 1318 1319 /* Not a path, must be one of the others */ 1320 if (b != '/') { 1321 return ARES_SUCCESS; 1322 } 1323 1324 ares_buf_tag(buf); 1325 ares_buf_consume_until_charset(buf, (const unsigned char *)"?#", 2, 1326 ARES_FALSE); 1327 status = ares_buf_tag_fetch_strdup(buf, &path); 1328 if (status != ARES_SUCCESS) { 1329 goto done; 1330 } 1331 1332 if (!ares_uri_str_isvalid(path, SIZE_MAX, ares_uri_chis_path_enc)) { 1333 status = ARES_EBADSTR; 1334 goto done; 1335 } 1336 1337 status = ares_uri_decode_inplace(path, ARES_FALSE, ARES_TRUE, &len); 1338 if (status != ARES_SUCCESS) { 1339 goto done; 1340 } 1341 1342 status = ares_uri_set_path(uri, path); 1343 if (status != ARES_SUCCESS) { 1344 goto done; 1345 } 1346 1347 done: 1348 ares_free(path); 1349 return status; 1350 } 1351 1352 static ares_status_t ares_uri_parse_query_buf(ares_uri_t *uri, ares_buf_t *buf) 1353 { 1354 ares_status_t status = ARES_SUCCESS; 1355 char *key = NULL; 1356 char *val = NULL; 1357 1358 while (ares_buf_len(buf) > 0) { 1359 unsigned char b = 0; 1360 size_t len; 1361 1362 ares_buf_tag(buf); 1363 1364 /* Its valid to have only a key with no value, so we search for both 1365 * delims */ 1366 len = ares_buf_consume_until_charset(buf, (const unsigned char *)"&=", 2, 1367 ARES_FALSE); 1368 if (len == 0) { 1369 /* If we're here, we have a zero length key which is invalid */ 1370 status = ARES_EBADSTR; 1371 goto done; 1372 } 1373 1374 if (ares_buf_len(buf) > 0) { 1375 /* Determine if we stopped on & or = */ 1376 status = ares_buf_peek_byte(buf, &b); 1377 if (status != ARES_SUCCESS) { 1378 goto done; 1379 } 1380 } 1381 1382 status = ares_buf_tag_fetch_strdup(buf, &key); 1383 if (status != ARES_SUCCESS) { 1384 goto done; 1385 } 1386 1387 if (!ares_uri_str_isvalid(key, SIZE_MAX, ares_uri_chis_query_enc)) { 1388 status = ARES_EBADSTR; 1389 goto done; 1390 } 1391 1392 status = ares_uri_decode_inplace(key, ARES_TRUE, ARES_TRUE, &len); 1393 if (status != ARES_SUCCESS) { 1394 goto done; 1395 } 1396 1397 /* Fetch Value */ 1398 if (b == '=') { 1399 /* Skip delimiter */ 1400 ares_buf_consume(buf, 1); 1401 ares_buf_tag(buf); 1402 len = ares_buf_consume_until_charset(buf, (const unsigned char *)"&", 1, 1403 ARES_FALSE); 1404 if (len > 0) { 1405 status = ares_buf_tag_fetch_strdup(buf, &val); 1406 if (status != ARES_SUCCESS) { 1407 goto done; 1408 } 1409 1410 if (!ares_uri_str_isvalid(val, SIZE_MAX, ares_uri_chis_query_enc)) { 1411 status = ARES_EBADSTR; 1412 goto done; 1413 } 1414 1415 status = ares_uri_decode_inplace(val, ARES_TRUE, ARES_TRUE, &len); 1416 if (status != ARES_SUCCESS) { 1417 goto done; 1418 } 1419 } 1420 } 1421 1422 if (b != 0) { 1423 /* Consume '&' */ 1424 ares_buf_consume(buf, 1); 1425 } 1426 1427 status = ares_uri_set_query_key(uri, key, val); 1428 if (status != ARES_SUCCESS) { 1429 goto done; 1430 } 1431 1432 ares_free(key); 1433 key = NULL; 1434 ares_free(val); 1435 val = NULL; 1436 } 1437 1438 done: 1439 ares_free(key); 1440 ares_free(val); 1441 return status; 1442 } 1443 1444 static ares_status_t ares_uri_parse_query(ares_uri_t *uri, ares_buf_t *buf) 1445 { 1446 unsigned char b; 1447 ares_status_t status; 1448 ares_buf_t *query = NULL; 1449 size_t len; 1450 1451 if (ares_buf_len(buf) == 0) { 1452 return ARES_SUCCESS; 1453 } 1454 1455 status = ares_buf_peek_byte(buf, &b); 1456 if (status != ARES_SUCCESS) { 1457 return status; 1458 } 1459 1460 /* Not a query, must be one of the others */ 1461 if (b != '?') { 1462 return ARES_SUCCESS; 1463 } 1464 1465 /* Only possible terminator is fragment indicator of '#' */ 1466 ares_buf_consume(buf, 1); 1467 ares_buf_tag(buf); 1468 len = ares_buf_consume_until_charset(buf, (const unsigned char *)"#", 1, 1469 ARES_FALSE); 1470 if (len == 0) { 1471 /* No data, return */ 1472 return ARES_SUCCESS; 1473 } 1474 1475 status = ares_buf_tag_fetch_constbuf(buf, &query); 1476 if (status != ARES_SUCCESS) { 1477 return status; 1478 } 1479 1480 status = ares_uri_parse_query_buf(uri, query); 1481 ares_buf_destroy(query); 1482 1483 return status; 1484 } 1485 1486 static ares_status_t ares_uri_parse_fragment(ares_uri_t *uri, ares_buf_t *buf) 1487 { 1488 unsigned char b; 1489 char *fragment = NULL; 1490 ares_status_t status; 1491 size_t len; 1492 1493 if (ares_buf_len(buf) == 0) { 1494 return ARES_SUCCESS; 1495 } 1496 1497 status = ares_buf_peek_byte(buf, &b); 1498 if (status != ARES_SUCCESS) { 1499 return status; 1500 } 1501 1502 /* Not a fragment, must be one of the others */ 1503 if (b != '#') { 1504 return ARES_SUCCESS; 1505 } 1506 1507 ares_buf_consume(buf, 1); 1508 1509 if (ares_buf_len(buf) == 0) { 1510 return ARES_SUCCESS; 1511 } 1512 1513 /* Rest of the buffer is the fragment */ 1514 status = ares_buf_fetch_str_dup(buf, ares_buf_len(buf), &fragment); 1515 if (status != ARES_SUCCESS) { 1516 goto done; 1517 } 1518 1519 if (!ares_uri_str_isvalid(fragment, SIZE_MAX, ares_uri_chis_fragment_enc)) { 1520 status = ARES_EBADSTR; 1521 goto done; 1522 } 1523 1524 status = ares_uri_decode_inplace(fragment, ARES_FALSE, ARES_TRUE, &len); 1525 if (status != ARES_SUCCESS) { 1526 goto done; 1527 } 1528 1529 status = ares_uri_set_fragment_own(uri, fragment); 1530 if (status != ARES_SUCCESS) { 1531 goto done; 1532 } 1533 fragment = NULL; 1534 1535 done: 1536 ares_free(fragment); 1537 return status; 1538 } 1539 1540 ares_status_t ares_uri_parse_buf(ares_uri_t **out, ares_buf_t *buf) 1541 { 1542 ares_status_t status; 1543 ares_uri_t *uri = NULL; 1544 size_t orig_pos; 1545 1546 if (out == NULL || buf == NULL) { 1547 return ARES_EFORMERR; 1548 } 1549 1550 *out = NULL; 1551 1552 orig_pos = ares_buf_get_position(buf); 1553 1554 uri = ares_uri_create(); 1555 if (uri == NULL) { 1556 status = ARES_ENOMEM; 1557 goto done; 1558 } 1559 1560 status = ares_uri_parse_scheme(uri, buf); 1561 if (status != ARES_SUCCESS) { 1562 goto done; 1563 } 1564 1565 status = ares_uri_parse_authority(uri, buf); 1566 if (status != ARES_SUCCESS) { 1567 goto done; 1568 } 1569 1570 status = ares_uri_parse_path(uri, buf); 1571 if (status != ARES_SUCCESS) { 1572 goto done; 1573 } 1574 1575 status = ares_uri_parse_query(uri, buf); 1576 if (status != ARES_SUCCESS) { 1577 goto done; 1578 } 1579 1580 status = ares_uri_parse_fragment(uri, buf); 1581 if (status != ARES_SUCCESS) { 1582 goto done; 1583 } 1584 1585 done: 1586 if (status != ARES_SUCCESS) { 1587 ares_buf_set_position(buf, orig_pos); 1588 ares_uri_destroy(uri); 1589 } else { 1590 *out = uri; 1591 } 1592 return status; 1593 } 1594 1595 ares_status_t ares_uri_parse(ares_uri_t **out, const char *str) 1596 { 1597 ares_status_t status; 1598 ares_buf_t *buf = NULL; 1599 1600 if (out == NULL || str == NULL) { 1601 return ARES_EFORMERR; 1602 } 1603 1604 *out = NULL; 1605 1606 buf = ares_buf_create(); 1607 if (buf == NULL) { 1608 status = ARES_ENOMEM; 1609 goto done; 1610 } 1611 1612 status = ares_buf_append_str(buf, str); 1613 if (status != ARES_SUCCESS) { 1614 goto done; 1615 } 1616 1617 status = ares_uri_parse_buf(out, buf); 1618 if (status != ARES_SUCCESS) { 1619 goto done; 1620 } 1621 1622 done: 1623 ares_buf_destroy(buf); 1624 1625 return status; 1626 }