Coverage Report

Created: 2026-09-01 06:58

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
148
  ((('a' <= (str)[0] && (str)[0] <= 'z') ||                \
50
148
    ('A' <= (str)[0] && (str)[0] <= 'Z')) &&               \
51
148
   ((str)[1] == ':' || (str)[1] == '|') &&                 \
52
148
   ((str)[2] == '/' || (str)[2] == '\\' || (str)[2] == 0))
53
54
/* scheme is not URL encoded, the longest libcurl supported ones are... */
55
49.8k
#define MAX_SCHEME_LEN 40
56
266
#define MAX_ZONEID_LEN 16
57
58
/* characters not allowed in hostnames */
59
1.78k
#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
14.0k
{
74
14.0k
  curlx_free(u->scheme);
75
14.0k
  curlx_free(u->user);
76
14.0k
  curlx_strzero(u->password);
77
14.0k
  curlx_free(u->password);
78
14.0k
  curlx_free(u->options);
79
14.0k
  curlx_free(u->host);
80
14.0k
  curlx_free(u->zoneid);
81
14.0k
  curlx_free(u->path);
82
14.0k
  curlx_free(u->query);
83
14.0k
  curlx_free(u->fragment);
84
14.0k
}
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
0
{
92
  /* Find the start of the hostname */
93
0
  const char *sep = strstr(url, "//");
94
0
  if(!sep)
95
0
    sep = url;
96
0
  else
97
0
    sep += 2;
98
99
  /* Find first / or ? */
100
0
  while(*sep && *sep != '/' && *sep != '?')
101
0
    sep++;
102
103
0
  return sep;
104
0
}
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
4.00k
{
134
  /* we must add this with whitespace-replacing */
135
4.00k
  const unsigned char *iptr;
136
4.00k
  const unsigned char *host_sep = (const unsigned char *)url;
137
4.00k
  CURLcode result = CURLE_OK;
138
139
4.00k
  DEBUGASSERT((query >= QUERY_NO) && (query <= QUERY_YES));
140
141
4.00k
  if(!relative) {
142
0
    size_t n;
143
0
    host_sep = (const unsigned char *)find_host_sep(url);
144
145
    /* output the first piece as-is */
146
0
    n = (const char *)host_sep - url;
147
0
    result = curlx_dyn_addn(o, url, n);
148
0
    len -= n;
149
0
  }
150
151
24.8M
  for(iptr = host_sep; len && !result; iptr++, len--) {
152
24.8M
    if(*iptr == ' ') {
153
0
      if(query != QUERY_YES)
154
0
        result = curlx_dyn_addn(o, "%20", 3);
155
0
      else
156
0
        result = curlx_dyn_addn(o, "+", 1);
157
0
    }
158
24.8M
    else if((*iptr < ' ') || (*iptr >= 0x7f)) {
159
10.3M
      unsigned char out[3] = { '%' };
160
10.3M
      Curl_hexbyte(&out[1], *iptr);
161
10.3M
      result = curlx_dyn_addn(o, out, 3);
162
10.3M
    }
163
14.4M
    else if(*iptr == '%' && (len >= 3) &&
164
10.0M
            ISXDIGIT(iptr[1]) && ISXDIGIT(iptr[2]) &&
165
54.7k
            (ISLOWER(iptr[1]) || ISLOWER(iptr[2]))) {
166
      /* uppercase it */
167
6.29k
      unsigned char hex = (unsigned char)((curlx_hexval(iptr[1]) << 4) |
168
6.29k
                                          curlx_hexval(iptr[2]));
169
6.29k
      unsigned char out[3] = { '%' };
170
6.29k
      Curl_hexbyte(&out[1], hex);
171
6.29k
      result = curlx_dyn_addn(o, out, 3);
172
6.29k
      iptr += 2;
173
6.29k
      len -= 2;
174
6.29k
    }
175
14.4M
    else {
176
14.4M
      result = curlx_dyn_addn(o, iptr, 1);
177
14.4M
      if(*iptr == '?' && (query == QUERY_NOT_YET))
178
0
        query = QUERY_YES;
179
14.4M
    }
180
24.8M
  }
181
182
4.00k
  if(result)
183
0
    return cc2cu(result);
184
4.00k
  return CURLUE_OK;
185
4.00k
}
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
13.7k
{
198
13.7k
  size_t i = 0;
199
13.7k
  DEBUGASSERT(!buf || (buflen > MAX_SCHEME_LEN));
200
13.7k
  (void)buflen; /* only used in debug-builds */
201
13.7k
  if(buf)
202
6.82k
    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
13.7k
  if(ISALPHA(url[0]))
208
49.8k
    for(i = 1; i < MAX_SCHEME_LEN; ++i) {
209
49.8k
      char s = url[i];
210
49.8k
      if(s && (ISALNUM(s) || (s == '+') || (s == '-') || (s == '.'))) {
211
        /* RFC 3986 3.1 explains:
212
           scheme      = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
213
         */
214
38.1k
      }
215
11.6k
      else {
216
11.6k
        break;
217
11.6k
      }
218
49.8k
    }
219
13.7k
  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
10.7k
    size_t len = i;
226
10.7k
    if(buf) {
227
5.37k
      Curl_strntolower(buf, url, i);
228
5.37k
      buf[i] = 0;
229
5.37k
    }
230
10.7k
    return len;
231
10.7k
  }
232
3.00k
  return 0;
233
13.7k
}
234
235
/* scan for byte values <= 31, 127 and sometimes space */
236
CURLUcode Curl_junkscan(const char *url, size_t *urllen, bool allowspace)
237
6.84k
{
238
6.84k
  size_t n = strlen(url);
239
6.84k
  size_t i;
240
6.84k
  unsigned char control;
241
6.84k
  const unsigned char *p = (const unsigned char *)url;
242
6.84k
  if(n > CURL_MAX_INPUT_LENGTH)
243
0
    return CURLUE_MALFORMED_INPUT;
244
245
6.84k
  control = allowspace ? 0x1f : 0x20;
246
42.8M
  for(i = 0; i < n; i++) {
247
42.8M
    if(p[i] <= control || p[i] == 127)
248
19
      return CURLUE_MALFORMED_INPUT;
249
42.8M
  }
250
6.82k
  *urllen = n;
251
6.82k
  return CURLUE_OK;
252
6.84k
}
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
6.70k
{
273
6.70k
  CURLUcode ures = CURLUE_OK;
274
6.70k
  CURLcode result;
275
6.70k
  char *userp = NULL;
276
6.70k
  char *passwdp = NULL;
277
6.70k
  char *optionsp = NULL;
278
6.70k
  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
6.70k
  const char *ptr;
288
289
6.70k
  DEBUGASSERT(login);
290
291
6.70k
  *hostname_offset = 0;
292
6.70k
  ptr = memchr(login, '@', len);
293
6.70k
  if(!ptr)
294
5.57k
    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
1.13k
  ptr++;
300
301
  /* if this is a known scheme, get some details */
302
1.13k
  if(u->scheme)
303
1.03k
    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
1.13k
  result = Curl_parse_login_details(login, ptr - login - 1,
308
1.13k
                                    &userp, &passwdp,
309
1.13k
                                    (h && (h->flags & PROTOPT_URLOPTIONS)) ?
310
1.13k
                                    &optionsp : NULL);
311
1.13k
  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
1.13k
  if(userp) {
319
1.13k
    if(flags & CURLU_DISALLOW_USER) {
320
      /* Option DISALLOW_USER is set and URL contains username. */
321
1
      ures = CURLUE_USER_NOT_ALLOWED;
322
1
      goto out;
323
1
    }
324
1.13k
    curlx_free(u->user);
325
1.13k
    u->user = userp;
326
1.13k
  }
327
328
1.13k
  if(passwdp) {
329
133
    curlx_strzero(u->password);
330
133
    curlx_free(u->password);
331
133
    u->password = passwdp;
332
133
  }
333
334
1.13k
  if(optionsp) {
335
7
    curlx_free(u->options);
336
7
    u->options = optionsp;
337
7
  }
338
339
  /* the hostname starts at this offset */
340
1.13k
  *hostname_offset = ptr - login;
341
1.13k
  return CURLUE_OK;
342
343
5.57k
out:
344
345
5.57k
  curlx_free(userp);
346
5.57k
  curlx_strzero(passwdp);
347
5.57k
  curlx_free(passwdp);
348
5.57k
  curlx_free(optionsp);
349
5.57k
  curlx_safefree(u->user);
350
5.57k
  curlx_strzero(u->password);
351
5.57k
  curlx_safefree(u->password);
352
5.57k
  curlx_safefree(u->options);
353
354
5.57k
  return ures;
355
1.13k
}
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
6.70k
{
363
6.70k
  const char *portptr;
364
6.70k
  const char *hostname = curlx_dyn_ptr(host);
365
  /*
366
   * Find the end of an IPv6 address on the ']' ending bracket.
367
   */
368
6.70k
  u->portnum = 0;
369
6.70k
  u->port_present = FALSE;
370
6.70k
  if(hostname[0] == '[') {
371
301
    portptr = strchr(hostname, ']');
372
301
    if(!portptr)
373
2
      return CURLUE_BAD_IPV6;
374
299
    portptr++;
375
    /* this is a RFC2732-style specified IP-address */
376
299
    if(*portptr) {
377
34
      if(*portptr != ':')
378
6
        return CURLUE_BAD_PORT_NUMBER;
379
34
    }
380
265
    else
381
265
      portptr = NULL;
382
299
  }
383
6.40k
  else
384
6.40k
    portptr = strchr(hostname, ':');
385
386
6.69k
  if(portptr) {
387
244
    curl_off_t port;
388
244
    size_t keep = portptr - hostname;
389
244
    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
244
    curlx_dyn_setlen(host, keep);
398
244
    portptr++;
399
244
    if(!*portptr)
400
132
      return has_scheme ? CURLUE_OK : CURLUE_BAD_PORT_NUMBER;
401
112
    if(*portptr == '\\')
402
1
      return CURLUE_BACKSLASH;
403
111
    rc = curlx_str_number(&portptr, &port, 0xffff);
404
111
    if(rc)
405
27
      return CURLUE_BAD_PORT_NUMBER;
406
84
    else if(*portptr == '\\')
407
2
      return CURLUE_BACKSLASH;
408
82
    else if(*portptr)
409
8
      return CURLUE_BAD_PORT_NUMBER;
410
411
74
    u->portnum = (uint16_t)port;
412
74
    u->port_present = TRUE;
413
74
  }
414
415
6.52k
  return CURLUE_OK;
416
6.69k
}
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
322
{
428
322
  size_t len;
429
322
  DEBUGASSERT(*hostname == '[');
430
322
  if(hlen < 4) /* '[::]' is the shortest possible valid string */
431
1
    return CURLUE_BAD_IPV6;
432
321
  hostname++;
433
321
  hlen -= 2;
434
435
  /* only valid IPv6 letters are ok */
436
321
  len = strspn(hostname, "0123456789abcdefABCDEF:.");
437
438
321
  if(hlen != len) {
439
97
    hlen = len;
440
97
    if(hostname[len] == '%') {
441
      /* this could now be '%[zone id]' */
442
77
      char zoneid[MAX_ZONEID_LEN];
443
77
      int i = 0;
444
77
      char *h = &hostname[len + 1];
445
      /* pass '25' if present and is a URL encoded percent sign */
446
77
      if(!strncmp(h, "25", 2) && h[2] && (h[2] != ']'))
447
5
        h += 2;
448
337
      while(*h && (*h != ']') && (i < (MAX_ZONEID_LEN - 1)))
449
260
        zoneid[i++] = *h++;
450
77
      if(!i || (']' != *h))
451
17
        return CURLUE_BAD_IPV6;
452
60
      zoneid[i] = 0;
453
60
      u->zoneid = curlx_strdup(zoneid);
454
60
      if(!u->zoneid)
455
0
        return CURLUE_OUT_OF_MEMORY;
456
60
      hostname[len] = ']'; /* insert end bracket */
457
60
      hostname[len + 1] = 0; /* terminate the hostname */
458
60
    }
459
20
    else
460
20
      return CURLUE_BAD_IPV6;
461
    /* hostname is fine */
462
97
  }
463
464
  /* Normalize the IPv6 address */
465
284
  {
466
284
    char dest[16]; /* fits a binary IPv6 address */
467
284
    hostname[hlen] = 0; /* end the address there */
468
284
    if(curlx_inet_pton(AF_INET6, hostname, dest) != 1)
469
57
      return CURLUE_BAD_IPV6;
470
227
    if(!curlx_inet_ntop(AF_INET6, dest, hostname, hlen + 1)) {
471
169
      hlen = strlen(hostname); /* might be shorter now */
472
169
      hostname[hlen + 1] = 0;
473
169
    }
474
227
    hostname[hlen] = ']'; /* restore ending bracket */
475
227
  }
476
0
  return CURLUE_OK;
477
284
}
478
479
static CURLUcode hostname_check(struct Curl_URL *u, char *hostname,
480
                                size_t hlen) /* length of hostname */
481
1.78k
{
482
1.78k
  size_t len;
483
1.78k
  DEBUGASSERT(hostname);
484
485
1.78k
  if(!hlen)
486
0
    return CURLUE_NO_HOST;
487
1.78k
  else if(hostname[0] == '[')
488
0
    return ipv6_parse(u, hostname, hlen);
489
1.78k
  else {
490
    /* letters from the second string are not ok */
491
1.78k
    len = strcspn(hostname, HOSTNAME_INVALID_CHARS);
492
1.78k
    if(hlen != len)
493
      /* hostname with bad content */
494
122
      return CURLUE_BAD_HOSTNAME;
495
1.66k
    else if((hlen >= 2) &&
496
753
            (hostname[hlen - 1] == '.') && (hostname[hlen - 2] == '.'))
497
      /* more than one trailing dot is not allowed */
498
9
      return CURLUE_BAD_HOSTNAME;
499
1.65k
    else if((hlen == 1) && (hostname[0] == '.'))
500
      /* a single dot alone is not allowed */
501
1
      return CURLUE_BAD_HOSTNAME;
502
1.78k
  }
503
1.65k
  return CURLUE_OK;
504
1.78k
}
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
6.47k
{
526
6.47k
  bool done = FALSE;
527
6.47k
  int n = 0;
528
6.47k
  const char *c = curlx_dyn_ptr(host);
529
6.47k
  unsigned int parts[4] = { 0, 0, 0, 0 };
530
6.47k
  CURLcode result = CURLE_OK;
531
532
6.47k
  if(*c == '[')
533
322
    return HOST_IPV6;
534
535
18.3k
  while(!done) {
536
13.6k
    int rc;
537
13.6k
    curl_off_t l;
538
13.6k
    if(*c == '0') {
539
5.27k
      if(Curl_raw_tolower(c[1]) == 'x') {
540
20
        c += 2; /* skip the prefix */
541
20
        rc = curlx_str_hex(&c, &l, UINT_MAX);
542
20
        if(rc)
543
4
          return HOST_NAME;
544
20
      }
545
5.25k
      else
546
5.25k
        rc = curlx_str_octal(&c, &l, UINT_MAX);
547
5.27k
    }
548
8.39k
    else
549
8.39k
      rc = curlx_str_number(&c, &l, UINT_MAX);
550
551
13.6k
    if(rc) {
552
1.46k
      if(!n || (rc != STRE_NO_NUM) || *c)
553
1.42k
        return HOST_NAME;
554
37
      n--;
555
37
    }
556
12.1k
    else
557
12.1k
      parts[n] = (unsigned int)l;
558
559
12.2k
    switch(*c) {
560
7.51k
    case '.':
561
7.51k
      if(n == 3) {
562
5
        if(c[1])
563
          /* something follows this dot */
564
3
          return HOST_NAME;
565
2
        done = TRUE;
566
2
      }
567
7.51k
      else {
568
7.51k
        n++;
569
7.51k
        c++;
570
7.51k
      }
571
7.51k
      break;
572
573
7.51k
    case '\0':
574
4.64k
      done = TRUE;
575
4.64k
      break;
576
577
75
    default:
578
75
      return HOST_NAME;
579
12.2k
    }
580
12.2k
  }
581
582
4.64k
  switch(n) {
583
1.98k
  case 0: /* a -- 32 bits */
584
1.98k
    curlx_dyn_reset(host);
585
586
1.98k
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
587
1.98k
                            (parts[0] >> 24),
588
1.98k
                            ((parts[0] >> 16) & 0xff),
589
1.98k
                            ((parts[0] >> 8) & 0xff),
590
1.98k
                            (parts[0] & 0xff));
591
1.98k
    break;
592
216
  case 1: /* a.b -- 8.24 bits */
593
216
    if((parts[0] > 0xff) || (parts[1] > 0xffffff))
594
54
      return HOST_NAME;
595
162
    curlx_dyn_reset(host);
596
162
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
597
162
                            parts[0],
598
162
                            ((parts[1] >> 16) & 0xff),
599
162
                            ((parts[1] >> 8) & 0xff),
600
162
                            (parts[1] & 0xff));
601
162
    break;
602
124
  case 2: /* a.b.c -- 8.8.16 bits */
603
124
    if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xffff))
604
94
      return HOST_NAME;
605
30
    curlx_dyn_reset(host);
606
30
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
607
30
                            parts[0],
608
30
                            parts[1],
609
30
                            ((parts[2] >> 8) & 0xff),
610
30
                            (parts[2] & 0xff));
611
30
    break;
612
2.31k
  case 3: /* a.b.c.d -- 8.8.8.8 bits */
613
2.31k
    if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xff) ||
614
2.20k
       (parts[3] > 0xff))
615
133
      return HOST_NAME;
616
2.18k
    curlx_dyn_reset(host);
617
2.18k
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
618
2.18k
                            parts[0],
619
2.18k
                            parts[1],
620
2.18k
                            parts[2],
621
2.18k
                            parts[3]);
622
2.18k
    break;
623
4.64k
  }
624
4.36k
  if(result)
625
0
    return HOST_ERROR;
626
4.36k
  return HOST_IPV4;
627
4.36k
}
628
629
/* if necessary, replace the host content with a URL decoded version */
630
static CURLUcode urldecode_host(struct dynbuf *host)
631
6.47k
{
632
6.47k
  const char *per;
633
6.47k
  const char *hostname = curlx_dyn_ptr(host);
634
6.47k
  per = strchr(hostname, '%');
635
6.47k
  if(!per)
636
    /* nothing to decode */
637
6.30k
    return CURLUE_OK;
638
169
  else {
639
    /* encoded */
640
169
    size_t dlen;
641
169
    char *decoded;
642
169
    CURLcode result = Curl_urldecode(hostname, 0, &decoded, &dlen,
643
169
                                     REJECT_CTRL);
644
169
    if(result)
645
3
      return CURLUE_BAD_HOSTNAME;
646
166
    curlx_dyn_reset(host);
647
166
    result = curlx_dyn_addn(host, decoded, dlen);
648
166
    curlx_free(decoded);
649
166
    if(result)
650
0
      return cc2cu(result);
651
166
  }
652
653
166
  return CURLUE_OK;
654
6.47k
}
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
6.70k
{
662
6.70k
  size_t offset;
663
6.70k
  CURLUcode uc;
664
6.70k
  CURLcode result;
665
666
  /*
667
   * Parse the login details and strip them out of the hostname.
668
   */
669
6.70k
  uc = parse_hostname_login(u, auth, authlen, flags, &offset);
670
6.70k
  if(uc)
671
1
    return uc;
672
673
6.70k
  result = curlx_dyn_addn(host, auth + offset, authlen - offset);
674
6.70k
  if(result) {
675
0
    uc = cc2cu(result);
676
0
    return uc;
677
0
  }
678
679
6.70k
  uc = parse_port(u, host, has_scheme);
680
681
6.70k
  if(!curlx_dyn_len(host))
682
    /* this makes no-host errors override port number problems */
683
188
    uc = CURLUE_NO_HOST;
684
6.70k
  if(!uc)
685
6.47k
    uc = urldecode_host(host);
686
6.70k
  if(uc)
687
233
    return uc;
688
689
6.47k
  switch(ipv4_normalize(host)) {
690
4.36k
  case HOST_IPV4:
691
4.36k
    break;
692
322
  case HOST_IPV6:
693
322
    uc = ipv6_parse(u, curlx_dyn_ptr(host), curlx_dyn_len(host));
694
322
    break;
695
1.78k
  case HOST_NAME:
696
1.78k
    uc = hostname_check(u, curlx_dyn_ptr(host), curlx_dyn_len(host));
697
1.78k
    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
6.47k
  }
705
706
6.47k
  return uc;
707
6.47k
}
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
10.3M
{
736
10.3M
  const char *p = *str;
737
10.3M
  if(*p == '.') {
738
199k
    (*str)++;
739
199k
    (*clen)--;
740
199k
    return TRUE;
741
199k
  }
742
10.1M
  else if((*clen >= 3) &&
743
10.1M
          (p[0] == '%') && (p[1] == '2') && ((p[2] | 0x20) == 'e')) {
744
37.4k
    *str += 3;
745
37.4k
    *clen -= 3;
746
37.4k
    return TRUE;
747
37.4k
  }
748
10.1M
  return FALSE;
749
10.3M
}
750
751
26.7M
#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
1.11k
{
756
  /* a single byte path cannot be cleaned up */
757
1.11k
  if(pn < 2)
758
7
    return FALSE;
759
7.62M
  while(pn) {
760
7.62M
    if(is_dot(&p, &pn)) {
761
      /* "./" or dot before end of string */
762
4.11k
      if(!pn || ISSLASH(*p))
763
248
        return TRUE;
764
      /* "../" or ".." before end of string */
765
3.86k
      else if(is_dot(&p, &pn) && (!pn || ISSLASH(*p)))
766
118
        return TRUE;
767
4.11k
    }
768
7.62M
    else {
769
7.62M
      p++;
770
7.62M
      pn--;
771
7.62M
    }
772
7.62M
  }
773
738
  return FALSE;
774
1.10k
}
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
1.11k
{
795
1.11k
  struct dynbuf out;
796
1.11k
  CURLcode result = CURLE_OK;
797
798
  /* variables for leading dot checks */
799
1.11k
  const char *dinput = input;
800
1.11k
  size_t dlen = clen;
801
802
1.11k
  *outp = NULL;
803
1.11k
  if(!needs_dedotdot(input, clen))
804
745
    return 0;
805
806
366
  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
366
  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
26.3M
  while(clen && !result) { /* until end of path content */
832
26.3M
    if(ISSLASH(*input)) {
833
2.63M
      const char *p = &input[1];
834
2.63M
      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
2.63M
      if(is_dot(&p, &blen)) {
839
175k
        if(!blen) { /* /. */
840
39
          result = curlx_dyn_addn(&out, "/", 1);
841
39
          break;
842
39
        }
843
175k
        else if(ISSLASH(*p)) { /* /./ */
844
50.3k
          input = p;
845
50.3k
          clen = blen;
846
50.3k
          continue;
847
50.3k
        }
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
125k
        else if(is_dot(&p, &blen) && (ISSLASH(*p) || !blen)) {
854
          /* remove the last segment from the output buffer */
855
46.3k
          size_t len = curlx_dyn_len(&out);
856
46.3k
          if(len) {
857
45.9k
            const char *ptr = curlx_dyn_ptr(&out);
858
45.9k
            const char *last = memrchr(ptr, '/', len);
859
45.9k
            if(last)
860
              /* trim the output at the slash */
861
45.9k
              curlx_dyn_setlen(&out, last - ptr);
862
45.9k
          }
863
864
46.3k
          if(blen) { /* /../ */
865
46.2k
            input = p;
866
46.2k
            clen = blen;
867
46.2k
            continue;
868
46.2k
          }
869
32
          result = curlx_dyn_addn(&out, "/", 1);
870
32
          break;
871
46.3k
        }
872
175k
      }
873
2.63M
    }
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
26.2M
    result = curlx_dyn_addn(&out, input, 1);
881
26.2M
    input++;
882
26.2M
    clen--;
883
26.2M
  }
884
366
end:
885
366
  if(!result) {
886
366
    if(curlx_dyn_len(&out))
887
366
      *outp = curlx_dyn_ptr(&out);
888
0
    else {
889
0
      *outp = curlx_strdup("");
890
0
      if(!*outp)
891
0
        return 1;
892
0
    }
893
366
  }
894
366
  return result ? 1 : 0; /* success */
895
366
}
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
89
{
905
89
  const char *path;
906
89
  size_t pathlen;
907
908
89
  *pathp = NULL;
909
89
  *pathlenp = 0;
910
89
  if(urllen <= 6)
911
    /* file:/ is not enough to actually be a complete file: URL */
912
1
    return CURLUE_BAD_FILE_URL;
913
914
  /* path has been allocated large enough to hold this */
915
88
  path = &url[5];
916
88
  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
88
  if(path[0] != '/')
922
0
    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
88
  if(path[1] == '/') {
931
    /* swallow the two slashes */
932
46
    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
46
    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
28
      if(checkprefix("localhost/", ptr) ||
954
27
         checkprefix("127.0.0.1/", ptr)) {
955
2
        ptr += 9; /* now points to the slash after the host */
956
2
      }
957
26
      else
958
        /* Invalid file://hostname/, expected localhost or 127.0.0.1 or
959
           none */
960
26
        return CURLUE_BAD_FILE_URL;
961
28
    }
962
963
20
    path = ptr;
964
20
    pathlen = urllen - (ptr - url);
965
20
  }
966
967
62
#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
62
  if(('/' == path[0] && STARTS_WITH_URL_DRIVE_PREFIX(&path[1])) ||
971
58
     STARTS_WITH_URL_DRIVE_PREFIX(path)) {
972
    /* File drive letters are only accepted in MS-DOS/Windows */
973
12
    return CURLUE_BAD_FILE_URL;
974
12
  }
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
50
  u->scheme = curlx_strdup("file");
984
50
  if(!u->scheme)
985
0
    return CURLUE_OUT_OF_MEMORY;
986
987
50
  *pathp = path;
988
50
  *pathlenp = pathlen;
989
50
  return CURLUE_OK;
990
50
}
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
6.73k
{
996
  /* clear path */
997
6.73k
  const char *schemep = NULL;
998
999
6.73k
  if(schemelen) {
1000
5.28k
    int num_slashes = 0;
1001
5.28k
    const char *p = &url[schemelen + 1];
1002
5.28k
    if(!Curl_get_scheme(schemebuf) && !(flags & CURLU_NON_SUPPORT_SCHEME))
1003
0
      return CURLUE_UNSUPPORTED_SCHEME;
1004
1005
5.28k
    if(!ISSLASH(*p))
1006
      /* less than one */
1007
0
      return CURLUE_BAD_SLASHES;
1008
5.28k
    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
5.28k
    else {
1015
12.8k
      while(ISSLASH(*p) && (num_slashes < 4)) {
1016
7.56k
        p++;
1017
7.56k
        num_slashes++;
1018
7.56k
      }
1019
5.28k
      if(num_slashes > 3)
1020
1
        return CURLUE_BAD_SLASHES;
1021
5.28k
    }
1022
1023
5.28k
    schemep = schemebuf;
1024
5.28k
    *hostpp = p; /* hostname starts here */
1025
5.28k
  }
1026
1.44k
  else {
1027
    /* no scheme! */
1028
1029
1.44k
    if(!(flags & (CURLU_DEFAULT_SCHEME | CURLU_GUESS_SCHEME)))
1030
0
      return CURLUE_BAD_SCHEME;
1031
1032
1.44k
    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
1.44k
    *hostpp = url;
1039
1.44k
  }
1040
1041
6.73k
  if(schemep) {
1042
5.28k
    u->scheme = curlx_strdup(schemep);
1043
5.28k
    if(!u->scheme)
1044
0
      return CURLUE_OUT_OF_MEMORY;
1045
5.28k
  }
1046
6.73k
  return CURLUE_OK;
1047
6.73k
}
1048
1049
static CURLUcode guess_scheme(CURLU *u, struct dynbuf *host)
1050
1.09k
{
1051
1.09k
  const char *hostname = curlx_dyn_ptr(host);
1052
1.09k
  const char *schemep = NULL;
1053
  /* legacy curl-style guess based on hostname */
1054
1.09k
  if(checkprefix("ftp.", hostname))
1055
1
    schemep = "ftp";
1056
1.09k
  else if(checkprefix("dict.", hostname))
1057
1
    schemep = "dict";
1058
1.09k
  else if(checkprefix("ldap.", hostname))
1059
1
    schemep = "ldap";
1060
1.09k
  else if(checkprefix("imap.", hostname))
1061
1
    schemep = "imap";
1062
1.08k
  else if(checkprefix("smtp.", hostname))
1063
1
    schemep = "smtp";
1064
1.08k
  else if(checkprefix("pop3.", hostname))
1065
1
    schemep = "pop3";
1066
1.08k
  else
1067
1.08k
    schemep = "http";
1068
1069
1.09k
  u->scheme = curlx_strdup(schemep);
1070
1.09k
  if(!u->scheme)
1071
0
    return CURLUE_OUT_OF_MEMORY;
1072
1073
1.09k
  u->guessed_scheme = TRUE;
1074
1.09k
  return CURLUE_OK;
1075
1.09k
}
1076
1077
static CURLUcode handle_fragment(CURLU *u, const char *fragment,
1078
                                 size_t fraglen, unsigned int flags)
1079
60
{
1080
60
  CURLUcode ures;
1081
60
  u->fragment_present = TRUE;
1082
60
  if(fraglen > 1) {
1083
    /* skip the leading '#' in the copy but include the null-terminator */
1084
30
    if(flags & CURLU_URLENCODE) {
1085
0
      struct dynbuf enc;
1086
0
      curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1087
0
      ures = urlencode_str(&enc, fragment + 1, fraglen - 1, TRUE, QUERY_NO);
1088
0
      if(ures)
1089
0
        return ures;
1090
0
      u->fragment = curlx_dyn_ptr(&enc);
1091
0
    }
1092
30
    else {
1093
30
      u->fragment = curlx_memdup0(fragment + 1, fraglen - 1);
1094
30
      if(!u->fragment)
1095
0
        return CURLUE_OUT_OF_MEMORY;
1096
30
    }
1097
30
  }
1098
60
  return CURLUE_OK;
1099
60
}
1100
1101
static CURLUcode handle_query(CURLU *u, const char *query,
1102
                              size_t qlen, unsigned int flags)
1103
557
{
1104
557
  u->query_present = TRUE;
1105
557
  if(qlen > 1) {
1106
471
    if(flags & CURLU_URLENCODE) {
1107
0
      struct dynbuf enc;
1108
0
      CURLUcode ures;
1109
0
      curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1110
      /* skip the leading question mark */
1111
0
      ures = urlencode_str(&enc, query + 1, qlen - 1, TRUE, QUERY_YES);
1112
0
      if(ures)
1113
0
        return ures;
1114
0
      u->query = curlx_dyn_ptr(&enc);
1115
0
    }
1116
471
    else {
1117
471
      u->query = curlx_memdup0(query + 1, qlen - 1);
1118
471
      if(!u->query)
1119
0
        return CURLUE_OUT_OF_MEMORY;
1120
471
    }
1121
471
  }
1122
86
  else {
1123
    /* single byte query */
1124
86
    u->query = curlx_strdup("");
1125
86
    if(!u->query)
1126
0
      return CURLUE_OUT_OF_MEMORY;
1127
86
  }
1128
557
  return CURLUE_OK;
1129
557
}
1130
1131
static CURLUcode handle_path(CURLU *u, const char *path,
1132
                             size_t pathlen, unsigned int flags,
1133
                             bool is_file)
1134
6.29k
{
1135
6.29k
  CURLUcode ures;
1136
6.29k
  if(pathlen && (flags & CURLU_URLENCODE)) {
1137
0
    struct dynbuf enc;
1138
0
    curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1139
0
    ures = urlencode_str(&enc, path, pathlen, TRUE, QUERY_NO);
1140
0
    if(ures)
1141
0
      return ures;
1142
0
    pathlen = curlx_dyn_len(&enc);
1143
0
    path = u->path = curlx_dyn_ptr(&enc);
1144
0
  }
1145
1146
6.29k
  if(pathlen >= (size_t)(1 + !is_file)) {
1147
    /* paths for file:// scheme can be one byte, others need to be two */
1148
1.11k
    if(!u->path) {
1149
1.11k
      u->path = curlx_memdup0(path, pathlen);
1150
1.11k
      if(!u->path)
1151
0
        return CURLUE_OUT_OF_MEMORY;
1152
1.11k
      path = u->path;
1153
1.11k
    }
1154
0
    else if(flags & CURLU_URLENCODE)
1155
      /* it might have encoded more than the path so cut it */
1156
0
      u->path[pathlen] = 0;
1157
1158
1.11k
    if(!(flags & CURLU_PATH_AS_IS)) {
1159
      /* remove ../ and ./ sequences according to RFC3986 */
1160
1.11k
      char *dedot;
1161
1.11k
      int err = dedotdotify(path, pathlen, &dedot);
1162
1.11k
      if(err)
1163
0
        return CURLUE_OUT_OF_MEMORY;
1164
1.11k
      if(dedot) {
1165
366
        curlx_free(u->path);
1166
366
        u->path = dedot;
1167
366
      }
1168
1.11k
    }
1169
1.11k
  }
1170
6.29k
  return CURLUE_OK;
1171
6.29k
}
1172
1173
static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
1174
6.84k
{
1175
6.84k
  const char *path;
1176
6.84k
  size_t pathlen;
1177
6.84k
  char schemebuf[MAX_SCHEME_LEN + 1];
1178
6.84k
  size_t schemelen = 0;
1179
6.84k
  size_t urllen;
1180
6.84k
  CURLUcode ures = CURLUE_OK;
1181
6.84k
  struct dynbuf host;
1182
6.84k
  bool is_file = FALSE;
1183
1184
6.84k
  DEBUGASSERT(url);
1185
1186
6.84k
  curlx_dyn_init(&host, CURL_MAX_INPUT_LENGTH);
1187
1188
6.84k
  ures = Curl_junkscan(url, &urllen, !!(flags & CURLU_ALLOW_SPACE));
1189
6.84k
  if(ures)
1190
19
    goto fail;
1191
1192
6.82k
  schemelen = Curl_is_absolute_url(url, schemebuf, sizeof(schemebuf),
1193
6.82k
                                   flags & (CURLU_GUESS_SCHEME |
1194
6.82k
                                            CURLU_DEFAULT_SCHEME));
1195
1196
  /* handle the file: scheme */
1197
6.82k
  if(schemelen && !strcmp(schemebuf, "file")) {
1198
89
    is_file = TRUE;
1199
89
    ures = parse_file(url, urllen, u, &path, &pathlen);
1200
89
  }
1201
6.73k
  else {
1202
6.73k
    const char *hostp = NULL;
1203
6.73k
    size_t hostlen;
1204
6.73k
    ures = parse_scheme(url, u, schemebuf, schemelen, flags, &hostp);
1205
6.73k
    if(ures)
1206
1
      goto fail;
1207
1208
    /* find the end of the hostname + port number */
1209
6.73k
    hostlen = strcspn(hostp, "/?#");
1210
6.73k
    path = &hostp[hostlen];
1211
1212
    /* this pathlen also contains the query and the fragment */
1213
6.73k
    pathlen = urllen - (path - url);
1214
6.73k
    if(hostlen) {
1215
6.70k
      ures = parse_authority(u, hostp, hostlen, flags, &host, !!u->scheme);
1216
6.70k
      if(!ures && (flags & CURLU_GUESS_SCHEME) && !u->scheme)
1217
1.09k
        ures = guess_scheme(u, &host);
1218
6.70k
    }
1219
24
    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
24
    else
1225
24
      ures = CURLUE_NO_HOST;
1226
6.73k
  }
1227
6.82k
  if(!ures) {
1228
    /* The path might at this point contain a fragment and/or a query to
1229
       handle */
1230
6.29k
    const char *fragment = strchr(path, '#');
1231
6.29k
    if(fragment) {
1232
60
      size_t fraglen = pathlen - (fragment - path);
1233
60
      ures = handle_fragment(u, fragment, fraglen, flags);
1234
      /* after this, pathlen still contains the query */
1235
60
      pathlen -= fraglen;
1236
60
    }
1237
6.29k
  }
1238
6.82k
  if(!ures) {
1239
6.29k
    const char *query = memchr(path, '?', pathlen);
1240
6.29k
    if(query) {
1241
557
      size_t qlen = pathlen - (query - path);
1242
557
      ures = handle_query(u, query, qlen, flags);
1243
557
      pathlen -= qlen;
1244
557
    }
1245
6.29k
  }
1246
6.82k
  if(!ures)
1247
    /* the fragment and query parts are trimmed off from the path */
1248
6.29k
    ures = handle_path(u, path, pathlen, flags, is_file);
1249
6.82k
  if(!ures) {
1250
6.29k
    u->host = curlx_dyn_ptr(&host);
1251
6.29k
    return CURLUE_OK;
1252
6.29k
  }
1253
544
fail:
1254
544
  curlx_dyn_free(&host);
1255
544
  free_urlhandle(u);
1256
544
  return ures;
1257
6.82k
}
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
6.84k
{
1265
6.84k
  CURLUcode ures;
1266
6.84k
  CURLU tmpurl;
1267
6.84k
  memset(&tmpurl, 0, sizeof(tmpurl));
1268
6.84k
  ures = parseurl(url, &tmpurl, flags);
1269
6.84k
  if(!ures) {
1270
6.29k
    free_urlhandle(u);
1271
6.29k
    *u = tmpurl;
1272
6.29k
  }
1273
6.84k
  return ures;
1274
6.84k
}
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
0
{
1282
0
  struct dynbuf urlbuf;
1283
0
  bool host_changed = FALSE;
1284
0
  const char *useurl = relurl;
1285
0
  const char *cutoff = NULL;
1286
0
  size_t prelen;
1287
0
  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
0
  const char *scheme = u->scheme ? u->scheme : DEFAULT_SCHEME;
1291
1292
  /* protsep points to the start of the hostname, after [scheme]:// */
1293
0
  const char *protsep = base + strlen(scheme) + 3;
1294
0
  DEBUGASSERT(base && relurl && u); /* all set here */
1295
0
  if(!base)
1296
0
    return CURLUE_MALFORMED_INPUT; /* should never happen */
1297
1298
  /* handle different relative URL types */
1299
0
  switch(relurl[0]) {
1300
0
  case '/':
1301
0
    if(relurl[1] == '/') {
1302
      /* protocol-relative URL: //example.com/path */
1303
0
      cutoff = protsep;
1304
0
      useurl = &relurl[2];
1305
0
      host_changed = TRUE;
1306
0
    }
1307
0
    else
1308
      /* absolute /path */
1309
0
      cutoff = strchr(protsep, '/');
1310
0
    break;
1311
1312
0
  case '#':
1313
    /* fragment-only change */
1314
0
    if(u->fragment_present)
1315
0
      cutoff = strchr(protsep, '#');
1316
0
    break;
1317
1318
0
  default:
1319
    /* path or query-only change */
1320
0
    if(u->query_present)
1321
      /* remove existing query */
1322
0
      cutoff = strchr(protsep, '?');
1323
0
    else if(u->fragment_present)
1324
      /* Remove existing fragment */
1325
0
      cutoff = strchr(protsep, '#');
1326
1327
0
    if(relurl[0] != '?') {
1328
      /* append a relative path after the last slash */
1329
0
      cutoff = memrchr(protsep, '/',
1330
0
                       cutoff ? (size_t)(cutoff - protsep) : strlen(protsep));
1331
0
      if(cutoff)
1332
0
        cutoff++; /* truncate after last slash */
1333
0
    }
1334
0
    break;
1335
0
  }
1336
1337
0
  prelen = cutoff ? (size_t)(cutoff - base) : strlen(base);
1338
1339
  /* build new URL */
1340
0
  curlx_dyn_init(&urlbuf, CURL_MAX_INPUT_LENGTH);
1341
1342
0
  if(!curlx_dyn_addn(&urlbuf, base, prelen) &&
1343
0
     !urlencode_str(&urlbuf, useurl, strlen(useurl), !host_changed,
1344
0
                    QUERY_NOT_YET)) {
1345
0
    uc = parseurl_and_replace(curlx_dyn_ptr(&urlbuf), u,
1346
0
                              flags & ~U_CURLU_PATH_AS_IS);
1347
0
  }
1348
0
  else
1349
0
    uc = CURLUE_OUT_OF_MEMORY;
1350
1351
0
  curlx_dyn_free(&urlbuf);
1352
0
  return uc;
1353
0
}
1354
1355
/*
1356
 */
1357
CURLU *curl_url(void)
1358
7.17k
{
1359
7.17k
  return curlx_calloc(1, sizeof(struct Curl_URL));
1360
7.17k
}
1361
1362
void curl_url_cleanup(CURLU *u)
1363
28.3k
{
1364
28.3k
  if(u) {
1365
7.17k
    free_urlhandle(u);
1366
7.17k
    curlx_free(u);
1367
7.17k
  }
1368
28.3k
}
1369
1370
#define DUP(dest, src, name)                    \
1371
0
  do {                                          \
1372
0
    if((src)->name) {                           \
1373
0
      (dest)->name = curlx_strdup((src)->name); \
1374
0
      if(!(dest)->name)                         \
1375
0
        goto fail;                              \
1376
0
    }                                           \
1377
0
  } while(0)
1378
1379
CURLU *curl_url_dup(const CURLU *in)
1380
0
{
1381
0
  struct Curl_URL *u = curlx_calloc(1, sizeof(struct Curl_URL));
1382
0
  if(u) {
1383
0
    DUP(u, in, scheme);
1384
0
    DUP(u, in, user);
1385
0
    DUP(u, in, password);
1386
0
    DUP(u, in, options);
1387
0
    DUP(u, in, host);
1388
0
    DUP(u, in, path);
1389
0
    DUP(u, in, query);
1390
0
    DUP(u, in, fragment);
1391
0
    DUP(u, in, zoneid);
1392
0
    u->portnum = in->portnum;
1393
0
    u->port_present = in->port_present;
1394
0
    u->fragment_present = in->fragment_present;
1395
0
    u->query_present = in->query_present;
1396
0
  }
1397
0
  return u;
1398
0
fail:
1399
0
  curl_url_cleanup(u);
1400
0
  return NULL;
1401
0
}
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
19.9k
{
1430
19.9k
  CURLUcode uc = CURLUE_OK;
1431
19.9k
  size_t partlen = strlen(ptr);
1432
19.9k
  bool urldecode = (flags & CURLU_URLDECODE) ? 1 : 0;
1433
19.9k
  bool urlencode = (flags & CURLU_URLENCODE) ? 1 : 0;
1434
19.9k
  bool punycode = (flags & CURLU_PUNYCODE) && (what == CURLUPART_HOST);
1435
19.9k
  bool depunyfy = (flags & CURLU_PUNY2IDN) && (what == CURLUPART_HOST);
1436
19.9k
  char *part = curlx_memdup0(ptr, partlen);
1437
19.9k
  *partp = NULL;
1438
19.9k
  if(!part)
1439
0
    return CURLUE_OUT_OF_MEMORY;
1440
19.9k
  if(plusdecode) {
1441
    /* convert + to space */
1442
0
    char *plus = part;
1443
0
    size_t i = 0;
1444
0
    for(i = 0; i < partlen; ++plus, i++) {
1445
0
      if(*plus == '+')
1446
0
        *plus = ' ';
1447
0
    }
1448
0
  }
1449
19.9k
  if(urldecode) {
1450
1.99k
    char *decoded;
1451
1.99k
    size_t dlen;
1452
    /* this unconditional rejection of control bytes is documented API
1453
       behavior */
1454
1.99k
    CURLcode result = Curl_urldecode(part, partlen, &decoded, &dlen,
1455
1.99k
                                     REJECT_CTRL);
1456
1.99k
    curlx_free(part);
1457
1.99k
    if(result)
1458
3
      return CURLUE_URLDECODE;
1459
1.99k
    part = decoded;
1460
1.99k
    partlen = dlen;
1461
1.99k
  }
1462
19.9k
  if(urlencode) {
1463
4.00k
    struct dynbuf enc;
1464
4.00k
    curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1465
4.00k
    uc = urlencode_str(&enc, part, partlen, TRUE, what == CURLUPART_QUERY ?
1466
4.00k
                       QUERY_YES : QUERY_NO);
1467
4.00k
    curlx_free(part);
1468
4.00k
    if(uc)
1469
0
      return uc;
1470
4.00k
    part = curlx_dyn_ptr(&enc);
1471
4.00k
  }
1472
15.9k
  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
15.9k
  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
19.9k
  *partp = part;
1493
19.9k
  return CURLUE_OK;
1494
19.9k
}
1495
1496
static CURLUcode file_url(const CURLU *u, char **part,
1497
                          const char *fragmentsep,
1498
                          const char *querysep)
1499
50
{
1500
50
  char *url = curl_maprintf("file://%s%s%s%s%s",
1501
50
                            u->path, querysep, u->query ? u->query : "",
1502
50
                            fragmentsep, u->fragment ? u->fragment : "");
1503
50
  if(!url)
1504
0
    return CURLUE_OUT_OF_MEMORY;
1505
1506
50
  *part = url;
1507
50
  return CURLUE_OK;
1508
50
}
1509
1510
static CURLUcode urlget_url(const CURLU *u, char **part, unsigned int flags)
1511
6.10k
{
1512
6.10k
  char *url;
1513
6.10k
  char *allochost = NULL;
1514
6.10k
  const char *fragmentsep =
1515
6.10k
    (u->fragment || (u->fragment_present && flags & CURLU_GET_EMPTY)) ?
1516
6.04k
    "#" : "";
1517
6.10k
  const char *querysep = ((u->query && u->query[0]) ||
1518
5.63k
                          (u->query_present && flags & CURLU_GET_EMPTY)) ?
1519
5.55k
    "?" : "";
1520
6.10k
  char portbuf[7];
1521
6.10k
  if(curl_strequal("file", u->scheme))
1522
50
    return file_url(u, part, fragmentsep, querysep);
1523
6.05k
  else if(!u->host)
1524
1.79k
    return CURLUE_NO_HOST;
1525
4.25k
  else {
1526
4.25k
    const char *scheme;
1527
4.25k
    char *options = u->options;
1528
4.25k
    char *port = NULL;
1529
4.25k
    const struct Curl_scheme *h = NULL;
1530
4.25k
    char schemebuf[MAX_SCHEME_LEN + 5];
1531
4.25k
    if(u->scheme)
1532
4.25k
      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
4.25k
    if(u->port_present) {
1539
69
      curl_msnprintf(portbuf, sizeof(portbuf), "%u", u->portnum);
1540
69
      port = portbuf;
1541
69
    }
1542
1543
4.25k
    h = Curl_get_scheme(scheme);
1544
4.25k
    if(h) {
1545
4.09k
      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
4.09k
      else if(u->port_present && (h->defport == u->portnum) &&
1552
1
              (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
0
        port = NULL;
1556
0
      }
1557
1558
4.09k
      if(!(h->flags & PROTOPT_URLOPTIONS))
1559
4.07k
        options = NULL;
1560
4.09k
    }
1561
1562
4.25k
    if(u->host[0] == '[') {
1563
227
      if(u->zoneid) {
1564
        /* make it '[ host %25 zoneid ]' */
1565
43
        struct dynbuf enc;
1566
43
        size_t hostlen = strlen(u->host);
1567
43
        curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1568
43
        if(curlx_dyn_addf(&enc, "%.*s%%25%s]", (int)hostlen - 1, u->host,
1569
43
                          u->zoneid))
1570
0
          return CURLUE_OUT_OF_MEMORY;
1571
43
        allochost = curlx_dyn_ptr(&enc);
1572
43
      }
1573
227
    }
1574
4.03k
    else if(flags & CURLU_URLENCODE) {
1575
0
      allochost = curl_easy_escape(NULL, u->host, 0);
1576
0
      if(!allochost)
1577
0
        return CURLUE_OUT_OF_MEMORY;
1578
0
    }
1579
4.03k
    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
4.03k
    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
4.25k
    if(!(flags & CURLU_NO_GUESS_SCHEME) || !u->guessed_scheme)
1595
4.25k
      curl_msnprintf(schemebuf, sizeof(schemebuf), "%s://", scheme);
1596
0
    else
1597
0
      schemebuf[0] = 0;
1598
1599
4.25k
    url = curl_maprintf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
1600
4.25k
                        schemebuf,
1601
4.25k
                        u->user ? u->user : "",
1602
4.25k
                        u->password ? ":" : "",
1603
4.25k
                        u->password ? u->password : "",
1604
4.25k
                        options ? ";" : "",
1605
4.25k
                        options ? options : "",
1606
4.25k
                        (u->user || u->password || options) ? "@" : "",
1607
4.25k
                        allochost ? allochost : u->host,
1608
4.25k
                        port ? ":" : "",
1609
4.25k
                        port ? port : "",
1610
4.25k
                        u->path ? u->path : "/",
1611
4.25k
                        querysep,
1612
4.25k
                        u->query ? u->query : "",
1613
4.25k
                        fragmentsep,
1614
4.25k
                        u->fragment ? u->fragment : "");
1615
4.25k
    curlx_free(allochost);
1616
4.25k
  }
1617
4.25k
  if(!url)
1618
0
    return CURLUE_OUT_OF_MEMORY;
1619
4.25k
  *part = url;
1620
4.25k
  return CURLUE_OK;
1621
4.25k
}
1622
1623
CURLUcode curl_url_get(const CURLU *u, CURLUPart what,
1624
                       char **part, unsigned int flags)
1625
52.1k
{
1626
52.1k
  const char *ptr;
1627
52.1k
  CURLUcode ifmissing = CURLUE_UNKNOWN_PART;
1628
52.1k
  char portbuf[7];
1629
52.1k
  bool plusdecode = FALSE;
1630
52.1k
  if(!u)
1631
0
    return CURLUE_BAD_HANDLE;
1632
52.1k
  if(!part)
1633
0
    return CURLUE_BAD_PARTPOINTER;
1634
52.1k
  *part = NULL;
1635
1636
52.1k
  switch(what) {
1637
8.28k
  case CURLUPART_SCHEME:
1638
8.28k
    ptr = u->scheme;
1639
8.28k
    ifmissing = CURLUE_NO_SCHEME;
1640
8.28k
    flags &= ~U_CURLU_URLDECODE; /* never for schemes */
1641
8.28k
    if((flags & CURLU_NO_GUESS_SCHEME) && u->guessed_scheme)
1642
0
      return CURLUE_NO_SCHEME;
1643
8.28k
    break;
1644
8.28k
  case CURLUPART_USER:
1645
5.85k
    ptr = u->user;
1646
5.85k
    ifmissing = CURLUE_NO_USER;
1647
5.85k
    break;
1648
5.85k
  case CURLUPART_PASSWORD:
1649
5.85k
    ptr = u->password;
1650
5.85k
    ifmissing = CURLUE_NO_PASSWORD;
1651
5.85k
    break;
1652
4.00k
  case CURLUPART_OPTIONS:
1653
4.00k
    ptr = u->options;
1654
4.00k
    ifmissing = CURLUE_NO_OPTIONS;
1655
4.00k
    break;
1656
6.13k
  case CURLUPART_HOST:
1657
6.13k
    ptr = u->host;
1658
6.13k
    ifmissing = CURLUE_NO_HOST;
1659
6.13k
    break;
1660
5.86k
  case CURLUPART_ZONEID:
1661
5.86k
    ptr = u->zoneid;
1662
5.86k
    ifmissing = CURLUE_NO_ZONEID;
1663
5.86k
    break;
1664
1.98k
  case CURLUPART_PORT:
1665
1.98k
    ptr = NULL;
1666
1.98k
    ifmissing = CURLUE_NO_PORT;
1667
1.98k
    flags &= ~U_CURLU_URLDECODE; /* never for port */
1668
1.98k
    if(u->port_present) {
1669
0
      const struct Curl_scheme *h = u->scheme ?
1670
0
                                    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
0
      if(h && (h->defport == u->portnum) &&
1674
0
         (flags & CURLU_NO_DEFAULT_PORT)) {
1675
0
        ptr = NULL;
1676
0
      }
1677
0
      else {
1678
0
        curl_msnprintf(portbuf, sizeof(portbuf), "%u", u->portnum);
1679
0
        ptr = portbuf;
1680
0
      }
1681
0
    }
1682
1.98k
    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
1.98k
    break;
1692
4.00k
  case CURLUPART_PATH:
1693
4.00k
    ptr = u->path;
1694
4.00k
    if(!ptr)
1695
2.94k
      ptr = "/";
1696
4.00k
    break;
1697
4.00k
  case CURLUPART_QUERY:
1698
4.00k
    ptr = u->query;
1699
4.00k
    ifmissing = CURLUE_NO_QUERY;
1700
4.00k
    plusdecode = flags & CURLU_URLDECODE;
1701
4.00k
    if(ptr && !ptr[0] && !(flags & CURLU_GET_EMPTY))
1702
      /* there was a blank query and the user does not ask for it */
1703
0
      ptr = NULL;
1704
4.00k
    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
6.10k
  case CURLUPART_URL:
1713
6.10k
    return urlget_url(u, part, flags);
1714
0
  default:
1715
0
    ptr = NULL;
1716
0
    break;
1717
52.1k
  }
1718
46.0k
  if(ptr)
1719
19.9k
    return urlget_format(u, what, ptr, part, plusdecode, flags);
1720
1721
26.0k
  return ifmissing;
1722
46.0k
}
1723
1724
static CURLUcode set_url_scheme(CURLU *u, const char *scheme,
1725
                                unsigned int flags)
1726
0
{
1727
0
  size_t plen = strlen(scheme);
1728
0
  const struct Curl_scheme *h = NULL;
1729
0
  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
0
  h = Curl_get_scheme(scheme);
1734
0
  if(!(flags & CURLU_NON_SUPPORT_SCHEME) && (!h || !h->run))
1735
0
    return CURLUE_UNSUPPORTED_SCHEME;
1736
0
  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
0
  u->guessed_scheme = FALSE;
1752
0
  return CURLUE_OK;
1753
0
}
1754
1755
static CURLUcode set_url_port(CURLU *u, const char *provided_port)
1756
45
{
1757
45
  curl_off_t port;
1758
45
  if(!ISDIGIT(provided_port[0]))
1759
    /* not a number */
1760
0
    return CURLUE_BAD_PORT_NUMBER;
1761
45
  if(curlx_str_number(&provided_port, &port, 0xffff) || *provided_port)
1762
    /* weirdly provided number, not good! */
1763
0
    return CURLUE_BAD_PORT_NUMBER;
1764
45
  u->portnum = (uint16_t)port;
1765
45
  u->port_present = TRUE;
1766
45
  return CURLUE_OK;
1767
45
}
1768
1769
static CURLUcode set_url(CURLU *u, const char *url, size_t part_size,
1770
                         unsigned int flags)
1771
7.17k
{
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
7.17k
  CURLUcode uc;
1779
7.17k
  char *oldurl = NULL;
1780
1781
7.17k
  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
333
    uc = curl_url_get(u, CURLUPART_URL, &oldurl, flags);
1785
333
    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
333
    if(uc == CURLUE_OUT_OF_MEMORY)
1795
0
      return uc;
1796
333
    return CURLUE_MALFORMED_INPUT;
1797
333
  }
1798
1799
  /* if the new URL is absolute replace the existing with the new. */
1800
6.84k
  if(Curl_is_absolute_url(url, NULL, 0,
1801
6.84k
                          flags & (CURLU_GUESS_SCHEME | CURLU_DEFAULT_SCHEME)))
1802
5.37k
    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
1.46k
  uc = curl_url_get(u, CURLUPART_URL, &oldurl,
1810
1.46k
                    (flags & ~CURLU_NO_GUESS_SCHEME) | CURLU_GET_EMPTY);
1811
1.46k
  if(uc == CURLUE_OUT_OF_MEMORY)
1812
0
    return uc;
1813
1.46k
  else if(uc)
1814
1.46k
    return parseurl_and_replace(url, u, flags);
1815
1816
0
  DEBUGASSERT(oldurl); /* it is set here */
1817
  /* apply the relative part to create a new URL */
1818
0
  uc = redirect_url(oldurl, url, u, flags);
1819
0
  curlx_free(oldurl);
1820
0
  return uc;
1821
0
}
1822
1823
static CURLUcode urlset_clear(CURLU *u, CURLUPart what)
1824
0
{
1825
0
  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
0
  case CURLUPART_USER:
1835
0
    curlx_safefree(u->user);
1836
0
    break;
1837
0
  case CURLUPART_PASSWORD:
1838
0
    curlx_strzero(u->password);
1839
0
    curlx_safefree(u->password);
1840
0
    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
0
  case CURLUPART_FRAGMENT:
1862
0
    curlx_safefree(u->fragment);
1863
0
    u->fragment_present = FALSE;
1864
0
    break;
1865
0
  default:
1866
0
    return CURLUE_UNKNOWN_PART;
1867
0
  }
1868
0
  return CURLUE_OK;
1869
0
}
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
0
{
1903
0
  const unsigned char *i;
1904
1905
0
  for(i = (const unsigned char *)part; *i; i++) {
1906
0
    CURLcode result;
1907
0
    if((*i == ' ') && plusencode)
1908
0
      result = curlx_dyn_addn(encp, "+", 1);
1909
0
    else if(ISUNRESERVED(*i) ||
1910
0
            (pathmode && allowed_in_path(*i)) ||
1911
0
            ((*i == '=') && equalsencode)) {
1912
0
      if((*i == '=') && equalsencode)
1913
        /* only skip the first equals sign */
1914
0
        equalsencode = FALSE;
1915
0
      result = curlx_dyn_addn(encp, i, 1);
1916
0
    }
1917
0
    else {
1918
0
      unsigned char out[3] = { '%' };
1919
0
      Curl_hexbyte(&out[1], *i);
1920
0
      result = curlx_dyn_addn(encp, out, 3);
1921
0
    }
1922
0
    if(result)
1923
0
      return cc2cu(result);
1924
0
  }
1925
0
  return CURLUE_OK;
1926
0
}
1927
1928
static CURLUcode url_uppercasehex_part(struct dynbuf *encp,
1929
                                       const char *part)
1930
0
{
1931
0
  char *p;
1932
0
  CURLcode result = curlx_dyn_add(encp, part);
1933
0
  if(result)
1934
0
    return cc2cu(result);
1935
0
  p = curlx_dyn_ptr(encp);
1936
0
  while(*p) {
1937
    /* make sure percent encoded are upper case */
1938
0
    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
0
    else
1945
0
      p++;
1946
0
  }
1947
0
  return CURLUE_OK;
1948
0
}
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
7.21k
{
2020
7.21k
  char **storep = NULL;
2021
7.21k
  bool urlencode = (flags & CURLU_URLENCODE) ? 1 : 0;
2022
7.21k
  bool plusencode = FALSE;
2023
7.21k
  bool pathmode = FALSE;
2024
7.21k
  bool leadingslash = FALSE;
2025
7.21k
  bool appendquery = FALSE;
2026
7.21k
  bool equalsencode = FALSE;
2027
7.21k
  size_t nalloc;
2028
2029
7.21k
  if(!u)
2030
0
    return CURLUE_BAD_HANDLE;
2031
7.21k
  if(!part)
2032
    /* setting a part to NULL clears it */
2033
0
    return urlset_clear(u, what);
2034
2035
7.21k
  nalloc = strlen(part);
2036
7.21k
  if(nalloc > CURL_MAX_INPUT_LENGTH)
2037
    /* excessive input length */
2038
0
    return CURLUE_MALFORMED_INPUT;
2039
2040
7.21k
  switch(what) {
2041
0
  case CURLUPART_SCHEME: {
2042
0
    CURLUcode status = set_url_scheme(u, part, flags);
2043
0
    if(status)
2044
0
      return status;
2045
0
    storep = &u->scheme;
2046
0
    urlencode = FALSE; /* never */
2047
0
    break;
2048
0
  }
2049
0
  case CURLUPART_USER:
2050
0
    storep = &u->user;
2051
0
    break;
2052
0
  case CURLUPART_PASSWORD:
2053
0
    storep = &u->password;
2054
0
    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
45
  case CURLUPART_PORT:
2066
45
    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
7.17k
  case CURLUPART_URL:
2084
7.17k
    return set_url(u, part, nalloc, flags);
2085
0
  default:
2086
0
    return CURLUE_UNKNOWN_PART;
2087
7.21k
  }
2088
0
  DEBUGASSERT(storep);
2089
0
  {
2090
0
    const char *newp = NULL;
2091
0
    struct dynbuf enc;
2092
0
    CURLUcode status;
2093
0
    curlx_dyn_init(&enc, (nalloc * 3) + 1 + leadingslash);
2094
2095
0
    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
0
    if(urlencode)
2101
0
      status = url_encode_part(&enc, part, plusencode, pathmode, equalsencode);
2102
0
    else
2103
0
      status = url_uppercasehex_part(&enc, part);
2104
0
    if(!status) {
2105
0
      newp = curlx_dyn_ptr(&enc);
2106
2107
0
      if(appendquery && newp)
2108
0
        return url_append_query(u, &enc);
2109
0
      else if(what == CURLUPART_HOST)
2110
0
        status = url_sethost(u, &enc, urlencode, flags);
2111
0
    }
2112
0
    if(status)
2113
0
      return status;
2114
2115
0
    if(what == CURLUPART_PASSWORD)
2116
0
      curlx_strzero(*storep);
2117
0
    curlx_free(*storep);
2118
0
    *storep = (char *)CURL_UNCONST(newp);
2119
0
  }
2120
0
  return CURLUE_OK;
2121
0
}
2122
2123
bool Curl_url_same_origin(CURLU *base, CURLU *href)
2124
0
{
2125
0
  const struct Curl_scheme *s = NULL;
2126
2127
  /* base must be an absolute URL */
2128
0
  if(!base->scheme || !base->host)
2129
0
    return FALSE;
2130
0
  if(href->scheme && !curl_strequal(base->scheme, href->scheme))
2131
0
    return FALSE;
2132
0
  if(href->host) {
2133
0
    if(!curl_strequal(base->host, href->host))
2134
0
      return FALSE;
2135
2136
0
    if(base->port_present != href->port_present) {
2137
      /* one is present, one is not */
2138
0
      s = Curl_get_scheme(base->scheme);
2139
0
      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
0
      if((base->port_present && (base->portnum != s->defport)) ||
2143
0
         (href->port_present && (href->portnum != s->defport)))
2144
0
        return FALSE;
2145
0
    }
2146
0
    else if(base->portnum != href->portnum) /* both present or missing */
2147
0
      return FALSE;
2148
2149
0
    if(!curl_strequal(base->zoneid ? base->zoneid : "",
2150
0
                      href->zoneid ? href->zoneid : ""))
2151
0
      return FALSE;
2152
0
  }
2153
0
  else if(href->port_present) /* no host in href, then there must be no port */
2154
0
    return FALSE;
2155
0
  return TRUE;
2156
0
}
2157
2158
CURLUcode Curl_url_get_port(CURLU *u, uint16_t *pport)
2159
3.95k
{
2160
3.95k
  if(u->port_present) {
2161
63
    *pport = u->portnum;
2162
63
    return CURLUE_OK;
2163
63
  }
2164
3.89k
  else if(u->scheme) {
2165
3.89k
    const struct Curl_scheme *s = Curl_get_scheme(u->scheme);
2166
3.89k
    if(s && s->defport) {
2167
3.85k
      *pport = s->defport;
2168
3.85k
      return CURLUE_OK;
2169
3.85k
    }
2170
3.89k
  }
2171
45
  *pport = 0;
2172
45
  return CURLUE_NO_PORT;
2173
3.95k
}