quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

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 }