Coverage Report

Created: 2026-10-15 06:24

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