Coverage Report

Created: 2026-10-22 06:48

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
121
  ((('a' <= (str)[0] && (str)[0] <= 'z') ||                \
50
121
    ('A' <= (str)[0] && (str)[0] <= 'Z')) &&               \
51
121
   ((str)[1] == ':' || (str)[1] == '|') &&                 \
52
121
   ((str)[2] == '/' || (str)[2] == '\\' || (str)[2] == 0))
53
54
/* scheme is not URL encoded, the longest libcurl supported ones are... */
55
118k
#define MAX_SCHEME_LEN 40
56
83
#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
27.6k
{
71
27.6k
  curlx_free(u->scheme);
72
27.6k
  curlx_free(u->user);
73
27.6k
  curlx_strzero(u->password);
74
27.6k
  curlx_free(u->password);
75
27.6k
  curlx_free(u->options);
76
27.6k
  curlx_free(u->host);
77
27.6k
  curlx_free(u->zoneid);
78
27.6k
  curlx_free(u->path);
79
27.6k
  curlx_free(u->query);
80
27.6k
  curlx_free(u->fragment);
81
27.6k
}
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
10
{
89
  /* Find the start of the hostname */
90
10
  const char *sep = strstr(url, "//");
91
10
  if(!sep)
92
0
    sep = url;
93
10
  else
94
10
    sep += 2;
95
96
  /* Find first / or ? */
97
208
  while(*sep && *sep != '/' && *sep != '?')
98
198
    sep++;
99
100
10
  return sep;
101
10
}
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
10.8k
{
131
  /* we must add this with whitespace-replacing */
132
10.8k
  const unsigned char *iptr;
133
10.8k
  const unsigned char *host_sep = (const unsigned char *)url;
134
10.8k
  CURLcode result = CURLE_OK;
135
136
10.8k
  DEBUGASSERT((query >= QUERY_NO) && (query <= QUERY_YES));
137
138
10.8k
  if(!relative) {
139
10
    size_t n;
140
10
    host_sep = (const unsigned char *)find_host_sep(url);
141
142
    /* output the first piece as-is */
143
10
    n = (const char *)host_sep - url;
144
10
    result = curlx_dyn_addn(o, url, n);
145
10
    len -= n;
146
10
  }
147
148
11.9M
  for(iptr = host_sep; len && !result;) {
149
11.9M
    if(*iptr == ' ') {
150
1.53k
      if(query != QUERY_YES)
151
1.00k
        result = curlx_dyn_addn(o, "%20", 3);
152
525
      else
153
525
        result = curlx_dyn_addn(o, "+", 1);
154
1.53k
      iptr++;
155
1.53k
      len--;
156
1.53k
    }
157
11.9M
    else if((*iptr < ' ') || (*iptr >= 0x7f)) {
158
11.8M
      unsigned char out[3] = { '%' };
159
11.8M
      Curl_hexbyte(&out[1], *iptr);
160
11.8M
      result = curlx_dyn_addn(o, out, 3);
161
11.8M
      iptr++;
162
11.8M
      len--;
163
11.8M
    }
164
112k
    else if(*iptr == '%' && (len >= 3) &&
165
31.4k
            ISXDIGIT(iptr[1]) && ISXDIGIT(iptr[2]) &&
166
14.8k
            (ISLOWER(iptr[1]) || ISLOWER(iptr[2]))) {
167
      /* uppercase it */
168
13.2k
      unsigned char hex = (unsigned char)((curlx_hexval(iptr[1]) << 4) |
169
13.2k
                                          curlx_hexval(iptr[2]));
170
13.2k
      unsigned char out[3] = { '%' };
171
13.2k
      Curl_hexbyte(&out[1], hex);
172
13.2k
      result = curlx_dyn_addn(o, out, 3);
173
13.2k
      iptr += 3;
174
13.2k
      len -= 3;
175
13.2k
    }
176
98.9k
    else {
177
98.9k
      const unsigned char *start = iptr;
178
32.5M
      while(len) {
179
32.5M
        if(*iptr == ' ' || *iptr < ' ' || *iptr >= 0x7f)
180
79.9k
          break;
181
32.4M
        if(*iptr == '%' && (len >= 3) &&
182
11.3M
           ISXDIGIT(iptr[1]) && ISXDIGIT(iptr[2]) &&
183
9.46M
           (ISLOWER(iptr[1]) || ISLOWER(iptr[2])))
184
8.25k
          break;
185
32.4M
        if(*iptr == '?') {
186
427
          if(query == QUERY_NOT_YET) {
187
86
            iptr++;
188
86
            len--;
189
86
            query = QUERY_YES;
190
86
            break;
191
86
          }
192
427
        }
193
32.4M
        iptr++;
194
32.4M
        len--;
195
32.4M
      }
196
98.9k
      result = curlx_dyn_addn(o, (const char *)start, (size_t)(iptr - start));
197
98.9k
    }
198
11.9M
  }
199
200
10.8k
  if(result)
201
0
    return cc2cu(result);
202
10.8k
  return CURLUE_OK;
203
10.8k
}
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
27.0k
{
216
27.0k
  size_t i = 0;
217
27.0k
  DEBUGASSERT(!buf || (buflen > MAX_SCHEME_LEN));
218
27.0k
  (void)buflen; /* only used in debug-builds */
219
27.0k
  if(buf)
220
13.5k
    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
27.0k
  if(ISALPHA(url[0])) {
226
25.0k
    if(buf)
227
12.6k
      buf[0] = Curl_raw_tolower(url[0]);
228
118k
    for(i = 1; i < MAX_SCHEME_LEN; ++i) {
229
118k
      char s = url[i];
230
118k
      if(s && (ISALNUM(s) || (s == '+') || (s == '-') || (s == '.'))) {
231
93.1k
        if(buf)
232
46.9k
          buf[i] = Curl_raw_tolower(s);
233
93.1k
      }
234
24.9k
      else {
235
24.9k
        break;
236
24.9k
      }
237
118k
    }
238
25.0k
  }
239
27.0k
  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
15.9k
    size_t len = i;
246
15.9k
    if(buf)
247
8.19k
      buf[i] = 0;
248
15.9k
    return len;
249
15.9k
  }
250
11.1k
  if(buf)
251
5.30k
    buf[0] = 0;
252
11.1k
  return 0;
253
27.0k
}
254
255
/* scan for byte values <= 31, 127 and maybe space */
256
static bool badoctets(const char *input, size_t n, int flags)
257
3.33k
{
258
3.33k
  const uint8_t *p = (const unsigned char *)input;
259
3.33k
  const uint8_t control = flags & CURLU_ALLOW_SPACE ? 0x1f : 0x20;
260
43.8M
  while(n--) {
261
43.8M
    if(*p <= control || *p == 127)
262
21
      return TRUE;
263
43.8M
    p++;
264
43.8M
  }
265
3.31k
  return FALSE;
266
3.33k
}
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
13.3k
{
287
13.3k
  CURLUcode ures = CURLUE_OK;
288
13.3k
  CURLcode result;
289
13.3k
  char *userp = NULL;
290
13.3k
  char *passwdp = NULL;
291
13.3k
  char *optionsp = NULL;
292
13.3k
  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
13.3k
  const char *ptr;
302
303
13.3k
  DEBUGASSERT(login);
304
305
13.3k
  *hostname_offset = 0;
306
13.3k
  ptr = memchr(login, '@', len);
307
13.3k
  if(!ptr)
308
13.1k
    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
276
  ptr++;
314
315
  /* if this is a known scheme, get some details */
316
276
  if(u->scheme)
317
198
    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
276
  result = Curl_parse_login_details(login, ptr - login - 1,
322
276
                                    &userp, &passwdp,
323
276
                                    (h && (h->flags & PROTOPT_URLOPTIONS)) ?
324
276
                                    &optionsp : NULL);
325
276
  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
276
  if(userp) {
333
276
    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
274
    curlx_free(u->user);
339
274
    u->user = userp;
340
274
  }
341
342
274
  if(passwdp) {
343
67
    curlx_strzero(u->password);
344
67
    curlx_free(u->password);
345
67
    u->password = passwdp;
346
67
  }
347
348
274
  if(optionsp) {
349
10
    curlx_free(u->options);
350
10
    u->options = optionsp;
351
10
  }
352
353
274
  if(userp && badoctets(userp, strlen(userp), flags))
354
3
    ures = CURLUE_BAD_USER;
355
271
  else if(passwdp && badoctets(passwdp, strlen(passwdp), flags))
356
4
    ures = CURLUE_BAD_PASSWORD;
357
267
  else if(optionsp && badoctets(optionsp, strlen(optionsp), flags))
358
4
    ures = CURLUE_MALFORMED_INPUT;
359
360
274
  userp = passwdp = optionsp = NULL;
361
362
274
  if(!ures) {
363
    /* the hostname starts at this offset */
364
263
    *hostname_offset = ptr - login;
365
263
    return CURLUE_OK;
366
263
  }
367
368
13.1k
out:
369
370
13.1k
  curlx_free(userp);
371
13.1k
  curlx_strzero(passwdp);
372
13.1k
  curlx_free(passwdp);
373
13.1k
  curlx_free(optionsp);
374
13.1k
  curlx_safefree(u->user);
375
13.1k
  curlx_strzero(u->password);
376
13.1k
  curlx_safefree(u->password);
377
13.1k
  curlx_safefree(u->options);
378
379
13.1k
  return ures;
380
274
}
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
13.3k
{
388
13.3k
  const char *portptr;
389
13.3k
  const char *hostname = curlx_dyn_ptr(host);
390
  /*
391
   * Find the end of an IPv6 address on the ']' ending bracket.
392
   */
393
13.3k
  u->portnum = 0;
394
13.3k
  u->port_present = FALSE;
395
13.3k
  if(hostname[0] == '[') {
396
340
    portptr = memchr(hostname + 1, ']', curlx_dyn_len(host) - 1);
397
340
    if(!portptr)
398
4
      return CURLUE_BAD_IPV6;
399
336
    portptr++;
400
    /* this is a RFC2732-style specified IP-address */
401
336
    if(*portptr) {
402
9
      if(*portptr != ':')
403
3
        return CURLUE_BAD_PORT_NUMBER;
404
9
    }
405
327
    else
406
327
      portptr = NULL;
407
336
  }
408
13.0k
  else
409
13.0k
    portptr = memchr(hostname, ':', curlx_dyn_len(host));
410
411
13.3k
  if(portptr) {
412
172
    curl_off_t port;
413
172
    size_t keep = portptr - hostname;
414
172
    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
172
    curlx_dyn_setlen(host, keep);
423
172
    portptr++;
424
172
    if(!*portptr)
425
70
      return has_scheme ? CURLUE_OK : CURLUE_BAD_PORT_NUMBER;
426
102
    if(*portptr == '\\')
427
2
      return CURLUE_BACKSLASH;
428
100
    rc = curlx_str_number(&portptr, &port, 0xffff);
429
100
    if(rc)
430
24
      return CURLUE_BAD_PORT_NUMBER;
431
76
    else if(*portptr == '\\')
432
1
      return CURLUE_BACKSLASH;
433
75
    else if(*portptr)
434
3
      return CURLUE_BAD_PORT_NUMBER;
435
436
72
    u->portnum = (uint16_t)port;
437
72
    u->port_present = TRUE;
438
72
  }
439
440
13.2k
  return CURLUE_OK;
441
13.3k
}
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
331
{
467
331
  size_t len;
468
331
  DEBUGASSERT(*hostname == '[');
469
331
  if(hlen < 4) /* '[::]' is the shortest possible valid string */
470
1
    return CURLUE_BAD_IPV6;
471
330
  hostname++;
472
330
  hlen -= 2;
473
474
  /* only valid IPv6 letters are ok */
475
330
  len = strspn(hostname, "0123456789abcdefABCDEF:.");
476
477
330
  if(hlen != len) {
478
39
    hlen = len;
479
39
    if(hostname[len] == '%') {
480
      /* this could now be '%[zone id]' */
481
34
      char zoneid[MAX_ZONEID_LEN];
482
34
      int i = 0;
483
34
      char *h = &hostname[len + 1];
484
      /* pass '25' if present and is a URL encoded percent sign */
485
34
      if(!strncmp(h, "25", 2) && h[2] && (h[2] != ']'))
486
0
        h += 2;
487
116
      while(!invalid_host_char[(unsigned char)*h] && (*h != ']') &&
488
83
            (i < (MAX_ZONEID_LEN - 1)))
489
82
        zoneid[i++] = *h++;
490
34
      if(!i || (']' != *h))
491
11
        return CURLUE_BAD_IPV6;
492
23
      zoneid[i] = 0;
493
23
      u->zoneid = curlx_strdup(zoneid);
494
23
      if(!u->zoneid)
495
0
        return CURLUE_OUT_OF_MEMORY;
496
23
      hostname[len] = ']'; /* insert end bracket */
497
23
      hostname[len + 1] = 0; /* terminate the hostname */
498
23
    }
499
5
    else
500
5
      return CURLUE_BAD_IPV6;
501
    /* hostname is fine */
502
39
  }
503
504
  /* Normalize the IPv6 address */
505
314
  {
506
314
    char dest[16]; /* fits a binary IPv6 address */
507
314
    hostname[hlen] = 0; /* end the address there */
508
314
    if(curlx_inet_pton(AF_INET6, hostname, dest) != 1)
509
38
      return CURLUE_BAD_IPV6;
510
276
    if(!curlx_inet_ntop(AF_INET6, dest, hostname, hlen + 1)) {
511
238
      hlen = strlen(hostname); /* might be shorter now */
512
238
      hostname[hlen + 1] = 0;
513
238
    }
514
276
    hostname[hlen] = ']'; /* restore ending bracket */
515
276
  }
516
0
  return CURLUE_OK;
517
314
}
518
519
/* the input is a confirmed hostname, never an IPv6 address */
520
static CURLUcode hostname_check(char *hostname, size_t hlen)
521
8.37k
{
522
8.37k
  size_t i;
523
1.09M
  for(i = 0; i < hlen; i++) {
524
1.09M
    if(invalid_host_char[(unsigned char)hostname[i]])
525
102
      return CURLUE_BAD_HOSTNAME;
526
1.09M
  }
527
8.27k
  if((hlen >= 2) &&
528
5.68k
     (hostname[hlen - 1] == '.') && (hostname[hlen - 2] == '.'))
529
    /* more than one trailing dot is not allowed */
530
5
    return CURLUE_BAD_HOSTNAME;
531
8.27k
  else if((hlen == 1) && (hostname[0] == '.'))
532
    /* a single dot alone is not allowed */
533
9
    return CURLUE_BAD_HOSTNAME;
534
8.26k
  return CURLUE_OK;
535
8.27k
}
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
12.8k
{
571
12.8k
  bool done = FALSE;
572
12.8k
  int n = 0;
573
12.8k
  const char *c = curlx_dyn_ptr(host);
574
12.8k
  unsigned int parts[4] = { 0, 0, 0, 0 };
575
12.8k
  CURLcode result = CURLE_OK;
576
577
12.8k
  if(!ISDIGIT(*c))
578
7.96k
    return HOST_NAME;
579
580
16.3k
  while(!done) {
581
11.6k
    int rc;
582
11.6k
    curl_off_t l;
583
11.6k
    if(*c == '0') {
584
5.02k
      if((c[1] | 0x20) == 'x') {
585
52
        c += 2; /* skip the prefix */
586
52
        rc = curlx_str_hex(&c, &l, UINT_MAX);
587
52
        if(rc)
588
6
          return HOST_NAME;
589
52
      }
590
4.97k
      else
591
4.97k
        rc = curlx_str_octal(&c, &l, UINT_MAX);
592
5.02k
    }
593
6.59k
    else
594
6.59k
      rc = curlx_str_number(&c, &l, UINT_MAX);
595
596
11.6k
    if(rc) {
597
102
      if(!n || (rc != STRE_NO_NUM) || *c)
598
60
        return HOST_NAME;
599
42
      n--;
600
42
    }
601
11.5k
    else
602
11.5k
      parts[n] = (unsigned int)l;
603
604
11.5k
    switch(*c) {
605
6.70k
    case '.':
606
6.70k
      if(n == 3) {
607
9
        if(c[1])
608
          /* something follows this dot */
609
6
          return HOST_NAME;
610
3
        done = TRUE;
611
3
      }
612
6.70k
      else {
613
6.70k
        n++;
614
6.70k
        c++;
615
6.70k
      }
616
6.70k
      break;
617
618
6.70k
    case '\0':
619
4.73k
      done = TRUE;
620
4.73k
      break;
621
622
108
    default:
623
108
      return HOST_NAME;
624
11.5k
    }
625
11.5k
  }
626
627
4.74k
  switch(n) {
628
2.46k
  case 0: /* a -- 32 bits */
629
2.46k
    curlx_dyn_reset(host);
630
631
2.46k
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
632
2.46k
                            (parts[0] >> 24),
633
2.46k
                            ((parts[0] >> 16) & 0xff),
634
2.46k
                            ((parts[0] >> 8) & 0xff),
635
2.46k
                            (parts[0] & 0xff));
636
2.46k
    break;
637
95
  case 1: /* a.b -- 8.24 bits */
638
95
    if((parts[0] > 0xff) || (parts[1] > 0xffffff))
639
43
      return HOST_NAME;
640
52
    curlx_dyn_reset(host);
641
52
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
642
52
                            parts[0],
643
52
                            ((parts[1] >> 16) & 0xff),
644
52
                            ((parts[1] >> 8) & 0xff),
645
52
                            (parts[1] & 0xff));
646
52
    break;
647
106
  case 2: /* a.b.c -- 8.8.16 bits */
648
106
    if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xffff))
649
80
      return HOST_NAME;
650
26
    curlx_dyn_reset(host);
651
26
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
652
26
                            parts[0],
653
26
                            parts[1],
654
26
                            ((parts[2] >> 8) & 0xff),
655
26
                            (parts[2] & 0xff));
656
26
    break;
657
2.07k
  case 3: /* a.b.c.d -- 8.8.8.8 bits */
658
2.07k
    if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xff) ||
659
1.99k
       (parts[3] > 0xff))
660
113
      return HOST_NAME;
661
1.96k
    curlx_dyn_reset(host);
662
1.96k
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
663
1.96k
                            parts[0],
664
1.96k
                            parts[1],
665
1.96k
                            parts[2],
666
1.96k
                            parts[3]);
667
1.96k
    break;
668
4.74k
  }
669
4.50k
  if(result)
670
0
    return HOST_ERROR;
671
4.50k
  return HOST_IPV4;
672
4.50k
}
673
674
/* if necessary, replace the host content with a URL decoded version */
675
static CURLUcode urldecode_host(struct dynbuf *host)
676
13.2k
{
677
13.2k
  const char *per;
678
13.2k
  const char *hostname = curlx_dyn_ptr(host);
679
13.2k
  per = memchr(hostname, '%', curlx_dyn_len(host));
680
13.2k
  if(!per)
681
    /* nothing to decode */
682
13.1k
    return CURLUE_OK;
683
88
  else {
684
    /* encoded */
685
88
    size_t dlen;
686
88
    char *decoded;
687
88
    CURLcode result = Curl_urldecode(hostname, 0, &decoded, &dlen,
688
88
                                     REJECT_CTRL);
689
88
    if(result)
690
7
      return CURLUE_BAD_HOSTNAME;
691
81
    curlx_dyn_reset(host);
692
81
    result = curlx_dyn_addn(host, decoded, dlen);
693
81
    curlx_free(decoded);
694
81
    if(result)
695
0
      return cc2cu(result);
696
81
  }
697
698
81
  return CURLUE_OK;
699
13.2k
}
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
13.3k
{
707
13.3k
  size_t offset;
708
13.3k
  CURLUcode uc;
709
13.3k
  CURLcode result;
710
711
  /*
712
   * Parse the login details and strip them out of the hostname.
713
   */
714
13.3k
  uc = parse_hostname_login(u, auth, authlen, flags, &offset);
715
13.3k
  if(uc)
716
13
    return uc;
717
718
13.3k
  result = curlx_dyn_addn(host, auth + offset, authlen - offset);
719
13.3k
  if(result) {
720
0
    uc = cc2cu(result);
721
0
    return uc;
722
0
  }
723
724
  /* parse_port() also sets the hostname length correctly */
725
13.3k
  uc = parse_port(u, host, has_scheme);
726
727
13.3k
  if(!curlx_dyn_len(host))
728
    /* this makes no-host errors override port number problems */
729
124
    uc = CURLUE_NO_HOST;
730
13.3k
  if(!uc)
731
13.2k
    uc = urldecode_host(host);
732
13.3k
  if(uc)
733
165
    ;
734
13.2k
  else if(auth[offset] == '[')
735
331
    uc = ipv6_parse(u, curlx_dyn_ptr(host), curlx_dyn_len(host));
736
12.8k
  else {
737
    /* ipv4_normalize() returns *NAME, *IPV4 or *ERROR */
738
12.8k
    int type = ipv4_normalize(host);
739
740
12.8k
    if(type == HOST_NAME)
741
8.37k
      uc = hostname_check(curlx_dyn_ptr(host), curlx_dyn_len(host));
742
4.50k
    else if(type == HOST_ERROR)
743
0
      uc = CURLUE_OUT_OF_MEMORY;
744
12.8k
  }
745
746
13.3k
  return uc;
747
13.3k
}
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
38.7M
{
776
38.7M
  const char *p = *str;
777
38.7M
  if(*p == '.') {
778
158k
    (*str)++;
779
158k
    (*clen)--;
780
158k
    return TRUE;
781
158k
  }
782
38.6M
  else if((*clen >= 3) &&
783
38.6M
          (p[0] == '%') && (p[1] == '2') && ((p[2] | 0x20) == 'e')) {
784
7.11k
    *str += 3;
785
7.11k
    *clen -= 3;
786
7.11k
    return TRUE;
787
7.11k
  }
788
38.6M
  return FALSE;
789
38.7M
}
790
791
37.5M
#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.29k
{
796
  /* a single byte path cannot be cleaned up */
797
1.29k
  if(pn < 2)
798
6
    return FALSE;
799
1.29k
  if(!memchr(p, '.', pn) && !memchr(p, '%', pn))
800
323
    return FALSE;
801
36.7M
  while(pn) {
802
36.7M
    if(is_dot(&p, &pn)) {
803
      /* "./" or dot before end of string */
804
28.6k
      if(!pn || ISSLASH(*p))
805
456
        return TRUE;
806
      /* "../" or ".." before end of string */
807
28.2k
      else if(is_dot(&p, &pn) && (!pn || ISSLASH(*p)))
808
66
        return TRUE;
809
28.6k
    }
810
36.6M
    else {
811
36.6M
      p++;
812
36.6M
      pn--;
813
36.6M
    }
814
36.7M
  }
815
447
  return FALSE;
816
969
}
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.29k
{
837
1.29k
  struct dynbuf out;
838
1.29k
  CURLcode result = CURLE_OK;
839
840
  /* variables for leading dot checks */
841
1.29k
  const char *dinput = input;
842
1.29k
  size_t dlen = clen;
843
844
1.29k
  *outp = NULL;
845
1.29k
  if(!needs_dedotdot(input, clen))
846
776
    return 0;
847
848
522
  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
522
  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
37.2M
  while(clen && !result) { /* until end of path content */
874
37.2M
    if(ISSLASH(*input)) {
875
1.95M
      const char *p = &input[1];
876
1.95M
      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
1.95M
      if(is_dot(&p, &blen)) {
881
85.5k
        if(!blen) { /* /. */
882
41
          result = curlx_dyn_addn(&out, "/", 1);
883
41
          break;
884
41
        }
885
85.5k
        else if(ISSLASH(*p)) { /* /./ */
886
4.76k
          input = p;
887
4.76k
          clen = blen;
888
4.76k
          continue;
889
4.76k
        }
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
80.7k
        else if(is_dot(&p, &blen) && (ISSLASH(*p) || !blen)) {
896
          /* remove the last segment from the output buffer */
897
32.2k
          size_t len = curlx_dyn_len(&out);
898
32.2k
          if(len) {
899
31.8k
            const char *ptr = curlx_dyn_ptr(&out);
900
31.8k
            const char *last = memrchr(ptr, '/', len);
901
31.8k
            if(last)
902
              /* trim the output at the slash */
903
31.8k
              curlx_dyn_setlen(&out, last - ptr);
904
31.8k
          }
905
906
32.2k
          if(blen) { /* /../ */
907
32.2k
            input = p;
908
32.2k
            clen = blen;
909
32.2k
            continue;
910
32.2k
          }
911
23
          result = curlx_dyn_addn(&out, "/", 1);
912
23
          break;
913
32.2k
        }
914
85.5k
      }
915
1.95M
    }
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
37.2M
    result = curlx_dyn_addn(&out, input, 1);
923
37.2M
    input++;
924
37.2M
    clen--;
925
37.2M
  }
926
522
end:
927
522
  if(!result) {
928
522
    if(curlx_dyn_len(&out))
929
522
      *outp = curlx_dyn_ptr(&out);
930
0
    else {
931
0
      *outp = curlx_strdup("");
932
0
      if(!*outp)
933
0
        return 1;
934
0
    }
935
522
  }
936
522
  return result ? 1 : 0; /* success */
937
522
}
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
93
{
947
93
  const char *path;
948
93
  size_t pathlen;
949
950
93
  *pathp = NULL;
951
93
  *pathlenp = 0;
952
93
  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
92
  path = &url[5];
958
92
  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
92
  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
92
  if(path[1] == '/') {
973
    /* swallow the two slashes */
974
72
    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
72
    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
63
      if(checkprefix("localhost/", ptr) ||
996
62
         checkprefix("127.0.0.1/", ptr)) {
997
2
        ptr += 9; /* now points to the slash after the host */
998
2
      }
999
61
      else
1000
        /* Invalid file://hostname/, expected localhost or 127.0.0.1 or
1001
           none */
1002
61
        return CURLUE_BAD_FILE_URL;
1003
63
    }
1004
1005
11
    path = ptr;
1006
11
    pathlen = urllen - (ptr - url);
1007
11
  }
1008
1009
31
#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
31
  if(('/' == path[0] && STARTS_WITH_URL_DRIVE_PREFIX(&path[1])) ||
1013
27
     STARTS_WITH_URL_DRIVE_PREFIX(path)) {
1014
    /* File drive letters are only accepted in MS-DOS/Windows */
1015
11
    return CURLUE_BAD_FILE_URL;
1016
11
  }
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
20
  u->scheme = curlx_strdup("file");
1026
20
  if(!u->scheme)
1027
0
    return CURLUE_OUT_OF_MEMORY;
1028
1029
20
  *pathp = path;
1030
20
  *pathlenp = pathlen;
1031
20
  return CURLUE_OK;
1032
20
}
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
13.4k
{
1038
  /* clear path */
1039
13.4k
  const char *schemep = NULL;
1040
1041
13.4k
  if(schemelen) {
1042
8.10k
    int num_slashes = 0;
1043
8.10k
    const char *p = &url[schemelen + 1];
1044
8.10k
    if(!Curl_getn_scheme(schemebuf, schemelen) &&
1045
271
       !(flags & CURLU_NON_SUPPORT_SCHEME))
1046
0
      return CURLUE_UNSUPPORTED_SCHEME;
1047
1048
8.10k
    if(!ISSLASH(*p))
1049
      /* less than one */
1050
0
      return CURLUE_BAD_SLASHES;
1051
8.10k
    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
8.10k
    else {
1058
19.1k
      while(ISSLASH(*p) && (num_slashes < 4)) {
1059
11.0k
        p++;
1060
11.0k
        num_slashes++;
1061
11.0k
      }
1062
8.10k
      if(num_slashes > 3)
1063
1
        return CURLUE_BAD_SLASHES;
1064
8.10k
    }
1065
1066
8.10k
    schemep = schemebuf;
1067
8.10k
    *hostpp = p; /* hostname starts here */
1068
8.10k
  }
1069
5.30k
  else {
1070
    /* no scheme! */
1071
1072
5.30k
    if(!(flags & (CURLU_DEFAULT_SCHEME | CURLU_GUESS_SCHEME)))
1073
0
      return CURLUE_BAD_SCHEME;
1074
1075
5.30k
    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
5.30k
    *hostpp = url;
1082
5.30k
  }
1083
1084
13.4k
  if(schemep) {
1085
8.10k
    u->scheme = curlx_strdup(schemep);
1086
8.10k
    if(!u->scheme)
1087
0
      return CURLUE_OUT_OF_MEMORY;
1088
8.10k
  }
1089
13.4k
  return CURLUE_OK;
1090
13.4k
}
1091
1092
static CURLUcode guess_scheme(CURLU *u, struct dynbuf *host)
1093
5.07k
{
1094
5.07k
  const char *hostname = curlx_dyn_ptr(host);
1095
5.07k
  const char *schemep = NULL;
1096
  /* legacy curl-style guess based on hostname */
1097
5.07k
  if(checkprefix("ftp.", hostname))
1098
1
    schemep = "ftp";
1099
5.07k
  else if(checkprefix("dict.", hostname))
1100
1
    schemep = "dict";
1101
5.07k
  else if(checkprefix("ldap.", hostname))
1102
3.88k
    schemep = "ldap";
1103
1.19k
  else if(checkprefix("imap.", hostname))
1104
1
    schemep = "imap";
1105
1.19k
  else if(checkprefix("smtp.", hostname))
1106
2
    schemep = "smtp";
1107
1.18k
  else if(checkprefix("pop3.", hostname))
1108
1
    schemep = "pop3";
1109
1.18k
  else
1110
1.18k
    schemep = "http";
1111
1112
5.07k
  u->scheme = curlx_strdup(schemep);
1113
5.07k
  if(!u->scheme)
1114
0
    return CURLUE_OUT_OF_MEMORY;
1115
1116
5.07k
  u->guessed_scheme = TRUE;
1117
5.07k
  return CURLUE_OK;
1118
5.07k
}
1119
1120
static CURLUcode handle_fragment(CURLU *u, const char *fragment,
1121
                                 size_t fraglen, unsigned int flags)
1122
86
{
1123
86
  CURLUcode ures;
1124
86
  u->fragment_present = TRUE;
1125
86
  if(fraglen > 1) {
1126
    /* skip the leading '#' in the copy but include the null-terminator */
1127
79
    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
79
    else {
1136
79
      if(badoctets(fragment, fraglen, flags))
1137
4
        return CURLUE_BAD_FRAGMENT;
1138
75
      u->fragment = curlx_memdup0(fragment + 1, fraglen - 1);
1139
75
      if(!u->fragment)
1140
0
        return CURLUE_OUT_OF_MEMORY;
1141
75
    }
1142
79
  }
1143
82
  return CURLUE_OK;
1144
86
}
1145
1146
static CURLUcode handle_query(CURLU *u, const char *query,
1147
                              size_t qlen, unsigned int flags)
1148
1.64k
{
1149
1.64k
  u->query_present = TRUE;
1150
1.64k
  if(qlen > 1) {
1151
1.60k
    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
1.60k
    else {
1162
1.60k
      if(badoctets(query, qlen, flags))
1163
2
        return CURLUE_BAD_QUERY;
1164
1165
1.59k
      u->query = curlx_memdup0(query + 1, qlen - 1);
1166
1.59k
      if(!u->query)
1167
0
        return CURLUE_OUT_OF_MEMORY;
1168
1.59k
    }
1169
1.60k
  }
1170
42
  else {
1171
    /* single byte query */
1172
42
    u->query = curlx_strdup("");
1173
42
    if(!u->query)
1174
0
      return CURLUE_OUT_OF_MEMORY;
1175
42
  }
1176
1.64k
  return CURLUE_OK;
1177
1.64k
}
1178
1179
static CURLUcode handle_path(CURLU *u, const char *path,
1180
                             size_t pathlen, unsigned int flags,
1181
                             bool is_file)
1182
13.0k
{
1183
13.0k
  CURLUcode ures;
1184
13.0k
  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
13.0k
  if(pathlen >= (size_t)(1 + !is_file)) {
1195
1.30k
    if(badoctets(path, pathlen, flags))
1196
4
      return CURLUE_BAD_PATH;
1197
1198
    /* paths for file:// scheme can be one byte, others need to be two */
1199
1.30k
    if(!u->path) {
1200
1.30k
      u->path = curlx_memdup0(path, pathlen);
1201
1.30k
      if(!u->path)
1202
0
        return CURLUE_OUT_OF_MEMORY;
1203
1.30k
      path = u->path;
1204
1.30k
    }
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.30k
    if(!(flags & CURLU_PATH_AS_IS)) {
1210
      /* remove ../ and ./ sequences according to RFC3986 */
1211
1.29k
      char *dedot;
1212
1.29k
      int err = dedotdotify(path, pathlen, &dedot);
1213
1.29k
      if(err)
1214
0
        return CURLUE_OUT_OF_MEMORY;
1215
1.29k
      if(dedot) {
1216
522
        curlx_free(u->path);
1217
522
        u->path = dedot;
1218
522
      }
1219
1.29k
    }
1220
1.30k
  }
1221
13.0k
  return CURLUE_OK;
1222
13.0k
}
1223
1224
static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
1225
13.5k
{
1226
13.5k
  const char *path;
1227
13.5k
  size_t pathlen;
1228
13.5k
  char schemebuf[MAX_SCHEME_LEN + 1];
1229
13.5k
  size_t schemelen = 0;
1230
13.5k
  size_t urllen;
1231
13.5k
  CURLUcode ures = CURLUE_OK;
1232
13.5k
  struct dynbuf host;
1233
13.5k
  bool is_file = FALSE;
1234
1235
13.5k
  DEBUGASSERT(url);
1236
1237
13.5k
  urllen = strlen(url);
1238
13.5k
  if(urllen > CURL_MAX_INPUT_LENGTH)
1239
0
    return CURLUE_MALFORMED_INPUT;
1240
1241
13.5k
  curlx_dyn_init(&host, CURL_MAX_INPUT_LENGTH);
1242
1243
13.5k
  schemelen = Curl_is_absolute_url(url, schemebuf, sizeof(schemebuf),
1244
13.5k
                                   flags & (CURLU_GUESS_SCHEME |
1245
13.5k
                                            CURLU_DEFAULT_SCHEME));
1246
1247
  /* handle the file: scheme */
1248
13.5k
  if(schemelen == 4 && !memcmp(schemebuf, "file", 4)) {
1249
93
    is_file = TRUE;
1250
93
    ures = parse_file(url, urllen, u, &path, &pathlen);
1251
93
  }
1252
13.4k
  else {
1253
13.4k
    const char *hostp = NULL;
1254
13.4k
    const char *p;
1255
13.4k
    size_t hostlen;
1256
13.4k
    ures = parse_scheme(url, u, schemebuf, schemelen, flags, &hostp);
1257
13.4k
    if(ures)
1258
1
      goto fail;
1259
1260
    /* find the end of the hostname + port number */
1261
13.4k
    p = hostp;
1262
3.14M
    while(*p && *p != '/' && *p != '?' && *p != '#')
1263
3.13M
      p++;
1264
13.4k
    hostlen = p - hostp;
1265
13.4k
    path = p;
1266
1267
    /* this pathlen also contains the query and the fragment */
1268
13.4k
    pathlen = urllen - (path - url);
1269
13.4k
    if(hostlen) {
1270
13.3k
      ures = parse_authority(u, hostp, hostlen, flags, &host, !!u->scheme);
1271
13.3k
      if(!ures && (flags & CURLU_GUESS_SCHEME) && !u->scheme)
1272
5.07k
        ures = guess_scheme(u, &host);
1273
13.3k
    }
1274
15
    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
15
    else
1280
15
      ures = CURLUE_NO_HOST;
1281
13.4k
  }
1282
13.5k
  if(!ures) {
1283
    /* The path might at this point contain a fragment and/or a query to
1284
       handle */
1285
13.0k
    const char *fragment = memchr(path, '#', pathlen);
1286
13.0k
    if(fragment) {
1287
86
      size_t fraglen = pathlen - (fragment - path);
1288
86
      ures = handle_fragment(u, fragment, fraglen, flags);
1289
      /* after this, pathlen still contains the query */
1290
86
      pathlen -= fraglen;
1291
86
    }
1292
13.0k
  }
1293
13.5k
  if(!ures) {
1294
13.0k
    const char *query = memchr(path, '?', pathlen);
1295
13.0k
    if(query) {
1296
1.64k
      size_t qlen = pathlen - (query - path);
1297
1.64k
      ures = handle_query(u, query, qlen, flags);
1298
1.64k
      pathlen -= qlen;
1299
1.64k
    }
1300
13.0k
  }
1301
13.5k
  if(!ures)
1302
    /* the fragment and query parts are trimmed off from the path */
1303
13.0k
    ures = handle_path(u, path, pathlen, flags, is_file);
1304
13.5k
  if(!ures) {
1305
13.0k
    u->host = curlx_dyn_ptr(&host);
1306
13.0k
    return CURLUE_OK;
1307
13.0k
  }
1308
448
fail:
1309
448
  curlx_dyn_free(&host);
1310
448
  free_urlhandle(u);
1311
448
  return ures;
1312
13.5k
}
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
13.5k
{
1320
13.5k
  CURLUcode ures;
1321
13.5k
  CURLU tmpurl;
1322
13.5k
  memset(&tmpurl, 0, sizeof(tmpurl));
1323
13.5k
  ures = parseurl(url, &tmpurl, flags);
1324
13.5k
  if(!ures) {
1325
13.0k
    free_urlhandle(u);
1326
13.0k
    *u = tmpurl;
1327
13.0k
  }
1328
13.5k
  return ures;
1329
13.5k
}
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
474
{
1337
474
  struct dynbuf urlbuf;
1338
474
  bool host_changed = FALSE;
1339
474
  const char *useurl = relurl;
1340
474
  const char *cutoff = NULL;
1341
474
  size_t prelen;
1342
474
  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
474
  const char *scheme = u->scheme ? u->scheme : DEFAULT_SCHEME;
1346
1347
  /* protsep points to the start of the hostname, after [scheme]:// */
1348
474
  const char *protsep = base + strlen(scheme) + 3;
1349
474
  DEBUGASSERT(base && relurl && u); /* all set here */
1350
474
  if(!base)
1351
0
    return CURLUE_MALFORMED_INPUT; /* should never happen */
1352
1353
  /* handle different relative URL types */
1354
474
  switch(relurl[0]) {
1355
12
  case '/':
1356
12
    if(relurl[1] == '/') {
1357
      /* protocol-relative URL: //example.com/path */
1358
10
      cutoff = protsep;
1359
10
      useurl = &relurl[2];
1360
10
      host_changed = TRUE;
1361
10
    }
1362
2
    else
1363
      /* absolute /path */
1364
2
      cutoff = strchr(protsep, '/');
1365
12
    break;
1366
1367
10
  case '#':
1368
    /* fragment-only change */
1369
10
    if(u->fragment_present)
1370
1
      cutoff = strchr(protsep, '#');
1371
10
    break;
1372
1373
452
  default:
1374
    /* path or query-only change */
1375
452
    if(u->query_present)
1376
      /* remove existing query */
1377
23
      cutoff = strchr(protsep, '?');
1378
429
    else if(u->fragment_present)
1379
      /* Remove existing fragment */
1380
5
      cutoff = strchr(protsep, '#');
1381
1382
452
    if(relurl[0] != '?') {
1383
      /* append a relative path after the last slash */
1384
407
      cutoff = memrchr(protsep, '/',
1385
407
                       cutoff ? (size_t)(cutoff - protsep) : strlen(protsep));
1386
407
      if(cutoff)
1387
407
        cutoff++; /* truncate after last slash */
1388
407
    }
1389
452
    break;
1390
474
  }
1391
1392
474
  prelen = cutoff ? (size_t)(cutoff - base) : strlen(base);
1393
1394
  /* build new URL */
1395
474
  curlx_dyn_init(&urlbuf, CURL_MAX_INPUT_LENGTH);
1396
1397
474
  if(!curlx_dyn_addn(&urlbuf, base, prelen) &&
1398
474
     !urlencode_str(&urlbuf, useurl, strlen(useurl), !host_changed,
1399
474
                    QUERY_NOT_YET)) {
1400
474
    uc = parseurl_and_replace(curlx_dyn_ptr(&urlbuf), u,
1401
474
                              flags & ~U_CURLU_PATH_AS_IS);
1402
474
  }
1403
0
  else
1404
0
    uc = CURLUE_OUT_OF_MEMORY;
1405
1406
474
  curlx_dyn_free(&urlbuf);
1407
474
  return uc;
1408
474
}
1409
1410
/*
1411
 */
1412
CURLU *curl_url(void)
1413
14.1k
{
1414
14.1k
  return curlx_calloc(1, sizeof(struct Curl_URL));
1415
14.1k
}
1416
1417
void curl_url_cleanup(CURLU *u)
1418
46.9k
{
1419
46.9k
  if(u) {
1420
14.1k
    free_urlhandle(u);
1421
14.1k
    curlx_free(u);
1422
14.1k
  }
1423
46.9k
}
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
38.7k
{
1491
38.7k
  CURLUcode uc = CURLUE_OK;
1492
38.7k
  size_t partlen = strlen(ptr);
1493
38.7k
  bool urldecode = (flags & CURLU_URLDECODE) ? 1 : 0;
1494
38.7k
  bool urlencode = (flags & CURLU_URLENCODE) ? 1 : 0;
1495
38.7k
  bool punycode = (flags & CURLU_PUNYCODE) && (what == CURLUPART_HOST);
1496
38.7k
  bool depunyfy = (flags & CURLU_PUNY2IDN) && (what == CURLUPART_HOST);
1497
38.7k
  char *part = curlx_memdup0(ptr, partlen);
1498
38.7k
  *partp = NULL;
1499
38.7k
  if(!part)
1500
0
    return CURLUE_OUT_OF_MEMORY;
1501
38.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
38.7k
  if(urldecode) {
1511
1.75k
    char *decoded;
1512
1.75k
    size_t dlen;
1513
    /* this unconditional rejection of control bytes is documented API
1514
       behavior */
1515
1.75k
    CURLcode result = Curl_urldecode(part, partlen, &decoded, &dlen,
1516
1.75k
                                     REJECT_CTRL);
1517
1.75k
    curlx_free(part);
1518
1.75k
    if(result)
1519
0
      return CURLUE_URLDECODE;
1520
1.75k
    part = decoded;
1521
1.75k
    partlen = dlen;
1522
1.75k
  }
1523
38.7k
  if(urlencode) {
1524
10.4k
    struct dynbuf enc;
1525
10.4k
    curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1526
10.4k
    uc = urlencode_str(&enc, part, partlen, TRUE, what == CURLUPART_QUERY ?
1527
10.4k
                       QUERY_YES : QUERY_NO);
1528
10.4k
    curlx_free(part);
1529
10.4k
    if(uc)
1530
0
      return uc;
1531
10.4k
    part = curlx_dyn_ptr(&enc);
1532
10.4k
  }
1533
28.3k
  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
      uc = hostname_check(punyversion, strlen(punyversion));
1541
0
      if(uc) {
1542
0
        curlx_free(punyversion);
1543
0
        return uc;
1544
0
      }
1545
0
      part = punyversion;
1546
0
    }
1547
0
  }
1548
28.3k
  else if(depunyfy && Curl_is_ASCII_name(u->host)) {
1549
0
    char *unpunified = NULL;
1550
0
    uc = host_encode(part, &unpunified);
1551
0
    curlx_free(part);
1552
0
    if(uc)
1553
0
      return uc;
1554
0
    part = unpunified;
1555
0
  }
1556
38.7k
  *partp = part;
1557
38.7k
  return CURLUE_OK;
1558
38.7k
}
1559
1560
static CURLUcode file_url(const CURLU *u, char **part,
1561
                          const char *fragmentsep,
1562
                          const char *querysep)
1563
18
{
1564
18
  char *url = curl_maprintf("file://%s%s%s%s%s",
1565
18
                            u->path, querysep, u->query ? u->query : "",
1566
18
                            fragmentsep, u->fragment ? u->fragment : "");
1567
18
  if(!url)
1568
0
    return CURLUE_OUT_OF_MEMORY;
1569
1570
18
  *part = url;
1571
18
  return CURLUE_OK;
1572
18
}
1573
1574
static CURLUcode urlget_url(const CURLU *u, char **part, unsigned int flags)
1575
17.7k
{
1576
17.7k
  char *url;
1577
17.7k
  char *allochost = NULL;
1578
17.7k
  const char *fragmentsep =
1579
17.7k
    (u->fragment || (u->fragment_present && flags & CURLU_GET_EMPTY)) ?
1580
17.6k
    "#" : "";
1581
17.7k
  const char *querysep = ((u->query && u->query[0]) ||
1582
16.1k
                          (u->query_present && flags & CURLU_GET_EMPTY)) ?
1583
16.1k
    "?" : "";
1584
17.7k
  char portbuf[7];
1585
17.7k
  if(curl_strequal("file", u->scheme))
1586
18
    return file_url(u, part, fragmentsep, querysep);
1587
17.7k
  else if(!u->host)
1588
6.48k
    return CURLUE_NO_HOST;
1589
11.2k
  else {
1590
11.2k
    const char *scheme;
1591
11.2k
    char *options = u->options;
1592
11.2k
    char *port = NULL;
1593
11.2k
    const struct Curl_scheme *h = NULL;
1594
11.2k
    char schemebuf[MAX_SCHEME_LEN + 5];
1595
11.2k
    if(u->scheme)
1596
11.2k
      scheme = u->scheme;
1597
0
    else if(flags & CURLU_DEFAULT_SCHEME)
1598
0
      scheme = DEFAULT_SCHEME;
1599
0
    else
1600
0
      return CURLUE_NO_SCHEME;
1601
1602
11.2k
    if(u->port_present) {
1603
69
      curl_msnprintf(portbuf, sizeof(portbuf), "%u", u->portnum);
1604
69
      port = portbuf;
1605
69
    }
1606
1607
11.2k
    h = Curl_get_scheme(scheme);
1608
11.2k
    if(h) {
1609
11.0k
      if(!u->port_present && (flags & CURLU_DEFAULT_PORT)) {
1610
        /* there is no stored port number, but asked to deliver a default one
1611
           for the scheme */
1612
0
        curl_msnprintf(portbuf, sizeof(portbuf), "%u", h->defport);
1613
0
        port = portbuf;
1614
0
      }
1615
11.0k
      else if(u->port_present && (h->defport == u->portnum) &&
1616
3
              (flags & CURLU_NO_DEFAULT_PORT)) {
1617
        /* there is a stored port number, but asked to inhibit if it matches
1618
           the default port for the scheme */
1619
0
        port = NULL;
1620
0
      }
1621
1622
11.0k
      if(!(h->flags & PROTOPT_URLOPTIONS))
1623
11.0k
        options = NULL;
1624
11.0k
    }
1625
1626
11.2k
    if(u->host[0] == '[') {
1627
272
      if(u->zoneid) {
1628
        /* make it '[ host %25 zoneid ]' */
1629
14
        struct dynbuf enc;
1630
14
        size_t hostlen = strlen(u->host);
1631
14
        curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1632
14
        if(curlx_dyn_addf(&enc, "%.*s%%25%s]", (int)hostlen - 1, u->host,
1633
14
                          u->zoneid))
1634
0
          return CURLUE_OUT_OF_MEMORY;
1635
14
        allochost = curlx_dyn_ptr(&enc);
1636
14
      }
1637
272
    }
1638
10.9k
    else if(flags & CURLU_URLENCODE) {
1639
0
      allochost = curl_easy_escape(NULL, u->host, 0);
1640
0
      if(!allochost)
1641
0
        return CURLUE_OUT_OF_MEMORY;
1642
0
    }
1643
10.9k
    else if(flags & CURLU_PUNYCODE) {
1644
0
      if(!Curl_is_ASCII_name(u->host)) {
1645
0
        CURLUcode ret = host_decode(u->host, &allochost);
1646
0
        if(!ret)
1647
0
          ret = hostname_check(allochost, strlen(allochost));
1648
0
        if(ret) {
1649
0
          curlx_safefree(allochost);
1650
0
          return ret;
1651
0
        }
1652
0
      }
1653
0
    }
1654
10.9k
    else if(flags & CURLU_PUNY2IDN) {
1655
0
      if(Curl_is_ASCII_name(u->host)) {
1656
0
        CURLUcode ret = host_encode(u->host, &allochost);
1657
0
        if(ret)
1658
0
          return ret;
1659
0
      }
1660
0
    }
1661
1662
11.2k
    if(!(flags & CURLU_NO_GUESS_SCHEME) || !u->guessed_scheme)
1663
11.2k
      curl_msnprintf(schemebuf, sizeof(schemebuf), "%s://", scheme);
1664
0
    else
1665
0
      schemebuf[0] = 0;
1666
1667
11.2k
    url = curl_maprintf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
1668
11.2k
                        schemebuf,
1669
11.2k
                        u->user ? u->user : "",
1670
11.2k
                        u->password ? ":" : "",
1671
11.2k
                        u->password ? u->password : "",
1672
11.2k
                        options ? ";" : "",
1673
11.2k
                        options ? options : "",
1674
11.2k
                        (u->user || u->password || options) ? "@" : "",
1675
11.2k
                        allochost ? allochost : u->host,
1676
11.2k
                        port ? ":" : "",
1677
11.2k
                        port ? port : "",
1678
11.2k
                        u->path ? u->path : "/",
1679
11.2k
                        querysep,
1680
11.2k
                        u->query ? u->query : "",
1681
11.2k
                        fragmentsep,
1682
11.2k
                        u->fragment ? u->fragment : "");
1683
11.2k
    curlx_free(allochost);
1684
11.2k
  }
1685
11.2k
  if(!url)
1686
0
    return CURLUE_OUT_OF_MEMORY;
1687
11.2k
  *part = url;
1688
11.2k
  return CURLUE_OK;
1689
11.2k
}
1690
1691
CURLUcode curl_url_get(const CURLU *u, CURLUPart what,
1692
                       char **part, unsigned int flags)
1693
113k
{
1694
113k
  const char *ptr;
1695
113k
  CURLUcode ifmissing = CURLUE_UNKNOWN_PART;
1696
113k
  char portbuf[7];
1697
113k
  bool plusdecode = FALSE;
1698
113k
  if(!u)
1699
0
    return CURLUE_BAD_HANDLE;
1700
113k
  if(!part)
1701
0
    return CURLUE_BAD_PARTPOINTER;
1702
113k
  *part = NULL;
1703
1704
113k
  switch(what) {
1705
14.8k
  case CURLUPART_SCHEME:
1706
14.8k
    ptr = u->scheme;
1707
14.8k
    ifmissing = CURLUE_NO_SCHEME;
1708
14.8k
    flags &= ~U_CURLU_URLDECODE; /* never for schemes */
1709
14.8k
    if((flags & CURLU_NO_GUESS_SCHEME) && u->guessed_scheme)
1710
511
      return CURLUE_NO_SCHEME;
1711
14.3k
    break;
1712
14.3k
  case CURLUPART_USER:
1713
11.8k
    ptr = u->user;
1714
11.8k
    ifmissing = CURLUE_NO_USER;
1715
11.8k
    break;
1716
11.8k
  case CURLUPART_PASSWORD:
1717
11.8k
    ptr = u->password;
1718
11.8k
    ifmissing = CURLUE_NO_PASSWORD;
1719
11.8k
    break;
1720
10.4k
  case CURLUPART_OPTIONS:
1721
10.4k
    ptr = u->options;
1722
10.4k
    ifmissing = CURLUE_NO_OPTIONS;
1723
10.4k
    break;
1724
12.3k
  case CURLUPART_HOST:
1725
12.3k
    ptr = u->host;
1726
12.3k
    ifmissing = CURLUE_NO_HOST;
1727
12.3k
    break;
1728
12.0k
  case CURLUPART_ZONEID:
1729
12.0k
    ptr = u->zoneid;
1730
12.0k
    ifmissing = CURLUE_NO_ZONEID;
1731
12.0k
    break;
1732
1.75k
  case CURLUPART_PORT:
1733
1.75k
    ptr = NULL;
1734
1.75k
    ifmissing = CURLUE_NO_PORT;
1735
1.75k
    flags &= ~U_CURLU_URLDECODE; /* never for port */
1736
1.75k
    if(u->port_present) {
1737
0
      const struct Curl_scheme *h = u->scheme ?
1738
0
                                    Curl_get_scheme(u->scheme) : NULL;
1739
      /* there is a stored port number, but ask to inhibit if
1740
         it matches the default one for the scheme */
1741
0
      if(h && (h->defport == u->portnum) &&
1742
0
         (flags & CURLU_NO_DEFAULT_PORT)) {
1743
0
        ptr = NULL;
1744
0
      }
1745
0
      else {
1746
0
        curl_msnprintf(portbuf, sizeof(portbuf), "%u", u->portnum);
1747
0
        ptr = portbuf;
1748
0
      }
1749
0
    }
1750
1.75k
    else if((flags & CURLU_DEFAULT_PORT) && u->scheme) {
1751
      /* there is no stored port number, but asked to deliver
1752
         a default one for the scheme */
1753
0
      const struct Curl_scheme *h = Curl_get_scheme(u->scheme);
1754
0
      if(h) {
1755
0
        curl_msnprintf(portbuf, sizeof(portbuf), "%u", h->defport);
1756
0
        ptr = portbuf;
1757
0
      }
1758
0
    }
1759
1.75k
    break;
1760
10.4k
  case CURLUPART_PATH:
1761
10.4k
    ptr = u->path;
1762
10.4k
    if(!ptr)
1763
9.45k
      ptr = "/";
1764
10.4k
    break;
1765
10.4k
  case CURLUPART_QUERY:
1766
10.4k
    ptr = u->query;
1767
10.4k
    ifmissing = CURLUE_NO_QUERY;
1768
10.4k
    plusdecode = flags & CURLU_URLDECODE;
1769
10.4k
    if(ptr && !ptr[0] && !(flags & CURLU_GET_EMPTY))
1770
      /* there was a blank query and the user does not ask for it */
1771
0
      ptr = NULL;
1772
10.4k
    break;
1773
0
  case CURLUPART_FRAGMENT:
1774
0
    ptr = u->fragment;
1775
0
    ifmissing = CURLUE_NO_FRAGMENT;
1776
0
    if(!ptr && u->fragment_present && flags & CURLU_GET_EMPTY)
1777
      /* there was a blank fragment and the user asks for it */
1778
0
      ptr = "";
1779
0
    break;
1780
17.7k
  case CURLUPART_URL:
1781
17.7k
    return urlget_url(u, part, flags);
1782
0
  default:
1783
0
    ptr = NULL;
1784
0
    break;
1785
113k
  }
1786
95.4k
  if(ptr)
1787
38.7k
    return urlget_format(u, what, ptr, part, plusdecode, flags);
1788
1789
56.6k
  return ifmissing;
1790
95.4k
}
1791
1792
static CURLUcode set_url_scheme(CURLU *u, const char *scheme,
1793
                                unsigned int flags)
1794
0
{
1795
0
  size_t plen = strlen(scheme);
1796
0
  const struct Curl_scheme *h = NULL;
1797
0
  if((plen > MAX_SCHEME_LEN) || (plen < 1))
1798
    /* too long or too short */
1799
0
    return CURLUE_BAD_SCHEME;
1800
  /* verify that it is a fine scheme */
1801
0
  h = Curl_get_scheme(scheme);
1802
0
  if(!(flags & CURLU_NON_SUPPORT_SCHEME) && (!h || !h->run))
1803
0
    return CURLUE_UNSUPPORTED_SCHEME;
1804
0
  if(!h) {
1805
0
    const char *s = scheme;
1806
0
    if(ISALPHA(*s)) {
1807
      /* ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) */
1808
0
      s++;
1809
0
      while(--plen) {
1810
0
        if(ISALNUM(*s) || (*s == '+') || (*s == '-') || (*s == '.'))
1811
0
          s++; /* fine */
1812
0
        else
1813
0
          return CURLUE_BAD_SCHEME;
1814
0
      }
1815
0
    }
1816
0
    else
1817
0
      return CURLUE_BAD_SCHEME;
1818
0
  }
1819
0
  u->guessed_scheme = FALSE;
1820
0
  return CURLUE_OK;
1821
0
}
1822
1823
static CURLUcode set_url_port(CURLU *u, const char *provided_port)
1824
42
{
1825
42
  curl_off_t port;
1826
42
  if(!ISDIGIT(provided_port[0]))
1827
    /* not a number */
1828
0
    return CURLUE_BAD_PORT_NUMBER;
1829
42
  if(curlx_str_number(&provided_port, &port, 0xffff) || *provided_port)
1830
    /* weirdly provided number, not good! */
1831
0
    return CURLUE_BAD_PORT_NUMBER;
1832
42
  u->portnum = (uint16_t)port;
1833
42
  u->port_present = TRUE;
1834
42
  return CURLUE_OK;
1835
42
}
1836
1837
static CURLUcode set_url(CURLU *u, const char *url, size_t part_size,
1838
                         unsigned int flags)
1839
14.6k
{
1840
  /*
1841
   * Allow a new URL to replace the existing (if any) contents.
1842
   *
1843
   * If the existing contents is enough for a URL, allow a relative URL to
1844
   * replace it.
1845
   */
1846
14.6k
  CURLUcode uc;
1847
14.6k
  char *oldurl = NULL;
1848
1849
14.6k
  if(!part_size) {
1850
    /* a blank URL is not a valid URL unless we already have a complete one
1851
       and this is a redirect */
1852
1.18k
    uc = curl_url_get(u, CURLUPART_URL, &oldurl, flags);
1853
1.18k
    if(!uc) {
1854
      /* success, meaning the "" is a fine relative URL, and the new URL
1855
         inherits scheme/authority/path/query, but not fragment, from the
1856
         existing URL (RFC 3986 section 5.2.2) */
1857
0
      curlx_safefree(u->fragment);
1858
0
      u->fragment_present = FALSE;
1859
0
      curlx_free(oldurl);
1860
0
      return CURLUE_OK;
1861
0
    }
1862
1.18k
    if(uc == CURLUE_OUT_OF_MEMORY)
1863
0
      return uc;
1864
1.18k
    return CURLUE_MALFORMED_INPUT;
1865
1.18k
  }
1866
1867
  /* if the new URL is absolute replace the existing with the new. */
1868
13.5k
  if(Curl_is_absolute_url(url, NULL, 0,
1869
13.5k
                          flags & (CURLU_GUESS_SCHEME | CURLU_DEFAULT_SCHEME)))
1870
7.72k
    return parseurl_and_replace(url, u, flags);
1871
1872
  /* if the old URL is incomplete (we cannot get an absolute URL in
1873
     'oldurl'), replace the existing with the new.
1874
     Always include "scheme://" to make the URL "complete" */
1875
  /* Preserve empty query/fragment separators: they affect where relative
1876
     references splice into the base URL. */
1877
5.77k
  uc = curl_url_get(u, CURLUPART_URL, &oldurl,
1878
5.77k
                    (flags & ~CURLU_NO_GUESS_SCHEME) | CURLU_GET_EMPTY);
1879
5.77k
  if(uc == CURLUE_OUT_OF_MEMORY)
1880
0
    return uc;
1881
5.77k
  else if(uc)
1882
5.30k
    return parseurl_and_replace(url, u, flags);
1883
1884
474
  DEBUGASSERT(oldurl); /* it is set here */
1885
  /* apply the relative part to create a new URL */
1886
474
  uc = redirect_url(oldurl, url, u, flags);
1887
474
  curlx_free(oldurl);
1888
474
  return uc;
1889
474
}
1890
1891
static CURLUcode urlset_clear(CURLU *u, CURLUPart what)
1892
0
{
1893
0
  switch(what) {
1894
0
  case CURLUPART_URL:
1895
0
    free_urlhandle(u);
1896
0
    memset(u, 0, sizeof(struct Curl_URL));
1897
0
    break;
1898
0
  case CURLUPART_SCHEME:
1899
0
    curlx_safefree(u->scheme);
1900
0
    u->guessed_scheme = FALSE;
1901
0
    break;
1902
0
  case CURLUPART_USER:
1903
0
    curlx_safefree(u->user);
1904
0
    break;
1905
0
  case CURLUPART_PASSWORD:
1906
0
    curlx_strzero(u->password);
1907
0
    curlx_safefree(u->password);
1908
0
    break;
1909
0
  case CURLUPART_OPTIONS:
1910
0
    curlx_safefree(u->options);
1911
0
    break;
1912
0
  case CURLUPART_HOST:
1913
0
    curlx_safefree(u->host);
1914
0
    break;
1915
0
  case CURLUPART_ZONEID:
1916
0
    curlx_safefree(u->zoneid);
1917
0
    break;
1918
0
  case CURLUPART_PORT:
1919
0
    u->portnum = 0;
1920
0
    u->port_present = FALSE;
1921
0
    break;
1922
0
  case CURLUPART_PATH:
1923
0
    curlx_safefree(u->path);
1924
0
    break;
1925
0
  case CURLUPART_QUERY:
1926
0
    curlx_safefree(u->query);
1927
0
    u->query_present = FALSE;
1928
0
    break;
1929
0
  case CURLUPART_FRAGMENT:
1930
0
    curlx_safefree(u->fragment);
1931
0
    u->fragment_present = FALSE;
1932
0
    break;
1933
0
  default:
1934
0
    return CURLUE_UNKNOWN_PART;
1935
0
  }
1936
0
  return CURLUE_OK;
1937
0
}
1938
1939
static bool allowed_in_path(unsigned char x)
1940
0
{
1941
0
  switch(x) {
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
  case ']':
1952
0
  case '*':
1953
0
  case '+':
1954
0
  case ',':
1955
0
  case ';':
1956
0
  case '=':
1957
0
  case ':':
1958
0
  case '@':
1959
0
  case '/':
1960
0
    return TRUE;
1961
0
  }
1962
0
  return FALSE;
1963
0
}
1964
1965
static CURLUcode url_encode_part(struct dynbuf *encp,
1966
                                 const char *part,
1967
                                 bool plusencode,
1968
                                 bool pathmode,
1969
                                 bool equalsencode)
1970
0
{
1971
0
  const unsigned char *i;
1972
1973
0
  for(i = (const unsigned char *)part; *i; i++) {
1974
0
    CURLcode result;
1975
0
    if((*i == ' ') && plusencode)
1976
0
      result = curlx_dyn_addn(encp, "+", 1);
1977
0
    else if(ISUNRESERVED(*i) ||
1978
0
            (pathmode && allowed_in_path(*i)) ||
1979
0
            ((*i == '=') && equalsencode)) {
1980
0
      if((*i == '=') && equalsencode)
1981
        /* only skip the first equals sign */
1982
0
        equalsencode = FALSE;
1983
0
      result = curlx_dyn_addn(encp, i, 1);
1984
0
    }
1985
0
    else {
1986
0
      unsigned char out[3] = { '%' };
1987
0
      Curl_hexbyte(&out[1], *i);
1988
0
      result = curlx_dyn_addn(encp, out, 3);
1989
0
    }
1990
0
    if(result)
1991
0
      return cc2cu(result);
1992
0
  }
1993
0
  return CURLUE_OK;
1994
0
}
1995
1996
static CURLUcode url_uppercasehex_part(struct dynbuf *encp,
1997
                                       const char *part)
1998
0
{
1999
0
  char *p;
2000
0
  CURLcode result = curlx_dyn_add(encp, part);
2001
0
  if(result)
2002
0
    return cc2cu(result);
2003
0
  p = curlx_dyn_ptr(encp);
2004
0
  while(*p) {
2005
    /* make sure percent encoded are upper case */
2006
0
    if((*p == '%') && ISXDIGIT(p[1]) && ISXDIGIT(p[2]) &&
2007
0
       (ISLOWER(p[1]) || ISLOWER(p[2]))) {
2008
0
      p[1] = Curl_raw_toupper(p[1]);
2009
0
      p[2] = Curl_raw_toupper(p[2]);
2010
0
      p += 3;
2011
0
    }
2012
0
    else
2013
0
      p++;
2014
0
  }
2015
0
  return CURLUE_OK;
2016
0
}
2017
2018
static CURLUcode url_append_query(CURLU *u, struct dynbuf *encp)
2019
0
{
2020
  /* Append the 'encp' string onto the old query. Add a '&' separator if none
2021
     is already present at the end of the existing query */
2022
2023
0
  size_t querylen = u->query ? strlen(u->query) : 0;
2024
0
  bool addamperand = querylen && (u->query[querylen - 1] != '&');
2025
0
  if(querylen) {
2026
0
    struct dynbuf qbuf;
2027
0
    CURLcode result;
2028
0
    const char *newp = curlx_dyn_ptr(encp);
2029
0
    curlx_dyn_init(&qbuf, CURL_MAX_INPUT_LENGTH);
2030
2031
    /* add original query */
2032
0
    result = curlx_dyn_addn(&qbuf, u->query, querylen);
2033
0
    if(!result && addamperand)
2034
      /* add ampersand */
2035
0
      result = curlx_dyn_addn(&qbuf, "&", 1);
2036
0
    if(!result)
2037
      /* add new query part */
2038
0
      result = curlx_dyn_add(&qbuf, newp);
2039
0
    if(result)
2040
0
      goto nomem;
2041
0
    curlx_dyn_free(encp);
2042
0
    curlx_free(u->query);
2043
0
    u->query = curlx_dyn_ptr(&qbuf);
2044
0
    return CURLUE_OK;
2045
0
nomem:
2046
0
    curlx_dyn_free(encp);
2047
0
    return cc2cu(result);
2048
0
  }
2049
0
  else {
2050
0
    curlx_free(u->query);
2051
0
    u->query = curlx_dyn_ptr(encp);
2052
0
  }
2053
0
  return CURLUE_OK;
2054
0
}
2055
2056
static CURLUcode url_sethost(CURLU *u, struct dynbuf *encp,
2057
                             bool urlencode,
2058
                             unsigned int flags)
2059
0
{
2060
0
  size_t n = curlx_dyn_len(encp);
2061
0
  bool bad = FALSE;
2062
0
  char *newp = curlx_dyn_ptr(encp);
2063
0
  if(!n)
2064
    /* an empty hostname is okay if told so */
2065
0
    bad = (flags & CURLU_NO_AUTHORITY) ? FALSE : TRUE;
2066
0
  else if(!urlencode) {
2067
    /* if the hostname part was not URL encoded here, it was set already URL
2068
       encoded so we need to decode it to check */
2069
0
    size_t dlen;
2070
0
    char *decoded = NULL;
2071
0
    CURLcode result = Curl_urldecode(newp, n, &decoded, &dlen, REJECT_CTRL);
2072
0
    if(result || hostname_check6(u, decoded, dlen))
2073
0
      bad = TRUE;
2074
0
    else {
2075
0
      struct dynbuf dbuf;
2076
0
      curlx_dyn_init(&dbuf, CURL_MAX_INPUT_LENGTH);
2077
0
      if(curlx_dyn_addn(&dbuf, decoded, dlen))
2078
0
        bad = TRUE;
2079
0
      else if(ipv4_normalize(&dbuf) == HOST_IPV4) {
2080
0
        curlx_dyn_reset(encp);
2081
0
        if(curlx_dyn_addn(encp, curlx_dyn_ptr(&dbuf), curlx_dyn_len(&dbuf)))
2082
0
          bad = TRUE;
2083
0
      }
2084
0
      curlx_dyn_free(&dbuf);
2085
0
    }
2086
0
    curlx_free(decoded);
2087
0
  }
2088
0
  else if(hostname_check6(u, newp, n))
2089
0
    bad = TRUE;
2090
0
  else if(ipv4_normalize(encp) == HOST_ERROR)
2091
0
    bad = TRUE;
2092
0
  if(bad) {
2093
0
    curlx_dyn_free(encp);
2094
0
    return CURLUE_BAD_HOSTNAME;
2095
0
  }
2096
0
  return CURLUE_OK;
2097
0
}
2098
2099
CURLUcode curl_url_set(CURLU *u, CURLUPart what,
2100
                       const char *part, unsigned int flags)
2101
14.7k
{
2102
14.7k
  char **storep = NULL;
2103
14.7k
  bool urlencode = (flags & CURLU_URLENCODE) ? 1 : 0;
2104
14.7k
  bool plusencode = FALSE;
2105
14.7k
  bool pathmode = FALSE;
2106
14.7k
  bool leadingslash = FALSE;
2107
14.7k
  bool appendquery = FALSE;
2108
14.7k
  bool equalsencode = FALSE;
2109
14.7k
  size_t nalloc;
2110
2111
14.7k
  if(!u)
2112
0
    return CURLUE_BAD_HANDLE;
2113
14.7k
  if(!part)
2114
    /* setting a part to NULL clears it */
2115
0
    return urlset_clear(u, what);
2116
2117
14.7k
  nalloc = strlen(part);
2118
14.7k
  if(nalloc > CURL_MAX_INPUT_LENGTH)
2119
    /* excessive input length */
2120
0
    return CURLUE_MALFORMED_INPUT;
2121
2122
14.7k
  switch(what) {
2123
0
  case CURLUPART_SCHEME: {
2124
0
    CURLUcode status = set_url_scheme(u, part, flags);
2125
0
    if(status)
2126
0
      return status;
2127
0
    storep = &u->scheme;
2128
0
    urlencode = FALSE; /* never */
2129
0
    break;
2130
0
  }
2131
0
  case CURLUPART_USER:
2132
0
    storep = &u->user;
2133
0
    break;
2134
0
  case CURLUPART_PASSWORD:
2135
0
    storep = &u->password;
2136
0
    break;
2137
0
  case CURLUPART_OPTIONS:
2138
0
    storep = &u->options;
2139
0
    break;
2140
0
  case CURLUPART_HOST:
2141
0
    storep = &u->host;
2142
0
    curlx_safefree(u->zoneid);
2143
0
    break;
2144
0
  case CURLUPART_ZONEID:
2145
0
    storep = &u->zoneid;
2146
0
    break;
2147
42
  case CURLUPART_PORT:
2148
42
    return set_url_port(u, part);
2149
0
  case CURLUPART_PATH:
2150
0
    pathmode = TRUE;
2151
0
    leadingslash = TRUE; /* enforce */
2152
0
    storep = &u->path;
2153
0
    break;
2154
0
  case CURLUPART_QUERY:
2155
0
    plusencode = urlencode;
2156
0
    appendquery = (flags & CURLU_APPENDQUERY) ? 1 : 0;
2157
0
    equalsencode = appendquery;
2158
0
    storep = &u->query;
2159
0
    u->query_present = TRUE;
2160
0
    break;
2161
0
  case CURLUPART_FRAGMENT:
2162
0
    storep = &u->fragment;
2163
0
    u->fragment_present = TRUE;
2164
0
    break;
2165
14.6k
  case CURLUPART_URL:
2166
14.6k
    return set_url(u, part, nalloc, flags);
2167
0
  default:
2168
0
    return CURLUE_UNKNOWN_PART;
2169
14.7k
  }
2170
0
  DEBUGASSERT(storep);
2171
0
  {
2172
0
    const char *newp = NULL;
2173
0
    struct dynbuf enc;
2174
0
    CURLUcode status;
2175
0
    curlx_dyn_init(&enc, (what == CURLUPART_HOST) ?
2176
0
                   CURLMAX((nalloc * 3) + 1 + leadingslash, 16) :
2177
0
                   (nalloc * 3) + 1 + leadingslash);
2178
2179
0
    if(leadingslash && (part[0] != '/')) {
2180
0
      CURLcode result = curlx_dyn_addn(&enc, "/", 1);
2181
0
      if(result)
2182
0
        return cc2cu(result);
2183
0
    }
2184
0
    if(urlencode)
2185
0
      status = url_encode_part(&enc, part, plusencode, pathmode, equalsencode);
2186
0
    else
2187
0
      status = url_uppercasehex_part(&enc, part);
2188
0
    if(!status) {
2189
0
      newp = curlx_dyn_ptr(&enc);
2190
2191
0
      if(appendquery && newp)
2192
0
        return url_append_query(u, &enc);
2193
0
      else if(what == CURLUPART_HOST)
2194
0
        status = url_sethost(u, &enc, urlencode, flags);
2195
0
    }
2196
0
    if(status)
2197
0
      return status;
2198
2199
0
    if(what == CURLUPART_PASSWORD)
2200
0
      curlx_strzero(*storep);
2201
0
    curlx_free(*storep);
2202
0
    *storep = (char *)CURL_UNCONST(newp);
2203
0
  }
2204
0
  return CURLUE_OK;
2205
0
}
2206
2207
bool Curl_url_same_origin(CURLU *base, CURLU *href)
2208
0
{
2209
0
  const struct Curl_scheme *s = NULL;
2210
2211
  /* base must be an absolute URL */
2212
0
  if(!base->scheme || !base->host)
2213
0
    return FALSE;
2214
0
  if(href->scheme && !curl_strequal(base->scheme, href->scheme))
2215
0
    return FALSE;
2216
0
  if(href->host) {
2217
0
    if(!curl_strequal(base->host, href->host))
2218
0
      return FALSE;
2219
2220
0
    if(base->port_present != href->port_present) {
2221
      /* one is present, one is not */
2222
0
      s = Curl_get_scheme(base->scheme);
2223
0
      if(!s) /* Cannot match default port for unknown scheme */
2224
0
        return FALSE;
2225
      /* to match, the present one must be the default port */
2226
0
      if((base->port_present && (base->portnum != s->defport)) ||
2227
0
         (href->port_present && (href->portnum != s->defport)))
2228
0
        return FALSE;
2229
0
    }
2230
0
    else if(base->portnum != href->portnum) /* both present or missing */
2231
0
      return FALSE;
2232
2233
0
    if(!curl_strequal(base->zoneid ? base->zoneid : "",
2234
0
                      href->zoneid ? href->zoneid : ""))
2235
0
      return FALSE;
2236
0
  }
2237
0
  else if(href->port_present) /* no host in href, then there must be no port */
2238
0
    return FALSE;
2239
0
  return TRUE;
2240
0
}
2241
2242
CURLUcode Curl_url_get_port(CURLU *u, uint16_t *pport)
2243
10.3k
{
2244
10.3k
  if(u->port_present) {
2245
62
    *pport = u->portnum;
2246
62
    return CURLUE_OK;
2247
62
  }
2248
10.3k
  else if(u->scheme) {
2249
10.3k
    const struct Curl_scheme *s = Curl_get_scheme(u->scheme);
2250
10.3k
    if(s && s->defport) {
2251
10.3k
      *pport = s->defport;
2252
10.3k
      return CURLUE_OK;
2253
10.3k
    }
2254
10.3k
  }
2255
17
  *pport = 0;
2256
17
  return CURLUE_NO_PORT;
2257
10.3k
}