Coverage Report

Created: 2026-09-14 07:08

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