Coverage Report

Created: 2026-09-14 07:06

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
79
  ((('a' <= (str)[0] && (str)[0] <= 'z') ||                \
50
79
    ('A' <= (str)[0] && (str)[0] <= 'Z')) &&               \
51
79
   ((str)[1] == ':' || (str)[1] == '|') &&                 \
52
79
   ((str)[2] == '/' || (str)[2] == '\\' || (str)[2] == 0))
53
54
/* scheme is not URL encoded, the longest libcurl supported ones are... */
55
143k
#define MAX_SCHEME_LEN 40
56
788
#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
60.1k
{
71
60.1k
  curlx_free(u->scheme);
72
60.1k
  curlx_free(u->user);
73
60.1k
  curlx_strzero(u->password);
74
60.1k
  curlx_free(u->password);
75
60.1k
  curlx_free(u->options);
76
60.1k
  curlx_free(u->host);
77
60.1k
  curlx_free(u->zoneid);
78
60.1k
  curlx_free(u->path);
79
60.1k
  curlx_free(u->query);
80
60.1k
  curlx_free(u->fragment);
81
60.1k
}
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
157
{
89
  /* Find the start of the hostname */
90
157
  const char *sep = strstr(url, "//");
91
157
  if(!sep)
92
156
    sep = url;
93
1
  else
94
1
    sep += 2;
95
96
  /* Find first / or ? */
97
929
  while(*sep && *sep != '/' && *sep != '?')
98
772
    sep++;
99
100
157
  return sep;
101
157
}
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
27.6k
{
131
  /* we must add this with whitespace-replacing */
132
27.6k
  const unsigned char *iptr;
133
27.6k
  const unsigned char *host_sep = (const unsigned char *)url;
134
27.6k
  CURLcode result = CURLE_OK;
135
136
27.6k
  DEBUGASSERT((query >= QUERY_NO) && (query <= QUERY_YES));
137
138
27.6k
  if(!relative) {
139
157
    size_t n;
140
157
    host_sep = (const unsigned char *)find_host_sep(url);
141
142
    /* output the first piece as-is */
143
157
    n = (const char *)host_sep - url;
144
157
    result = curlx_dyn_addn(o, url, n);
145
157
    len -= n;
146
157
  }
147
148
17.2M
  for(iptr = host_sep; len && !result;) {
149
17.2M
    if(*iptr == ' ') {
150
903
      if(query != QUERY_YES)
151
699
        result = curlx_dyn_addn(o, "%20", 3);
152
204
      else
153
204
        result = curlx_dyn_addn(o, "+", 1);
154
903
      iptr++;
155
903
      len--;
156
903
    }
157
17.2M
    else if((*iptr < ' ') || (*iptr >= 0x7f)) {
158
17.0M
      unsigned char out[3] = { '%' };
159
17.0M
      Curl_hexbyte(&out[1], *iptr);
160
17.0M
      result = curlx_dyn_addn(o, out, 3);
161
17.0M
      iptr++;
162
17.0M
      len--;
163
17.0M
    }
164
227k
    else if(*iptr == '%' && (len >= 3) &&
165
53.6k
            ISXDIGIT(iptr[1]) && ISXDIGIT(iptr[2]) &&
166
20.0k
            (ISLOWER(iptr[1]) || ISLOWER(iptr[2]))) {
167
      /* uppercase it */
168
18.4k
      unsigned char hex = (unsigned char)((curlx_hexval(iptr[1]) << 4) |
169
18.4k
                                          curlx_hexval(iptr[2]));
170
18.4k
      unsigned char out[3] = { '%' };
171
18.4k
      Curl_hexbyte(&out[1], hex);
172
18.4k
      result = curlx_dyn_addn(o, out, 3);
173
18.4k
      iptr += 3;
174
18.4k
      len -= 3;
175
18.4k
    }
176
209k
    else {
177
209k
      const unsigned char *start = iptr;
178
16.9M
      while(len) {
179
16.8M
        if(*iptr == ' ' || *iptr < ' ' || *iptr >= 0x7f)
180
165k
          break;
181
16.7M
        if(*iptr == '%' && (len >= 3) &&
182
3.48M
           ISXDIGIT(iptr[1]) && ISXDIGIT(iptr[2]) &&
183
2.96M
           (ISLOWER(iptr[1]) || ISLOWER(iptr[2])))
184
17.0k
          break;
185
16.7M
        if(*iptr == '?') {
186
513
          if(query == QUERY_NOT_YET) {
187
75
            iptr++;
188
75
            len--;
189
75
            query = QUERY_YES;
190
75
            break;
191
75
          }
192
513
        }
193
16.7M
        iptr++;
194
16.7M
        len--;
195
16.7M
      }
196
209k
      result = curlx_dyn_addn(o, (const char *)start, (size_t)(iptr - start));
197
209k
    }
198
17.2M
  }
199
200
27.6k
  if(result)
201
0
    return cc2cu(result);
202
27.6k
  return CURLUE_OK;
203
27.6k
}
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
65.2k
{
216
65.2k
  size_t i = 0;
217
65.2k
  DEBUGASSERT(!buf || (buflen > MAX_SCHEME_LEN));
218
65.2k
  (void)buflen; /* only used in debug-builds */
219
65.2k
  if(buf)
220
31.6k
    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
65.2k
  if(ISALPHA(url[0])) {
226
46.3k
    if(buf)
227
23.4k
      buf[0] = Curl_raw_tolower(url[0]);
228
143k
    for(i = 1; i < MAX_SCHEME_LEN; ++i) {
229
143k
      char s = url[i];
230
143k
      if(s && (ISALNUM(s) || (s == '+') || (s == '-') || (s == '.'))) {
231
96.9k
        if(buf)
232
48.9k
          buf[i] = Curl_raw_tolower(s);
233
96.9k
      }
234
46.3k
      else {
235
46.3k
        break;
236
46.3k
      }
237
143k
    }
238
46.3k
  }
239
65.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
32.3k
    size_t len = i;
246
32.3k
    if(buf)
247
16.5k
      buf[i] = 0;
248
32.3k
    return len;
249
32.3k
  }
250
32.9k
  if(buf)
251
15.1k
    buf[0] = 0;
252
32.9k
  return 0;
253
65.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
9.21k
{
258
9.21k
  const uint8_t *p = (const unsigned char *)input;
259
9.21k
  const uint8_t control = flags & CURLU_ALLOW_SPACE ? 0x1f : 0x20;
260
51.9M
  while(n--) {
261
51.9M
    if(*p <= control || *p == 127)
262
1
      return TRUE;
263
51.9M
    p++;
264
51.9M
  }
265
9.21k
  return FALSE;
266
9.21k
}
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
31.5k
{
287
31.5k
  CURLUcode ures = CURLUE_OK;
288
31.5k
  CURLcode result;
289
31.5k
  char *userp = NULL;
290
31.5k
  char *passwdp = NULL;
291
31.5k
  char *optionsp = NULL;
292
31.5k
  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
31.5k
  const char *ptr;
302
303
31.5k
  DEBUGASSERT(login);
304
305
31.5k
  *hostname_offset = 0;
306
31.5k
  ptr = memchr(login, '@', len);
307
31.5k
  if(!ptr)
308
27.6k
    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
3.97k
  ptr++;
314
315
  /* if this is a known scheme, get some details */
316
3.97k
  if(u->scheme)
317
2.30k
    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
3.97k
  result = Curl_parse_login_details(login, ptr - login - 1,
322
3.97k
                                    &userp, &passwdp,
323
3.97k
                                    (h && (h->flags & PROTOPT_URLOPTIONS)) ?
324
3.97k
                                    &optionsp : NULL);
325
3.97k
  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
3.97k
  if(userp) {
333
3.97k
    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
3.97k
    curlx_free(u->user);
339
3.97k
    u->user = userp;
340
3.97k
  }
341
342
3.97k
  if(passwdp) {
343
450
    curlx_strzero(u->password);
344
450
    curlx_free(u->password);
345
450
    u->password = passwdp;
346
450
  }
347
348
3.97k
  if(optionsp) {
349
3
    curlx_free(u->options);
350
3
    u->options = optionsp;
351
3
  }
352
353
3.97k
  if(userp && badoctets(userp, strlen(userp), flags))
354
0
    ures = CURLUE_BAD_USER;
355
3.97k
  else if(passwdp && badoctets(passwdp, strlen(passwdp), flags))
356
0
    ures = CURLUE_BAD_PASSWORD;
357
3.97k
  else if(optionsp && badoctets(optionsp, strlen(optionsp), flags))
358
0
    ures = CURLUE_MALFORMED_INPUT;
359
360
3.97k
  userp = passwdp = optionsp = NULL;
361
362
3.97k
  if(!ures) {
363
    /* the hostname starts at this offset */
364
3.97k
    *hostname_offset = ptr - login;
365
3.97k
    return CURLUE_OK;
366
3.97k
  }
367
368
27.6k
out:
369
370
27.6k
  curlx_free(userp);
371
27.6k
  curlx_strzero(passwdp);
372
27.6k
  curlx_free(passwdp);
373
27.6k
  curlx_free(optionsp);
374
27.6k
  curlx_safefree(u->user);
375
27.6k
  curlx_strzero(u->password);
376
27.6k
  curlx_safefree(u->password);
377
27.6k
  curlx_safefree(u->options);
378
379
27.6k
  return ures;
380
3.97k
}
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
31.5k
{
388
31.5k
  const char *portptr;
389
31.5k
  const char *hostname = curlx_dyn_ptr(host);
390
  /*
391
   * Find the end of an IPv6 address on the ']' ending bracket.
392
   */
393
31.5k
  u->portnum = 0;
394
31.5k
  u->port_present = FALSE;
395
31.5k
  if(hostname[0] == '[') {
396
908
    portptr = memchr(hostname + 1, ']', curlx_dyn_len(host) - 1);
397
908
    if(!portptr)
398
2
      return CURLUE_BAD_IPV6;
399
906
    portptr++;
400
    /* this is a RFC2732-style specified IP-address */
401
906
    if(*portptr) {
402
40
      if(*portptr != ':')
403
1
        return CURLUE_BAD_PORT_NUMBER;
404
40
    }
405
866
    else
406
866
      portptr = NULL;
407
906
  }
408
30.6k
  else
409
30.6k
    portptr = memchr(hostname, ':', curlx_dyn_len(host));
410
411
31.5k
  if(portptr) {
412
611
    curl_off_t port;
413
611
    size_t keep = portptr - hostname;
414
611
    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
611
    curlx_dyn_setlen(host, keep);
423
611
    portptr++;
424
611
    if(!*portptr)
425
232
      return has_scheme ? CURLUE_OK : CURLUE_BAD_PORT_NUMBER;
426
379
    if(*portptr == '\\')
427
1
      return CURLUE_BACKSLASH;
428
378
    rc = curlx_str_number(&portptr, &port, 0xffff);
429
378
    if(rc)
430
19
      return CURLUE_BAD_PORT_NUMBER;
431
359
    else if(*portptr == '\\')
432
1
      return CURLUE_BACKSLASH;
433
358
    else if(*portptr)
434
6
      return CURLUE_BAD_PORT_NUMBER;
435
436
352
    u->portnum = (uint16_t)port;
437
352
    u->port_present = TRUE;
438
352
  }
439
440
31.3k
  return CURLUE_OK;
441
31.5k
}
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
904
{
453
904
  size_t len;
454
904
  DEBUGASSERT(*hostname == '[');
455
904
  if(hlen < 4) /* '[::]' is the shortest possible valid string */
456
2
    return CURLUE_BAD_IPV6;
457
902
  hostname++;
458
902
  hlen -= 2;
459
460
  /* only valid IPv6 letters are ok */
461
902
  len = strspn(hostname, "0123456789abcdefABCDEF:.");
462
463
902
  if(hlen != len) {
464
256
    hlen = len;
465
256
    if(hostname[len] == '%') {
466
      /* this could now be '%[zone id]' */
467
254
      char zoneid[MAX_ZONEID_LEN];
468
254
      int i = 0;
469
254
      char *h = &hostname[len + 1];
470
      /* pass '25' if present and is a URL encoded percent sign */
471
254
      if(!strncmp(h, "25", 2) && h[2] && (h[2] != ']'))
472
1
        h += 2;
473
1.04k
      while(*h && (*h != ']') && (i < (MAX_ZONEID_LEN - 1)) &&
474
787
            (*h != ' '))
475
787
        zoneid[i++] = *h++;
476
254
      if(!i || (']' != *h))
477
2
        return CURLUE_BAD_IPV6;
478
252
      zoneid[i] = 0;
479
252
      u->zoneid = curlx_strdup(zoneid);
480
252
      if(!u->zoneid)
481
0
        return CURLUE_OUT_OF_MEMORY;
482
252
      hostname[len] = ']'; /* insert end bracket */
483
252
      hostname[len + 1] = 0; /* terminate the hostname */
484
252
    }
485
2
    else
486
2
      return CURLUE_BAD_IPV6;
487
    /* hostname is fine */
488
256
  }
489
490
  /* Normalize the IPv6 address */
491
898
  {
492
898
    char dest[16]; /* fits a binary IPv6 address */
493
898
    hostname[hlen] = 0; /* end the address there */
494
898
    if(curlx_inet_pton(AF_INET6, hostname, dest) != 1)
495
28
      return CURLUE_BAD_IPV6;
496
870
    if(!curlx_inet_ntop(AF_INET6, dest, hostname, hlen + 1)) {
497
738
      hlen = strlen(hostname); /* might be shorter now */
498
738
      hostname[hlen + 1] = 0;
499
738
    }
500
870
    hostname[hlen] = ']'; /* restore ending bracket */
501
870
  }
502
0
  return CURLUE_OK;
503
898
}
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
13.9k
{
522
13.9k
  size_t i;
523
722k
  for(i = 0; i < hlen; i++) {
524
709k
    if(invalid_host_char[(unsigned char)hostname[i]])
525
81
      return CURLUE_BAD_HOSTNAME;
526
709k
  }
527
13.8k
  if((hlen >= 2) &&
528
2.37k
     (hostname[hlen - 1] == '.') && (hostname[hlen - 2] == '.'))
529
    /* more than one trailing dot is not allowed */
530
6
    return CURLUE_BAD_HOSTNAME;
531
13.8k
  else if((hlen == 1) && (hostname[0] == '.'))
532
    /* a single dot alone is not allowed */
533
2
    return CURLUE_BAD_HOSTNAME;
534
13.8k
  return CURLUE_OK;
535
13.8k
}
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
30.5k
{
571
30.5k
  bool done = FALSE;
572
30.5k
  int n = 0;
573
30.5k
  const char *c = curlx_dyn_ptr(host);
574
30.5k
  unsigned int parts[4] = { 0, 0, 0, 0 };
575
30.5k
  CURLcode result = CURLE_OK;
576
577
30.5k
  if(!ISDIGIT(*c))
578
12.8k
    return HOST_NAME;
579
580
58.6k
  while(!done) {
581
41.6k
    int rc;
582
41.6k
    curl_off_t l;
583
41.6k
    if(*c == '0') {
584
24.7k
      if((c[1] | 0x20) == 'x') {
585
113
        c += 2; /* skip the prefix */
586
113
        rc = curlx_str_hex(&c, &l, UINT_MAX);
587
113
        if(rc)
588
64
          return HOST_NAME;
589
113
      }
590
24.5k
      else
591
24.5k
        rc = curlx_str_octal(&c, &l, UINT_MAX);
592
24.7k
    }
593
16.9k
    else
594
16.9k
      rc = curlx_str_number(&c, &l, UINT_MAX);
595
596
41.5k
    if(rc) {
597
192
      if(!n || (rc != STRE_NO_NUM) || *c)
598
111
        return HOST_NAME;
599
81
      n--;
600
81
    }
601
41.3k
    else
602
41.3k
      parts[n] = (unsigned int)l;
603
604
41.4k
    switch(*c) {
605
24.0k
    case '.':
606
24.0k
      if(n == 3) {
607
75
        if(c[1])
608
          /* something follows this dot */
609
50
          return HOST_NAME;
610
25
        done = TRUE;
611
25
      }
612
23.9k
      else {
613
23.9k
        n++;
614
23.9k
        c++;
615
23.9k
      }
616
24.0k
      break;
617
618
24.0k
    case '\0':
619
16.9k
      done = TRUE;
620
16.9k
      break;
621
622
390
    default:
623
390
      return HOST_NAME;
624
41.4k
    }
625
41.4k
  }
626
627
17.0k
  switch(n) {
628
8.96k
  case 0: /* a -- 32 bits */
629
8.96k
    curlx_dyn_reset(host);
630
631
8.96k
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
632
8.96k
                            (parts[0] >> 24),
633
8.96k
                            ((parts[0] >> 16) & 0xff),
634
8.96k
                            ((parts[0] >> 8) & 0xff),
635
8.96k
                            (parts[0] & 0xff));
636
8.96k
    break;
637
177
  case 1: /* a.b -- 8.24 bits */
638
177
    if((parts[0] > 0xff) || (parts[1] > 0xffffff))
639
92
      return HOST_NAME;
640
85
    curlx_dyn_reset(host);
641
85
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
642
85
                            parts[0],
643
85
                            ((parts[1] >> 16) & 0xff),
644
85
                            ((parts[1] >> 8) & 0xff),
645
85
                            (parts[1] & 0xff));
646
85
    break;
647
224
  case 2: /* a.b.c -- 8.8.16 bits */
648
224
    if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xffff))
649
147
      return HOST_NAME;
650
77
    curlx_dyn_reset(host);
651
77
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
652
77
                            parts[0],
653
77
                            parts[1],
654
77
                            ((parts[2] >> 8) & 0xff),
655
77
                            (parts[2] & 0xff));
656
77
    break;
657
7.65k
  case 3: /* a.b.c.d -- 8.8.8.8 bits */
658
7.65k
    if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xff) ||
659
7.52k
       (parts[3] > 0xff))
660
199
      return HOST_NAME;
661
7.45k
    curlx_dyn_reset(host);
662
7.45k
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
663
7.45k
                            parts[0],
664
7.45k
                            parts[1],
665
7.45k
                            parts[2],
666
7.45k
                            parts[3]);
667
7.45k
    break;
668
17.0k
  }
669
16.5k
  if(result)
670
0
    return HOST_ERROR;
671
16.5k
  return HOST_IPV4;
672
16.5k
}
673
674
/* if necessary, replace the host content with a URL decoded version */
675
static CURLUcode urldecode_host(struct dynbuf *host)
676
31.4k
{
677
31.4k
  const char *per;
678
31.4k
  const char *hostname = curlx_dyn_ptr(host);
679
31.4k
  per = memchr(hostname, '%', curlx_dyn_len(host));
680
31.4k
  if(!per)
681
    /* nothing to decode */
682
31.1k
    return CURLUE_OK;
683
292
  else {
684
    /* encoded */
685
292
    size_t dlen;
686
292
    char *decoded;
687
292
    CURLcode result = Curl_urldecode(hostname, 0, &decoded, &dlen,
688
292
                                     REJECT_CTRL);
689
292
    if(result)
690
2
      return CURLUE_BAD_HOSTNAME;
691
290
    curlx_dyn_reset(host);
692
290
    result = curlx_dyn_addn(host, decoded, dlen);
693
290
    curlx_free(decoded);
694
290
    if(result)
695
0
      return cc2cu(result);
696
290
  }
697
698
290
  return CURLUE_OK;
699
31.4k
}
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
31.5k
{
707
31.5k
  size_t offset;
708
31.5k
  CURLUcode uc;
709
31.5k
  CURLcode result;
710
711
  /*
712
   * Parse the login details and strip them out of the hostname.
713
   */
714
31.5k
  uc = parse_hostname_login(u, auth, authlen, flags, &offset);
715
31.5k
  if(uc)
716
1
    return uc;
717
718
31.5k
  result = curlx_dyn_addn(host, auth + offset, authlen - offset);
719
31.5k
  if(result) {
720
0
    uc = cc2cu(result);
721
0
    return uc;
722
0
  }
723
724
  /* parse_port() also sets the hostname length correctly */
725
31.5k
  uc = parse_port(u, host, has_scheme);
726
727
31.5k
  if(!curlx_dyn_len(host))
728
    /* this makes no-host errors override port number problems */
729
129
    uc = CURLUE_NO_HOST;
730
31.5k
  if(!uc)
731
31.4k
    uc = urldecode_host(host);
732
31.5k
  if(uc)
733
181
    ;
734
31.4k
  else if(auth[offset] == '[')
735
904
    uc = ipv6_parse(u, curlx_dyn_ptr(host), curlx_dyn_len(host));
736
30.5k
  else {
737
    /* ipv4_normalize() returns *NAME, *IPV4 or *ERROR */
738
30.5k
    int type = ipv4_normalize(host);
739
740
30.5k
    if(type == HOST_NAME)
741
13.9k
      uc = hostname_check(curlx_dyn_ptr(host), curlx_dyn_len(host));
742
16.5k
    else if(type == HOST_ERROR)
743
0
      uc = CURLUE_OUT_OF_MEMORY;
744
30.5k
  }
745
746
31.5k
  return uc;
747
31.5k
}
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
3.66M
{
776
3.66M
  const char *p = *str;
777
3.66M
  if(*p == '.') {
778
100k
    (*str)++;
779
100k
    (*clen)--;
780
100k
    return TRUE;
781
100k
  }
782
3.56M
  else if((*clen >= 3) &&
783
3.56M
          (p[0] == '%') && (p[1] == '2') && ((p[2] | 0x20) == 'e')) {
784
1.27k
    *str += 3;
785
1.27k
    *clen -= 3;
786
1.27k
    return TRUE;
787
1.27k
  }
788
3.56M
  return FALSE;
789
3.66M
}
790
791
46.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
3.30k
{
796
  /* a single byte path cannot be cleaned up */
797
3.30k
  if(pn < 2)
798
8
    return FALSE;
799
3.29k
  if(!memchr(p, '.', pn) && !memchr(p, '%', pn))
800
679
    return FALSE;
801
3.28M
  while(pn) {
802
3.28M
    if(is_dot(&p, &pn)) {
803
      /* "./" or dot before end of string */
804
5.58k
      if(!pn || ISSLASH(*p))
805
498
        return TRUE;
806
      /* "../" or ".." before end of string */
807
5.08k
      else if(is_dot(&p, &pn) && (!pn || ISSLASH(*p)))
808
248
        return TRUE;
809
5.58k
    }
810
3.27M
    else {
811
3.27M
      p++;
812
3.27M
      pn--;
813
3.27M
    }
814
3.28M
  }
815
1.87k
  return FALSE;
816
2.61k
}
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
3.30k
{
837
3.30k
  struct dynbuf out;
838
3.30k
  CURLcode result = CURLE_OK;
839
840
  /* variables for leading dot checks */
841
3.30k
  const char *dinput = input;
842
3.30k
  size_t dlen = clen;
843
844
3.30k
  *outp = NULL;
845
3.30k
  if(!needs_dedotdot(input, clen))
846
2.55k
    return 0;
847
848
746
  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
746
  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
46.4M
  while(clen && !result) { /* until end of path content */
874
46.4M
    if(ISSLASH(*input)) {
875
337k
      const char *p = &input[1];
876
337k
      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
337k
      if(is_dot(&p, &blen)) {
881
74.5k
        if(!blen) { /* /. */
882
47
          result = curlx_dyn_addn(&out, "/", 1);
883
47
          break;
884
47
        }
885
74.4k
        else if(ISSLASH(*p)) { /* /./ */
886
30.2k
          input = p;
887
30.2k
          clen = blen;
888
30.2k
          continue;
889
30.2k
        }
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
44.1k
        else if(is_dot(&p, &blen) && (ISSLASH(*p) || !blen)) {
896
          /* remove the last segment from the output buffer */
897
7.70k
          size_t len = curlx_dyn_len(&out);
898
7.70k
          if(len) {
899
7.27k
            const char *ptr = curlx_dyn_ptr(&out);
900
7.27k
            const char *last = memrchr(ptr, '/', len);
901
7.27k
            if(last)
902
              /* trim the output at the slash */
903
7.27k
              curlx_dyn_setlen(&out, last - ptr);
904
7.27k
          }
905
906
7.70k
          if(blen) { /* /../ */
907
7.68k
            input = p;
908
7.68k
            clen = blen;
909
7.68k
            continue;
910
7.68k
          }
911
19
          result = curlx_dyn_addn(&out, "/", 1);
912
19
          break;
913
7.70k
        }
914
74.5k
      }
915
337k
    }
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
46.4M
    result = curlx_dyn_addn(&out, input, 1);
923
46.4M
    input++;
924
46.4M
    clen--;
925
46.4M
  }
926
746
end:
927
746
  if(!result) {
928
746
    if(curlx_dyn_len(&out))
929
746
      *outp = curlx_dyn_ptr(&out);
930
0
    else {
931
0
      *outp = curlx_strdup("");
932
0
      if(!*outp)
933
0
        return 1;
934
0
    }
935
746
  }
936
746
  return result ? 1 : 0; /* success */
937
746
}
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
45
{
947
45
  const char *path;
948
45
  size_t pathlen;
949
950
45
  *pathp = NULL;
951
45
  *pathlenp = 0;
952
45
  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
44
  path = &url[5];
958
44
  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
44
  if(path[0] != '/')
964
1
    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
43
  if(path[1] == '/') {
973
    /* swallow the two slashes */
974
13
    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
13
    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
5
      if(checkprefix("localhost/", ptr) ||
996
5
         checkprefix("127.0.0.1/", ptr)) {
997
1
        ptr += 9; /* now points to the slash after the host */
998
1
      }
999
4
      else
1000
        /* Invalid file://hostname/, expected localhost or 127.0.0.1 or
1001
           none */
1002
4
        return CURLUE_BAD_FILE_URL;
1003
5
    }
1004
1005
9
    path = ptr;
1006
9
    pathlen = urllen - (ptr - url);
1007
9
  }
1008
1009
39
#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
39
  if(('/' == path[0] && STARTS_WITH_URL_DRIVE_PREFIX(&path[1])) ||
1013
35
     STARTS_WITH_URL_DRIVE_PREFIX(path)) {
1014
    /* File drive letters are only accepted in MS-DOS/Windows */
1015
7
    return CURLUE_BAD_FILE_URL;
1016
7
  }
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
32
  u->scheme = curlx_strdup("file");
1026
32
  if(!u->scheme)
1027
0
    return CURLUE_OUT_OF_MEMORY;
1028
1029
32
  *pathp = path;
1030
32
  *pathlenp = pathlen;
1031
32
  return CURLUE_OK;
1032
32
}
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
31.6k
{
1038
  /* clear path */
1039
31.6k
  const char *schemep = NULL;
1040
1041
31.6k
  if(schemelen) {
1042
16.4k
    int num_slashes = 0;
1043
16.4k
    const char *p = &url[schemelen + 1];
1044
16.4k
    if(!Curl_getn_scheme(schemebuf, schemelen) &&
1045
331
       !(flags & CURLU_NON_SUPPORT_SCHEME))
1046
1
      return CURLUE_UNSUPPORTED_SCHEME;
1047
1048
16.4k
    if(!ISSLASH(*p))
1049
      /* less than one */
1050
23
      return CURLUE_BAD_SLASHES;
1051
16.4k
    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
16.4k
    else {
1058
46.1k
      while(ISSLASH(*p) && (num_slashes < 4)) {
1059
29.6k
        p++;
1060
29.6k
        num_slashes++;
1061
29.6k
      }
1062
16.4k
      if(num_slashes > 3)
1063
1
        return CURLUE_BAD_SLASHES;
1064
16.4k
    }
1065
1066
16.4k
    schemep = schemebuf;
1067
16.4k
    *hostpp = p; /* hostname starts here */
1068
16.4k
  }
1069
15.1k
  else {
1070
    /* no scheme! */
1071
1072
15.1k
    if(!(flags & (CURLU_DEFAULT_SCHEME | CURLU_GUESS_SCHEME)))
1073
0
      return CURLUE_BAD_SCHEME;
1074
1075
15.1k
    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
15.1k
    *hostpp = url;
1082
15.1k
  }
1083
1084
31.6k
  if(schemep) {
1085
16.4k
    u->scheme = curlx_strdup(schemep);
1086
16.4k
    if(!u->scheme)
1087
0
      return CURLUE_OUT_OF_MEMORY;
1088
16.4k
  }
1089
31.6k
  return CURLUE_OK;
1090
31.6k
}
1091
1092
static CURLUcode guess_scheme(CURLU *u, struct dynbuf *host)
1093
14.9k
{
1094
14.9k
  const char *hostname = curlx_dyn_ptr(host);
1095
14.9k
  const char *schemep = NULL;
1096
  /* legacy curl-style guess based on hostname */
1097
14.9k
  if(checkprefix("ftp.", hostname))
1098
3
    schemep = "ftp";
1099
14.9k
  else if(checkprefix("dict.", hostname))
1100
2
    schemep = "dict";
1101
14.9k
  else if(checkprefix("ldap.", hostname))
1102
2
    schemep = "ldap";
1103
14.9k
  else if(checkprefix("imap.", hostname))
1104
1
    schemep = "imap";
1105
14.9k
  else if(checkprefix("smtp.", hostname))
1106
1
    schemep = "smtp";
1107
14.9k
  else if(checkprefix("pop3.", hostname))
1108
7
    schemep = "pop3";
1109
14.9k
  else
1110
14.9k
    schemep = "http";
1111
1112
14.9k
  u->scheme = curlx_strdup(schemep);
1113
14.9k
  if(!u->scheme)
1114
0
    return CURLUE_OUT_OF_MEMORY;
1115
1116
14.9k
  u->guessed_scheme = TRUE;
1117
14.9k
  return CURLUE_OK;
1118
14.9k
}
1119
1120
static CURLUcode handle_fragment(CURLU *u, const char *fragment,
1121
                                 size_t fraglen, unsigned int flags)
1122
513
{
1123
513
  CURLUcode ures;
1124
513
  u->fragment_present = TRUE;
1125
513
  if(fraglen > 1) {
1126
    /* skip the leading '#' in the copy but include the null-terminator */
1127
353
    if(flags & CURLU_URLENCODE) {
1128
172
      struct dynbuf enc;
1129
172
      curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1130
172
      ures = urlencode_str(&enc, fragment + 1, fraglen - 1, TRUE, QUERY_NO);
1131
172
      if(ures)
1132
0
        return ures;
1133
172
      u->fragment = curlx_dyn_ptr(&enc);
1134
172
    }
1135
181
    else {
1136
181
      if(badoctets(fragment, fraglen, flags))
1137
0
        return CURLUE_BAD_FRAGMENT;
1138
181
      u->fragment = curlx_memdup0(fragment + 1, fraglen - 1);
1139
181
      if(!u->fragment)
1140
0
        return CURLUE_OUT_OF_MEMORY;
1141
181
    }
1142
353
  }
1143
513
  return CURLUE_OK;
1144
513
}
1145
1146
static CURLUcode handle_query(CURLU *u, const char *query,
1147
                              size_t qlen, unsigned int flags)
1148
1.65k
{
1149
1.65k
  u->query_present = TRUE;
1150
1.65k
  if(qlen > 1) {
1151
1.53k
    if(flags & CURLU_URLENCODE) {
1152
310
      struct dynbuf enc;
1153
310
      CURLUcode ures;
1154
310
      curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1155
      /* skip the leading question mark */
1156
310
      ures = urlencode_str(&enc, query + 1, qlen - 1, TRUE, QUERY_YES);
1157
310
      if(ures)
1158
0
        return ures;
1159
310
      u->query = curlx_dyn_ptr(&enc);
1160
310
    }
1161
1.22k
    else {
1162
1.22k
      if(badoctets(query, qlen, flags))
1163
0
        return CURLUE_BAD_QUERY;
1164
1165
1.22k
      u->query = curlx_memdup0(query + 1, qlen - 1);
1166
1.22k
      if(!u->query)
1167
0
        return CURLUE_OUT_OF_MEMORY;
1168
1.22k
    }
1169
1.53k
  }
1170
123
  else {
1171
    /* single byte query */
1172
123
    u->query = curlx_strdup("");
1173
123
    if(!u->query)
1174
0
      return CURLUE_OUT_OF_MEMORY;
1175
123
  }
1176
1.65k
  return CURLUE_OK;
1177
1.65k
}
1178
1179
static CURLUcode handle_path(CURLU *u, const char *path,
1180
                             size_t pathlen, unsigned int flags,
1181
                             bool is_file)
1182
31.3k
{
1183
31.3k
  CURLUcode ures;
1184
31.3k
  if(pathlen && (flags & CURLU_URLENCODE)) {
1185
4.61k
    struct dynbuf enc;
1186
4.61k
    curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1187
4.61k
    ures = urlencode_str(&enc, path, pathlen, TRUE, QUERY_NO);
1188
4.61k
    if(ures)
1189
0
      return ures;
1190
4.61k
    pathlen = curlx_dyn_len(&enc);
1191
4.61k
    path = u->path = curlx_dyn_ptr(&enc);
1192
4.61k
  }
1193
1194
31.3k
  if(pathlen >= (size_t)(1 + !is_file)) {
1195
3.38k
    if(badoctets(path, pathlen, flags))
1196
1
      return CURLUE_BAD_PATH;
1197
1198
    /* paths for file:// scheme can be one byte, others need to be two */
1199
3.38k
    if(!u->path) {
1200
2.27k
      u->path = curlx_memdup0(path, pathlen);
1201
2.27k
      if(!u->path)
1202
0
        return CURLUE_OUT_OF_MEMORY;
1203
2.27k
      path = u->path;
1204
2.27k
    }
1205
1.10k
    else if(flags & CURLU_URLENCODE)
1206
      /* it might have encoded more than the path so cut it */
1207
1.10k
      u->path[pathlen] = 0;
1208
1209
3.38k
    if(!(flags & CURLU_PATH_AS_IS)) {
1210
      /* remove ../ and ./ sequences according to RFC3986 */
1211
3.30k
      char *dedot;
1212
3.30k
      int err = dedotdotify(path, pathlen, &dedot);
1213
3.30k
      if(err)
1214
0
        return CURLUE_OUT_OF_MEMORY;
1215
3.30k
      if(dedot) {
1216
746
        curlx_free(u->path);
1217
746
        u->path = dedot;
1218
746
      }
1219
3.30k
    }
1220
3.38k
  }
1221
31.3k
  return CURLUE_OK;
1222
31.3k
}
1223
1224
static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
1225
31.6k
{
1226
31.6k
  const char *path;
1227
31.6k
  size_t pathlen;
1228
31.6k
  char schemebuf[MAX_SCHEME_LEN + 1];
1229
31.6k
  size_t schemelen = 0;
1230
31.6k
  size_t urllen;
1231
31.6k
  CURLUcode ures = CURLUE_OK;
1232
31.6k
  struct dynbuf host;
1233
31.6k
  bool is_file = FALSE;
1234
1235
31.6k
  DEBUGASSERT(url);
1236
1237
31.6k
  urllen = strlen(url);
1238
31.6k
  if(urllen > CURL_MAX_INPUT_LENGTH)
1239
0
    return CURLUE_MALFORMED_INPUT;
1240
1241
31.6k
  curlx_dyn_init(&host, CURL_MAX_INPUT_LENGTH);
1242
1243
31.6k
  schemelen = Curl_is_absolute_url(url, schemebuf, sizeof(schemebuf),
1244
31.6k
                                   flags & (CURLU_GUESS_SCHEME |
1245
31.6k
                                            CURLU_DEFAULT_SCHEME));
1246
1247
  /* handle the file: scheme */
1248
31.6k
  if(schemelen == 4 && !memcmp(schemebuf, "file", 4)) {
1249
45
    is_file = TRUE;
1250
45
    ures = parse_file(url, urllen, u, &path, &pathlen);
1251
45
  }
1252
31.6k
  else {
1253
31.6k
    const char *hostp = NULL;
1254
31.6k
    const char *p;
1255
31.6k
    size_t hostlen;
1256
31.6k
    ures = parse_scheme(url, u, schemebuf, schemelen, flags, &hostp);
1257
31.6k
    if(ures)
1258
25
      goto fail;
1259
1260
    /* find the end of the hostname + port number */
1261
31.6k
    p = hostp;
1262
1.06M
    while(*p && *p != '/' && *p != '?' && *p != '#')
1263
1.03M
      p++;
1264
31.6k
    hostlen = p - hostp;
1265
31.6k
    path = p;
1266
1267
    /* this pathlen also contains the query and the fragment */
1268
31.6k
    pathlen = urllen - (path - url);
1269
31.6k
    if(hostlen) {
1270
31.5k
      ures = parse_authority(u, hostp, hostlen, flags, &host, !!u->scheme);
1271
31.5k
      if(!ures && (flags & CURLU_GUESS_SCHEME) && !u->scheme)
1272
14.9k
        ures = guess_scheme(u, &host);
1273
31.5k
    }
1274
14
    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
14
    else
1280
14
      ures = CURLUE_NO_HOST;
1281
31.6k
  }
1282
31.6k
  if(!ures) {
1283
    /* The path might at this point contain a fragment and/or a query to
1284
       handle */
1285
31.3k
    const char *fragment = memchr(path, '#', pathlen);
1286
31.3k
    if(fragment) {
1287
513
      size_t fraglen = pathlen - (fragment - path);
1288
513
      ures = handle_fragment(u, fragment, fraglen, flags);
1289
      /* after this, pathlen still contains the query */
1290
513
      pathlen -= fraglen;
1291
513
    }
1292
31.3k
  }
1293
31.6k
  if(!ures) {
1294
31.3k
    const char *query = memchr(path, '?', pathlen);
1295
31.3k
    if(query) {
1296
1.65k
      size_t qlen = pathlen - (query - path);
1297
1.65k
      ures = handle_query(u, query, qlen, flags);
1298
1.65k
      pathlen -= qlen;
1299
1.65k
    }
1300
31.3k
  }
1301
31.6k
  if(!ures)
1302
    /* the fragment and query parts are trimmed off from the path */
1303
31.3k
    ures = handle_path(u, path, pathlen, flags, is_file);
1304
31.6k
  if(!ures) {
1305
31.3k
    u->host = curlx_dyn_ptr(&host);
1306
31.3k
    return CURLUE_OK;
1307
31.3k
  }
1308
358
fail:
1309
358
  curlx_dyn_free(&host);
1310
358
  free_urlhandle(u);
1311
358
  return ures;
1312
31.6k
}
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
31.6k
{
1320
31.6k
  CURLUcode ures;
1321
31.6k
  CURLU tmpurl;
1322
31.6k
  memset(&tmpurl, 0, sizeof(tmpurl));
1323
31.6k
  ures = parseurl(url, &tmpurl, flags);
1324
31.6k
  if(!ures) {
1325
31.3k
    free_urlhandle(u);
1326
31.3k
    *u = tmpurl;
1327
31.3k
  }
1328
31.6k
  return ures;
1329
31.6k
}
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
1.05k
{
1337
1.05k
  struct dynbuf urlbuf;
1338
1.05k
  bool host_changed = FALSE;
1339
1.05k
  const char *useurl = relurl;
1340
1.05k
  const char *cutoff = NULL;
1341
1.05k
  size_t prelen;
1342
1.05k
  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
1.05k
  const char *scheme = u->scheme ? u->scheme : DEFAULT_SCHEME;
1346
1347
  /* protsep points to the start of the hostname, after [scheme]:// */
1348
1.05k
  const char *protsep = base + strlen(scheme) + 3;
1349
1.05k
  DEBUGASSERT(base && relurl && u); /* all set here */
1350
1.05k
  if(!base)
1351
0
    return CURLUE_MALFORMED_INPUT; /* should never happen */
1352
1353
  /* handle different relative URL types */
1354
1.05k
  switch(relurl[0]) {
1355
239
  case '/':
1356
239
    if(relurl[1] == '/') {
1357
      /* protocol-relative URL: //example.com/path */
1358
157
      cutoff = protsep;
1359
157
      useurl = &relurl[2];
1360
157
      host_changed = TRUE;
1361
157
    }
1362
82
    else
1363
      /* absolute /path */
1364
82
      cutoff = strchr(protsep, '/');
1365
239
    break;
1366
1367
100
  case '#':
1368
    /* fragment-only change */
1369
100
    if(u->fragment_present)
1370
14
      cutoff = strchr(protsep, '#');
1371
100
    break;
1372
1373
719
  default:
1374
    /* path or query-only change */
1375
719
    if(u->query_present)
1376
      /* remove existing query */
1377
54
      cutoff = strchr(protsep, '?');
1378
665
    else if(u->fragment_present)
1379
      /* Remove existing fragment */
1380
30
      cutoff = strchr(protsep, '#');
1381
1382
719
    if(relurl[0] != '?') {
1383
      /* append a relative path after the last slash */
1384
666
      cutoff = memrchr(protsep, '/',
1385
666
                       cutoff ? (size_t)(cutoff - protsep) : strlen(protsep));
1386
666
      if(cutoff)
1387
666
        cutoff++; /* truncate after last slash */
1388
666
    }
1389
719
    break;
1390
1.05k
  }
1391
1392
1.05k
  prelen = cutoff ? (size_t)(cutoff - base) : strlen(base);
1393
1394
  /* build new URL */
1395
1.05k
  curlx_dyn_init(&urlbuf, CURL_MAX_INPUT_LENGTH);
1396
1397
1.05k
  if(!curlx_dyn_addn(&urlbuf, base, prelen) &&
1398
1.05k
     !urlencode_str(&urlbuf, useurl, strlen(useurl), !host_changed,
1399
1.05k
                    QUERY_NOT_YET)) {
1400
1.05k
    uc = parseurl_and_replace(curlx_dyn_ptr(&urlbuf), u,
1401
1.05k
                              flags & ~U_CURLU_PATH_AS_IS);
1402
1.05k
  }
1403
0
  else
1404
0
    uc = CURLUE_OUT_OF_MEMORY;
1405
1406
1.05k
  curlx_dyn_free(&urlbuf);
1407
1.05k
  return uc;
1408
1.05k
}
1409
1410
/*
1411
 */
1412
CURLU *curl_url(void)
1413
28.3k
{
1414
28.3k
  return curlx_calloc(1, sizeof(struct Curl_URL));
1415
28.3k
}
1416
1417
void curl_url_cleanup(CURLU *u)
1418
81.5k
{
1419
81.5k
  if(u) {
1420
28.5k
    free_urlhandle(u);
1421
28.5k
    curlx_free(u);
1422
28.5k
  }
1423
81.5k
}
1424
1425
#define DUP(dest, src, name)                    \
1426
1.30k
  do {                                          \
1427
1.30k
    if((src)->name) {                           \
1428
365
      (dest)->name = curlx_strdup((src)->name); \
1429
365
      if(!(dest)->name)                         \
1430
365
        goto fail;                              \
1431
365
    }                                           \
1432
1.30k
  } while(0)
1433
1434
CURLU *curl_url_dup(const CURLU *in)
1435
145
{
1436
145
  struct Curl_URL *u = curlx_calloc(1, sizeof(struct Curl_URL));
1437
145
  if(u) {
1438
145
    DUP(u, in, scheme);
1439
145
    DUP(u, in, user);
1440
145
    DUP(u, in, password);
1441
145
    DUP(u, in, options);
1442
145
    DUP(u, in, host);
1443
145
    DUP(u, in, path);
1444
145
    DUP(u, in, query);
1445
145
    DUP(u, in, fragment);
1446
145
    DUP(u, in, zoneid);
1447
145
    u->portnum = in->portnum;
1448
145
    u->port_present = in->port_present;
1449
145
    u->fragment_present = in->fragment_present;
1450
145
    u->query_present = in->query_present;
1451
145
  }
1452
145
  return u;
1453
0
fail:
1454
0
  curl_url_cleanup(u);
1455
0
  return NULL;
1456
145
}
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
74.0k
{
1485
74.0k
  CURLUcode uc = CURLUE_OK;
1486
74.0k
  size_t partlen = strlen(ptr);
1487
74.0k
  bool urldecode = (flags & CURLU_URLDECODE) ? 1 : 0;
1488
74.0k
  bool urlencode = (flags & CURLU_URLENCODE) ? 1 : 0;
1489
74.0k
  bool punycode = (flags & CURLU_PUNYCODE) && (what == CURLUPART_HOST);
1490
74.0k
  bool depunyfy = (flags & CURLU_PUNY2IDN) && (what == CURLUPART_HOST);
1491
74.0k
  char *part = curlx_memdup0(ptr, partlen);
1492
74.0k
  *partp = NULL;
1493
74.0k
  if(!part)
1494
0
    return CURLUE_OUT_OF_MEMORY;
1495
74.0k
  if(plusdecode) {
1496
    /* convert + to space */
1497
0
    char *plus = part;
1498
0
    size_t i = 0;
1499
0
    for(i = 0; i < partlen; ++plus, i++) {
1500
0
      if(*plus == '+')
1501
0
        *plus = ' ';
1502
0
    }
1503
0
  }
1504
74.0k
  if(urldecode) {
1505
1.71k
    char *decoded;
1506
1.71k
    size_t dlen;
1507
    /* this unconditional rejection of control bytes is documented API
1508
       behavior */
1509
1.71k
    CURLcode result = Curl_urldecode(part, partlen, &decoded, &dlen,
1510
1.71k
                                     REJECT_CTRL);
1511
1.71k
    curlx_free(part);
1512
1.71k
    if(result)
1513
1
      return CURLUE_URLDECODE;
1514
1.70k
    part = decoded;
1515
1.70k
    partlen = dlen;
1516
1.70k
  }
1517
74.0k
  if(urlencode) {
1518
21.5k
    struct dynbuf enc;
1519
21.5k
    curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1520
21.5k
    uc = urlencode_str(&enc, part, partlen, TRUE, what == CURLUPART_QUERY ?
1521
21.5k
                       QUERY_YES : QUERY_NO);
1522
21.5k
    curlx_free(part);
1523
21.5k
    if(uc)
1524
0
      return uc;
1525
21.5k
    part = curlx_dyn_ptr(&enc);
1526
21.5k
  }
1527
52.5k
  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
52.5k
  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
74.0k
  *partp = part;
1546
74.0k
  return CURLUE_OK;
1547
74.0k
}
1548
1549
static CURLUcode file_url(const CURLU *u, char **part,
1550
                          const char *fragmentsep,
1551
                          const char *querysep)
1552
19
{
1553
19
  char *url = curl_maprintf("file://%s%s%s%s%s",
1554
19
                            u->path, querysep, u->query ? u->query : "",
1555
19
                            fragmentsep, u->fragment ? u->fragment : "");
1556
19
  if(!url)
1557
0
    return CURLUE_OUT_OF_MEMORY;
1558
1559
19
  *part = url;
1560
19
  return CURLUE_OK;
1561
19
}
1562
1563
static CURLUcode urlget_url(const CURLU *u, char **part, unsigned int flags)
1564
42.5k
{
1565
42.5k
  char *url;
1566
42.5k
  char *allochost = NULL;
1567
42.5k
  const char *fragmentsep =
1568
42.5k
    (u->fragment || (u->fragment_present && flags & CURLU_GET_EMPTY)) ?
1569
42.2k
    "#" : "";
1570
42.5k
  const char *querysep = ((u->query && u->query[0]) ||
1571
41.2k
                          (u->query_present && flags & CURLU_GET_EMPTY)) ?
1572
41.1k
    "?" : "";
1573
42.5k
  char portbuf[7];
1574
42.5k
  if(curl_strequal("file", u->scheme))
1575
19
    return file_url(u, part, fragmentsep, querysep);
1576
42.5k
  else if(!u->host)
1577
15.6k
    return CURLUE_NO_HOST;
1578
26.8k
  else {
1579
26.8k
    const char *scheme;
1580
26.8k
    char *options = u->options;
1581
26.8k
    char *port = NULL;
1582
26.8k
    const struct Curl_scheme *h = NULL;
1583
26.8k
    char schemebuf[MAX_SCHEME_LEN + 5];
1584
26.8k
    if(u->scheme)
1585
26.8k
      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
26.8k
    if(u->port_present) {
1592
343
      curl_msnprintf(portbuf, sizeof(portbuf), "%u", u->portnum);
1593
343
      port = portbuf;
1594
343
    }
1595
1596
26.8k
    h = Curl_get_scheme(scheme);
1597
26.8k
    if(h) {
1598
26.7k
      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
26.7k
      else if(u->port_present && (h->defport == u->portnum) &&
1605
43
              (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
3
        port = NULL;
1609
3
      }
1610
1611
26.7k
      if(!(h->flags & PROTOPT_URLOPTIONS))
1612
26.6k
        options = NULL;
1613
26.7k
    }
1614
1615
26.8k
    if(u->host[0] == '[') {
1616
762
      if(u->zoneid) {
1617
        /* make it '[ host %25 zoneid ]' */
1618
212
        struct dynbuf enc;
1619
212
        size_t hostlen = strlen(u->host);
1620
212
        curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1621
212
        if(curlx_dyn_addf(&enc, "%.*s%%25%s]", (int)hostlen - 1, u->host,
1622
212
                          u->zoneid))
1623
0
          return CURLUE_OUT_OF_MEMORY;
1624
212
        allochost = curlx_dyn_ptr(&enc);
1625
212
      }
1626
762
    }
1627
26.1k
    else if(flags & CURLU_URLENCODE) {
1628
688
      allochost = curl_easy_escape(NULL, u->host, 0);
1629
688
      if(!allochost)
1630
0
        return CURLUE_OUT_OF_MEMORY;
1631
688
    }
1632
25.4k
    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
25.4k
    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
26.8k
    if(!(flags & CURLU_NO_GUESS_SCHEME) || !u->guessed_scheme)
1648
26.8k
      curl_msnprintf(schemebuf, sizeof(schemebuf), "%s://", scheme);
1649
0
    else
1650
0
      schemebuf[0] = 0;
1651
1652
26.8k
    url = curl_maprintf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
1653
26.8k
                        schemebuf,
1654
26.8k
                        u->user ? u->user : "",
1655
26.8k
                        u->password ? ":" : "",
1656
26.8k
                        u->password ? u->password : "",
1657
26.8k
                        options ? ";" : "",
1658
26.8k
                        options ? options : "",
1659
26.8k
                        (u->user || u->password || options) ? "@" : "",
1660
26.8k
                        allochost ? allochost : u->host,
1661
26.8k
                        port ? ":" : "",
1662
26.8k
                        port ? port : "",
1663
26.8k
                        u->path ? u->path : "/",
1664
26.8k
                        querysep,
1665
26.8k
                        u->query ? u->query : "",
1666
26.8k
                        fragmentsep,
1667
26.8k
                        u->fragment ? u->fragment : "");
1668
26.8k
    curlx_free(allochost);
1669
26.8k
  }
1670
26.8k
  if(!url)
1671
0
    return CURLUE_OUT_OF_MEMORY;
1672
26.8k
  *part = url;
1673
26.8k
  return CURLUE_OK;
1674
26.8k
}
1675
1676
CURLUcode curl_url_get(const CURLU *u, CURLUPart what,
1677
                       char **part, unsigned int flags)
1678
226k
{
1679
226k
  const char *ptr;
1680
226k
  CURLUcode ifmissing = CURLUE_UNKNOWN_PART;
1681
226k
  char portbuf[7];
1682
226k
  bool plusdecode = FALSE;
1683
226k
  if(!u)
1684
0
    return CURLUE_BAD_HANDLE;
1685
226k
  if(!part)
1686
0
    return CURLUE_BAD_PARTPOINTER;
1687
226k
  *part = NULL;
1688
1689
226k
  switch(what) {
1690
25.2k
  case CURLUPART_SCHEME:
1691
25.2k
    ptr = u->scheme;
1692
25.2k
    ifmissing = CURLUE_NO_SCHEME;
1693
25.2k
    flags &= ~U_CURLU_URLDECODE; /* never for schemes */
1694
25.2k
    if((flags & CURLU_NO_GUESS_SCHEME) && u->guessed_scheme)
1695
0
      return CURLUE_NO_SCHEME;
1696
25.2k
    break;
1697
25.2k
  case CURLUPART_USER:
1698
22.6k
    ptr = u->user;
1699
22.6k
    ifmissing = CURLUE_NO_USER;
1700
22.6k
    break;
1701
22.6k
  case CURLUPART_PASSWORD:
1702
22.6k
    ptr = u->password;
1703
22.6k
    ifmissing = CURLUE_NO_PASSWORD;
1704
22.6k
    break;
1705
21.5k
  case CURLUPART_OPTIONS:
1706
21.5k
    ptr = u->options;
1707
21.5k
    ifmissing = CURLUE_NO_OPTIONS;
1708
21.5k
    break;
1709
23.4k
  case CURLUPART_HOST:
1710
23.4k
    ptr = u->host;
1711
23.4k
    ifmissing = CURLUE_NO_HOST;
1712
23.4k
    break;
1713
23.0k
  case CURLUPART_ZONEID:
1714
23.0k
    ptr = u->zoneid;
1715
23.0k
    ifmissing = CURLUE_NO_ZONEID;
1716
23.0k
    break;
1717
1.79k
  case CURLUPART_PORT:
1718
1.79k
    ptr = NULL;
1719
1.79k
    ifmissing = CURLUE_NO_PORT;
1720
1.79k
    flags &= ~U_CURLU_URLDECODE; /* never for port */
1721
1.79k
    if(u->port_present) {
1722
14
      const struct Curl_scheme *h = u->scheme ?
1723
14
                                    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
14
      if(h && (h->defport == u->portnum) &&
1727
0
         (flags & CURLU_NO_DEFAULT_PORT)) {
1728
0
        ptr = NULL;
1729
0
      }
1730
14
      else {
1731
14
        curl_msnprintf(portbuf, sizeof(portbuf), "%u", u->portnum);
1732
14
        ptr = portbuf;
1733
14
      }
1734
14
    }
1735
1.77k
    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.79k
    break;
1745
21.5k
  case CURLUPART_PATH:
1746
21.5k
    ptr = u->path;
1747
21.5k
    if(!ptr)
1748
19.8k
      ptr = "/";
1749
21.5k
    break;
1750
21.5k
  case CURLUPART_QUERY:
1751
21.5k
    ptr = u->query;
1752
21.5k
    ifmissing = CURLUE_NO_QUERY;
1753
21.5k
    plusdecode = flags & CURLU_URLDECODE;
1754
21.5k
    if(ptr && !ptr[0] && !(flags & CURLU_GET_EMPTY))
1755
      /* there was a blank query and the user does not ask for it */
1756
3
      ptr = NULL;
1757
21.5k
    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
42.5k
  case CURLUPART_URL:
1766
42.5k
    return urlget_url(u, part, flags);
1767
0
  default:
1768
0
    ptr = NULL;
1769
0
    break;
1770
226k
  }
1771
183k
  if(ptr)
1772
74.0k
    return urlget_format(u, what, ptr, part, plusdecode, flags);
1773
1774
109k
  return ifmissing;
1775
183k
}
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
141
{
1810
141
  curl_off_t port;
1811
141
  if(!ISDIGIT(provided_port[0]))
1812
    /* not a number */
1813
0
    return CURLUE_BAD_PORT_NUMBER;
1814
141
  if(curlx_str_number(&provided_port, &port, 0xffff) || *provided_port)
1815
    /* weirdly provided number, not good! */
1816
0
    return CURLUE_BAD_PORT_NUMBER;
1817
141
  u->portnum = (uint16_t)port;
1818
141
  u->port_present = TRUE;
1819
141
  return CURLUE_OK;
1820
141
}
1821
1822
static CURLUcode set_url(CURLU *u, const char *url, size_t part_size,
1823
                         unsigned int flags)
1824
32.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
32.1k
  CURLUcode uc;
1832
32.1k
  char *oldurl = NULL;
1833
1834
32.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
514
    uc = curl_url_get(u, CURLUPART_URL, &oldurl, flags);
1838
514
    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
514
    if(uc == CURLUE_OUT_OF_MEMORY)
1848
0
      return uc;
1849
514
    return CURLUE_MALFORMED_INPUT;
1850
514
  }
1851
1852
  /* if the new URL is absolute replace the existing with the new. */
1853
31.6k
  if(Curl_is_absolute_url(url, NULL, 0,
1854
31.6k
                          flags & (CURLU_GUESS_SCHEME | CURLU_DEFAULT_SCHEME)))
1855
15.4k
    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
16.2k
  uc = curl_url_get(u, CURLUPART_URL, &oldurl,
1863
16.2k
                    (flags & ~CURLU_NO_GUESS_SCHEME) | CURLU_GET_EMPTY);
1864
16.2k
  if(uc == CURLUE_OUT_OF_MEMORY)
1865
0
    return uc;
1866
16.2k
  else if(uc)
1867
15.1k
    return parseurl_and_replace(url, u, flags);
1868
1869
1.05k
  DEBUGASSERT(oldurl); /* it is set here */
1870
  /* apply the relative part to create a new URL */
1871
1.05k
  uc = redirect_url(oldurl, url, u, flags);
1872
1.05k
  curlx_free(oldurl);
1873
1.05k
  return uc;
1874
1.05k
}
1875
1876
static CURLUcode urlset_clear(CURLU *u, CURLUPart what)
1877
846
{
1878
846
  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
282
  case CURLUPART_USER:
1888
282
    curlx_safefree(u->user);
1889
282
    break;
1890
282
  case CURLUPART_PASSWORD:
1891
282
    curlx_strzero(u->password);
1892
282
    curlx_safefree(u->password);
1893
282
    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
282
  case CURLUPART_FRAGMENT:
1915
282
    curlx_safefree(u->fragment);
1916
282
    u->fragment_present = FALSE;
1917
282
    break;
1918
0
  default:
1919
0
    return CURLUE_UNKNOWN_PART;
1920
846
  }
1921
846
  return CURLUE_OK;
1922
846
}
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
33.1k
{
2073
33.1k
  char **storep = NULL;
2074
33.1k
  bool urlencode = (flags & CURLU_URLENCODE) ? 1 : 0;
2075
33.1k
  bool plusencode = FALSE;
2076
33.1k
  bool pathmode = FALSE;
2077
33.1k
  bool leadingslash = FALSE;
2078
33.1k
  bool appendquery = FALSE;
2079
33.1k
  bool equalsencode = FALSE;
2080
33.1k
  size_t nalloc;
2081
2082
33.1k
  if(!u)
2083
0
    return CURLUE_BAD_HANDLE;
2084
33.1k
  if(!part)
2085
    /* setting a part to NULL clears it */
2086
846
    return urlset_clear(u, what);
2087
2088
32.3k
  nalloc = strlen(part);
2089
32.3k
  if(nalloc > CURL_MAX_INPUT_LENGTH)
2090
    /* excessive input length */
2091
0
    return CURLUE_MALFORMED_INPUT;
2092
2093
32.3k
  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
141
  case CURLUPART_PORT:
2119
141
    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
32.1k
  case CURLUPART_URL:
2137
32.1k
    return set_url(u, part, nalloc, flags);
2138
0
  default:
2139
0
    return CURLUE_UNKNOWN_PART;
2140
32.3k
  }
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
3.81k
{
2178
3.81k
  const struct Curl_scheme *s = NULL;
2179
2180
  /* base must be an absolute URL */
2181
3.81k
  if(!base->scheme || !base->host)
2182
0
    return FALSE;
2183
3.81k
  if(href->scheme && !curl_strequal(base->scheme, href->scheme))
2184
17
    return FALSE;
2185
3.79k
  if(href->host) {
2186
3.79k
    if(!curl_strequal(base->host, href->host))
2187
139
      return FALSE;
2188
2189
3.65k
    if(base->port_present != href->port_present) {
2190
      /* one is present, one is not */
2191
28
      s = Curl_get_scheme(base->scheme);
2192
28
      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
28
      if((base->port_present && (base->portnum != s->defport)) ||
2196
23
         (href->port_present && (href->portnum != s->defport)))
2197
26
        return FALSE;
2198
28
    }
2199
3.62k
    else if(base->portnum != href->portnum) /* both present or missing */
2200
2
      return FALSE;
2201
2202
3.62k
    if(!curl_strequal(base->zoneid ? base->zoneid : "",
2203
3.62k
                      href->zoneid ? href->zoneid : ""))
2204
0
      return FALSE;
2205
3.62k
  }
2206
0
  else if(href->port_present) /* no host in href, then there must be no port */
2207
0
    return FALSE;
2208
3.62k
  return TRUE;
2209
3.79k
}
2210
2211
CURLUcode Curl_url_get_port(CURLU *u, uint16_t *pport)
2212
21.3k
{
2213
21.3k
  if(u->port_present) {
2214
150
    *pport = u->portnum;
2215
150
    return CURLUE_OK;
2216
150
  }
2217
21.2k
  else if(u->scheme) {
2218
21.2k
    const struct Curl_scheme *s = Curl_get_scheme(u->scheme);
2219
21.2k
    if(s && s->defport) {
2220
21.1k
      *pport = s->defport;
2221
21.1k
      return CURLUE_OK;
2222
21.1k
    }
2223
21.2k
  }
2224
16
  *pport = 0;
2225
16
  return CURLUE_NO_PORT;
2226
21.3k
}