Coverage Report

Created: 2026-08-31 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/urlapi.c
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
#include "curl_setup.h"
25
26
#include "urldata.h"
27
#include "urlapi-int.h"
28
#include "strcase.h"
29
#include "url.h"
30
#include "escape.h"
31
#include "curlx/inet_pton.h"
32
#include "curlx/inet_ntop.h"
33
#include "curlx/strdup.h"
34
#include "idn.h"
35
#include "curlx/strparse.h"
36
#include "curl_memrchr.h"
37
38
#ifdef _WIN32
39
/* MS-DOS/Windows style drive prefix, eg c: in c:foo */
40
#define STARTS_WITH_DRIVE_PREFIX(str)        \
41
  ((('a' <= (str)[0] && (str)[0] <= 'z') ||  \
42
    ('A' <= (str)[0] && (str)[0] <= 'Z')) && \
43
   ((str)[1] == ':'))
44
#endif
45
46
/* MS-DOS/Windows style drive prefix, optionally with
47
 * a '|' instead of ':', followed by a slash or NUL */
48
#define STARTS_WITH_URL_DRIVE_PREFIX(str)                  \
49
4.53k
  ((('a' <= (str)[0] && (str)[0] <= 'z') ||                \
50
4.53k
    ('A' <= (str)[0] && (str)[0] <= 'Z')) &&               \
51
4.53k
   ((str)[1] == ':' || (str)[1] == '|') &&                 \
52
4.53k
   ((str)[2] == '/' || (str)[2] == '\\' || (str)[2] == 0))
53
54
/* scheme is not URL encoded, the longest libcurl supported ones are... */
55
2.14M
#define MAX_SCHEME_LEN 40
56
16.7k
#define MAX_ZONEID_LEN 16
57
58
/* characters not allowed in hostnames */
59
125k
#define HOSTNAME_INVALID_CHARS " \r\n\t/:#?!@{}[]\\$\'\"^`*<>=;,+&()%|"
60
61
/*
62
 * If USE_IPV6 is disabled, we still want to parse IPv6 addresses, so make
63
 * sure we have _some_ value for AF_INET6 without polluting our fake value
64
 * everywhere.
65
 */
66
#if !defined(USE_IPV6) && !defined(AF_INET6)
67
#define AF_INET6 (AF_INET + 1)
68
#endif
69
70
0
#define DEFAULT_SCHEME "https"
71
72
static void free_urlhandle(struct Curl_URL *u)
73
564k
{
74
564k
  curlx_free(u->scheme);
75
564k
  curlx_free(u->user);
76
564k
  curlx_strzero(u->password);
77
564k
  curlx_free(u->password);
78
564k
  curlx_free(u->options);
79
564k
  curlx_free(u->host);
80
564k
  curlx_free(u->zoneid);
81
564k
  curlx_free(u->path);
82
564k
  curlx_free(u->query);
83
564k
  curlx_free(u->fragment);
84
564k
}
85
86
/*
87
 * Find the separator at the end of the hostname, or the '?' in cases like
88
 * http://www.example.com?id=2380
89
 */
90
static const char *find_host_sep(const char *url)
91
309
{
92
  /* Find the start of the hostname */
93
309
  const char *sep = strstr(url, "//");
94
309
  if(!sep)
95
283
    sep = url;
96
26
  else
97
26
    sep += 2;
98
99
  /* Find first / or ? */
100
2.98k
  while(*sep && *sep != '/' && *sep != '?')
101
2.67k
    sep++;
102
103
309
  return sep;
104
309
}
105
106
/* convert CURLcode to CURLUcode */
107
#define cc2cu(x) \
108
0
  ((x) == CURLE_TOO_LARGE ? CURLUE_TOO_LARGE : CURLUE_OUT_OF_MEMORY)
109
110
/* urlencode_str() writes data into an output dynbuf and URL-encodes the
111
 * spaces in the source URL accordingly.
112
 *
113
 * This function re-encodes the string, meaning that it leaves already encoded
114
 * bytes as-is and works by encoding only what *has* to be encoded - unless it
115
 * has to uppercase the hex to normalize.
116
 *
117
 * Illegal percent-encoding sequences are left as-is.
118
 *
119
 * URL encoding should be skipped for hostnames, otherwise IDN resolution
120
 * will fail.
121
 *
122
 * 'query' tells if it is a query part or not, or if it is allowed to
123
 * "transition" into a query part with a question mark.
124
 *
125
 * @unittest 1675
126
 */
127
UNITTEST CURLUcode urlencode_str(struct dynbuf *o, const char *url,
128
                                 size_t len, bool relative,
129
                                 unsigned int query);
130
UNITTEST CURLUcode urlencode_str(struct dynbuf *o, const char *url,
131
                                 size_t len, bool relative,
132
                                 unsigned int query)
133
272k
{
134
  /* we must add this with whitespace-replacing */
135
272k
  const unsigned char *iptr;
136
272k
  const unsigned char *host_sep = (const unsigned char *)url;
137
272k
  CURLcode result = CURLE_OK;
138
139
272k
  DEBUGASSERT((query >= QUERY_NO) && (query <= QUERY_YES));
140
141
272k
  if(!relative) {
142
309
    size_t n;
143
309
    host_sep = (const unsigned char *)find_host_sep(url);
144
145
    /* output the first piece as-is */
146
309
    n = (const char *)host_sep - url;
147
309
    result = curlx_dyn_addn(o, url, n);
148
309
    len -= n;
149
309
  }
150
151
307M
  for(iptr = host_sep; len && !result; iptr++, len--) {
152
306M
    if(*iptr == ' ') {
153
23.3k
      if(query != QUERY_YES)
154
12.2k
        result = curlx_dyn_addn(o, "%20", 3);
155
11.1k
      else
156
11.1k
        result = curlx_dyn_addn(o, "+", 1);
157
23.3k
    }
158
306M
    else if((*iptr < ' ') || (*iptr >= 0x7f)) {
159
153M
      unsigned char out[3] = { '%' };
160
153M
      Curl_hexbyte(&out[1], *iptr);
161
153M
      result = curlx_dyn_addn(o, out, 3);
162
153M
    }
163
153M
    else if(*iptr == '%' && (len >= 3) &&
164
69.2M
            ISXDIGIT(iptr[1]) && ISXDIGIT(iptr[2]) &&
165
9.01M
            (ISLOWER(iptr[1]) || ISLOWER(iptr[2]))) {
166
      /* uppercase it */
167
100k
      unsigned char hex = (unsigned char)((curlx_hexval(iptr[1]) << 4) |
168
100k
                                          curlx_hexval(iptr[2]));
169
100k
      unsigned char out[3] = { '%' };
170
100k
      Curl_hexbyte(&out[1], hex);
171
100k
      result = curlx_dyn_addn(o, out, 3);
172
100k
      iptr += 2;
173
100k
      len -= 2;
174
100k
    }
175
153M
    else {
176
153M
      result = curlx_dyn_addn(o, iptr, 1);
177
153M
      if(*iptr == '?' && (query == QUERY_NOT_YET))
178
4.41k
        query = QUERY_YES;
179
153M
    }
180
306M
  }
181
182
272k
  if(result)
183
0
    return cc2cu(result);
184
272k
  return CURLUE_OK;
185
272k
}
186
187
/*
188
 * Returns the length of the scheme if the given URL is absolute (as opposed
189
 * to relative). Stores the scheme in the buffer if TRUE and 'buf' is
190
 * non-NULL. The buflen must be larger than MAX_SCHEME_LEN if buf is set.
191
 *
192
 * If 'guess_scheme' is TRUE, it means the URL might be provided without
193
 * scheme.
194
 */
195
size_t Curl_is_absolute_url(const char *url, char *buf, size_t buflen,
196
                            bool guess_scheme)
197
616k
{
198
616k
  size_t i = 0;
199
616k
  DEBUGASSERT(!buf || (buflen > MAX_SCHEME_LEN));
200
616k
  (void)buflen; /* only used in debug-builds */
201
616k
  if(buf)
202
292k
    buf[0] = 0; /* always leave a defined value in buf */
203
#ifdef _WIN32
204
  if(guess_scheme && STARTS_WITH_DRIVE_PREFIX(url))
205
    return 0;
206
#endif
207
616k
  if(ISALPHA(url[0]))
208
2.14M
    for(i = 1; i < MAX_SCHEME_LEN; ++i) {
209
2.14M
      char s = url[i];
210
2.14M
      if(s && (ISALNUM(s) || (s == '+') || (s == '-') || (s == '.'))) {
211
        /* RFC 3986 3.1 explains:
212
           scheme      = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
213
         */
214
1.61M
      }
215
521k
      else {
216
521k
        break;
217
521k
      }
218
2.14M
    }
219
616k
  if(i && (url[i] == ':') && ((url[i + 1] == '/') || !guess_scheme)) {
220
    /* If this does not guess scheme, the scheme always ends with the colon so
221
       that this also detects data: URLs etc. In guessing mode, data: could
222
       be the hostname "data" with a specified port number. */
223
224
    /* the length of the scheme is the name part only */
225
430k
    size_t len = i;
226
430k
    if(buf) {
227
223k
      Curl_strntolower(buf, url, i);
228
223k
      buf[i] = 0;
229
223k
    }
230
430k
    return len;
231
430k
  }
232
186k
  return 0;
233
616k
}
234
235
/* scan for byte values <= 31, 127 and sometimes space */
236
CURLUcode Curl_junkscan(const char *url, size_t *urllen, bool allowspace)
237
292k
{
238
292k
  size_t n = strlen(url);
239
292k
  size_t i;
240
292k
  unsigned char control;
241
292k
  const unsigned char *p = (const unsigned char *)url;
242
292k
  if(n > CURL_MAX_INPUT_LENGTH)
243
0
    return CURLUE_MALFORMED_INPUT;
244
245
292k
  control = allowspace ? 0x1f : 0x20;
246
598M
  for(i = 0; i < n; i++) {
247
597M
    if(p[i] <= control || p[i] == 127)
248
401
      return CURLUE_MALFORMED_INPUT;
249
597M
  }
250
292k
  *urllen = n;
251
292k
  return CURLUE_OK;
252
292k
}
253
254
/*
255
 * parse_hostname_login()
256
 *
257
 * Parse the login details (username, password and options) from the URL and
258
 * strip them out of the hostname
259
 *
260
 * @unittest 1675
261
 */
262
UNITTEST CURLUcode parse_hostname_login(struct Curl_URL *u,
263
                                        const char *login,
264
                                        size_t len,
265
                                        unsigned int flags,
266
                                        size_t *hostname_offset);
267
UNITTEST CURLUcode parse_hostname_login(struct Curl_URL *u,
268
                                        const char *login,
269
                                        size_t len,
270
                                        unsigned int flags,
271
                                        size_t *hostname_offset)
272
289k
{
273
289k
  CURLUcode ures = CURLUE_OK;
274
289k
  CURLcode result;
275
289k
  char *userp = NULL;
276
289k
  char *passwdp = NULL;
277
289k
  char *optionsp = NULL;
278
289k
  const struct Curl_scheme *h = NULL;
279
280
  /* At this point, we assume all the other special cases have been taken
281
   * care of, so the host is at most
282
   *
283
   *   [user[:password][;options]]@]hostname
284
   *
285
   * We need somewhere to put the embedded details, so do that first.
286
   */
287
289k
  const char *ptr;
288
289
289k
  DEBUGASSERT(login);
290
291
289k
  *hostname_offset = 0;
292
289k
  ptr = memchr(login, '@', len);
293
289k
  if(!ptr)
294
253k
    goto out;
295
296
  /* We will now try to extract the
297
   * possible login information in a string like:
298
   * ftp://user:password@ftp.site.example:8021/README */
299
35.8k
  ptr++;
300
301
  /* if this is a known scheme, get some details */
302
35.8k
  if(u->scheme)
303
29.4k
    h = Curl_get_scheme(u->scheme);
304
305
  /* We could use the login information in the URL so extract it. Only parse
306
     options if the handler says we should. Note that 'h' might be NULL! */
307
35.8k
  result = Curl_parse_login_details(login, ptr - login - 1,
308
35.8k
                                    &userp, &passwdp,
309
35.8k
                                    (h && (h->flags & PROTOPT_URLOPTIONS)) ?
310
35.8k
                                    &optionsp : NULL);
311
35.8k
  if(result) {
312
    /* the only possible error from Curl_parse_login_details is out of
313
       memory: */
314
0
    ures = CURLUE_OUT_OF_MEMORY;
315
0
    goto out;
316
0
  }
317
318
35.8k
  if(userp) {
319
35.8k
    if(flags & CURLU_DISALLOW_USER) {
320
      /* Option DISALLOW_USER is set and URL contains username. */
321
14
      ures = CURLUE_USER_NOT_ALLOWED;
322
14
      goto out;
323
14
    }
324
35.8k
    curlx_free(u->user);
325
35.8k
    u->user = userp;
326
35.8k
  }
327
328
35.8k
  if(passwdp) {
329
8.05k
    curlx_strzero(u->password);
330
8.05k
    curlx_free(u->password);
331
8.05k
    u->password = passwdp;
332
8.05k
  }
333
334
35.8k
  if(optionsp) {
335
182
    curlx_free(u->options);
336
182
    u->options = optionsp;
337
182
  }
338
339
  /* the hostname starts at this offset */
340
35.8k
  *hostname_offset = ptr - login;
341
35.8k
  return CURLUE_OK;
342
343
253k
out:
344
345
253k
  curlx_free(userp);
346
253k
  curlx_strzero(passwdp);
347
253k
  curlx_free(passwdp);
348
253k
  curlx_free(optionsp);
349
253k
  curlx_safefree(u->user);
350
253k
  curlx_strzero(u->password);
351
253k
  curlx_safefree(u->password);
352
253k
  curlx_safefree(u->options);
353
354
253k
  return ures;
355
35.8k
}
356
357
/* @unittest 1653 */
358
UNITTEST CURLUcode parse_port(struct Curl_URL *u, struct dynbuf *host,
359
                              bool has_scheme);
360
UNITTEST CURLUcode parse_port(struct Curl_URL *u, struct dynbuf *host,
361
                              bool has_scheme)
362
289k
{
363
289k
  const char *portptr;
364
289k
  const char *hostname = curlx_dyn_ptr(host);
365
  /*
366
   * Find the end of an IPv6 address on the ']' ending bracket.
367
   */
368
289k
  u->portnum = 0;
369
289k
  u->port_present = FALSE;
370
289k
  if(hostname[0] == '[') {
371
7.59k
    portptr = strchr(hostname, ']');
372
7.59k
    if(!portptr)
373
35
      return CURLUE_BAD_IPV6;
374
7.55k
    portptr++;
375
    /* this is a RFC2732-style specified IP-address */
376
7.55k
    if(*portptr) {
377
412
      if(*portptr != ':')
378
79
        return CURLUE_BAD_PORT_NUMBER;
379
412
    }
380
7.14k
    else
381
7.14k
      portptr = NULL;
382
7.55k
  }
383
281k
  else
384
281k
    portptr = strchr(hostname, ':');
385
386
288k
  if(portptr) {
387
10.8k
    curl_off_t port;
388
10.8k
    size_t keep = portptr - hostname;
389
10.8k
    int rc;
390
391
    /* Browser behavior adaptation. If there is a colon with no digits after,
392
       cut off the name there which makes us ignore the colon and use the
393
       default port. Firefox, Chrome and Safari all do that.
394
395
       Do not do it if the URL has no scheme, to make something that looks like
396
       a scheme not work! */
397
10.8k
    curlx_dyn_setlen(host, keep);
398
10.8k
    portptr++;
399
10.8k
    if(!*portptr)
400
2.15k
      return has_scheme ? CURLUE_OK : CURLUE_BAD_PORT_NUMBER;
401
8.64k
    if(*portptr == '\\')
402
17
      return CURLUE_BACKSLASH;
403
8.63k
    rc = curlx_str_number(&portptr, &port, 0xffff);
404
8.63k
    if(rc)
405
384
      return CURLUE_BAD_PORT_NUMBER;
406
8.24k
    else if(*portptr == '\\')
407
16
      return CURLUE_BACKSLASH;
408
8.23k
    else if(*portptr)
409
137
      return CURLUE_BAD_PORT_NUMBER;
410
411
8.09k
    u->portnum = (uint16_t)port;
412
8.09k
    u->port_present = TRUE;
413
8.09k
  }
414
415
286k
  return CURLUE_OK;
416
288k
}
417
418
/* This function assumes 'hostname' now starts with [. It trims 'hostname' in
419
 * place and it sets u->zoneid if present.
420
 *
421
 * @unittest 1675
422
 */
423
UNITTEST CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname,
424
                              size_t hlen);
425
UNITTEST CURLUcode ipv6_parse(struct Curl_URL *u, char *hostname,
426
                              size_t hlen) /* length of hostname */
427
7.94k
{
428
7.94k
  size_t len;
429
7.94k
  DEBUGASSERT(*hostname == '[');
430
7.94k
  if(hlen < 4) /* '[::]' is the shortest possible valid string */
431
30
    return CURLUE_BAD_IPV6;
432
7.91k
  hostname++;
433
7.91k
  hlen -= 2;
434
435
  /* only valid IPv6 letters are ok */
436
7.91k
  len = strspn(hostname, "0123456789abcdefABCDEF:.");
437
438
7.91k
  if(hlen != len) {
439
3.07k
    hlen = len;
440
3.07k
    if(hostname[len] == '%') {
441
      /* this could now be '%[zone id]' */
442
2.86k
      char zoneid[MAX_ZONEID_LEN];
443
2.86k
      int i = 0;
444
2.86k
      char *h = &hostname[len + 1];
445
      /* pass '25' if present and is a URL encoded percent sign */
446
2.86k
      if(!strncmp(h, "25", 2) && h[2] && (h[2] != ']'))
447
70
        h += 2;
448
19.3k
      while(*h && (*h != ']') && (i < (MAX_ZONEID_LEN - 1)))
449
16.4k
        zoneid[i++] = *h++;
450
2.86k
      if(!i || (']' != *h))
451
406
        return CURLUE_BAD_IPV6;
452
2.45k
      zoneid[i] = 0;
453
2.45k
      u->zoneid = curlx_strdup(zoneid);
454
2.45k
      if(!u->zoneid)
455
0
        return CURLUE_OUT_OF_MEMORY;
456
2.45k
      hostname[len] = ']'; /* insert end bracket */
457
2.45k
      hostname[len + 1] = 0; /* terminate the hostname */
458
2.45k
    }
459
213
    else
460
213
      return CURLUE_BAD_IPV6;
461
    /* hostname is fine */
462
3.07k
  }
463
464
  /* Normalize the IPv6 address */
465
7.29k
  {
466
7.29k
    char dest[16]; /* fits a binary IPv6 address */
467
7.29k
    hostname[hlen] = 0; /* end the address there */
468
7.29k
    if(curlx_inet_pton(AF_INET6, hostname, dest) != 1)
469
868
      return CURLUE_BAD_IPV6;
470
6.42k
    if(!curlx_inet_ntop(AF_INET6, dest, hostname, hlen + 1)) {
471
4.91k
      hlen = strlen(hostname); /* might be shorter now */
472
4.91k
      hostname[hlen + 1] = 0;
473
4.91k
    }
474
6.42k
    hostname[hlen] = ']'; /* restore ending bracket */
475
6.42k
  }
476
0
  return CURLUE_OK;
477
7.29k
}
478
479
static CURLUcode hostname_check(struct Curl_URL *u, char *hostname,
480
                                size_t hlen) /* length of hostname */
481
125k
{
482
125k
  size_t len;
483
125k
  DEBUGASSERT(hostname);
484
485
125k
  if(!hlen)
486
0
    return CURLUE_NO_HOST;
487
125k
  else if(hostname[0] == '[')
488
0
    return ipv6_parse(u, hostname, hlen);
489
125k
  else {
490
    /* letters from the second string are not ok */
491
125k
    len = strcspn(hostname, HOSTNAME_INVALID_CHARS);
492
125k
    if(hlen != len)
493
      /* hostname with bad content */
494
1.71k
      return CURLUE_BAD_HOSTNAME;
495
123k
    else if((hlen >= 2) &&
496
68.0k
            (hostname[hlen - 1] == '.') && (hostname[hlen - 2] == '.'))
497
      /* more than one trailing dot is not allowed */
498
112
      return CURLUE_BAD_HOSTNAME;
499
123k
    else if((hlen == 1) && (hostname[0] == '.'))
500
      /* a single dot alone is not allowed */
501
84
      return CURLUE_BAD_HOSTNAME;
502
125k
  }
503
123k
  return CURLUE_OK;
504
125k
}
505
506
/*
507
 * Handle partial IPv4 numerical addresses and different bases, like
508
 * '16843009', '0x7f', '0x7f.1' '0177.1.1.1' etc.
509
 *
510
 * If the given input string is syntactically wrong IPv4 or any part for
511
 * example is too big, this function returns HOST_NAME.
512
 *
513
 * Output the "normalized" version of that input string in plain quad decimal
514
 * integers.
515
 *
516
 * A single dot following the numerical address is accepted and "swallowed" as
517
 * if it was never there.
518
 *
519
 * Returns the host type.
520
 *
521
 * @unittest 1675
522
 */
523
UNITTEST int ipv4_normalize(struct dynbuf *host);
524
UNITTEST int ipv4_normalize(struct dynbuf *host)
525
286k
{
526
286k
  bool done = FALSE;
527
286k
  int n = 0;
528
286k
  const char *c = curlx_dyn_ptr(host);
529
286k
  unsigned int parts[4] = { 0, 0, 0, 0 };
530
286k
  CURLcode result = CURLE_OK;
531
532
286k
  if(*c == '[')
533
7.94k
    return HOST_IPV6;
534
535
777k
  while(!done) {
536
618k
    int rc;
537
618k
    curl_off_t l;
538
618k
    if(*c == '0') {
539
282k
      if(Curl_raw_tolower(c[1]) == 'x') {
540
1.29k
        c += 2; /* skip the prefix */
541
1.29k
        rc = curlx_str_hex(&c, &l, UINT_MAX);
542
1.29k
        if(rc)
543
388
          return HOST_NAME;
544
1.29k
      }
545
280k
      else
546
280k
        rc = curlx_str_octal(&c, &l, UINT_MAX);
547
282k
    }
548
336k
    else
549
336k
      rc = curlx_str_number(&c, &l, UINT_MAX);
550
551
618k
    if(rc) {
552
107k
      if(!n || (rc != STRE_NO_NUM) || *c)
553
105k
        return HOST_NAME;
554
1.58k
      n--;
555
1.58k
    }
556
510k
    else
557
510k
      parts[n] = (unsigned int)l;
558
559
512k
    switch(*c) {
560
340k
    case '.':
561
340k
      if(n == 3) {
562
403
        if(c[1])
563
          /* something follows this dot */
564
234
          return HOST_NAME;
565
169
        done = TRUE;
566
169
      }
567
340k
      else {
568
340k
        n++;
569
340k
        c++;
570
340k
      }
571
340k
      break;
572
573
340k
    case '\0':
574
158k
      done = TRUE;
575
158k
      break;
576
577
13.4k
    default:
578
13.4k
      return HOST_NAME;
579
512k
    }
580
512k
  }
581
582
158k
  switch(n) {
583
44.5k
  case 0: /* a -- 32 bits */
584
44.5k
    curlx_dyn_reset(host);
585
586
44.5k
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
587
44.5k
                            (parts[0] >> 24),
588
44.5k
                            ((parts[0] >> 16) & 0xff),
589
44.5k
                            ((parts[0] >> 8) & 0xff),
590
44.5k
                            (parts[0] & 0xff));
591
44.5k
    break;
592
2.30k
  case 1: /* a.b -- 8.24 bits */
593
2.30k
    if((parts[0] > 0xff) || (parts[1] > 0xffffff))
594
1.09k
      return HOST_NAME;
595
1.21k
    curlx_dyn_reset(host);
596
1.21k
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
597
1.21k
                            parts[0],
598
1.21k
                            ((parts[1] >> 16) & 0xff),
599
1.21k
                            ((parts[1] >> 8) & 0xff),
600
1.21k
                            (parts[1] & 0xff));
601
1.21k
    break;
602
2.43k
  case 2: /* a.b.c -- 8.8.16 bits */
603
2.43k
    if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xffff))
604
1.77k
      return HOST_NAME;
605
663
    curlx_dyn_reset(host);
606
663
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
607
663
                            parts[0],
608
663
                            parts[1],
609
663
                            ((parts[2] >> 8) & 0xff),
610
663
                            (parts[2] & 0xff));
611
663
    break;
612
109k
  case 3: /* a.b.c.d -- 8.8.8.8 bits */
613
109k
    if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xff) ||
614
107k
       (parts[3] > 0xff))
615
2.67k
      return HOST_NAME;
616
106k
    curlx_dyn_reset(host);
617
106k
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
618
106k
                            parts[0],
619
106k
                            parts[1],
620
106k
                            parts[2],
621
106k
                            parts[3]);
622
106k
    break;
623
158k
  }
624
153k
  if(result)
625
0
    return HOST_ERROR;
626
153k
  return HOST_IPV4;
627
153k
}
628
629
/* if necessary, replace the host content with a URL decoded version */
630
static CURLUcode urldecode_host(struct dynbuf *host)
631
286k
{
632
286k
  const char *per;
633
286k
  const char *hostname = curlx_dyn_ptr(host);
634
286k
  per = strchr(hostname, '%');
635
286k
  if(!per)
636
    /* nothing to decode */
637
282k
    return CURLUE_OK;
638
4.29k
  else {
639
    /* encoded */
640
4.29k
    size_t dlen;
641
4.29k
    char *decoded;
642
4.29k
    CURLcode result = Curl_urldecode(hostname, 0, &decoded, &dlen,
643
4.29k
                                     REJECT_CTRL);
644
4.29k
    if(result)
645
69
      return CURLUE_BAD_HOSTNAME;
646
4.22k
    curlx_dyn_reset(host);
647
4.22k
    result = curlx_dyn_addn(host, decoded, dlen);
648
4.22k
    curlx_free(decoded);
649
4.22k
    if(result)
650
0
      return cc2cu(result);
651
4.22k
  }
652
653
4.22k
  return CURLUE_OK;
654
286k
}
655
656
static CURLUcode parse_authority(struct Curl_URL *u,
657
                                 const char *auth, size_t authlen,
658
                                 unsigned int flags,
659
                                 struct dynbuf *host,
660
                                 bool has_scheme)
661
289k
{
662
289k
  size_t offset;
663
289k
  CURLUcode uc;
664
289k
  CURLcode result;
665
666
  /*
667
   * Parse the login details and strip them out of the hostname.
668
   */
669
289k
  uc = parse_hostname_login(u, auth, authlen, flags, &offset);
670
289k
  if(uc)
671
14
    return uc;
672
673
289k
  result = curlx_dyn_addn(host, auth + offset, authlen - offset);
674
289k
  if(result) {
675
0
    uc = cc2cu(result);
676
0
    return uc;
677
0
  }
678
679
289k
  uc = parse_port(u, host, has_scheme);
680
681
289k
  if(!curlx_dyn_len(host))
682
    /* this makes no-host errors override port number problems */
683
2.13k
    uc = CURLUE_NO_HOST;
684
289k
  if(!uc)
685
286k
    uc = urldecode_host(host);
686
289k
  if(uc)
687
2.69k
    return uc;
688
689
286k
  switch(ipv4_normalize(host)) {
690
153k
  case HOST_IPV4:
691
153k
    break;
692
7.94k
  case HOST_IPV6:
693
7.94k
    uc = ipv6_parse(u, curlx_dyn_ptr(host), curlx_dyn_len(host));
694
7.94k
    break;
695
125k
  case HOST_NAME:
696
125k
    uc = hostname_check(u, curlx_dyn_ptr(host), curlx_dyn_len(host));
697
125k
    break;
698
0
  case HOST_ERROR:
699
0
    uc = CURLUE_OUT_OF_MEMORY;
700
0
    break;
701
0
  default:
702
0
    uc = CURLUE_BAD_HOSTNAME; /* Bad IPv4 address even */
703
0
    break;
704
286k
  }
705
706
286k
  return uc;
707
286k
}
708
709
/* used for HTTP/2 server push */
710
CURLUcode Curl_url_set_authority(CURLU *u, const char *authority)
711
0
{
712
0
  CURLUcode ures;
713
0
  struct dynbuf host;
714
715
0
  DEBUGASSERT(authority);
716
0
  curlx_dyn_init(&host, CURL_MAX_INPUT_LENGTH);
717
718
0
  ures = parse_authority(u, authority, strlen(authority),
719
0
                         CURLU_DISALLOW_USER, &host, !!u->scheme);
720
0
  if(ures)
721
0
    curlx_dyn_free(&host);
722
0
  else {
723
0
    curlx_free(u->host);
724
0
    u->host = curlx_dyn_ptr(&host);
725
0
  }
726
0
  return ures;
727
0
}
728
729
/*
730
 * "Remove Dot Segments"
731
 * https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4
732
 */
733
734
static bool is_dot(const char **str, size_t *clen)
735
139M
{
736
139M
  const char *p = *str;
737
139M
  if(*p == '.') {
738
2.04M
    (*str)++;
739
2.04M
    (*clen)--;
740
2.04M
    return TRUE;
741
2.04M
  }
742
137M
  else if((*clen >= 3) &&
743
137M
          (p[0] == '%') && (p[1] == '2') && ((p[2] | 0x20) == 'e')) {
744
386k
    *str += 3;
745
386k
    *clen -= 3;
746
386k
    return TRUE;
747
386k
  }
748
136M
  return FALSE;
749
139M
}
750
751
293M
#define ISSLASH(x) ((x) == '/')
752
753
/* prescan the string to see if it needs work */
754
static bool needs_dedotdot(const char *p, size_t pn)
755
85.2k
{
756
  /* a single byte path cannot be cleaned up */
757
85.2k
  if(pn < 2)
758
752
    return FALSE;
759
106M
  while(pn) {
760
106M
    if(is_dot(&p, &pn)) {
761
      /* "./" or dot before end of string */
762
534k
      if(!pn || ISSLASH(*p))
763
7.80k
        return TRUE;
764
      /* "../" or ".." before end of string */
765
526k
      else if(is_dot(&p, &pn) && (!pn || ISSLASH(*p)))
766
2.36k
        return TRUE;
767
534k
    }
768
105M
    else {
769
105M
      p++;
770
105M
      pn--;
771
105M
    }
772
106M
  }
773
74.3k
  return FALSE;
774
84.5k
}
775
776
/*
777
 * dedotdotify()
778
 *
779
 * This function gets a null-terminated path with dot and dotdot sequences
780
 * passed in and strips them off according to the rules in RFC 3986 section
781
 * 5.2.4.
782
 *
783
 * The function handles a path. It should not contain the query nor fragment.
784
 *
785
 * RETURNS
786
 *
787
 * Zero for success and 'out' set to an allocated string (or NULL if there's
788
 * nothing to do).
789
 *
790
 * @unittest 1395
791
 */
792
UNITTEST int dedotdotify(const char *input, size_t clen, char **outp);
793
UNITTEST int dedotdotify(const char *input, size_t clen, char **outp)
794
85.2k
{
795
85.2k
  struct dynbuf out;
796
85.2k
  CURLcode result = CURLE_OK;
797
798
  /* variables for leading dot checks */
799
85.2k
  const char *dinput = input;
800
85.2k
  size_t dlen = clen;
801
802
85.2k
  *outp = NULL;
803
85.2k
  if(!needs_dedotdot(input, clen))
804
75.0k
    return 0;
805
806
10.1k
  curlx_dyn_init(&out, clen + 1);
807
808
  /* if the input buffer begins with a prefix of "../" or "./", then remove
809
     that prefix from the input buffer; otherwise, */
810
10.1k
  if(is_dot(&dinput, &dlen)) {
811
0
    if(ISSLASH(*dinput)) {
812
      /* one dot followed by a slash */
813
0
      input = dinput + 1;
814
0
      clen = dlen - 1;
815
0
    }
816
817
    /* if the input buffer consists only of "." or "..", then remove
818
       that from the input buffer; otherwise, */
819
0
    else if(is_dot(&dinput, &dlen)) {
820
0
      if(!dlen)
821
        /* .. [end] */
822
0
        goto end;
823
0
      else if(ISSLASH(*dinput)) {
824
        /* ../ */
825
0
        input = dinput + 1;
826
0
        clen = dlen - 1;
827
0
      }
828
0
    }
829
0
  }
830
831
289M
  while(clen && !result) { /* until end of path content */
832
289M
    if(ISSLASH(*input)) {
833
31.5M
      const char *p = &input[1];
834
31.5M
      size_t blen = clen - 1;
835
      /* if the input buffer begins with a prefix of "/./" or "/.", where "."
836
         is a complete path segment, then replace that prefix with "/" in the
837
         input buffer; otherwise, */
838
31.5M
      if(is_dot(&p, &blen)) {
839
1.35M
        if(!blen) { /* /. */
840
625
          result = curlx_dyn_addn(&out, "/", 1);
841
625
          break;
842
625
        }
843
1.35M
        else if(ISSLASH(*p)) { /* /./ */
844
351k
          input = p;
845
351k
          clen = blen;
846
351k
          continue;
847
351k
        }
848
849
        /* if the input buffer begins with a prefix of "/../" or "/..", where
850
           ".." is a complete path segment, then replace that prefix with "/"
851
           in the input buffer and remove the last segment and its preceding
852
           "/" (if any) from the output buffer; otherwise, */
853
999k
        else if(is_dot(&p, &blen) && (ISSLASH(*p) || !blen)) {
854
          /* remove the last segment from the output buffer */
855
360k
          size_t len = curlx_dyn_len(&out);
856
360k
          if(len) {
857
351k
            const char *ptr = curlx_dyn_ptr(&out);
858
351k
            const char *last = memrchr(ptr, '/', len);
859
351k
            if(last)
860
              /* trim the output at the slash */
861
351k
              curlx_dyn_setlen(&out, last - ptr);
862
351k
          }
863
864
360k
          if(blen) { /* /../ */
865
360k
            input = p;
866
360k
            clen = blen;
867
360k
            continue;
868
360k
          }
869
379
          result = curlx_dyn_addn(&out, "/", 1);
870
379
          break;
871
360k
        }
872
1.35M
      }
873
31.5M
    }
874
875
    /* move the first path segment in the input buffer to the end of the
876
       output buffer, including the initial "/" character (if any) and any
877
       subsequent characters up to, but not including, the next "/" character
878
       or the end of the input buffer. */
879
880
288M
    result = curlx_dyn_addn(&out, input, 1);
881
288M
    input++;
882
288M
    clen--;
883
288M
  }
884
10.1k
end:
885
10.1k
  if(!result) {
886
10.1k
    if(curlx_dyn_len(&out))
887
10.1k
      *outp = curlx_dyn_ptr(&out);
888
0
    else {
889
0
      *outp = curlx_strdup("");
890
0
      if(!*outp)
891
0
        return 1;
892
0
    }
893
10.1k
  }
894
10.1k
  return result ? 1 : 0; /* success */
895
10.1k
}
896
897
/*
898
 * @unittest 1675
899
 */
900
UNITTEST CURLUcode parse_file(const char *url, size_t urllen, CURLU *u,
901
                              const char **pathp, size_t *pathlenp);
902
UNITTEST CURLUcode parse_file(const char *url, size_t urllen, CURLU *u,
903
                              const char **pathp, size_t *pathlenp)
904
2.44k
{
905
2.44k
  const char *path;
906
2.44k
  size_t pathlen;
907
908
2.44k
  *pathp = NULL;
909
2.44k
  *pathlenp = 0;
910
2.44k
  if(urllen <= 6)
911
    /* file:/ is not enough to actually be a complete file: URL */
912
16
    return CURLUE_BAD_FILE_URL;
913
914
  /* path has been allocated large enough to hold this */
915
2.43k
  path = &url[5];
916
2.43k
  pathlen = urllen - 5;
917
918
  /* RFC 8089: file-hier-part = ( "//" auth-path ) / local-path, where
919
     local-path also starts with a "/". So reject anything that does not
920
     start with at least one "/" */
921
2.43k
  if(path[0] != '/')
922
5
    return CURLUE_BAD_FILE_URL;
923
924
  /* Extra handling URLs with an authority component (i.e. that start with
925
   * "file://")
926
   *
927
   * We allow omitted hostname (e.g. file:/<path>) -- valid according to
928
   * RFC 8089, but not the (current) WHAT-WG URL spec.
929
   */
930
2.42k
  if(path[1] == '/') {
931
    /* swallow the two slashes */
932
496
    const char *ptr = &path[2];
933
934
    /*
935
     * According to RFC 8089, a file: URL can be reliably dereferenced if:
936
     *
937
     *  o it has no/blank hostname, or
938
     *
939
     *  o the hostname matches "localhost" (case-insensitively), or
940
     *
941
     *  o the hostname is a FQDN that resolves to this machine, or
942
     *
943
     * For brevity, we only consider URLs with empty, "localhost", or
944
     * "127.0.0.1" hostnames as local, otherwise as an UNC String.
945
     *
946
     * Additionally, there is an exception for URLs with a Windows drive
947
     * letter in the authority (which was accidentally omitted from RFC 8089
948
     * Appendix E, but believe me, it was meant to be there. --MK)
949
     */
950
496
    if(ptr[0] != '/' && !STARTS_WITH_URL_DRIVE_PREFIX(ptr)) {
951
      /* the URL includes a hostname, it must match "localhost" or
952
         "127.0.0.1" to be valid */
953
332
      if(checkprefix("localhost/", ptr) ||
954
318
         checkprefix("127.0.0.1/", ptr)) {
955
36
        ptr += 9; /* now points to the slash after the host */
956
36
      }
957
296
      else
958
        /* Invalid file://hostname/, expected localhost or 127.0.0.1 or
959
           none */
960
296
        return CURLUE_BAD_FILE_URL;
961
332
    }
962
963
200
    path = ptr;
964
200
    pathlen = urllen - (ptr - url);
965
200
  }
966
967
2.13k
#if !defined(_WIN32) && !defined(MSDOS) && !defined(__CYGWIN__)
968
  /* Do not allow Windows drive letters when not in Windows.
969
   * This catches both "file:/c:" and "file:c:" */
970
2.13k
  if(('/' == path[0] && STARTS_WITH_URL_DRIVE_PREFIX(&path[1])) ||
971
2.06k
     STARTS_WITH_URL_DRIVE_PREFIX(path)) {
972
    /* File drive letters are only accepted in MS-DOS/Windows */
973
183
    return CURLUE_BAD_FILE_URL;
974
183
  }
975
#else
976
  /* If the path starts with a slash and a drive letter, ditch the slash */
977
  if('/' == path[0] && STARTS_WITH_URL_DRIVE_PREFIX(&path[1])) {
978
    /* This cannot be done with strcpy, as the memory chunks overlap! */
979
    path++;
980
    pathlen--;
981
  }
982
#endif
983
1.94k
  u->scheme = curlx_strdup("file");
984
1.94k
  if(!u->scheme)
985
0
    return CURLUE_OUT_OF_MEMORY;
986
987
1.94k
  *pathp = path;
988
1.94k
  *pathlenp = pathlen;
989
1.94k
  return CURLUE_OK;
990
1.94k
}
991
992
static CURLUcode parse_scheme(const char *url, CURLU *u, char *schemebuf,
993
                              size_t schemelen, unsigned int flags,
994
                              const char **hostpp)
995
290k
{
996
  /* clear path */
997
290k
  const char *schemep = NULL;
998
999
290k
  if(schemelen) {
1000
221k
    int num_slashes = 0;
1001
221k
    const char *p = &url[schemelen + 1];
1002
221k
    if(!Curl_get_scheme(schemebuf) && !(flags & CURLU_NON_SUPPORT_SCHEME))
1003
506
      return CURLUE_UNSUPPORTED_SCHEME;
1004
1005
220k
    if(!ISSLASH(*p))
1006
      /* less than one */
1007
81
      return CURLUE_BAD_SLASHES;
1008
220k
    if((flags & CURLU_NO_AUTHORITY)) {
1009
0
      while(ISSLASH(*p) && (num_slashes < 2)) {
1010
0
        p++;
1011
0
        num_slashes++;
1012
0
      }
1013
0
    }
1014
220k
    else {
1015
596k
      while(ISSLASH(*p) && (num_slashes < 4)) {
1016
375k
        p++;
1017
375k
        num_slashes++;
1018
375k
      }
1019
220k
      if(num_slashes > 3)
1020
20
        return CURLUE_BAD_SLASHES;
1021
220k
    }
1022
1023
220k
    schemep = schemebuf;
1024
220k
    *hostpp = p; /* hostname starts here */
1025
220k
  }
1026
68.5k
  else {
1027
    /* no scheme! */
1028
1029
68.5k
    if(!(flags & (CURLU_DEFAULT_SCHEME | CURLU_GUESS_SCHEME)))
1030
0
      return CURLUE_BAD_SCHEME;
1031
1032
68.5k
    if(flags & CURLU_DEFAULT_SCHEME)
1033
0
      schemep = DEFAULT_SCHEME;
1034
1035
    /*
1036
     * The URL was badly formatted, let's try without scheme specified.
1037
     */
1038
68.5k
    *hostpp = url;
1039
68.5k
  }
1040
1041
289k
  if(schemep) {
1042
220k
    u->scheme = curlx_strdup(schemep);
1043
220k
    if(!u->scheme)
1044
0
      return CURLUE_OUT_OF_MEMORY;
1045
220k
  }
1046
289k
  return CURLUE_OK;
1047
289k
}
1048
1049
static CURLUcode guess_scheme(CURLU *u, struct dynbuf *host)
1050
64.1k
{
1051
64.1k
  const char *hostname = curlx_dyn_ptr(host);
1052
64.1k
  const char *schemep = NULL;
1053
  /* legacy curl-style guess based on hostname */
1054
64.1k
  if(checkprefix("ftp.", hostname))
1055
3.22k
    schemep = "ftp";
1056
60.9k
  else if(checkprefix("dict.", hostname))
1057
5.15k
    schemep = "dict";
1058
55.7k
  else if(checkprefix("ldap.", hostname))
1059
2.02k
    schemep = "ldap";
1060
53.7k
  else if(checkprefix("imap.", hostname))
1061
1.89k
    schemep = "imap";
1062
51.8k
  else if(checkprefix("smtp.", hostname))
1063
3.79k
    schemep = "smtp";
1064
48.0k
  else if(checkprefix("pop3.", hostname))
1065
3.63k
    schemep = "pop3";
1066
44.4k
  else
1067
44.4k
    schemep = "http";
1068
1069
64.1k
  u->scheme = curlx_strdup(schemep);
1070
64.1k
  if(!u->scheme)
1071
0
    return CURLUE_OUT_OF_MEMORY;
1072
1073
64.1k
  u->guessed_scheme = TRUE;
1074
64.1k
  return CURLUE_OK;
1075
64.1k
}
1076
1077
static CURLUcode handle_fragment(CURLU *u, const char *fragment,
1078
                                 size_t fraglen, unsigned int flags)
1079
34.8k
{
1080
34.8k
  CURLUcode ures;
1081
34.8k
  u->fragment_present = TRUE;
1082
34.8k
  if(fraglen > 1) {
1083
    /* skip the leading '#' in the copy but include the null-terminator */
1084
32.5k
    if(flags & CURLU_URLENCODE) {
1085
16.8k
      struct dynbuf enc;
1086
16.8k
      curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1087
16.8k
      ures = urlencode_str(&enc, fragment + 1, fraglen - 1, TRUE, QUERY_NO);
1088
16.8k
      if(ures)
1089
0
        return ures;
1090
16.8k
      u->fragment = curlx_dyn_ptr(&enc);
1091
16.8k
    }
1092
15.7k
    else {
1093
15.7k
      u->fragment = curlx_memdup0(fragment + 1, fraglen - 1);
1094
15.7k
      if(!u->fragment)
1095
0
        return CURLUE_OUT_OF_MEMORY;
1096
15.7k
    }
1097
32.5k
  }
1098
34.8k
  return CURLUE_OK;
1099
34.8k
}
1100
1101
static CURLUcode handle_query(CURLU *u, const char *query,
1102
                              size_t qlen, unsigned int flags)
1103
30.8k
{
1104
30.8k
  u->query_present = TRUE;
1105
30.8k
  if(qlen > 1) {
1106
27.3k
    if(flags & CURLU_URLENCODE) {
1107
10.1k
      struct dynbuf enc;
1108
10.1k
      CURLUcode ures;
1109
10.1k
      curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1110
      /* skip the leading question mark */
1111
10.1k
      ures = urlencode_str(&enc, query + 1, qlen - 1, TRUE, QUERY_YES);
1112
10.1k
      if(ures)
1113
0
        return ures;
1114
10.1k
      u->query = curlx_dyn_ptr(&enc);
1115
10.1k
    }
1116
17.2k
    else {
1117
17.2k
      u->query = curlx_memdup0(query + 1, qlen - 1);
1118
17.2k
      if(!u->query)
1119
0
        return CURLUE_OUT_OF_MEMORY;
1120
17.2k
    }
1121
27.3k
  }
1122
3.43k
  else {
1123
    /* single byte query */
1124
3.43k
    u->query = curlx_strdup("");
1125
3.43k
    if(!u->query)
1126
0
      return CURLUE_OUT_OF_MEMORY;
1127
3.43k
  }
1128
30.8k
  return CURLUE_OK;
1129
30.8k
}
1130
1131
static CURLUcode handle_path(CURLU *u, const char *path,
1132
                             size_t pathlen, unsigned int flags,
1133
                             bool is_file)
1134
284k
{
1135
284k
  CURLUcode ures;
1136
284k
  if(pathlen && (flags & CURLU_URLENCODE)) {
1137
51.3k
    struct dynbuf enc;
1138
51.3k
    curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1139
51.3k
    ures = urlencode_str(&enc, path, pathlen, TRUE, QUERY_NO);
1140
51.3k
    if(ures)
1141
0
      return ures;
1142
51.3k
    pathlen = curlx_dyn_len(&enc);
1143
51.3k
    path = u->path = curlx_dyn_ptr(&enc);
1144
51.3k
  }
1145
1146
284k
  if(pathlen >= (size_t)(1 + !is_file)) {
1147
    /* paths for file:// scheme can be one byte, others need to be two */
1148
90.0k
    if(!u->path) {
1149
50.1k
      u->path = curlx_memdup0(path, pathlen);
1150
50.1k
      if(!u->path)
1151
0
        return CURLUE_OUT_OF_MEMORY;
1152
50.1k
      path = u->path;
1153
50.1k
    }
1154
39.9k
    else if(flags & CURLU_URLENCODE)
1155
      /* it might have encoded more than the path so cut it */
1156
39.9k
      u->path[pathlen] = 0;
1157
1158
90.0k
    if(!(flags & CURLU_PATH_AS_IS)) {
1159
      /* remove ../ and ./ sequences according to RFC3986 */
1160
85.2k
      char *dedot;
1161
85.2k
      int err = dedotdotify(path, pathlen, &dedot);
1162
85.2k
      if(err)
1163
0
        return CURLUE_OUT_OF_MEMORY;
1164
85.2k
      if(dedot) {
1165
10.1k
        curlx_free(u->path);
1166
10.1k
        u->path = dedot;
1167
10.1k
      }
1168
85.2k
    }
1169
90.0k
  }
1170
284k
  return CURLUE_OK;
1171
284k
}
1172
1173
static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
1174
292k
{
1175
292k
  const char *path;
1176
292k
  size_t pathlen;
1177
292k
  char schemebuf[MAX_SCHEME_LEN + 1];
1178
292k
  size_t schemelen = 0;
1179
292k
  size_t urllen;
1180
292k
  CURLUcode ures = CURLUE_OK;
1181
292k
  struct dynbuf host;
1182
292k
  bool is_file = FALSE;
1183
1184
292k
  DEBUGASSERT(url);
1185
1186
292k
  curlx_dyn_init(&host, CURL_MAX_INPUT_LENGTH);
1187
1188
292k
  ures = Curl_junkscan(url, &urllen, !!(flags & CURLU_ALLOW_SPACE));
1189
292k
  if(ures)
1190
401
    goto fail;
1191
1192
292k
  schemelen = Curl_is_absolute_url(url, schemebuf, sizeof(schemebuf),
1193
292k
                                   flags & (CURLU_GUESS_SCHEME |
1194
292k
                                            CURLU_DEFAULT_SCHEME));
1195
1196
  /* handle the file: scheme */
1197
292k
  if(schemelen && !strcmp(schemebuf, "file")) {
1198
2.44k
    is_file = TRUE;
1199
2.44k
    ures = parse_file(url, urllen, u, &path, &pathlen);
1200
2.44k
  }
1201
290k
  else {
1202
290k
    const char *hostp = NULL;
1203
290k
    size_t hostlen;
1204
290k
    ures = parse_scheme(url, u, schemebuf, schemelen, flags, &hostp);
1205
290k
    if(ures)
1206
607
      goto fail;
1207
1208
    /* find the end of the hostname + port number */
1209
289k
    hostlen = strcspn(hostp, "/?#");
1210
289k
    path = &hostp[hostlen];
1211
1212
    /* this pathlen also contains the query and the fragment */
1213
289k
    pathlen = urllen - (path - url);
1214
289k
    if(hostlen) {
1215
289k
      ures = parse_authority(u, hostp, hostlen, flags, &host, !!u->scheme);
1216
289k
      if(!ures && (flags & CURLU_GUESS_SCHEME) && !u->scheme)
1217
64.1k
        ures = guess_scheme(u, &host);
1218
289k
    }
1219
368
    else if(flags & CURLU_NO_AUTHORITY) {
1220
      /* allowed to be empty. */
1221
0
      if(curlx_dyn_add(&host, ""))
1222
0
        ures = CURLUE_OUT_OF_MEMORY;
1223
0
    }
1224
368
    else
1225
368
      ures = CURLUE_NO_HOST;
1226
289k
  }
1227
291k
  if(!ures) {
1228
    /* The path might at this point contain a fragment and/or a query to
1229
       handle */
1230
284k
    const char *fragment = strchr(path, '#');
1231
284k
    if(fragment) {
1232
34.8k
      size_t fraglen = pathlen - (fragment - path);
1233
34.8k
      ures = handle_fragment(u, fragment, fraglen, flags);
1234
      /* after this, pathlen still contains the query */
1235
34.8k
      pathlen -= fraglen;
1236
34.8k
    }
1237
284k
  }
1238
291k
  if(!ures) {
1239
284k
    const char *query = memchr(path, '?', pathlen);
1240
284k
    if(query) {
1241
30.8k
      size_t qlen = pathlen - (query - path);
1242
30.8k
      ures = handle_query(u, query, qlen, flags);
1243
30.8k
      pathlen -= qlen;
1244
30.8k
    }
1245
284k
  }
1246
291k
  if(!ures)
1247
    /* the fragment and query parts are trimmed off from the path */
1248
284k
    ures = handle_path(u, path, pathlen, flags, is_file);
1249
291k
  if(!ures) {
1250
284k
    u->host = curlx_dyn_ptr(&host);
1251
284k
    return CURLUE_OK;
1252
284k
  }
1253
8.01k
fail:
1254
8.01k
  curlx_dyn_free(&host);
1255
8.01k
  free_urlhandle(u);
1256
8.01k
  return ures;
1257
291k
}
1258
1259
/*
1260
 * Parse the URL and, if successful, replace everything in the Curl_URL struct.
1261
 */
1262
static CURLUcode parseurl_and_replace(const char *url, CURLU *u,
1263
                                      unsigned int flags)
1264
292k
{
1265
292k
  CURLUcode ures;
1266
292k
  CURLU tmpurl;
1267
292k
  memset(&tmpurl, 0, sizeof(tmpurl));
1268
292k
  ures = parseurl(url, &tmpurl, flags);
1269
292k
  if(!ures) {
1270
284k
    free_urlhandle(u);
1271
284k
    *u = tmpurl;
1272
284k
  }
1273
292k
  return ures;
1274
292k
}
1275
1276
/*
1277
 * Concatenate a relative URL onto a base URL making it absolute.
1278
 */
1279
static CURLUcode redirect_url(const char *base, const char *relurl,
1280
                              CURLU *u, unsigned int flags)
1281
23.8k
{
1282
23.8k
  struct dynbuf urlbuf;
1283
23.8k
  bool host_changed = FALSE;
1284
23.8k
  const char *useurl = relurl;
1285
23.8k
  const char *cutoff = NULL;
1286
23.8k
  size_t prelen;
1287
23.8k
  CURLUcode uc;
1288
  /* this can get here with a NULL u->scheme only if asked to use the default
1289
     scheme, so allow fallback to that */
1290
23.8k
  const char *scheme = u->scheme ? u->scheme : DEFAULT_SCHEME;
1291
1292
  /* protsep points to the start of the hostname, after [scheme]:// */
1293
23.8k
  const char *protsep = base + strlen(scheme) + 3;
1294
23.8k
  DEBUGASSERT(base && relurl && u); /* all set here */
1295
23.8k
  if(!base)
1296
0
    return CURLUE_MALFORMED_INPUT; /* should never happen */
1297
1298
  /* handle different relative URL types */
1299
23.8k
  switch(relurl[0]) {
1300
1.22k
  case '/':
1301
1.22k
    if(relurl[1] == '/') {
1302
      /* protocol-relative URL: //example.com/path */
1303
309
      cutoff = protsep;
1304
309
      useurl = &relurl[2];
1305
309
      host_changed = TRUE;
1306
309
    }
1307
916
    else
1308
      /* absolute /path */
1309
916
      cutoff = strchr(protsep, '/');
1310
1.22k
    break;
1311
1312
6.64k
  case '#':
1313
    /* fragment-only change */
1314
6.64k
    if(u->fragment_present)
1315
5.57k
      cutoff = strchr(protsep, '#');
1316
6.64k
    break;
1317
1318
15.9k
  default:
1319
    /* path or query-only change */
1320
15.9k
    if(u->query_present)
1321
      /* remove existing query */
1322
3.58k
      cutoff = strchr(protsep, '?');
1323
12.3k
    else if(u->fragment_present)
1324
      /* Remove existing fragment */
1325
2.14k
      cutoff = strchr(protsep, '#');
1326
1327
15.9k
    if(relurl[0] != '?') {
1328
      /* append a relative path after the last slash */
1329
15.2k
      cutoff = memrchr(protsep, '/',
1330
15.2k
                       cutoff ? (size_t)(cutoff - protsep) : strlen(protsep));
1331
15.2k
      if(cutoff)
1332
15.2k
        cutoff++; /* truncate after last slash */
1333
15.2k
    }
1334
15.9k
    break;
1335
23.8k
  }
1336
1337
23.8k
  prelen = cutoff ? (size_t)(cutoff - base) : strlen(base);
1338
1339
  /* build new URL */
1340
23.8k
  curlx_dyn_init(&urlbuf, CURL_MAX_INPUT_LENGTH);
1341
1342
23.8k
  if(!curlx_dyn_addn(&urlbuf, base, prelen) &&
1343
23.8k
     !urlencode_str(&urlbuf, useurl, strlen(useurl), !host_changed,
1344
23.8k
                    QUERY_NOT_YET)) {
1345
23.8k
    uc = parseurl_and_replace(curlx_dyn_ptr(&urlbuf), u,
1346
23.8k
                              flags & ~U_CURLU_PATH_AS_IS);
1347
23.8k
  }
1348
0
  else
1349
0
    uc = CURLUE_OUT_OF_MEMORY;
1350
1351
23.8k
  curlx_dyn_free(&urlbuf);
1352
23.8k
  return uc;
1353
23.8k
}
1354
1355
/*
1356
 */
1357
CURLU *curl_url(void)
1358
268k
{
1359
268k
  return curlx_calloc(1, sizeof(struct Curl_URL));
1360
268k
}
1361
1362
void curl_url_cleanup(CURLU *u)
1363
767k
{
1364
767k
  if(u) {
1365
272k
    free_urlhandle(u);
1366
272k
    curlx_free(u);
1367
272k
  }
1368
767k
}
1369
1370
#define DUP(dest, src, name)                    \
1371
29.5k
  do {                                          \
1372
29.5k
    if((src)->name) {                           \
1373
7.75k
      (dest)->name = curlx_strdup((src)->name); \
1374
7.75k
      if(!(dest)->name)                         \
1375
7.75k
        goto fail;                              \
1376
7.75k
    }                                           \
1377
29.5k
  } while(0)
1378
1379
CURLU *curl_url_dup(const CURLU *in)
1380
3.28k
{
1381
3.28k
  struct Curl_URL *u = curlx_calloc(1, sizeof(struct Curl_URL));
1382
3.28k
  if(u) {
1383
3.28k
    DUP(u, in, scheme);
1384
3.28k
    DUP(u, in, user);
1385
3.28k
    DUP(u, in, password);
1386
3.28k
    DUP(u, in, options);
1387
3.28k
    DUP(u, in, host);
1388
3.28k
    DUP(u, in, path);
1389
3.28k
    DUP(u, in, query);
1390
3.28k
    DUP(u, in, fragment);
1391
3.28k
    DUP(u, in, zoneid);
1392
3.28k
    u->portnum = in->portnum;
1393
3.28k
    u->port_present = in->port_present;
1394
3.28k
    u->fragment_present = in->fragment_present;
1395
3.28k
    u->query_present = in->query_present;
1396
3.28k
  }
1397
3.28k
  return u;
1398
0
fail:
1399
0
  curl_url_cleanup(u);
1400
0
  return NULL;
1401
3.28k
}
1402
1403
#ifndef USE_IDN
1404
#define host_decode(x, y) CURLUE_LACKS_IDN
1405
#define host_encode(x, y) CURLUE_LACKS_IDN
1406
#else
1407
static CURLUcode host_decode(const char *host, char **allochost)
1408
0
{
1409
0
  CURLcode result = Curl_idn_decode(host, allochost);
1410
0
  if(result)
1411
0
    return (result == CURLE_OUT_OF_MEMORY) ?
1412
0
      CURLUE_OUT_OF_MEMORY : CURLUE_BAD_HOSTNAME;
1413
0
  return CURLUE_OK;
1414
0
}
1415
1416
static CURLUcode host_encode(const char *host, char **allochost)
1417
0
{
1418
0
  CURLcode result = Curl_idn_encode(host, allochost);
1419
0
  if(result)
1420
0
    return (result == CURLE_OUT_OF_MEMORY) ?
1421
0
      CURLUE_OUT_OF_MEMORY : CURLUE_BAD_HOSTNAME;
1422
0
  return CURLUE_OK;
1423
0
}
1424
#endif
1425
1426
static CURLUcode urlget_format(const CURLU *u, CURLUPart what,
1427
                               const char *ptr, char **partp,
1428
                               bool plusdecode, unsigned int flags)
1429
695k
{
1430
695k
  CURLUcode uc = CURLUE_OK;
1431
695k
  size_t partlen = strlen(ptr);
1432
695k
  bool urldecode = (flags & CURLU_URLDECODE) ? 1 : 0;
1433
695k
  bool urlencode = (flags & CURLU_URLENCODE) ? 1 : 0;
1434
695k
  bool punycode = (flags & CURLU_PUNYCODE) && (what == CURLUPART_HOST);
1435
695k
  bool depunyfy = (flags & CURLU_PUNY2IDN) && (what == CURLUPART_HOST);
1436
695k
  char *part = curlx_memdup0(ptr, partlen);
1437
695k
  *partp = NULL;
1438
695k
  if(!part)
1439
0
    return CURLUE_OUT_OF_MEMORY;
1440
695k
  if(plusdecode) {
1441
    /* convert + to space */
1442
86
    char *plus = part;
1443
86
    size_t i = 0;
1444
691
    for(i = 0; i < partlen; ++plus, i++) {
1445
605
      if(*plus == '+')
1446
41
        *plus = ' ';
1447
605
    }
1448
86
  }
1449
695k
  if(urldecode) {
1450
46.2k
    char *decoded;
1451
46.2k
    size_t dlen;
1452
    /* this unconditional rejection of control bytes is documented API
1453
       behavior */
1454
46.2k
    CURLcode result = Curl_urldecode(part, partlen, &decoded, &dlen,
1455
46.2k
                                     REJECT_CTRL);
1456
46.2k
    curlx_free(part);
1457
46.2k
    if(result)
1458
8
      return CURLUE_URLDECODE;
1459
46.2k
    part = decoded;
1460
46.2k
    partlen = dlen;
1461
46.2k
  }
1462
695k
  if(urlencode) {
1463
170k
    struct dynbuf enc;
1464
170k
    curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1465
170k
    uc = urlencode_str(&enc, part, partlen, TRUE, what == CURLUPART_QUERY ?
1466
170k
                       QUERY_YES : QUERY_NO);
1467
170k
    curlx_free(part);
1468
170k
    if(uc)
1469
0
      return uc;
1470
170k
    part = curlx_dyn_ptr(&enc);
1471
170k
  }
1472
525k
  else if(punycode) {
1473
0
    if(!Curl_is_ASCII_name(u->host)) {
1474
0
      char *punyversion = NULL;
1475
0
      uc = host_decode(part, &punyversion);
1476
0
      curlx_free(part);
1477
0
      if(uc)
1478
0
        return uc;
1479
0
      part = punyversion;
1480
0
    }
1481
0
  }
1482
525k
  else if(depunyfy) {
1483
0
    if(Curl_is_ASCII_name(u->host)) {
1484
0
      char *unpunified = NULL;
1485
0
      uc = host_encode(part, &unpunified);
1486
0
      curlx_free(part);
1487
0
      if(uc)
1488
0
        return uc;
1489
0
      part = unpunified;
1490
0
    }
1491
0
  }
1492
695k
  *partp = part;
1493
695k
  return CURLUE_OK;
1494
695k
}
1495
1496
static CURLUcode file_url(const CURLU *u, char **part,
1497
                          const char *fragmentsep,
1498
                          const char *querysep)
1499
1.89k
{
1500
1.89k
  char *url = curl_maprintf("file://%s%s%s%s%s",
1501
1.89k
                            u->path, querysep, u->query ? u->query : "",
1502
1.89k
                            fragmentsep, u->fragment ? u->fragment : "");
1503
1.89k
  if(!url)
1504
0
    return CURLUE_OUT_OF_MEMORY;
1505
1506
1.89k
  *part = url;
1507
1.89k
  return CURLUE_OK;
1508
1.89k
}
1509
1510
static CURLUcode urlget_url(const CURLU *u, char **part, unsigned int flags)
1511
309k
{
1512
309k
  char *url;
1513
309k
  char *allochost = NULL;
1514
309k
  const char *fragmentsep =
1515
309k
    (u->fragment || (u->fragment_present && flags & CURLU_GET_EMPTY)) ?
1516
280k
    "#" : "";
1517
309k
  const char *querysep = ((u->query && u->query[0]) ||
1518
282k
                          (u->query_present && flags & CURLU_GET_EMPTY)) ?
1519
280k
    "?" : "";
1520
309k
  char portbuf[7];
1521
309k
  if(curl_strequal("file", u->scheme))
1522
1.89k
    return file_url(u, part, fragmentsep, querysep);
1523
307k
  else if(!u->host)
1524
72.9k
    return CURLUE_NO_HOST;
1525
234k
  else {
1526
234k
    const char *scheme;
1527
234k
    char *options = u->options;
1528
234k
    char *port = NULL;
1529
234k
    const struct Curl_scheme *h = NULL;
1530
234k
    char schemebuf[MAX_SCHEME_LEN + 5];
1531
234k
    if(u->scheme)
1532
234k
      scheme = u->scheme;
1533
0
    else if(flags & CURLU_DEFAULT_SCHEME)
1534
0
      scheme = DEFAULT_SCHEME;
1535
0
    else
1536
0
      return CURLUE_NO_SCHEME;
1537
1538
234k
    if(u->port_present) {
1539
8.68k
      curl_msnprintf(portbuf, sizeof(portbuf), "%u", u->portnum);
1540
8.68k
      port = portbuf;
1541
8.68k
    }
1542
1543
234k
    h = Curl_get_scheme(scheme);
1544
234k
    if(h) {
1545
232k
      if(!u->port_present && (flags & CURLU_DEFAULT_PORT)) {
1546
        /* there is no stored port number, but asked to deliver a default one
1547
           for the scheme */
1548
0
        curl_msnprintf(portbuf, sizeof(portbuf), "%u", h->defport);
1549
0
        port = portbuf;
1550
0
      }
1551
232k
      else if(u->port_present && (h->defport == u->portnum) &&
1552
754
              (flags & CURLU_NO_DEFAULT_PORT)) {
1553
        /* there is a stored port number, but asked to inhibit if it matches
1554
           the default port for the scheme */
1555
286
        port = NULL;
1556
286
      }
1557
1558
232k
      if(!(h->flags & PROTOPT_URLOPTIONS))
1559
209k
        options = NULL;
1560
232k
    }
1561
1562
234k
    if(u->host[0] == '[') {
1563
6.11k
      if(u->zoneid) {
1564
        /* make it '[ host %25 zoneid ]' */
1565
2.08k
        struct dynbuf enc;
1566
2.08k
        size_t hostlen = strlen(u->host);
1567
2.08k
        curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1568
2.08k
        if(curlx_dyn_addf(&enc, "%.*s%%25%s]", (int)hostlen - 1, u->host,
1569
2.08k
                          u->zoneid))
1570
0
          return CURLUE_OUT_OF_MEMORY;
1571
2.08k
        allochost = curlx_dyn_ptr(&enc);
1572
2.08k
      }
1573
6.11k
    }
1574
228k
    else if(flags & CURLU_URLENCODE) {
1575
22.2k
      allochost = curl_easy_escape(NULL, u->host, 0);
1576
22.2k
      if(!allochost)
1577
0
        return CURLUE_OUT_OF_MEMORY;
1578
22.2k
    }
1579
206k
    else if(flags & CURLU_PUNYCODE) {
1580
0
      if(!Curl_is_ASCII_name(u->host)) {
1581
0
        CURLUcode ret = host_decode(u->host, &allochost);
1582
0
        if(ret)
1583
0
          return ret;
1584
0
      }
1585
0
    }
1586
206k
    else if(flags & CURLU_PUNY2IDN) {
1587
0
      if(Curl_is_ASCII_name(u->host)) {
1588
0
        CURLUcode ret = host_encode(u->host, &allochost);
1589
0
        if(ret)
1590
0
          return ret;
1591
0
      }
1592
0
    }
1593
1594
234k
    if(!(flags & CURLU_NO_GUESS_SCHEME) || !u->guessed_scheme)
1595
234k
      curl_msnprintf(schemebuf, sizeof(schemebuf), "%s://", scheme);
1596
0
    else
1597
0
      schemebuf[0] = 0;
1598
1599
234k
    url = curl_maprintf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
1600
234k
                        schemebuf,
1601
234k
                        u->user ? u->user : "",
1602
234k
                        u->password ? ":" : "",
1603
234k
                        u->password ? u->password : "",
1604
234k
                        options ? ";" : "",
1605
234k
                        options ? options : "",
1606
234k
                        (u->user || u->password || options) ? "@" : "",
1607
234k
                        allochost ? allochost : u->host,
1608
234k
                        port ? ":" : "",
1609
234k
                        port ? port : "",
1610
234k
                        u->path ? u->path : "/",
1611
234k
                        querysep,
1612
234k
                        u->query ? u->query : "",
1613
234k
                        fragmentsep,
1614
234k
                        u->fragment ? u->fragment : "");
1615
234k
    curlx_free(allochost);
1616
234k
  }
1617
234k
  if(!url)
1618
0
    return CURLUE_OUT_OF_MEMORY;
1619
234k
  *part = url;
1620
234k
  return CURLUE_OK;
1621
234k
}
1622
1623
CURLUcode curl_url_get(const CURLU *u, CURLUPart what,
1624
                       char **part, unsigned int flags)
1625
1.98M
{
1626
1.98M
  const char *ptr;
1627
1.98M
  CURLUcode ifmissing = CURLUE_UNKNOWN_PART;
1628
1.98M
  char portbuf[7];
1629
1.98M
  bool plusdecode = FALSE;
1630
1.98M
  if(!u)
1631
0
    return CURLUE_BAD_HANDLE;
1632
1.98M
  if(!part)
1633
0
    return CURLUE_BAD_PARTPOINTER;
1634
1.98M
  *part = NULL;
1635
1636
1.98M
  switch(what) {
1637
265k
  case CURLUPART_SCHEME:
1638
265k
    ptr = u->scheme;
1639
265k
    ifmissing = CURLUE_NO_SCHEME;
1640
265k
    flags &= ~U_CURLU_URLDECODE; /* never for schemes */
1641
265k
    if((flags & CURLU_NO_GUESS_SCHEME) && u->guessed_scheme)
1642
0
      return CURLUE_NO_SCHEME;
1643
265k
    break;
1644
265k
  case CURLUPART_USER:
1645
213k
    ptr = u->user;
1646
213k
    ifmissing = CURLUE_NO_USER;
1647
213k
    break;
1648
213k
  case CURLUPART_PASSWORD:
1649
213k
    ptr = u->password;
1650
213k
    ifmissing = CURLUE_NO_PASSWORD;
1651
213k
    break;
1652
170k
  case CURLUPART_OPTIONS:
1653
170k
    ptr = u->options;
1654
170k
    ifmissing = CURLUE_NO_OPTIONS;
1655
170k
    break;
1656
217k
  case CURLUPART_HOST:
1657
217k
    ptr = u->host;
1658
217k
    ifmissing = CURLUE_NO_HOST;
1659
217k
    break;
1660
211k
  case CURLUPART_ZONEID:
1661
211k
    ptr = u->zoneid;
1662
211k
    ifmissing = CURLUE_NO_ZONEID;
1663
211k
    break;
1664
46.0k
  case CURLUPART_PORT:
1665
46.0k
    ptr = NULL;
1666
46.0k
    ifmissing = CURLUE_NO_PORT;
1667
46.0k
    flags &= ~U_CURLU_URLDECODE; /* never for port */
1668
46.0k
    if(u->port_present) {
1669
9
      const struct Curl_scheme *h = u->scheme ?
1670
9
                                    Curl_get_scheme(u->scheme) : NULL;
1671
      /* there is a stored port number, but ask to inhibit if
1672
         it matches the default one for the scheme */
1673
9
      if(h && (h->defport == u->portnum) &&
1674
0
         (flags & CURLU_NO_DEFAULT_PORT)) {
1675
0
        ptr = NULL;
1676
0
      }
1677
9
      else {
1678
9
        curl_msnprintf(portbuf, sizeof(portbuf), "%u", u->portnum);
1679
9
        ptr = portbuf;
1680
9
      }
1681
9
    }
1682
46.0k
    else if((flags & CURLU_DEFAULT_PORT) && u->scheme) {
1683
      /* there is no stored port number, but asked to deliver
1684
         a default one for the scheme */
1685
0
      const struct Curl_scheme *h = Curl_get_scheme(u->scheme);
1686
0
      if(h) {
1687
0
        curl_msnprintf(portbuf, sizeof(portbuf), "%u", h->defport);
1688
0
        ptr = portbuf;
1689
0
      }
1690
0
    }
1691
46.0k
    break;
1692
170k
  case CURLUPART_PATH:
1693
170k
    ptr = u->path;
1694
170k
    if(!ptr)
1695
129k
      ptr = "/";
1696
170k
    break;
1697
170k
  case CURLUPART_QUERY:
1698
170k
    ptr = u->query;
1699
170k
    ifmissing = CURLUE_NO_QUERY;
1700
170k
    plusdecode = flags & CURLU_URLDECODE;
1701
170k
    if(ptr && !ptr[0] && !(flags & CURLU_GET_EMPTY))
1702
      /* there was a blank query and the user does not ask for it */
1703
22
      ptr = NULL;
1704
170k
    break;
1705
0
  case CURLUPART_FRAGMENT:
1706
0
    ptr = u->fragment;
1707
0
    ifmissing = CURLUE_NO_FRAGMENT;
1708
0
    if(!ptr && u->fragment_present && flags & CURLU_GET_EMPTY)
1709
      /* there was a blank fragment and the user asks for it */
1710
0
      ptr = "";
1711
0
    break;
1712
309k
  case CURLUPART_URL:
1713
309k
    return urlget_url(u, part, flags);
1714
0
  default:
1715
0
    ptr = NULL;
1716
0
    break;
1717
1.98M
  }
1718
1.67M
  if(ptr)
1719
695k
    return urlget_format(u, what, ptr, part, plusdecode, flags);
1720
1721
984k
  return ifmissing;
1722
1.67M
}
1723
1724
static CURLUcode set_url_scheme(CURLU *u, const char *scheme,
1725
                                unsigned int flags)
1726
1
{
1727
1
  size_t plen = strlen(scheme);
1728
1
  const struct Curl_scheme *h = NULL;
1729
1
  if((plen > MAX_SCHEME_LEN) || (plen < 1))
1730
    /* too long or too short */
1731
0
    return CURLUE_BAD_SCHEME;
1732
  /* verify that it is a fine scheme */
1733
1
  h = Curl_get_scheme(scheme);
1734
1
  if(!(flags & CURLU_NON_SUPPORT_SCHEME) && (!h || !h->run))
1735
0
    return CURLUE_UNSUPPORTED_SCHEME;
1736
1
  if(!h) {
1737
0
    const char *s = scheme;
1738
0
    if(ISALPHA(*s)) {
1739
      /* ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) */
1740
0
      s++;
1741
0
      while(--plen) {
1742
0
        if(ISALNUM(*s) || (*s == '+') || (*s == '-') || (*s == '.'))
1743
0
          s++; /* fine */
1744
0
        else
1745
0
          return CURLUE_BAD_SCHEME;
1746
0
      }
1747
0
    }
1748
0
    else
1749
0
      return CURLUE_BAD_SCHEME;
1750
0
  }
1751
1
  u->guessed_scheme = FALSE;
1752
1
  return CURLUE_OK;
1753
1
}
1754
1755
static CURLUcode set_url_port(CURLU *u, const char *provided_port)
1756
1.45k
{
1757
1.45k
  curl_off_t port;
1758
1.45k
  if(!ISDIGIT(provided_port[0]))
1759
    /* not a number */
1760
0
    return CURLUE_BAD_PORT_NUMBER;
1761
1.45k
  if(curlx_str_number(&provided_port, &port, 0xffff) || *provided_port)
1762
    /* weirdly provided number, not good! */
1763
0
    return CURLUE_BAD_PORT_NUMBER;
1764
1.45k
  u->portnum = (uint16_t)port;
1765
1.45k
  u->port_present = TRUE;
1766
1.45k
  return CURLUE_OK;
1767
1.45k
}
1768
1769
static CURLUcode set_url(CURLU *u, const char *url, size_t part_size,
1770
                         unsigned int flags)
1771
296k
{
1772
  /*
1773
   * Allow a new URL to replace the existing (if any) contents.
1774
   *
1775
   * If the existing contents is enough for a URL, allow a relative URL to
1776
   * replace it.
1777
   */
1778
296k
  CURLUcode uc;
1779
296k
  char *oldurl = NULL;
1780
1781
296k
  if(!part_size) {
1782
    /* a blank URL is not a valid URL unless we already have a complete one
1783
       and this is a redirect */
1784
4.09k
    uc = curl_url_get(u, CURLUPART_URL, &oldurl, flags);
1785
4.09k
    if(!uc) {
1786
      /* success, meaning the "" is a fine relative URL, and the new URL
1787
         inherits scheme/authority/path/query, but not fragment, from the
1788
         existing URL (RFC 3986 section 5.2.2) */
1789
0
      curlx_safefree(u->fragment);
1790
0
      u->fragment_present = FALSE;
1791
0
      curlx_free(oldurl);
1792
0
      return CURLUE_OK;
1793
0
    }
1794
4.09k
    if(uc == CURLUE_OUT_OF_MEMORY)
1795
0
      return uc;
1796
4.09k
    return CURLUE_MALFORMED_INPUT;
1797
4.09k
  }
1798
1799
  /* if the new URL is absolute replace the existing with the new. */
1800
292k
  if(Curl_is_absolute_url(url, NULL, 0,
1801
292k
                          flags & (CURLU_GUESS_SCHEME | CURLU_DEFAULT_SCHEME)))
1802
200k
    return parseurl_and_replace(url, u, flags);
1803
1804
  /* if the old URL is incomplete (we cannot get an absolute URL in
1805
     'oldurl'), replace the existing with the new.
1806
     Always include "scheme://" to make the URL "complete" */
1807
  /* Preserve empty query/fragment separators: they affect where relative
1808
     references splice into the base URL. */
1809
92.7k
  uc = curl_url_get(u, CURLUPART_URL, &oldurl,
1810
92.7k
                    (flags & ~CURLU_NO_GUESS_SCHEME) | CURLU_GET_EMPTY);
1811
92.7k
  if(uc == CURLUE_OUT_OF_MEMORY)
1812
0
    return uc;
1813
92.7k
  else if(uc)
1814
68.8k
    return parseurl_and_replace(url, u, flags);
1815
1816
23.8k
  DEBUGASSERT(oldurl); /* it is set here */
1817
  /* apply the relative part to create a new URL */
1818
23.8k
  uc = redirect_url(oldurl, url, u, flags);
1819
23.8k
  curlx_free(oldurl);
1820
23.8k
  return uc;
1821
23.8k
}
1822
1823
static CURLUcode urlset_clear(CURLU *u, CURLUPart what)
1824
27.1k
{
1825
27.1k
  switch(what) {
1826
0
  case CURLUPART_URL:
1827
0
    free_urlhandle(u);
1828
0
    memset(u, 0, sizeof(struct Curl_URL));
1829
0
    break;
1830
0
  case CURLUPART_SCHEME:
1831
0
    curlx_safefree(u->scheme);
1832
0
    u->guessed_scheme = FALSE;
1833
0
    break;
1834
8.06k
  case CURLUPART_USER:
1835
8.06k
    curlx_safefree(u->user);
1836
8.06k
    break;
1837
8.06k
  case CURLUPART_PASSWORD:
1838
8.06k
    curlx_strzero(u->password);
1839
8.06k
    curlx_safefree(u->password);
1840
8.06k
    break;
1841
0
  case CURLUPART_OPTIONS:
1842
0
    curlx_safefree(u->options);
1843
0
    break;
1844
0
  case CURLUPART_HOST:
1845
0
    curlx_safefree(u->host);
1846
0
    break;
1847
0
  case CURLUPART_ZONEID:
1848
0
    curlx_safefree(u->zoneid);
1849
0
    break;
1850
0
  case CURLUPART_PORT:
1851
0
    u->portnum = 0;
1852
0
    u->port_present = FALSE;
1853
0
    break;
1854
0
  case CURLUPART_PATH:
1855
0
    curlx_safefree(u->path);
1856
0
    break;
1857
0
  case CURLUPART_QUERY:
1858
0
    curlx_safefree(u->query);
1859
0
    u->query_present = FALSE;
1860
0
    break;
1861
11.0k
  case CURLUPART_FRAGMENT:
1862
11.0k
    curlx_safefree(u->fragment);
1863
11.0k
    u->fragment_present = FALSE;
1864
11.0k
    break;
1865
0
  default:
1866
0
    return CURLUE_UNKNOWN_PART;
1867
27.1k
  }
1868
27.1k
  return CURLUE_OK;
1869
27.1k
}
1870
1871
static bool allowed_in_path(unsigned char x)
1872
0
{
1873
0
  switch(x) {
1874
0
  case '!':
1875
0
  case '$':
1876
0
  case '&':
1877
0
  case '\'':
1878
0
  case '(':
1879
0
  case ')':
1880
0
  case '{':
1881
0
  case '}':
1882
0
  case '[':
1883
0
  case ']':
1884
0
  case '*':
1885
0
  case '+':
1886
0
  case ',':
1887
0
  case ';':
1888
0
  case '=':
1889
0
  case ':':
1890
0
  case '@':
1891
0
  case '/':
1892
0
    return TRUE;
1893
0
  }
1894
0
  return FALSE;
1895
0
}
1896
1897
static CURLUcode url_encode_part(struct dynbuf *encp,
1898
                                 const char *part,
1899
                                 bool plusencode,
1900
                                 bool pathmode,
1901
                                 bool equalsencode)
1902
306
{
1903
306
  const unsigned char *i;
1904
1905
1.13M
  for(i = (const unsigned char *)part; *i; i++) {
1906
1.13M
    CURLcode result;
1907
1.13M
    if((*i == ' ') && plusencode)
1908
0
      result = curlx_dyn_addn(encp, "+", 1);
1909
1.13M
    else if(ISUNRESERVED(*i) ||
1910
750k
            (pathmode && allowed_in_path(*i)) ||
1911
750k
            ((*i == '=') && equalsencode)) {
1912
383k
      if((*i == '=') && equalsencode)
1913
        /* only skip the first equals sign */
1914
0
        equalsencode = FALSE;
1915
383k
      result = curlx_dyn_addn(encp, i, 1);
1916
383k
    }
1917
750k
    else {
1918
750k
      unsigned char out[3] = { '%' };
1919
750k
      Curl_hexbyte(&out[1], *i);
1920
750k
      result = curlx_dyn_addn(encp, out, 3);
1921
750k
    }
1922
1.13M
    if(result)
1923
0
      return cc2cu(result);
1924
1.13M
  }
1925
306
  return CURLUE_OK;
1926
306
}
1927
1928
static CURLUcode url_uppercasehex_part(struct dynbuf *encp,
1929
                                       const char *part)
1930
1
{
1931
1
  char *p;
1932
1
  CURLcode result = curlx_dyn_add(encp, part);
1933
1
  if(result)
1934
0
    return cc2cu(result);
1935
1
  p = curlx_dyn_ptr(encp);
1936
6
  while(*p) {
1937
    /* make sure percent encoded are upper case */
1938
5
    if((*p == '%') && ISXDIGIT(p[1]) && ISXDIGIT(p[2]) &&
1939
0
       (ISLOWER(p[1]) || ISLOWER(p[2]))) {
1940
0
      p[1] = Curl_raw_toupper(p[1]);
1941
0
      p[2] = Curl_raw_toupper(p[2]);
1942
0
      p += 3;
1943
0
    }
1944
5
    else
1945
5
      p++;
1946
5
  }
1947
1
  return CURLUE_OK;
1948
1
}
1949
1950
static CURLUcode url_append_query(CURLU *u, struct dynbuf *encp)
1951
0
{
1952
  /* Append the 'encp' string onto the old query. Add a '&' separator if none
1953
     is already present at the end of the existing query */
1954
1955
0
  size_t querylen = u->query ? strlen(u->query) : 0;
1956
0
  bool addamperand = querylen && (u->query[querylen - 1] != '&');
1957
0
  if(querylen) {
1958
0
    struct dynbuf qbuf;
1959
0
    CURLcode result;
1960
0
    const char *newp = curlx_dyn_ptr(encp);
1961
0
    curlx_dyn_init(&qbuf, CURL_MAX_INPUT_LENGTH);
1962
1963
    /* add original query */
1964
0
    result = curlx_dyn_addn(&qbuf, u->query, querylen);
1965
0
    if(!result && addamperand)
1966
      /* add ampersand */
1967
0
      result = curlx_dyn_addn(&qbuf, "&", 1);
1968
0
    if(!result)
1969
      /* add new query part */
1970
0
      result = curlx_dyn_add(&qbuf, newp);
1971
0
    if(result)
1972
0
      goto nomem;
1973
0
    curlx_dyn_free(encp);
1974
0
    curlx_free(u->query);
1975
0
    u->query = curlx_dyn_ptr(&qbuf);
1976
0
    return CURLUE_OK;
1977
0
nomem:
1978
0
    curlx_dyn_free(encp);
1979
0
    return cc2cu(result);
1980
0
  }
1981
0
  else {
1982
0
    curlx_free(u->query);
1983
0
    u->query = curlx_dyn_ptr(encp);
1984
0
  }
1985
0
  return CURLUE_OK;
1986
0
}
1987
1988
static CURLUcode url_sethost(CURLU *u, struct dynbuf *encp,
1989
                             bool urlencode,
1990
                             unsigned int flags)
1991
0
{
1992
0
  size_t n = curlx_dyn_len(encp);
1993
0
  bool bad = FALSE;
1994
0
  char *newp = curlx_dyn_ptr(encp);
1995
0
  if(!n)
1996
    /* an empty hostname is okay if told so */
1997
0
    bad = (flags & CURLU_NO_AUTHORITY) ? FALSE : TRUE;
1998
0
  else if(!urlencode) {
1999
    /* if the hostname part was not URL encoded here, it was set already URL
2000
       encoded so we need to decode it to check */
2001
0
    size_t dlen;
2002
0
    char *decoded = NULL;
2003
0
    CURLcode result = Curl_urldecode(newp, n, &decoded, &dlen, REJECT_CTRL);
2004
0
    if(result || hostname_check(u, decoded, dlen))
2005
0
      bad = TRUE;
2006
0
    curlx_free(decoded);
2007
0
  }
2008
0
  else if(hostname_check(u, newp, n))
2009
0
    bad = TRUE;
2010
0
  if(bad) {
2011
0
    curlx_dyn_free(encp);
2012
0
    return CURLUE_BAD_HOSTNAME;
2013
0
  }
2014
0
  return CURLUE_OK;
2015
0
}
2016
2017
CURLUcode curl_url_set(CURLU *u, CURLUPart what,
2018
                       const char *part, unsigned int flags)
2019
325k
{
2020
325k
  char **storep = NULL;
2021
325k
  bool urlencode = (flags & CURLU_URLENCODE) ? 1 : 0;
2022
325k
  bool plusencode = FALSE;
2023
325k
  bool pathmode = FALSE;
2024
325k
  bool leadingslash = FALSE;
2025
325k
  bool appendquery = FALSE;
2026
325k
  bool equalsencode = FALSE;
2027
325k
  size_t nalloc;
2028
2029
325k
  if(!u)
2030
0
    return CURLUE_BAD_HANDLE;
2031
325k
  if(!part)
2032
    /* setting a part to NULL clears it */
2033
27.1k
    return urlset_clear(u, what);
2034
2035
298k
  nalloc = strlen(part);
2036
298k
  if(nalloc > CURL_MAX_INPUT_LENGTH)
2037
    /* excessive input length */
2038
0
    return CURLUE_MALFORMED_INPUT;
2039
2040
298k
  switch(what) {
2041
1
  case CURLUPART_SCHEME: {
2042
1
    CURLUcode status = set_url_scheme(u, part, flags);
2043
1
    if(status)
2044
0
      return status;
2045
1
    storep = &u->scheme;
2046
1
    urlencode = FALSE; /* never */
2047
1
    break;
2048
1
  }
2049
153
  case CURLUPART_USER:
2050
153
    storep = &u->user;
2051
153
    break;
2052
153
  case CURLUPART_PASSWORD:
2053
153
    storep = &u->password;
2054
153
    break;
2055
0
  case CURLUPART_OPTIONS:
2056
0
    storep = &u->options;
2057
0
    break;
2058
0
  case CURLUPART_HOST:
2059
0
    storep = &u->host;
2060
0
    curlx_safefree(u->zoneid);
2061
0
    break;
2062
0
  case CURLUPART_ZONEID:
2063
0
    storep = &u->zoneid;
2064
0
    break;
2065
1.45k
  case CURLUPART_PORT:
2066
1.45k
    return set_url_port(u, part);
2067
0
  case CURLUPART_PATH:
2068
0
    pathmode = TRUE;
2069
0
    leadingslash = TRUE; /* enforce */
2070
0
    storep = &u->path;
2071
0
    break;
2072
0
  case CURLUPART_QUERY:
2073
0
    plusencode = urlencode;
2074
0
    appendquery = (flags & CURLU_APPENDQUERY) ? 1 : 0;
2075
0
    equalsencode = appendquery;
2076
0
    storep = &u->query;
2077
0
    u->query_present = TRUE;
2078
0
    break;
2079
0
  case CURLUPART_FRAGMENT:
2080
0
    storep = &u->fragment;
2081
0
    u->fragment_present = TRUE;
2082
0
    break;
2083
296k
  case CURLUPART_URL:
2084
296k
    return set_url(u, part, nalloc, flags);
2085
0
  default:
2086
0
    return CURLUE_UNKNOWN_PART;
2087
298k
  }
2088
307
  DEBUGASSERT(storep);
2089
307
  {
2090
307
    const char *newp = NULL;
2091
307
    struct dynbuf enc;
2092
307
    CURLUcode status;
2093
307
    curlx_dyn_init(&enc, (nalloc * 3) + 1 + leadingslash);
2094
2095
307
    if(leadingslash && (part[0] != '/')) {
2096
0
      CURLcode result = curlx_dyn_addn(&enc, "/", 1);
2097
0
      if(result)
2098
0
        return cc2cu(result);
2099
0
    }
2100
307
    if(urlencode)
2101
306
      status = url_encode_part(&enc, part, plusencode, pathmode, equalsencode);
2102
1
    else
2103
1
      status = url_uppercasehex_part(&enc, part);
2104
307
    if(!status) {
2105
307
      newp = curlx_dyn_ptr(&enc);
2106
2107
307
      if(appendquery && newp)
2108
0
        return url_append_query(u, &enc);
2109
307
      else if(what == CURLUPART_HOST)
2110
0
        status = url_sethost(u, &enc, urlencode, flags);
2111
307
    }
2112
307
    if(status)
2113
0
      return status;
2114
2115
307
    if(what == CURLUPART_PASSWORD)
2116
153
      curlx_strzero(*storep);
2117
307
    curlx_free(*storep);
2118
307
    *storep = (char *)CURL_UNCONST(newp);
2119
307
  }
2120
0
  return CURLUE_OK;
2121
307
}
2122
2123
bool Curl_url_same_origin(CURLU *base, CURLU *href)
2124
27.9k
{
2125
27.9k
  const struct Curl_scheme *s = NULL;
2126
2127
  /* base must be an absolute URL */
2128
27.9k
  if(!base->scheme || !base->host)
2129
0
    return FALSE;
2130
27.9k
  if(href->scheme && !curl_strequal(base->scheme, href->scheme))
2131
213
    return FALSE;
2132
27.7k
  if(href->host) {
2133
27.7k
    if(!curl_strequal(base->host, href->host))
2134
268
      return FALSE;
2135
2136
27.4k
    if(base->port_present != href->port_present) {
2137
      /* one is present, one is not */
2138
109
      s = Curl_get_scheme(base->scheme);
2139
109
      if(!s) /* Cannot match default port for unknown scheme */
2140
0
        return FALSE;
2141
      /* to match, the present one must be the default port */
2142
109
      if((base->port_present && (base->portnum != s->defport)) ||
2143
100
         (href->port_present && (href->portnum != s->defport)))
2144
104
        return FALSE;
2145
109
    }
2146
27.3k
    else if(base->portnum != href->portnum) /* both present or missing */
2147
33
      return FALSE;
2148
2149
27.3k
    if(!curl_strequal(base->zoneid ? base->zoneid : "",
2150
27.3k
                      href->zoneid ? href->zoneid : ""))
2151
0
      return FALSE;
2152
27.3k
  }
2153
0
  else if(href->port_present) /* no host in href, then there must be no port */
2154
0
    return FALSE;
2155
27.3k
  return TRUE;
2156
27.7k
}
2157
2158
CURLUcode Curl_url_get_port(CURLU *u, uint16_t *pport)
2159
168k
{
2160
168k
  if(u->port_present) {
2161
2.77k
    *pport = u->portnum;
2162
2.77k
    return CURLUE_OK;
2163
2.77k
  }
2164
165k
  else if(u->scheme) {
2165
165k
    const struct Curl_scheme *s = Curl_get_scheme(u->scheme);
2166
165k
    if(s && s->defport) {
2167
163k
      *pport = s->defport;
2168
163k
      return CURLUE_OK;
2169
163k
    }
2170
165k
  }
2171
1.83k
  *pport = 0;
2172
1.83k
  return CURLUE_NO_PORT;
2173
168k
}