Coverage Report

Created: 2026-09-14 07:12

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