Coverage Report

Created: 2026-10-28 07:17

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