Coverage Report

Created: 2026-10-22 06:51

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
135
  ((('a' <= (str)[0] && (str)[0] <= 'z') ||                \
50
135
    ('A' <= (str)[0] && (str)[0] <= 'Z')) &&               \
51
135
   ((str)[1] == ':' || (str)[1] == '|') &&                 \
52
135
   ((str)[2] == '/' || (str)[2] == '\\' || (str)[2] == 0))
53
54
/* scheme is not URL encoded, the longest libcurl supported ones are... */
55
125k
#define MAX_SCHEME_LEN 40
56
180
#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
24.8k
{
71
24.8k
  curlx_free(u->scheme);
72
24.8k
  curlx_free(u->user);
73
24.8k
  curlx_strzero(u->password);
74
24.8k
  curlx_free(u->password);
75
24.8k
  curlx_free(u->options);
76
24.8k
  curlx_free(u->host);
77
24.8k
  curlx_free(u->zoneid);
78
24.8k
  curlx_free(u->path);
79
24.8k
  curlx_free(u->query);
80
24.8k
  curlx_free(u->fragment);
81
24.8k
}
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
13
{
89
  /* Find the start of the hostname */
90
13
  const char *sep = strstr(url, "//");
91
13
  if(!sep)
92
0
    sep = url;
93
13
  else
94
13
    sep += 2;
95
96
  /* Find first / or ? */
97
233
  while(*sep && *sep != '/' && *sep != '?')
98
220
    sep++;
99
100
13
  return sep;
101
13
}
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
8.89k
{
131
  /* we must add this with whitespace-replacing */
132
8.89k
  const unsigned char *iptr;
133
8.89k
  const unsigned char *host_sep = (const unsigned char *)url;
134
8.89k
  CURLcode result = CURLE_OK;
135
136
8.89k
  DEBUGASSERT((query >= QUERY_NO) && (query <= QUERY_YES));
137
138
8.89k
  if(!relative) {
139
13
    size_t n;
140
13
    host_sep = (const unsigned char *)find_host_sep(url);
141
142
    /* output the first piece as-is */
143
13
    n = (const char *)host_sep - url;
144
13
    result = curlx_dyn_addn(o, url, n);
145
13
    len -= n;
146
13
  }
147
148
6.20M
  for(iptr = host_sep; len && !result;) {
149
6.19M
    if(*iptr == ' ') {
150
768
      if(query != QUERY_YES)
151
278
        result = curlx_dyn_addn(o, "%20", 3);
152
490
      else
153
490
        result = curlx_dyn_addn(o, "+", 1);
154
768
      iptr++;
155
768
      len--;
156
768
    }
157
6.19M
    else if((*iptr < ' ') || (*iptr >= 0x7f)) {
158
6.12M
      unsigned char out[3] = { '%' };
159
6.12M
      Curl_hexbyte(&out[1], *iptr);
160
6.12M
      result = curlx_dyn_addn(o, out, 3);
161
6.12M
      iptr++;
162
6.12M
      len--;
163
6.12M
    }
164
68.2k
    else if(*iptr == '%' && (len >= 3) &&
165
17.9k
            ISXDIGIT(iptr[1]) && ISXDIGIT(iptr[2]) &&
166
5.09k
            (ISLOWER(iptr[1]) || ISLOWER(iptr[2]))) {
167
      /* uppercase it */
168
4.28k
      unsigned char hex = (unsigned char)((curlx_hexval(iptr[1]) << 4) |
169
4.28k
                                          curlx_hexval(iptr[2]));
170
4.28k
      unsigned char out[3] = { '%' };
171
4.28k
      Curl_hexbyte(&out[1], hex);
172
4.28k
      result = curlx_dyn_addn(o, out, 3);
173
4.28k
      iptr += 3;
174
4.28k
      len -= 3;
175
4.28k
    }
176
63.9k
    else {
177
63.9k
      const unsigned char *start = iptr;
178
9.13M
      while(len) {
179
9.12M
        if(*iptr == ' ' || *iptr < ' ' || *iptr >= 0x7f)
180
52.0k
          break;
181
9.07M
        if(*iptr == '%' && (len >= 3) &&
182
3.33M
           ISXDIGIT(iptr[1]) && ISXDIGIT(iptr[2]) &&
183
1.38M
           (ISLOWER(iptr[1]) || ISLOWER(iptr[2])))
184
3.37k
          break;
185
9.07M
        if(*iptr == '?') {
186
293
          if(query == QUERY_NOT_YET) {
187
38
            iptr++;
188
38
            len--;
189
38
            query = QUERY_YES;
190
38
            break;
191
38
          }
192
293
        }
193
9.07M
        iptr++;
194
9.07M
        len--;
195
9.07M
      }
196
63.9k
      result = curlx_dyn_addn(o, (const char *)start, (size_t)(iptr - start));
197
63.9k
    }
198
6.19M
  }
199
200
8.89k
  if(result)
201
0
    return cc2cu(result);
202
8.89k
  return CURLUE_OK;
203
8.89k
}
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
24.2k
{
216
24.2k
  size_t i = 0;
217
24.2k
  DEBUGASSERT(!buf || (buflen > MAX_SCHEME_LEN));
218
24.2k
  (void)buflen; /* only used in debug-builds */
219
24.2k
  if(buf)
220
12.0k
    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
24.2k
  if(ISALPHA(url[0])) {
226
22.0k
    if(buf)
227
11.1k
      buf[0] = Curl_raw_tolower(url[0]);
228
125k
    for(i = 1; i < MAX_SCHEME_LEN; ++i) {
229
125k
      char s = url[i];
230
125k
      if(s && (ISALNUM(s) || (s == '+') || (s == '-') || (s == '.'))) {
231
103k
        if(buf)
232
52.1k
          buf[i] = Curl_raw_tolower(s);
233
103k
      }
234
22.0k
      else {
235
22.0k
        break;
236
22.0k
      }
237
125k
    }
238
22.0k
  }
239
24.2k
  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
20.6k
    size_t len = i;
246
20.6k
    if(buf)
247
10.4k
      buf[i] = 0;
248
20.6k
    return len;
249
20.6k
  }
250
3.58k
  if(buf)
251
1.56k
    buf[0] = 0;
252
3.58k
  return 0;
253
24.2k
}
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.40k
{
258
3.40k
  const uint8_t *p = (const unsigned char *)input;
259
3.40k
  const uint8_t control = flags & CURLU_ALLOW_SPACE ? 0x1f : 0x20;
260
20.5M
  while(n--) {
261
20.5M
    if(*p <= control || *p == 127)
262
25
      return TRUE;
263
20.5M
    p++;
264
20.5M
  }
265
3.37k
  return FALSE;
266
3.40k
}
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
11.9k
{
287
11.9k
  CURLUcode ures = CURLUE_OK;
288
11.9k
  CURLcode result;
289
11.9k
  char *userp = NULL;
290
11.9k
  char *passwdp = NULL;
291
11.9k
  char *optionsp = NULL;
292
11.9k
  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
11.9k
  const char *ptr;
302
303
11.9k
  DEBUGASSERT(login);
304
305
11.9k
  *hostname_offset = 0;
306
11.9k
  ptr = memchr(login, '@', len);
307
11.9k
  if(!ptr)
308
10.7k
    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.18k
  ptr++;
314
315
  /* if this is a known scheme, get some details */
316
1.18k
  if(u->scheme)
317
1.10k
    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.18k
  result = Curl_parse_login_details(login, ptr - login - 1,
322
1.18k
                                    &userp, &passwdp,
323
1.18k
                                    (h && (h->flags & PROTOPT_URLOPTIONS)) ?
324
1.18k
                                    &optionsp : NULL);
325
1.18k
  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.18k
  if(userp) {
333
1.18k
    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.18k
    curlx_free(u->user);
339
1.18k
    u->user = userp;
340
1.18k
  }
341
342
1.18k
  if(passwdp) {
343
205
    curlx_strzero(u->password);
344
205
    curlx_free(u->password);
345
205
    u->password = passwdp;
346
205
  }
347
348
1.18k
  if(optionsp) {
349
12
    curlx_free(u->options);
350
12
    u->options = optionsp;
351
12
  }
352
353
1.18k
  if(userp && badoctets(userp, strlen(userp), flags))
354
6
    ures = CURLUE_BAD_USER;
355
1.17k
  else if(passwdp && badoctets(passwdp, strlen(passwdp), flags))
356
4
    ures = CURLUE_BAD_PASSWORD;
357
1.17k
  else if(optionsp && badoctets(optionsp, strlen(optionsp), flags))
358
3
    ures = CURLUE_MALFORMED_INPUT;
359
360
1.18k
  userp = passwdp = optionsp = NULL;
361
362
1.18k
  if(!ures) {
363
    /* the hostname starts at this offset */
364
1.16k
    *hostname_offset = ptr - login;
365
1.16k
    return CURLUE_OK;
366
1.16k
  }
367
368
10.7k
out:
369
370
10.7k
  curlx_free(userp);
371
10.7k
  curlx_strzero(passwdp);
372
10.7k
  curlx_free(passwdp);
373
10.7k
  curlx_free(optionsp);
374
10.7k
  curlx_safefree(u->user);
375
10.7k
  curlx_strzero(u->password);
376
10.7k
  curlx_safefree(u->password);
377
10.7k
  curlx_safefree(u->options);
378
379
10.7k
  return ures;
380
1.18k
}
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
11.9k
{
388
11.9k
  const char *portptr;
389
11.9k
  const char *hostname = curlx_dyn_ptr(host);
390
  /*
391
   * Find the end of an IPv6 address on the ']' ending bracket.
392
   */
393
11.9k
  u->portnum = 0;
394
11.9k
  u->port_present = FALSE;
395
11.9k
  if(hostname[0] == '[') {
396
403
    portptr = memchr(hostname + 1, ']', curlx_dyn_len(host) - 1);
397
403
    if(!portptr)
398
5
      return CURLUE_BAD_IPV6;
399
398
    portptr++;
400
    /* this is a RFC2732-style specified IP-address */
401
398
    if(*portptr) {
402
6
      if(*portptr != ':')
403
3
        return CURLUE_BAD_PORT_NUMBER;
404
6
    }
405
392
    else
406
392
      portptr = NULL;
407
398
  }
408
11.5k
  else
409
11.5k
    portptr = memchr(hostname, ':', curlx_dyn_len(host));
410
411
11.9k
  if(portptr) {
412
157
    curl_off_t port;
413
157
    size_t keep = portptr - hostname;
414
157
    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
157
    curlx_dyn_setlen(host, keep);
423
157
    portptr++;
424
157
    if(!*portptr)
425
46
      return has_scheme ? CURLUE_OK : CURLUE_BAD_PORT_NUMBER;
426
111
    if(*portptr == '\\')
427
2
      return CURLUE_BACKSLASH;
428
109
    rc = curlx_str_number(&portptr, &port, 0xffff);
429
109
    if(rc)
430
29
      return CURLUE_BAD_PORT_NUMBER;
431
80
    else if(*portptr == '\\')
432
1
      return CURLUE_BACKSLASH;
433
79
    else if(*portptr)
434
3
      return CURLUE_BAD_PORT_NUMBER;
435
436
76
    u->portnum = (uint16_t)port;
437
76
    u->port_present = TRUE;
438
76
  }
439
440
11.8k
  return CURLUE_OK;
441
11.9k
}
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
394
{
467
394
  size_t len;
468
394
  DEBUGASSERT(*hostname == '[');
469
394
  if(hlen < 4) /* '[::]' is the shortest possible valid string */
470
1
    return CURLUE_BAD_IPV6;
471
393
  hostname++;
472
393
  hlen -= 2;
473
474
  /* only valid IPv6 letters are ok */
475
393
  len = strspn(hostname, "0123456789abcdefABCDEF:.");
476
477
393
  if(hlen != len) {
478
61
    hlen = len;
479
61
    if(hostname[len] == '%') {
480
      /* this could now be '%[zone id]' */
481
50
      char zoneid[MAX_ZONEID_LEN];
482
50
      int i = 0;
483
50
      char *h = &hostname[len + 1];
484
      /* pass '25' if present and is a URL encoded percent sign */
485
50
      if(!strncmp(h, "25", 2) && h[2] && (h[2] != ']'))
486
1
        h += 2;
487
229
      while(!invalid_host_char[(unsigned char)*h] && (*h != ']') &&
488
180
            (i < (MAX_ZONEID_LEN - 1)))
489
179
        zoneid[i++] = *h++;
490
50
      if(!i || (']' != *h))
491
9
        return CURLUE_BAD_IPV6;
492
41
      zoneid[i] = 0;
493
41
      u->zoneid = curlx_strdup(zoneid);
494
41
      if(!u->zoneid)
495
0
        return CURLUE_OUT_OF_MEMORY;
496
41
      hostname[len] = ']'; /* insert end bracket */
497
41
      hostname[len + 1] = 0; /* terminate the hostname */
498
41
    }
499
11
    else
500
11
      return CURLUE_BAD_IPV6;
501
    /* hostname is fine */
502
61
  }
503
504
  /* Normalize the IPv6 address */
505
373
  {
506
373
    char dest[16]; /* fits a binary IPv6 address */
507
373
    hostname[hlen] = 0; /* end the address there */
508
373
    if(curlx_inet_pton(AF_INET6, hostname, dest) != 1)
509
47
      return CURLUE_BAD_IPV6;
510
326
    if(!curlx_inet_ntop(AF_INET6, dest, hostname, hlen + 1)) {
511
276
      hlen = strlen(hostname); /* might be shorter now */
512
276
      hostname[hlen + 1] = 0;
513
276
    }
514
326
    hostname[hlen] = ']'; /* restore ending bracket */
515
326
  }
516
0
  return CURLUE_OK;
517
373
}
518
519
/* the input is a confirmed hostname, never an IPv6 address */
520
static CURLUcode hostname_check(char *hostname, size_t hlen)
521
5.46k
{
522
5.46k
  size_t i;
523
1.10M
  for(i = 0; i < hlen; i++) {
524
1.09M
    if(invalid_host_char[(unsigned char)hostname[i]])
525
101
      return CURLUE_BAD_HOSTNAME;
526
1.09M
  }
527
5.36k
  if((hlen >= 2) &&
528
946
     (hostname[hlen - 1] == '.') && (hostname[hlen - 2] == '.'))
529
    /* more than one trailing dot is not allowed */
530
8
    return CURLUE_BAD_HOSTNAME;
531
5.36k
  else if((hlen == 1) && (hostname[0] == '.'))
532
    /* a single dot alone is not allowed */
533
4
    return CURLUE_BAD_HOSTNAME;
534
5.35k
  return CURLUE_OK;
535
5.36k
}
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
11.3k
{
571
11.3k
  bool done = FALSE;
572
11.3k
  int n = 0;
573
11.3k
  const char *c = curlx_dyn_ptr(host);
574
11.3k
  unsigned int parts[4] = { 0, 0, 0, 0 };
575
11.3k
  CURLcode result = CURLE_OK;
576
577
11.3k
  if(!ISDIGIT(*c))
578
5.08k
    return HOST_NAME;
579
580
20.6k
  while(!done) {
581
14.5k
    int rc;
582
14.5k
    curl_off_t l;
583
14.5k
    if(*c == '0') {
584
6.48k
      if((c[1] | 0x20) == 'x') {
585
44
        c += 2; /* skip the prefix */
586
44
        rc = curlx_str_hex(&c, &l, UINT_MAX);
587
44
        if(rc)
588
14
          return HOST_NAME;
589
44
      }
590
6.43k
      else
591
6.43k
        rc = curlx_str_octal(&c, &l, UINT_MAX);
592
6.48k
    }
593
8.05k
    else
594
8.05k
      rc = curlx_str_number(&c, &l, UINT_MAX);
595
596
14.5k
    if(rc) {
597
81
      if(!n || (rc != STRE_NO_NUM) || *c)
598
53
        return HOST_NAME;
599
28
      n--;
600
28
    }
601
14.4k
    else
602
14.4k
      parts[n] = (unsigned int)l;
603
604
14.4k
    switch(*c) {
605
8.29k
    case '.':
606
8.29k
      if(n == 3) {
607
12
        if(c[1])
608
          /* something follows this dot */
609
5
          return HOST_NAME;
610
7
        done = TRUE;
611
7
      }
612
8.28k
      else {
613
8.28k
        n++;
614
8.28k
        c++;
615
8.28k
      }
616
8.29k
      break;
617
618
8.29k
    case '\0':
619
6.07k
      done = TRUE;
620
6.07k
      break;
621
622
97
    default:
623
97
      return HOST_NAME;
624
14.4k
    }
625
14.4k
  }
626
627
6.08k
  switch(n) {
628
3.28k
  case 0: /* a -- 32 bits */
629
3.28k
    curlx_dyn_reset(host);
630
631
3.28k
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
632
3.28k
                            (parts[0] >> 24),
633
3.28k
                            ((parts[0] >> 16) & 0xff),
634
3.28k
                            ((parts[0] >> 8) & 0xff),
635
3.28k
                            (parts[0] & 0xff));
636
3.28k
    break;
637
81
  case 1: /* a.b -- 8.24 bits */
638
81
    if((parts[0] > 0xff) || (parts[1] > 0xffffff))
639
39
      return HOST_NAME;
640
42
    curlx_dyn_reset(host);
641
42
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
642
42
                            parts[0],
643
42
                            ((parts[1] >> 16) & 0xff),
644
42
                            ((parts[1] >> 8) & 0xff),
645
42
                            (parts[1] & 0xff));
646
42
    break;
647
107
  case 2: /* a.b.c -- 8.8.16 bits */
648
107
    if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xffff))
649
78
      return HOST_NAME;
650
29
    curlx_dyn_reset(host);
651
29
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
652
29
                            parts[0],
653
29
                            parts[1],
654
29
                            ((parts[2] >> 8) & 0xff),
655
29
                            (parts[2] & 0xff));
656
29
    break;
657
2.60k
  case 3: /* a.b.c.d -- 8.8.8.8 bits */
658
2.60k
    if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xff) ||
659
2.53k
       (parts[3] > 0xff))
660
95
      return HOST_NAME;
661
2.51k
    curlx_dyn_reset(host);
662
2.51k
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
663
2.51k
                            parts[0],
664
2.51k
                            parts[1],
665
2.51k
                            parts[2],
666
2.51k
                            parts[3]);
667
2.51k
    break;
668
6.08k
  }
669
5.86k
  if(result)
670
0
    return HOST_ERROR;
671
5.86k
  return HOST_IPV4;
672
5.86k
}
673
674
/* if necessary, replace the host content with a URL decoded version */
675
static CURLUcode urldecode_host(struct dynbuf *host)
676
11.7k
{
677
11.7k
  const char *per;
678
11.7k
  const char *hostname = curlx_dyn_ptr(host);
679
11.7k
  per = memchr(hostname, '%', curlx_dyn_len(host));
680
11.7k
  if(!per)
681
    /* nothing to decode */
682
11.6k
    return CURLUE_OK;
683
108
  else {
684
    /* encoded */
685
108
    size_t dlen;
686
108
    char *decoded;
687
108
    CURLcode result = Curl_urldecode(hostname, 0, &decoded, &dlen,
688
108
                                     REJECT_CTRL);
689
108
    if(result)
690
10
      return CURLUE_BAD_HOSTNAME;
691
98
    curlx_dyn_reset(host);
692
98
    result = curlx_dyn_addn(host, decoded, dlen);
693
98
    curlx_free(decoded);
694
98
    if(result)
695
0
      return cc2cu(result);
696
98
  }
697
698
98
  return CURLUE_OK;
699
11.7k
}
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
11.9k
{
707
11.9k
  size_t offset;
708
11.9k
  CURLUcode uc;
709
11.9k
  CURLcode result;
710
711
  /*
712
   * Parse the login details and strip them out of the hostname.
713
   */
714
11.9k
  uc = parse_hostname_login(u, auth, authlen, flags, &offset);
715
11.9k
  if(uc)
716
15
    return uc;
717
718
11.9k
  result = curlx_dyn_addn(host, auth + offset, authlen - offset);
719
11.9k
  if(result) {
720
0
    uc = cc2cu(result);
721
0
    return uc;
722
0
  }
723
724
  /* parse_port() also sets the hostname length correctly */
725
11.9k
  uc = parse_port(u, host, has_scheme);
726
727
11.9k
  if(!curlx_dyn_len(host))
728
    /* this makes no-host errors override port number problems */
729
145
    uc = CURLUE_NO_HOST;
730
11.9k
  if(!uc)
731
11.7k
    uc = urldecode_host(host);
732
11.9k
  if(uc)
733
190
    ;
734
11.7k
  else if(auth[offset] == '[')
735
394
    uc = ipv6_parse(u, curlx_dyn_ptr(host), curlx_dyn_len(host));
736
11.3k
  else {
737
    /* ipv4_normalize() returns *NAME, *IPV4 or *ERROR */
738
11.3k
    int type = ipv4_normalize(host);
739
740
11.3k
    if(type == HOST_NAME)
741
5.46k
      uc = hostname_check(curlx_dyn_ptr(host), curlx_dyn_len(host));
742
5.86k
    else if(type == HOST_ERROR)
743
0
      uc = CURLUE_OUT_OF_MEMORY;
744
11.3k
  }
745
746
11.9k
  return uc;
747
11.9k
}
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
12.9M
{
776
12.9M
  const char *p = *str;
777
12.9M
  if(*p == '.') {
778
47.2k
    (*str)++;
779
47.2k
    (*clen)--;
780
47.2k
    return TRUE;
781
47.2k
  }
782
12.8M
  else if((*clen >= 3) &&
783
12.8M
          (p[0] == '%') && (p[1] == '2') && ((p[2] | 0x20) == 'e')) {
784
24.6k
    *str += 3;
785
24.6k
    *clen -= 3;
786
24.6k
    return TRUE;
787
24.6k
  }
788
12.8M
  return FALSE;
789
12.9M
}
790
791
10.7M
#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.34k
{
796
  /* a single byte path cannot be cleaned up */
797
1.34k
  if(pn < 2)
798
8
    return FALSE;
799
1.33k
  if(!memchr(p, '.', pn) && !memchr(p, '%', pn))
800
350
    return FALSE;
801
10.2M
  while(pn) {
802
10.2M
    if(is_dot(&p, &pn)) {
803
      /* "./" or dot before end of string */
804
7.12k
      if(!pn || ISSLASH(*p))
805
350
        return TRUE;
806
      /* "../" or ".." before end of string */
807
6.77k
      else if(is_dot(&p, &pn) && (!pn || ISSLASH(*p)))
808
91
        return TRUE;
809
7.12k
    }
810
10.2M
    else {
811
10.2M
      p++;
812
10.2M
      pn--;
813
10.2M
    }
814
10.2M
  }
815
541
  return FALSE;
816
982
}
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.34k
{
837
1.34k
  struct dynbuf out;
838
1.34k
  CURLcode result = CURLE_OK;
839
840
  /* variables for leading dot checks */
841
1.34k
  const char *dinput = input;
842
1.34k
  size_t dlen = clen;
843
844
1.34k
  *outp = NULL;
845
1.34k
  if(!needs_dedotdot(input, clen))
846
899
    return 0;
847
848
441
  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
441
  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
10.6M
  while(clen && !result) { /* until end of path content */
874
10.6M
    if(ISSLASH(*input)) {
875
2.65M
      const char *p = &input[1];
876
2.65M
      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
2.65M
      if(is_dot(&p, &blen)) {
881
43.3k
        if(!blen) { /* /. */
882
44
          result = curlx_dyn_addn(&out, "/", 1);
883
44
          break;
884
44
        }
885
43.2k
        else if(ISSLASH(*p)) { /* /./ */
886
4.80k
          input = p;
887
4.80k
          clen = blen;
888
4.80k
          continue;
889
4.80k
        }
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
38.4k
        else if(is_dot(&p, &blen) && (ISSLASH(*p) || !blen)) {
896
          /* remove the last segment from the output buffer */
897
12.3k
          size_t len = curlx_dyn_len(&out);
898
12.3k
          if(len) {
899
11.6k
            const char *ptr = curlx_dyn_ptr(&out);
900
11.6k
            const char *last = memrchr(ptr, '/', len);
901
11.6k
            if(last)
902
              /* trim the output at the slash */
903
11.6k
              curlx_dyn_setlen(&out, last - ptr);
904
11.6k
          }
905
906
12.3k
          if(blen) { /* /../ */
907
12.3k
            input = p;
908
12.3k
            clen = blen;
909
12.3k
            continue;
910
12.3k
          }
911
28
          result = curlx_dyn_addn(&out, "/", 1);
912
28
          break;
913
12.3k
        }
914
43.3k
      }
915
2.65M
    }
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
10.6M
    result = curlx_dyn_addn(&out, input, 1);
923
10.6M
    input++;
924
10.6M
    clen--;
925
10.6M
  }
926
441
end:
927
441
  if(!result) {
928
441
    if(curlx_dyn_len(&out))
929
441
      *outp = curlx_dyn_ptr(&out);
930
0
    else {
931
0
      *outp = curlx_strdup("");
932
0
      if(!*outp)
933
0
        return 1;
934
0
    }
935
441
  }
936
441
  return result ? 1 : 0; /* success */
937
441
}
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
106
{
947
106
  const char *path;
948
106
  size_t pathlen;
949
950
106
  *pathp = NULL;
951
106
  *pathlenp = 0;
952
106
  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
105
  path = &url[5];
958
105
  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
105
  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
105
  if(path[1] == '/') {
973
    /* swallow the two slashes */
974
82
    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
82
    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
75
      if(checkprefix("localhost/", ptr) ||
996
74
         checkprefix("127.0.0.1/", ptr)) {
997
2
        ptr += 9; /* now points to the slash after the host */
998
2
      }
999
73
      else
1000
        /* Invalid file://hostname/, expected localhost or 127.0.0.1 or
1001
           none */
1002
73
        return CURLUE_BAD_FILE_URL;
1003
75
    }
1004
1005
9
    path = ptr;
1006
9
    pathlen = urllen - (ptr - url);
1007
9
  }
1008
1009
32
#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
32
  if(('/' == path[0] && STARTS_WITH_URL_DRIVE_PREFIX(&path[1])) ||
1013
28
     STARTS_WITH_URL_DRIVE_PREFIX(path)) {
1014
    /* File drive letters are only accepted in MS-DOS/Windows */
1015
9
    return CURLUE_BAD_FILE_URL;
1016
9
  }
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
23
  u->scheme = curlx_strdup("file");
1026
23
  if(!u->scheme)
1027
0
    return CURLUE_OUT_OF_MEMORY;
1028
1029
23
  *pathp = path;
1030
23
  *pathlenp = pathlen;
1031
23
  return CURLUE_OK;
1032
23
}
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
11.9k
{
1038
  /* clear path */
1039
11.9k
  const char *schemep = NULL;
1040
1041
11.9k
  if(schemelen) {
1042
10.3k
    int num_slashes = 0;
1043
10.3k
    const char *p = &url[schemelen + 1];
1044
10.3k
    if(!Curl_getn_scheme(schemebuf, schemelen) &&
1045
290
       !(flags & CURLU_NON_SUPPORT_SCHEME))
1046
0
      return CURLUE_UNSUPPORTED_SCHEME;
1047
1048
10.3k
    if(!ISSLASH(*p))
1049
      /* less than one */
1050
0
      return CURLUE_BAD_SLASHES;
1051
10.3k
    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
10.3k
    else {
1058
23.7k
      while(ISSLASH(*p) && (num_slashes < 4)) {
1059
13.3k
        p++;
1060
13.3k
        num_slashes++;
1061
13.3k
      }
1062
10.3k
      if(num_slashes > 3)
1063
1
        return CURLUE_BAD_SLASHES;
1064
10.3k
    }
1065
1066
10.3k
    schemep = schemebuf;
1067
10.3k
    *hostpp = p; /* hostname starts here */
1068
10.3k
  }
1069
1.56k
  else {
1070
    /* no scheme! */
1071
1072
1.56k
    if(!(flags & (CURLU_DEFAULT_SCHEME | CURLU_GUESS_SCHEME)))
1073
0
      return CURLUE_BAD_SCHEME;
1074
1075
1.56k
    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
1.56k
    *hostpp = url;
1082
1.56k
  }
1083
1084
11.9k
  if(schemep) {
1085
10.3k
    u->scheme = curlx_strdup(schemep);
1086
10.3k
    if(!u->scheme)
1087
0
      return CURLUE_OUT_OF_MEMORY;
1088
10.3k
  }
1089
11.9k
  return CURLUE_OK;
1090
11.9k
}
1091
1092
static CURLUcode guess_scheme(CURLU *u, struct dynbuf *host)
1093
1.30k
{
1094
1.30k
  const char *hostname = curlx_dyn_ptr(host);
1095
1.30k
  const char *schemep = NULL;
1096
  /* legacy curl-style guess based on hostname */
1097
1.30k
  if(checkprefix("ftp.", hostname))
1098
1
    schemep = "ftp";
1099
1.30k
  else if(checkprefix("dict.", hostname))
1100
1
    schemep = "dict";
1101
1.30k
  else if(checkprefix("ldap.", hostname))
1102
2
    schemep = "ldap";
1103
1.30k
  else if(checkprefix("imap.", hostname))
1104
3
    schemep = "imap";
1105
1.30k
  else if(checkprefix("smtp.", hostname))
1106
2
    schemep = "smtp";
1107
1.29k
  else if(checkprefix("pop3.", hostname))
1108
1
    schemep = "pop3";
1109
1.29k
  else
1110
1.29k
    schemep = "http";
1111
1112
1.30k
  u->scheme = curlx_strdup(schemep);
1113
1.30k
  if(!u->scheme)
1114
0
    return CURLUE_OUT_OF_MEMORY;
1115
1116
1.30k
  u->guessed_scheme = TRUE;
1117
1.30k
  return CURLUE_OK;
1118
1.30k
}
1119
1120
static CURLUcode handle_fragment(CURLU *u, const char *fragment,
1121
                                 size_t fraglen, unsigned int flags)
1122
56
{
1123
56
  CURLUcode ures;
1124
56
  u->fragment_present = TRUE;
1125
56
  if(fraglen > 1) {
1126
    /* skip the leading '#' in the copy but include the null-terminator */
1127
44
    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
44
    else {
1136
44
      if(badoctets(fragment, fraglen, flags))
1137
4
        return CURLUE_BAD_FRAGMENT;
1138
40
      u->fragment = curlx_memdup0(fragment + 1, fraglen - 1);
1139
40
      if(!u->fragment)
1140
0
        return CURLUE_OUT_OF_MEMORY;
1141
40
    }
1142
44
  }
1143
52
  return CURLUE_OK;
1144
56
}
1145
1146
static CURLUcode handle_query(CURLU *u, const char *query,
1147
                              size_t qlen, unsigned int flags)
1148
672
{
1149
672
  u->query_present = TRUE;
1150
672
  if(qlen > 1) {
1151
606
    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
606
    else {
1162
606
      if(badoctets(query, qlen, flags))
1163
2
        return CURLUE_BAD_QUERY;
1164
1165
604
      u->query = curlx_memdup0(query + 1, qlen - 1);
1166
604
      if(!u->query)
1167
0
        return CURLUE_OUT_OF_MEMORY;
1168
604
    }
1169
606
  }
1170
66
  else {
1171
    /* single byte query */
1172
66
    u->query = curlx_strdup("");
1173
66
    if(!u->query)
1174
0
      return CURLUE_OUT_OF_MEMORY;
1175
66
  }
1176
670
  return CURLUE_OK;
1177
672
}
1178
1179
static CURLUcode handle_path(CURLU *u, const char *path,
1180
                             size_t pathlen, unsigned int flags,
1181
                             bool is_file)
1182
11.5k
{
1183
11.5k
  CURLUcode ures;
1184
11.5k
  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
11.5k
  if(pathlen >= (size_t)(1 + !is_file)) {
1195
1.35k
    if(badoctets(path, pathlen, flags))
1196
6
      return CURLUE_BAD_PATH;
1197
1198
    /* paths for file:// scheme can be one byte, others need to be two */
1199
1.34k
    if(!u->path) {
1200
1.34k
      u->path = curlx_memdup0(path, pathlen);
1201
1.34k
      if(!u->path)
1202
0
        return CURLUE_OUT_OF_MEMORY;
1203
1.34k
      path = u->path;
1204
1.34k
    }
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.34k
    if(!(flags & CURLU_PATH_AS_IS)) {
1210
      /* remove ../ and ./ sequences according to RFC3986 */
1211
1.34k
      char *dedot;
1212
1.34k
      int err = dedotdotify(path, pathlen, &dedot);
1213
1.34k
      if(err)
1214
0
        return CURLUE_OUT_OF_MEMORY;
1215
1.34k
      if(dedot) {
1216
441
        curlx_free(u->path);
1217
441
        u->path = dedot;
1218
441
      }
1219
1.34k
    }
1220
1.34k
  }
1221
11.5k
  return CURLUE_OK;
1222
11.5k
}
1223
1224
static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
1225
12.0k
{
1226
12.0k
  const char *path;
1227
12.0k
  size_t pathlen;
1228
12.0k
  char schemebuf[MAX_SCHEME_LEN + 1];
1229
12.0k
  size_t schemelen = 0;
1230
12.0k
  size_t urllen;
1231
12.0k
  CURLUcode ures = CURLUE_OK;
1232
12.0k
  struct dynbuf host;
1233
12.0k
  bool is_file = FALSE;
1234
1235
12.0k
  DEBUGASSERT(url);
1236
1237
12.0k
  urllen = strlen(url);
1238
12.0k
  if(urllen > CURL_MAX_INPUT_LENGTH)
1239
0
    return CURLUE_MALFORMED_INPUT;
1240
1241
12.0k
  curlx_dyn_init(&host, CURL_MAX_INPUT_LENGTH);
1242
1243
12.0k
  schemelen = Curl_is_absolute_url(url, schemebuf, sizeof(schemebuf),
1244
12.0k
                                   flags & (CURLU_GUESS_SCHEME |
1245
12.0k
                                            CURLU_DEFAULT_SCHEME));
1246
1247
  /* handle the file: scheme */
1248
12.0k
  if(schemelen == 4 && !memcmp(schemebuf, "file", 4)) {
1249
106
    is_file = TRUE;
1250
106
    ures = parse_file(url, urllen, u, &path, &pathlen);
1251
106
  }
1252
11.9k
  else {
1253
11.9k
    const char *hostp = NULL;
1254
11.9k
    const char *p;
1255
11.9k
    size_t hostlen;
1256
11.9k
    ures = parse_scheme(url, u, schemebuf, schemelen, flags, &hostp);
1257
11.9k
    if(ures)
1258
1
      goto fail;
1259
1260
    /* find the end of the hostname + port number */
1261
11.9k
    p = hostp;
1262
2.36M
    while(*p && *p != '/' && *p != '?' && *p != '#')
1263
2.35M
      p++;
1264
11.9k
    hostlen = p - hostp;
1265
11.9k
    path = p;
1266
1267
    /* this pathlen also contains the query and the fragment */
1268
11.9k
    pathlen = urllen - (path - url);
1269
11.9k
    if(hostlen) {
1270
11.9k
      ures = parse_authority(u, hostp, hostlen, flags, &host, !!u->scheme);
1271
11.9k
      if(!ures && (flags & CURLU_GUESS_SCHEME) && !u->scheme)
1272
1.30k
        ures = guess_scheme(u, &host);
1273
11.9k
    }
1274
21
    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
21
    else
1280
21
      ures = CURLUE_NO_HOST;
1281
11.9k
  }
1282
12.0k
  if(!ures) {
1283
    /* The path might at this point contain a fragment and/or a query to
1284
       handle */
1285
11.5k
    const char *fragment = memchr(path, '#', pathlen);
1286
11.5k
    if(fragment) {
1287
56
      size_t fraglen = pathlen - (fragment - path);
1288
56
      ures = handle_fragment(u, fragment, fraglen, flags);
1289
      /* after this, pathlen still contains the query */
1290
56
      pathlen -= fraglen;
1291
56
    }
1292
11.5k
  }
1293
12.0k
  if(!ures) {
1294
11.5k
    const char *query = memchr(path, '?', pathlen);
1295
11.5k
    if(query) {
1296
672
      size_t qlen = pathlen - (query - path);
1297
672
      ures = handle_query(u, query, qlen, flags);
1298
672
      pathlen -= qlen;
1299
672
    }
1300
11.5k
  }
1301
12.0k
  if(!ures)
1302
    /* the fragment and query parts are trimmed off from the path */
1303
11.5k
    ures = handle_path(u, path, pathlen, flags, is_file);
1304
12.0k
  if(!ures) {
1305
11.5k
    u->host = curlx_dyn_ptr(&host);
1306
11.5k
    return CURLUE_OK;
1307
11.5k
  }
1308
503
fail:
1309
503
  curlx_dyn_free(&host);
1310
503
  free_urlhandle(u);
1311
503
  return ures;
1312
12.0k
}
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
12.0k
{
1320
12.0k
  CURLUcode ures;
1321
12.0k
  CURLU tmpurl;
1322
12.0k
  memset(&tmpurl, 0, sizeof(tmpurl));
1323
12.0k
  ures = parseurl(url, &tmpurl, flags);
1324
12.0k
  if(!ures) {
1325
11.5k
    free_urlhandle(u);
1326
11.5k
    *u = tmpurl;
1327
11.5k
  }
1328
12.0k
  return ures;
1329
12.0k
}
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
384
{
1337
384
  struct dynbuf urlbuf;
1338
384
  bool host_changed = FALSE;
1339
384
  const char *useurl = relurl;
1340
384
  const char *cutoff = NULL;
1341
384
  size_t prelen;
1342
384
  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
384
  const char *scheme = u->scheme ? u->scheme : DEFAULT_SCHEME;
1346
1347
  /* protsep points to the start of the hostname, after [scheme]:// */
1348
384
  const char *protsep = base + strlen(scheme) + 3;
1349
384
  DEBUGASSERT(base && relurl && u); /* all set here */
1350
384
  if(!base)
1351
0
    return CURLUE_MALFORMED_INPUT; /* should never happen */
1352
1353
  /* handle different relative URL types */
1354
384
  switch(relurl[0]) {
1355
17
  case '/':
1356
17
    if(relurl[1] == '/') {
1357
      /* protocol-relative URL: //example.com/path */
1358
13
      cutoff = protsep;
1359
13
      useurl = &relurl[2];
1360
13
      host_changed = TRUE;
1361
13
    }
1362
4
    else
1363
      /* absolute /path */
1364
4
      cutoff = strchr(protsep, '/');
1365
17
    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
357
  default:
1374
    /* path or query-only change */
1375
357
    if(u->query_present)
1376
      /* remove existing query */
1377
6
      cutoff = strchr(protsep, '?');
1378
351
    else if(u->fragment_present)
1379
      /* Remove existing fragment */
1380
4
      cutoff = strchr(protsep, '#');
1381
1382
357
    if(relurl[0] != '?') {
1383
      /* append a relative path after the last slash */
1384
338
      cutoff = memrchr(protsep, '/',
1385
338
                       cutoff ? (size_t)(cutoff - protsep) : strlen(protsep));
1386
338
      if(cutoff)
1387
338
        cutoff++; /* truncate after last slash */
1388
338
    }
1389
357
    break;
1390
384
  }
1391
1392
384
  prelen = cutoff ? (size_t)(cutoff - base) : strlen(base);
1393
1394
  /* build new URL */
1395
384
  curlx_dyn_init(&urlbuf, CURL_MAX_INPUT_LENGTH);
1396
1397
384
  if(!curlx_dyn_addn(&urlbuf, base, prelen) &&
1398
384
     !urlencode_str(&urlbuf, useurl, strlen(useurl), !host_changed,
1399
384
                    QUERY_NOT_YET)) {
1400
384
    uc = parseurl_and_replace(curlx_dyn_ptr(&urlbuf), u,
1401
384
                              flags & ~U_CURLU_PATH_AS_IS);
1402
384
  }
1403
0
  else
1404
0
    uc = CURLUE_OUT_OF_MEMORY;
1405
1406
384
  curlx_dyn_free(&urlbuf);
1407
384
  return uc;
1408
384
}
1409
1410
/*
1411
 */
1412
CURLU *curl_url(void)
1413
12.8k
{
1414
12.8k
  return curlx_calloc(1, sizeof(struct Curl_URL));
1415
12.8k
}
1416
1417
void curl_url_cleanup(CURLU *u)
1418
41.3k
{
1419
41.3k
  if(u) {
1420
12.8k
    free_urlhandle(u);
1421
12.8k
    curlx_free(u);
1422
12.8k
  }
1423
41.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
34.6k
{
1491
34.6k
  CURLUcode uc = CURLUE_OK;
1492
34.6k
  size_t partlen = strlen(ptr);
1493
34.6k
  bool urldecode = (flags & CURLU_URLDECODE) ? 1 : 0;
1494
34.6k
  bool urlencode = (flags & CURLU_URLENCODE) ? 1 : 0;
1495
34.6k
  bool punycode = (flags & CURLU_PUNYCODE) && (what == CURLUPART_HOST);
1496
34.6k
  bool depunyfy = (flags & CURLU_PUNY2IDN) && (what == CURLUPART_HOST);
1497
34.6k
  char *part = curlx_memdup0(ptr, partlen);
1498
34.6k
  *partp = NULL;
1499
34.6k
  if(!part)
1500
0
    return CURLUE_OUT_OF_MEMORY;
1501
34.6k
  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
34.6k
  if(urldecode) {
1511
2.25k
    char *decoded;
1512
2.25k
    size_t dlen;
1513
    /* this unconditional rejection of control bytes is documented API
1514
       behavior */
1515
2.25k
    CURLcode result = Curl_urldecode(part, partlen, &decoded, &dlen,
1516
2.25k
                                     REJECT_CTRL);
1517
2.25k
    curlx_free(part);
1518
2.25k
    if(result)
1519
1
      return CURLUE_URLDECODE;
1520
2.24k
    part = decoded;
1521
2.24k
    partlen = dlen;
1522
2.24k
  }
1523
34.6k
  if(urlencode) {
1524
8.50k
    struct dynbuf enc;
1525
8.50k
    curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1526
8.50k
    uc = urlencode_str(&enc, part, partlen, TRUE, what == CURLUPART_QUERY ?
1527
8.50k
                       QUERY_YES : QUERY_NO);
1528
8.50k
    curlx_free(part);
1529
8.50k
    if(uc)
1530
0
      return uc;
1531
8.50k
    part = curlx_dyn_ptr(&enc);
1532
8.50k
  }
1533
26.1k
  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
26.1k
  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
34.6k
  *partp = part;
1557
34.6k
  return CURLUE_OK;
1558
34.6k
}
1559
1560
static CURLUcode file_url(const CURLU *u, char **part,
1561
                          const char *fragmentsep,
1562
                          const char *querysep)
1563
20
{
1564
20
  char *url = curl_maprintf("file://%s%s%s%s%s",
1565
20
                            u->path, querysep, u->query ? u->query : "",
1566
20
                            fragmentsep, u->fragment ? u->fragment : "");
1567
20
  if(!url)
1568
0
    return CURLUE_OUT_OF_MEMORY;
1569
1570
20
  *part = url;
1571
20
  return CURLUE_OK;
1572
20
}
1573
1574
static CURLUcode urlget_url(const CURLU *u, char **part, unsigned int flags)
1575
12.0k
{
1576
12.0k
  char *url;
1577
12.0k
  char *allochost = NULL;
1578
12.0k
  const char *fragmentsep =
1579
12.0k
    (u->fragment || (u->fragment_present && flags & CURLU_GET_EMPTY)) ?
1580
11.9k
    "#" : "";
1581
12.0k
  const char *querysep = ((u->query && u->query[0]) ||
1582
11.4k
                          (u->query_present && flags & CURLU_GET_EMPTY)) ?
1583
11.3k
    "?" : "";
1584
12.0k
  char portbuf[7];
1585
12.0k
  if(curl_strequal("file", u->scheme))
1586
20
    return file_url(u, part, fragmentsep, querysep);
1587
11.9k
  else if(!u->host)
1588
2.73k
    return CURLUE_NO_HOST;
1589
9.25k
  else {
1590
9.25k
    const char *scheme;
1591
9.25k
    char *options = u->options;
1592
9.25k
    char *port = NULL;
1593
9.25k
    const struct Curl_scheme *h = NULL;
1594
9.25k
    char schemebuf[MAX_SCHEME_LEN + 5];
1595
9.25k
    if(u->scheme)
1596
9.25k
      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
9.25k
    if(u->port_present) {
1603
74
      curl_msnprintf(portbuf, sizeof(portbuf), "%u", u->portnum);
1604
74
      port = portbuf;
1605
74
    }
1606
1607
9.25k
    h = Curl_get_scheme(scheme);
1608
9.25k
    if(h) {
1609
9.05k
      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
9.05k
      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
9.05k
      if(!(h->flags & PROTOPT_URLOPTIONS))
1623
9.03k
        options = NULL;
1624
9.05k
    }
1625
1626
9.25k
    if(u->host[0] == '[') {
1627
326
      if(u->zoneid) {
1628
        /* make it '[ host %25 zoneid ]' */
1629
30
        struct dynbuf enc;
1630
30
        size_t hostlen = strlen(u->host);
1631
30
        curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1632
30
        if(curlx_dyn_addf(&enc, "%.*s%%25%s]", (int)hostlen - 1, u->host,
1633
30
                          u->zoneid))
1634
0
          return CURLUE_OUT_OF_MEMORY;
1635
30
        allochost = curlx_dyn_ptr(&enc);
1636
30
      }
1637
326
    }
1638
8.92k
    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
8.92k
    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
8.92k
    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
9.25k
    if(!(flags & CURLU_NO_GUESS_SCHEME) || !u->guessed_scheme)
1663
9.25k
      curl_msnprintf(schemebuf, sizeof(schemebuf), "%s://", scheme);
1664
0
    else
1665
0
      schemebuf[0] = 0;
1666
1667
9.25k
    url = curl_maprintf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
1668
9.25k
                        schemebuf,
1669
9.25k
                        u->user ? u->user : "",
1670
9.25k
                        u->password ? ":" : "",
1671
9.25k
                        u->password ? u->password : "",
1672
9.25k
                        options ? ";" : "",
1673
9.25k
                        options ? options : "",
1674
9.25k
                        (u->user || u->password || options) ? "@" : "",
1675
9.25k
                        allochost ? allochost : u->host,
1676
9.25k
                        port ? ":" : "",
1677
9.25k
                        port ? port : "",
1678
9.25k
                        u->path ? u->path : "/",
1679
9.25k
                        querysep,
1680
9.25k
                        u->query ? u->query : "",
1681
9.25k
                        fragmentsep,
1682
9.25k
                        u->fragment ? u->fragment : "");
1683
9.25k
    curlx_free(allochost);
1684
9.25k
  }
1685
9.25k
  if(!url)
1686
0
    return CURLUE_OUT_OF_MEMORY;
1687
9.25k
  *part = url;
1688
9.25k
  return CURLUE_OK;
1689
9.25k
}
1690
1691
CURLUcode curl_url_get(const CURLU *u, CURLUPart what,
1692
                       char **part, unsigned int flags)
1693
96.4k
{
1694
96.4k
  const char *ptr;
1695
96.4k
  CURLUcode ifmissing = CURLUE_UNKNOWN_PART;
1696
96.4k
  char portbuf[7];
1697
96.4k
  bool plusdecode = FALSE;
1698
96.4k
  if(!u)
1699
0
    return CURLUE_BAD_HANDLE;
1700
96.4k
  if(!part)
1701
0
    return CURLUE_BAD_PARTPOINTER;
1702
96.4k
  *part = NULL;
1703
1704
96.4k
  switch(what) {
1705
13.8k
  case CURLUPART_SCHEME:
1706
13.8k
    ptr = u->scheme;
1707
13.8k
    ifmissing = CURLUE_NO_SCHEME;
1708
13.8k
    flags &= ~U_CURLU_URLDECODE; /* never for schemes */
1709
13.8k
    if((flags & CURLU_NO_GUESS_SCHEME) && u->guessed_scheme)
1710
427
      return CURLUE_NO_SCHEME;
1711
13.4k
    break;
1712
13.4k
  case CURLUPART_USER:
1713
10.6k
    ptr = u->user;
1714
10.6k
    ifmissing = CURLUE_NO_USER;
1715
10.6k
    break;
1716
10.6k
  case CURLUPART_PASSWORD:
1717
10.6k
    ptr = u->password;
1718
10.6k
    ifmissing = CURLUE_NO_PASSWORD;
1719
10.6k
    break;
1720
8.50k
  case CURLUPART_OPTIONS:
1721
8.50k
    ptr = u->options;
1722
8.50k
    ifmissing = CURLUE_NO_OPTIONS;
1723
8.50k
    break;
1724
10.9k
  case CURLUPART_HOST:
1725
10.9k
    ptr = u->host;
1726
10.9k
    ifmissing = CURLUE_NO_HOST;
1727
10.9k
    break;
1728
10.6k
  case CURLUPART_ZONEID:
1729
10.6k
    ptr = u->zoneid;
1730
10.6k
    ifmissing = CURLUE_NO_ZONEID;
1731
10.6k
    break;
1732
2.24k
  case CURLUPART_PORT:
1733
2.24k
    ptr = NULL;
1734
2.24k
    ifmissing = CURLUE_NO_PORT;
1735
2.24k
    flags &= ~U_CURLU_URLDECODE; /* never for port */
1736
2.24k
    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
2.24k
    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
2.24k
    break;
1760
8.50k
  case CURLUPART_PATH:
1761
8.50k
    ptr = u->path;
1762
8.50k
    if(!ptr)
1763
7.45k
      ptr = "/";
1764
8.50k
    break;
1765
8.50k
  case CURLUPART_QUERY:
1766
8.50k
    ptr = u->query;
1767
8.50k
    ifmissing = CURLUE_NO_QUERY;
1768
8.50k
    plusdecode = flags & CURLU_URLDECODE;
1769
8.50k
    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
8.50k
    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
12.0k
  case CURLUPART_URL:
1781
12.0k
    return urlget_url(u, part, flags);
1782
0
  default:
1783
0
    ptr = NULL;
1784
0
    break;
1785
96.4k
  }
1786
84.0k
  if(ptr)
1787
34.6k
    return urlget_format(u, what, ptr, part, plusdecode, flags);
1788
1789
49.4k
  return ifmissing;
1790
84.0k
}
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
50
{
1825
50
  curl_off_t port;
1826
50
  if(!ISDIGIT(provided_port[0]))
1827
    /* not a number */
1828
0
    return CURLUE_BAD_PORT_NUMBER;
1829
50
  if(curlx_str_number(&provided_port, &port, 0xffff) || *provided_port)
1830
    /* weirdly provided number, not good! */
1831
0
    return CURLUE_BAD_PORT_NUMBER;
1832
50
  u->portnum = (uint16_t)port;
1833
50
  u->port_present = TRUE;
1834
50
  return CURLUE_OK;
1835
50
}
1836
1837
static CURLUcode set_url(CURLU *u, const char *url, size_t part_size,
1838
                         unsigned int flags)
1839
13.2k
{
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
13.2k
  CURLUcode uc;
1847
13.2k
  char *oldurl = NULL;
1848
1849
13.2k
  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.17k
    uc = curl_url_get(u, CURLUPART_URL, &oldurl, flags);
1853
1.17k
    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.17k
    if(uc == CURLUE_OUT_OF_MEMORY)
1863
0
      return uc;
1864
1.17k
    return CURLUE_MALFORMED_INPUT;
1865
1.17k
  }
1866
1867
  /* if the new URL is absolute replace the existing with the new. */
1868
12.0k
  if(Curl_is_absolute_url(url, NULL, 0,
1869
12.0k
                          flags & (CURLU_GUESS_SCHEME | CURLU_DEFAULT_SCHEME)))
1870
10.1k
    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
1.95k
  uc = curl_url_get(u, CURLUPART_URL, &oldurl,
1878
1.95k
                    (flags & ~CURLU_NO_GUESS_SCHEME) | CURLU_GET_EMPTY);
1879
1.95k
  if(uc == CURLUE_OUT_OF_MEMORY)
1880
0
    return uc;
1881
1.95k
  else if(uc)
1882
1.56k
    return parseurl_and_replace(url, u, flags);
1883
1884
384
  DEBUGASSERT(oldurl); /* it is set here */
1885
  /* apply the relative part to create a new URL */
1886
384
  uc = redirect_url(oldurl, url, u, flags);
1887
384
  curlx_free(oldurl);
1888
384
  return uc;
1889
384
}
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
13.2k
{
2102
13.2k
  char **storep = NULL;
2103
13.2k
  bool urlencode = (flags & CURLU_URLENCODE) ? 1 : 0;
2104
13.2k
  bool plusencode = FALSE;
2105
13.2k
  bool pathmode = FALSE;
2106
13.2k
  bool leadingslash = FALSE;
2107
13.2k
  bool appendquery = FALSE;
2108
13.2k
  bool equalsencode = FALSE;
2109
13.2k
  size_t nalloc;
2110
2111
13.2k
  if(!u)
2112
0
    return CURLUE_BAD_HANDLE;
2113
13.2k
  if(!part)
2114
    /* setting a part to NULL clears it */
2115
0
    return urlset_clear(u, what);
2116
2117
13.2k
  nalloc = strlen(part);
2118
13.2k
  if(nalloc > CURL_MAX_INPUT_LENGTH)
2119
    /* excessive input length */
2120
0
    return CURLUE_MALFORMED_INPUT;
2121
2122
13.2k
  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
50
  case CURLUPART_PORT:
2148
50
    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
13.2k
  case CURLUPART_URL:
2166
13.2k
    return set_url(u, part, nalloc, flags);
2167
0
  default:
2168
0
    return CURLUE_UNKNOWN_PART;
2169
13.2k
  }
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
8.45k
{
2244
8.45k
  if(u->port_present) {
2245
53
    *pport = u->portnum;
2246
53
    return CURLUE_OK;
2247
53
  }
2248
8.40k
  else if(u->scheme) {
2249
8.40k
    const struct Curl_scheme *s = Curl_get_scheme(u->scheme);
2250
8.40k
    if(s && s->defport) {
2251
8.38k
      *pport = s->defport;
2252
8.38k
      return CURLUE_OK;
2253
8.38k
    }
2254
8.40k
  }
2255
19
  *pport = 0;
2256
19
  return CURLUE_NO_PORT;
2257
8.45k
}