Coverage Report

Created: 2026-09-14 07:05

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
54
  ((('a' <= (str)[0] && (str)[0] <= 'z') ||                \
50
54
    ('A' <= (str)[0] && (str)[0] <= 'Z')) &&               \
51
54
   ((str)[1] == ':' || (str)[1] == '|') &&                 \
52
54
   ((str)[2] == '/' || (str)[2] == '\\' || (str)[2] == 0))
53
54
/* scheme is not URL encoded, the longest libcurl supported ones are... */
55
255k
#define MAX_SCHEME_LEN 40
56
73
#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
50.2k
{
71
50.2k
  curlx_free(u->scheme);
72
50.2k
  curlx_free(u->user);
73
50.2k
  curlx_strzero(u->password);
74
50.2k
  curlx_free(u->password);
75
50.2k
  curlx_free(u->options);
76
50.2k
  curlx_free(u->host);
77
50.2k
  curlx_free(u->zoneid);
78
50.2k
  curlx_free(u->path);
79
50.2k
  curlx_free(u->query);
80
50.2k
  curlx_free(u->fragment);
81
50.2k
}
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
19
{
89
  /* Find the start of the hostname */
90
19
  const char *sep = strstr(url, "//");
91
19
  if(!sep)
92
17
    sep = url;
93
2
  else
94
2
    sep += 2;
95
96
  /* Find first / or ? */
97
286
  while(*sep && *sep != '/' && *sep != '?')
98
267
    sep++;
99
100
19
  return sep;
101
19
}
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
12.4k
{
131
  /* we must add this with whitespace-replacing */
132
12.4k
  const unsigned char *iptr;
133
12.4k
  const unsigned char *host_sep = (const unsigned char *)url;
134
12.4k
  CURLcode result = CURLE_OK;
135
136
12.4k
  DEBUGASSERT((query >= QUERY_NO) && (query <= QUERY_YES));
137
138
12.4k
  if(!relative) {
139
19
    size_t n;
140
19
    host_sep = (const unsigned char *)find_host_sep(url);
141
142
    /* output the first piece as-is */
143
19
    n = (const char *)host_sep - url;
144
19
    result = curlx_dyn_addn(o, url, n);
145
19
    len -= n;
146
19
  }
147
148
4.28M
  for(iptr = host_sep; len && !result;) {
149
4.27M
    if(*iptr == ' ') {
150
47
      if(query != QUERY_YES)
151
28
        result = curlx_dyn_addn(o, "%20", 3);
152
19
      else
153
19
        result = curlx_dyn_addn(o, "+", 1);
154
47
      iptr++;
155
47
      len--;
156
47
    }
157
4.27M
    else if((*iptr < ' ') || (*iptr >= 0x7f)) {
158
4.23M
      unsigned char out[3] = { '%' };
159
4.23M
      Curl_hexbyte(&out[1], *iptr);
160
4.23M
      result = curlx_dyn_addn(o, out, 3);
161
4.23M
      iptr++;
162
4.23M
      len--;
163
4.23M
    }
164
36.6k
    else if(*iptr == '%' && (len >= 3) &&
165
9.61k
            ISXDIGIT(iptr[1]) && ISXDIGIT(iptr[2]) &&
166
6.85k
            (ISLOWER(iptr[1]) || ISLOWER(iptr[2]))) {
167
      /* uppercase it */
168
6.84k
      unsigned char hex = (unsigned char)((curlx_hexval(iptr[1]) << 4) |
169
6.84k
                                          curlx_hexval(iptr[2]));
170
6.84k
      unsigned char out[3] = { '%' };
171
6.84k
      Curl_hexbyte(&out[1], hex);
172
6.84k
      result = curlx_dyn_addn(o, out, 3);
173
6.84k
      iptr += 3;
174
6.84k
      len -= 3;
175
6.84k
    }
176
29.8k
    else {
177
29.8k
      const unsigned char *start = iptr;
178
1.48M
      while(len) {
179
1.47M
        if(*iptr == ' ' || *iptr < ' ' || *iptr >= 0x7f)
180
10.8k
          break;
181
1.46M
        if(*iptr == '%' && (len >= 3) &&
182
672k
           ISXDIGIT(iptr[1]) && ISXDIGIT(iptr[2]) &&
183
9.57k
           (ISLOWER(iptr[1]) || ISLOWER(iptr[2])))
184
6.80k
          break;
185
1.45M
        if(*iptr == '?') {
186
279
          if(query == QUERY_NOT_YET) {
187
9
            iptr++;
188
9
            len--;
189
9
            query = QUERY_YES;
190
9
            break;
191
9
          }
192
279
        }
193
1.45M
        iptr++;
194
1.45M
        len--;
195
1.45M
      }
196
29.8k
      result = curlx_dyn_addn(o, (const char *)start, (size_t)(iptr - start));
197
29.8k
    }
198
4.27M
  }
199
200
12.4k
  if(result)
201
0
    return cc2cu(result);
202
12.4k
  return CURLUE_OK;
203
12.4k
}
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
51.6k
{
216
51.6k
  size_t i = 0;
217
51.6k
  DEBUGASSERT(!buf || (buflen > MAX_SCHEME_LEN));
218
51.6k
  (void)buflen; /* only used in debug-builds */
219
51.6k
  if(buf)
220
25.3k
    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
51.6k
  if(ISALPHA(url[0])) {
226
51.4k
    if(buf)
227
25.3k
      buf[0] = Curl_raw_tolower(url[0]);
228
255k
    for(i = 1; i < MAX_SCHEME_LEN; ++i) {
229
255k
      char s = url[i];
230
255k
      if(s && (ISALNUM(s) || (s == '+') || (s == '-') || (s == '.'))) {
231
203k
        if(buf)
232
100k
          buf[i] = Curl_raw_tolower(s);
233
203k
      }
234
51.4k
      else {
235
51.4k
        break;
236
51.4k
      }
237
255k
    }
238
51.4k
  }
239
51.6k
  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
51.2k
    size_t len = i;
246
51.2k
    if(buf)
247
25.3k
      buf[i] = 0;
248
51.2k
    return len;
249
51.2k
  }
250
407
  if(buf)
251
0
    buf[0] = 0;
252
407
  return 0;
253
51.6k
}
254
255
/* scan for byte values <= 31, 127 and maybe space */
256
static bool badoctets(const char *input, size_t n, int flags)
257
2.12k
{
258
2.12k
  const uint8_t *p = (const unsigned char *)input;
259
2.12k
  const uint8_t control = flags & CURLU_ALLOW_SPACE ? 0x1f : 0x20;
260
7.46M
  while(n--) {
261
7.46M
    if(*p <= control || *p == 127)
262
44
      return TRUE;
263
7.46M
    p++;
264
7.46M
  }
265
2.07k
  return FALSE;
266
2.12k
}
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
25.1k
{
287
25.1k
  CURLUcode ures = CURLUE_OK;
288
25.1k
  CURLcode result;
289
25.1k
  char *userp = NULL;
290
25.1k
  char *passwdp = NULL;
291
25.1k
  char *optionsp = NULL;
292
25.1k
  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
25.1k
  const char *ptr;
302
303
25.1k
  DEBUGASSERT(login);
304
305
25.1k
  *hostname_offset = 0;
306
25.1k
  ptr = memchr(login, '@', len);
307
25.1k
  if(!ptr)
308
24.7k
    goto out;
309
310
  /* We will now try to extract the
311
   * possible login information in a string like:
312
   * ftp://user:password@ftp.site.example:8021/README */
313
342
  ptr++;
314
315
  /* if this is a known scheme, get some details */
316
342
  if(u->scheme)
317
342
    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
342
  result = Curl_parse_login_details(login, ptr - login - 1,
322
342
                                    &userp, &passwdp,
323
342
                                    (h && (h->flags & PROTOPT_URLOPTIONS)) ?
324
342
                                    &optionsp : NULL);
325
342
  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
342
  if(userp) {
333
342
    if(flags & CURLU_DISALLOW_USER) {
334
      /* Option DISALLOW_USER is set and URL contains username. */
335
0
      ures = CURLUE_USER_NOT_ALLOWED;
336
0
      goto out;
337
0
    }
338
342
    curlx_free(u->user);
339
342
    u->user = userp;
340
342
  }
341
342
342
  if(passwdp) {
343
13
    curlx_strzero(u->password);
344
13
    curlx_free(u->password);
345
13
    u->password = passwdp;
346
13
  }
347
348
342
  if(optionsp) {
349
4
    curlx_free(u->options);
350
4
    u->options = optionsp;
351
4
  }
352
353
342
  if(userp && badoctets(userp, strlen(userp), flags))
354
0
    ures = CURLUE_BAD_USER;
355
342
  else if(passwdp && badoctets(passwdp, strlen(passwdp), flags))
356
0
    ures = CURLUE_BAD_PASSWORD;
357
342
  else if(optionsp && badoctets(optionsp, strlen(optionsp), flags))
358
0
    ures = CURLUE_MALFORMED_INPUT;
359
360
342
  userp = passwdp = optionsp = NULL;
361
362
342
  if(!ures) {
363
    /* the hostname starts at this offset */
364
342
    *hostname_offset = ptr - login;
365
342
    return CURLUE_OK;
366
342
  }
367
368
24.7k
out:
369
370
24.7k
  curlx_free(userp);
371
24.7k
  curlx_strzero(passwdp);
372
24.7k
  curlx_free(passwdp);
373
24.7k
  curlx_free(optionsp);
374
24.7k
  curlx_safefree(u->user);
375
24.7k
  curlx_strzero(u->password);
376
24.7k
  curlx_safefree(u->password);
377
24.7k
  curlx_safefree(u->options);
378
379
24.7k
  return ures;
380
342
}
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
25.1k
{
388
25.1k
  const char *portptr;
389
25.1k
  const char *hostname = curlx_dyn_ptr(host);
390
  /*
391
   * Find the end of an IPv6 address on the ']' ending bracket.
392
   */
393
25.1k
  u->portnum = 0;
394
25.1k
  u->port_present = FALSE;
395
25.1k
  if(hostname[0] == '[') {
396
113
    portptr = memchr(hostname + 1, ']', curlx_dyn_len(host) - 1);
397
113
    if(!portptr)
398
5
      return CURLUE_BAD_IPV6;
399
108
    portptr++;
400
    /* this is a RFC2732-style specified IP-address */
401
108
    if(*portptr) {
402
14
      if(*portptr != ':')
403
11
        return CURLUE_BAD_PORT_NUMBER;
404
14
    }
405
94
    else
406
94
      portptr = NULL;
407
108
  }
408
24.9k
  else
409
24.9k
    portptr = memchr(hostname, ':', curlx_dyn_len(host));
410
411
25.0k
  if(portptr) {
412
12.1k
    curl_off_t port;
413
12.1k
    size_t keep = portptr - hostname;
414
12.1k
    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
12.1k
    curlx_dyn_setlen(host, keep);
423
12.1k
    portptr++;
424
12.1k
    if(!*portptr)
425
16
      return has_scheme ? CURLUE_OK : CURLUE_BAD_PORT_NUMBER;
426
12.0k
    if(*portptr == '\\')
427
1
      return CURLUE_BACKSLASH;
428
12.0k
    rc = curlx_str_number(&portptr, &port, 0xffff);
429
12.0k
    if(rc)
430
19
      return CURLUE_BAD_PORT_NUMBER;
431
12.0k
    else if(*portptr == '\\')
432
1
      return CURLUE_BACKSLASH;
433
12.0k
    else if(*portptr)
434
8
      return CURLUE_BAD_PORT_NUMBER;
435
436
12.0k
    u->portnum = (uint16_t)port;
437
12.0k
    u->port_present = TRUE;
438
12.0k
  }
439
440
25.0k
  return CURLUE_OK;
441
25.0k
}
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
94
{
453
94
  size_t len;
454
94
  DEBUGASSERT(*hostname == '[');
455
94
  if(hlen < 4) /* '[::]' is the shortest possible valid string */
456
2
    return CURLUE_BAD_IPV6;
457
92
  hostname++;
458
92
  hlen -= 2;
459
460
  /* only valid IPv6 letters are ok */
461
92
  len = strspn(hostname, "0123456789abcdefABCDEF:.");
462
463
92
  if(hlen != len) {
464
16
    hlen = len;
465
16
    if(hostname[len] == '%') {
466
      /* this could now be '%[zone id]' */
467
12
      char zoneid[MAX_ZONEID_LEN];
468
12
      int i = 0;
469
12
      char *h = &hostname[len + 1];
470
      /* pass '25' if present and is a URL encoded percent sign */
471
12
      if(!strncmp(h, "25", 2) && h[2] && (h[2] != ']'))
472
0
        h += 2;
473
84
      while(*h && (*h != ']') && (i < (MAX_ZONEID_LEN - 1)) &&
474
72
            (*h != ' '))
475
72
        zoneid[i++] = *h++;
476
12
      if(!i || (']' != *h))
477
2
        return CURLUE_BAD_IPV6;
478
10
      zoneid[i] = 0;
479
10
      u->zoneid = curlx_strdup(zoneid);
480
10
      if(!u->zoneid)
481
0
        return CURLUE_OUT_OF_MEMORY;
482
10
      hostname[len] = ']'; /* insert end bracket */
483
10
      hostname[len + 1] = 0; /* terminate the hostname */
484
10
    }
485
4
    else
486
4
      return CURLUE_BAD_IPV6;
487
    /* hostname is fine */
488
16
  }
489
490
  /* Normalize the IPv6 address */
491
86
  {
492
86
    char dest[16]; /* fits a binary IPv6 address */
493
86
    hostname[hlen] = 0; /* end the address there */
494
86
    if(curlx_inet_pton(AF_INET6, hostname, dest) != 1)
495
36
      return CURLUE_BAD_IPV6;
496
50
    if(!curlx_inet_ntop(AF_INET6, dest, hostname, hlen + 1)) {
497
46
      hlen = strlen(hostname); /* might be shorter now */
498
46
      hostname[hlen + 1] = 0;
499
46
    }
500
50
    hostname[hlen] = ']'; /* restore ending bracket */
501
50
  }
502
0
  return CURLUE_OK;
503
86
}
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
12.9k
{
522
12.9k
  size_t i;
523
142k
  for(i = 0; i < hlen; i++) {
524
129k
    if(invalid_host_char[(unsigned char)hostname[i]])
525
84
      return CURLUE_BAD_HOSTNAME;
526
129k
  }
527
12.8k
  if((hlen >= 2) &&
528
12.7k
     (hostname[hlen - 1] == '.') && (hostname[hlen - 2] == '.'))
529
    /* more than one trailing dot is not allowed */
530
1
    return CURLUE_BAD_HOSTNAME;
531
12.8k
  else if((hlen == 1) && (hostname[0] == '.'))
532
    /* a single dot alone is not allowed */
533
2
    return CURLUE_BAD_HOSTNAME;
534
12.8k
  return CURLUE_OK;
535
12.8k
}
536
537
/* the input is a hostname or perhaps an IPv6 address */
538
static CURLUcode hostname_check6(struct Curl_URL *u, char *hostname,
539
                                size_t hlen) /* length of hostname */
540
0
{
541
0
  DEBUGASSERT(hostname);
542
543
0
  if(!hlen)
544
0
    return CURLUE_NO_HOST;
545
0
  else if(hostname[0] == '[')
546
0
    return ipv6_parse(u, hostname, hlen);
547
548
0
  return hostname_check(hostname, hlen);
549
0
}
550
551
/*
552
 * Handle partial IPv4 numerical addresses and different bases, like
553
 * '16843009', '0x7f', '0x7f.1' '0177.1.1.1' etc.
554
 *
555
 * If the given input string is syntactically wrong IPv4 or any part for
556
 * example is too big, this function returns HOST_NAME.
557
 *
558
 * Output the "normalized" version of that input string in plain quad decimal
559
 * integers.
560
 *
561
 * A single dot following the numerical address is accepted and "swallowed" as
562
 * if it was never there.
563
 *
564
 * Returns the host type.
565
 *
566
 * @unittest 1675
567
 */
568
UNITTEST int ipv4_normalize(struct dynbuf *host);
569
UNITTEST int ipv4_normalize(struct dynbuf *host)
570
24.9k
{
571
24.9k
  bool done = FALSE;
572
24.9k
  int n = 0;
573
24.9k
  const char *c = curlx_dyn_ptr(host);
574
24.9k
  unsigned int parts[4] = { 0, 0, 0, 0 };
575
24.9k
  CURLcode result = CURLE_OK;
576
577
24.9k
  if(!ISDIGIT(*c))
578
12.8k
    return HOST_NAME;
579
580
60.0k
  while(!done) {
581
48.0k
    int rc;
582
48.0k
    curl_off_t l;
583
48.0k
    if(*c == '0') {
584
23.9k
      if((c[1] | 0x20) == 'x') {
585
3
        c += 2; /* skip the prefix */
586
3
        rc = curlx_str_hex(&c, &l, UINT_MAX);
587
3
        if(rc)
588
1
          return HOST_NAME;
589
3
      }
590
23.9k
      else
591
23.9k
        rc = curlx_str_octal(&c, &l, UINT_MAX);
592
23.9k
    }
593
24.1k
    else
594
24.1k
      rc = curlx_str_number(&c, &l, UINT_MAX);
595
596
48.0k
    if(rc) {
597
10
      if(!n || (rc != STRE_NO_NUM) || *c)
598
6
        return HOST_NAME;
599
4
      n--;
600
4
    }
601
48.0k
    else
602
48.0k
      parts[n] = (unsigned int)l;
603
604
48.0k
    switch(*c) {
605
35.9k
    case '.':
606
35.9k
      if(n == 3) {
607
6
        if(c[1])
608
          /* something follows this dot */
609
1
          return HOST_NAME;
610
5
        done = TRUE;
611
5
      }
612
35.9k
      else {
613
35.9k
        n++;
614
35.9k
        c++;
615
35.9k
      }
616
35.9k
      break;
617
618
35.9k
    case '\0':
619
12.0k
      done = TRUE;
620
12.0k
      break;
621
622
28
    default:
623
28
      return HOST_NAME;
624
48.0k
    }
625
48.0k
  }
626
627
12.0k
  switch(n) {
628
24
  case 0: /* a -- 32 bits */
629
24
    curlx_dyn_reset(host);
630
631
24
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
632
24
                            (parts[0] >> 24),
633
24
                            ((parts[0] >> 16) & 0xff),
634
24
                            ((parts[0] >> 8) & 0xff),
635
24
                            (parts[0] & 0xff));
636
24
    break;
637
12
  case 1: /* a.b -- 8.24 bits */
638
12
    if((parts[0] > 0xff) || (parts[1] > 0xffffff))
639
6
      return HOST_NAME;
640
6
    curlx_dyn_reset(host);
641
6
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
642
6
                            parts[0],
643
6
                            ((parts[1] >> 16) & 0xff),
644
6
                            ((parts[1] >> 8) & 0xff),
645
6
                            (parts[1] & 0xff));
646
6
    break;
647
23
  case 2: /* a.b.c -- 8.8.16 bits */
648
23
    if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xffff))
649
17
      return HOST_NAME;
650
6
    curlx_dyn_reset(host);
651
6
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
652
6
                            parts[0],
653
6
                            parts[1],
654
6
                            ((parts[2] >> 8) & 0xff),
655
6
                            (parts[2] & 0xff));
656
6
    break;
657
11.9k
  case 3: /* a.b.c.d -- 8.8.8.8 bits */
658
11.9k
    if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xff) ||
659
11.9k
       (parts[3] > 0xff))
660
15
      return HOST_NAME;
661
11.9k
    curlx_dyn_reset(host);
662
11.9k
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
663
11.9k
                            parts[0],
664
11.9k
                            parts[1],
665
11.9k
                            parts[2],
666
11.9k
                            parts[3]);
667
11.9k
    break;
668
12.0k
  }
669
11.9k
  if(result)
670
0
    return HOST_ERROR;
671
11.9k
  return HOST_IPV4;
672
11.9k
}
673
674
/* if necessary, replace the host content with a URL decoded version */
675
static CURLUcode urldecode_host(struct dynbuf *host)
676
25.0k
{
677
25.0k
  const char *per;
678
25.0k
  const char *hostname = curlx_dyn_ptr(host);
679
25.0k
  per = memchr(hostname, '%', curlx_dyn_len(host));
680
25.0k
  if(!per)
681
    /* nothing to decode */
682
24.9k
    return CURLUE_OK;
683
74
  else {
684
    /* encoded */
685
74
    size_t dlen;
686
74
    char *decoded;
687
74
    CURLcode result = Curl_urldecode(hostname, 0, &decoded, &dlen,
688
74
                                     REJECT_CTRL);
689
74
    if(result)
690
1
      return CURLUE_BAD_HOSTNAME;
691
73
    curlx_dyn_reset(host);
692
73
    result = curlx_dyn_addn(host, decoded, dlen);
693
73
    curlx_free(decoded);
694
73
    if(result)
695
0
      return cc2cu(result);
696
73
  }
697
698
73
  return CURLUE_OK;
699
25.0k
}
700
701
static CURLUcode parse_authority(struct Curl_URL *u,
702
                                 const char *auth, size_t authlen,
703
                                 unsigned int flags,
704
                                 struct dynbuf *host,
705
                                 bool has_scheme)
706
25.1k
{
707
25.1k
  size_t offset;
708
25.1k
  CURLUcode uc;
709
25.1k
  CURLcode result;
710
711
  /*
712
   * Parse the login details and strip them out of the hostname.
713
   */
714
25.1k
  uc = parse_hostname_login(u, auth, authlen, flags, &offset);
715
25.1k
  if(uc)
716
0
    return uc;
717
718
25.1k
  result = curlx_dyn_addn(host, auth + offset, authlen - offset);
719
25.1k
  if(result) {
720
0
    uc = cc2cu(result);
721
0
    return uc;
722
0
  }
723
724
  /* parse_port() also sets the hostname length correctly */
725
25.1k
  uc = parse_port(u, host, has_scheme);
726
727
25.1k
  if(!curlx_dyn_len(host))
728
    /* this makes no-host errors override port number problems */
729
48
    uc = CURLUE_NO_HOST;
730
25.1k
  if(!uc)
731
25.0k
    uc = urldecode_host(host);
732
25.1k
  if(uc)
733
89
    ;
734
25.0k
  else if(auth[offset] == '[')
735
94
    uc = ipv6_parse(u, curlx_dyn_ptr(host), curlx_dyn_len(host));
736
24.9k
  else {
737
    /* ipv4_normalize() returns *NAME, *IPV4 or *ERROR */
738
24.9k
    int type = ipv4_normalize(host);
739
740
24.9k
    if(type == HOST_NAME)
741
12.9k
      uc = hostname_check(curlx_dyn_ptr(host), curlx_dyn_len(host));
742
11.9k
    else if(type == HOST_ERROR)
743
0
      uc = CURLUE_OUT_OF_MEMORY;
744
24.9k
  }
745
746
25.1k
  return uc;
747
25.1k
}
748
749
/* used for HTTP/2 server push */
750
CURLUcode Curl_url_set_authority(CURLU *u, const char *authority)
751
0
{
752
0
  CURLUcode ures;
753
0
  struct dynbuf host;
754
755
0
  DEBUGASSERT(authority);
756
0
  curlx_dyn_init(&host, CURL_MAX_INPUT_LENGTH);
757
758
0
  ures = parse_authority(u, authority, strlen(authority),
759
0
                         CURLU_DISALLOW_USER, &host, !!u->scheme);
760
0
  if(ures)
761
0
    curlx_dyn_free(&host);
762
0
  else {
763
0
    curlx_free(u->host);
764
0
    u->host = curlx_dyn_ptr(&host);
765
0
  }
766
0
  return ures;
767
0
}
768
769
/*
770
 * "Remove Dot Segments"
771
 * https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4
772
 */
773
774
static bool is_dot(const char **str, size_t *clen)
775
6.17M
{
776
6.17M
  const char *p = *str;
777
6.17M
  if(*p == '.') {
778
30.1k
    (*str)++;
779
30.1k
    (*clen)--;
780
30.1k
    return TRUE;
781
30.1k
  }
782
6.14M
  else if((*clen >= 3) &&
783
6.14M
          (p[0] == '%') && (p[1] == '2') && ((p[2] | 0x20) == 'e')) {
784
1.05k
    *str += 3;
785
1.05k
    *clen -= 3;
786
1.05k
    return TRUE;
787
1.05k
  }
788
6.14M
  return FALSE;
789
6.17M
}
790
791
1.70M
#define ISSLASH(x) ((x) == '/')
792
793
/* prescan the string to see if it needs work */
794
static bool needs_dedotdot(const char *p, size_t pn)
795
1.48k
{
796
  /* a single byte path cannot be cleaned up */
797
1.48k
  if(pn < 2)
798
0
    return FALSE;
799
1.48k
  if(!memchr(p, '.', pn) && !memchr(p, '%', pn))
800
181
    return FALSE;
801
6.12M
  while(pn) {
802
6.12M
    if(is_dot(&p, &pn)) {
803
      /* "./" or dot before end of string */
804
18.1k
      if(!pn || ISSLASH(*p))
805
447
        return TRUE;
806
      /* "../" or ".." before end of string */
807
17.7k
      else if(is_dot(&p, &pn) && (!pn || ISSLASH(*p)))
808
174
        return TRUE;
809
18.1k
    }
810
6.10M
    else {
811
6.10M
      p++;
812
6.10M
      pn--;
813
6.10M
    }
814
6.12M
  }
815
682
  return FALSE;
816
1.30k
}
817
818
/*
819
 * dedotdotify()
820
 *
821
 * This function gets a null-terminated path with dot and dotdot sequences
822
 * passed in and strips them off according to the rules in RFC 3986 section
823
 * 5.2.4.
824
 *
825
 * The function handles a path. It should not contain the query nor fragment.
826
 *
827
 * RETURNS
828
 *
829
 * Zero for success and 'out' set to an allocated string (or NULL if there's
830
 * nothing to do).
831
 *
832
 * @unittest 1395
833
 */
834
UNITTEST int dedotdotify(const char *input, size_t clen, char **outp);
835
UNITTEST int dedotdotify(const char *input, size_t clen, char **outp)
836
1.48k
{
837
1.48k
  struct dynbuf out;
838
1.48k
  CURLcode result = CURLE_OK;
839
840
  /* variables for leading dot checks */
841
1.48k
  const char *dinput = input;
842
1.48k
  size_t dlen = clen;
843
844
1.48k
  *outp = NULL;
845
1.48k
  if(!needs_dedotdot(input, clen))
846
863
    return 0;
847
848
621
  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
621
  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
1.49M
  while(clen && !result) { /* until end of path content */
874
1.49M
    if(ISSLASH(*input)) {
875
26.9k
      const char *p = &input[1];
876
26.9k
      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
26.9k
      if(is_dot(&p, &blen)) {
881
7.31k
        if(!blen) { /* /. */
882
43
          result = curlx_dyn_addn(&out, "/", 1);
883
43
          break;
884
43
        }
885
7.27k
        else if(ISSLASH(*p)) { /* /./ */
886
606
          input = p;
887
606
          clen = blen;
888
606
          continue;
889
606
        }
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
6.66k
        else if(is_dot(&p, &blen) && (ISSLASH(*p) || !blen)) {
896
          /* remove the last segment from the output buffer */
897
371
          size_t len = curlx_dyn_len(&out);
898
371
          if(len) {
899
243
            const char *ptr = curlx_dyn_ptr(&out);
900
243
            const char *last = memrchr(ptr, '/', len);
901
243
            if(last)
902
              /* trim the output at the slash */
903
243
              curlx_dyn_setlen(&out, last - ptr);
904
243
          }
905
906
371
          if(blen) { /* /../ */
907
360
            input = p;
908
360
            clen = blen;
909
360
            continue;
910
360
          }
911
11
          result = curlx_dyn_addn(&out, "/", 1);
912
11
          break;
913
371
        }
914
7.31k
      }
915
26.9k
    }
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
1.49M
    result = curlx_dyn_addn(&out, input, 1);
923
1.49M
    input++;
924
1.49M
    clen--;
925
1.49M
  }
926
621
end:
927
621
  if(!result) {
928
621
    if(curlx_dyn_len(&out))
929
621
      *outp = curlx_dyn_ptr(&out);
930
0
    else {
931
0
      *outp = curlx_strdup("");
932
0
      if(!*outp)
933
0
        return 1;
934
0
    }
935
621
  }
936
621
  return result ? 1 : 0; /* success */
937
621
}
938
939
/*
940
 * @unittest 1675
941
 */
942
UNITTEST CURLUcode parse_file(const char *url, size_t urllen, CURLU *u,
943
                              const char **pathp, size_t *pathlenp);
944
UNITTEST CURLUcode parse_file(const char *url, size_t urllen, CURLU *u,
945
                              const char **pathp, size_t *pathlenp)
946
45
{
947
45
  const char *path;
948
45
  size_t pathlen;
949
950
45
  *pathp = NULL;
951
45
  *pathlenp = 0;
952
45
  if(urllen <= 6)
953
    /* file:/ is not enough to actually be a complete file: URL */
954
1
    return CURLUE_BAD_FILE_URL;
955
956
  /* path has been allocated large enough to hold this */
957
44
  path = &url[5];
958
44
  pathlen = urllen - 5;
959
960
  /* RFC 8089: file-hier-part = ( "//" auth-path ) / local-path, where
961
     local-path also starts with a "/". So reject anything that does not
962
     start with at least one "/" */
963
44
  if(path[0] != '/')
964
9
    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
35
  if(path[1] == '/') {
973
    /* swallow the two slashes */
974
23
    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
23
    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
13
      if(checkprefix("localhost/", ptr) ||
996
13
         checkprefix("127.0.0.1/", ptr)) {
997
0
        ptr += 9; /* now points to the slash after the host */
998
0
      }
999
13
      else
1000
        /* Invalid file://hostname/, expected localhost or 127.0.0.1 or
1001
           none */
1002
13
        return CURLUE_BAD_FILE_URL;
1003
13
    }
1004
1005
10
    path = ptr;
1006
10
    pathlen = urllen - (ptr - url);
1007
10
  }
1008
1009
22
#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
22
  if(('/' == path[0] && STARTS_WITH_URL_DRIVE_PREFIX(&path[1])) ||
1013
19
     STARTS_WITH_URL_DRIVE_PREFIX(path)) {
1014
    /* File drive letters are only accepted in MS-DOS/Windows */
1015
8
    return CURLUE_BAD_FILE_URL;
1016
8
  }
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
14
  u->scheme = curlx_strdup("file");
1026
14
  if(!u->scheme)
1027
0
    return CURLUE_OUT_OF_MEMORY;
1028
1029
14
  *pathp = path;
1030
14
  *pathlenp = pathlen;
1031
14
  return CURLUE_OK;
1032
14
}
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
25.2k
{
1038
  /* clear path */
1039
25.2k
  const char *schemep = NULL;
1040
1041
25.2k
  if(schemelen) {
1042
25.2k
    int num_slashes = 0;
1043
25.2k
    const char *p = &url[schemelen + 1];
1044
25.2k
    if(!Curl_getn_scheme(schemebuf, schemelen) &&
1045
622
       !(flags & CURLU_NON_SUPPORT_SCHEME))
1046
0
      return CURLUE_UNSUPPORTED_SCHEME;
1047
1048
25.2k
    if(!ISSLASH(*p))
1049
      /* less than one */
1050
151
      return CURLUE_BAD_SLASHES;
1051
25.1k
    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
25.1k
    else {
1058
74.8k
      while(ISSLASH(*p) && (num_slashes < 4)) {
1059
49.7k
        p++;
1060
49.7k
        num_slashes++;
1061
49.7k
      }
1062
25.1k
      if(num_slashes > 3)
1063
1
        return CURLUE_BAD_SLASHES;
1064
25.1k
    }
1065
1066
25.1k
    schemep = schemebuf;
1067
25.1k
    *hostpp = p; /* hostname starts here */
1068
25.1k
  }
1069
0
  else {
1070
    /* no scheme! */
1071
1072
0
    if(!(flags & (CURLU_DEFAULT_SCHEME | CURLU_GUESS_SCHEME)))
1073
0
      return CURLUE_BAD_SCHEME;
1074
1075
0
    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
0
    *hostpp = url;
1082
0
  }
1083
1084
25.1k
  if(schemep) {
1085
25.1k
    u->scheme = curlx_strdup(schemep);
1086
25.1k
    if(!u->scheme)
1087
0
      return CURLUE_OUT_OF_MEMORY;
1088
25.1k
  }
1089
25.1k
  return CURLUE_OK;
1090
25.1k
}
1091
1092
static CURLUcode guess_scheme(CURLU *u, struct dynbuf *host)
1093
0
{
1094
0
  const char *hostname = curlx_dyn_ptr(host);
1095
0
  const char *schemep = NULL;
1096
  /* legacy curl-style guess based on hostname */
1097
0
  if(checkprefix("ftp.", hostname))
1098
0
    schemep = "ftp";
1099
0
  else if(checkprefix("dict.", hostname))
1100
0
    schemep = "dict";
1101
0
  else if(checkprefix("ldap.", hostname))
1102
0
    schemep = "ldap";
1103
0
  else if(checkprefix("imap.", hostname))
1104
0
    schemep = "imap";
1105
0
  else if(checkprefix("smtp.", hostname))
1106
0
    schemep = "smtp";
1107
0
  else if(checkprefix("pop3.", hostname))
1108
0
    schemep = "pop3";
1109
0
  else
1110
0
    schemep = "http";
1111
1112
0
  u->scheme = curlx_strdup(schemep);
1113
0
  if(!u->scheme)
1114
0
    return CURLUE_OUT_OF_MEMORY;
1115
1116
0
  u->guessed_scheme = TRUE;
1117
0
  return CURLUE_OK;
1118
0
}
1119
1120
static CURLUcode handle_fragment(CURLU *u, const char *fragment,
1121
                                 size_t fraglen, unsigned int flags)
1122
163
{
1123
163
  CURLUcode ures;
1124
163
  u->fragment_present = TRUE;
1125
163
  if(fraglen > 1) {
1126
    /* skip the leading '#' in the copy but include the null-terminator */
1127
58
    if(flags & CURLU_URLENCODE) {
1128
9
      struct dynbuf enc;
1129
9
      curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1130
9
      ures = urlencode_str(&enc, fragment + 1, fraglen - 1, TRUE, QUERY_NO);
1131
9
      if(ures)
1132
0
        return ures;
1133
9
      u->fragment = curlx_dyn_ptr(&enc);
1134
9
    }
1135
49
    else {
1136
49
      if(badoctets(fragment, fraglen, flags))
1137
3
        return CURLUE_BAD_FRAGMENT;
1138
46
      u->fragment = curlx_memdup0(fragment + 1, fraglen - 1);
1139
46
      if(!u->fragment)
1140
0
        return CURLUE_OUT_OF_MEMORY;
1141
46
    }
1142
58
  }
1143
160
  return CURLUE_OK;
1144
163
}
1145
1146
static CURLUcode handle_query(CURLU *u, const char *query,
1147
                              size_t qlen, unsigned int flags)
1148
460
{
1149
460
  u->query_present = TRUE;
1150
460
  if(qlen > 1) {
1151
167
    if(flags & CURLU_URLENCODE) {
1152
24
      struct dynbuf enc;
1153
24
      CURLUcode ures;
1154
24
      curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1155
      /* skip the leading question mark */
1156
24
      ures = urlencode_str(&enc, query + 1, qlen - 1, TRUE, QUERY_YES);
1157
24
      if(ures)
1158
0
        return ures;
1159
24
      u->query = curlx_dyn_ptr(&enc);
1160
24
    }
1161
143
    else {
1162
143
      if(badoctets(query, qlen, flags))
1163
0
        return CURLUE_BAD_QUERY;
1164
1165
143
      u->query = curlx_memdup0(query + 1, qlen - 1);
1166
143
      if(!u->query)
1167
0
        return CURLUE_OUT_OF_MEMORY;
1168
143
    }
1169
167
  }
1170
293
  else {
1171
    /* single byte query */
1172
293
    u->query = curlx_strdup("");
1173
293
    if(!u->query)
1174
0
      return CURLUE_OUT_OF_MEMORY;
1175
293
  }
1176
460
  return CURLUE_OK;
1177
460
}
1178
1179
static CURLUcode handle_path(CURLU *u, const char *path,
1180
                             size_t pathlen, unsigned int flags,
1181
                             bool is_file)
1182
24.8k
{
1183
24.8k
  CURLUcode ures;
1184
24.8k
  if(pathlen && (flags & CURLU_URLENCODE)) {
1185
323
    struct dynbuf enc;
1186
323
    curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1187
323
    ures = urlencode_str(&enc, path, pathlen, TRUE, QUERY_NO);
1188
323
    if(ures)
1189
0
      return ures;
1190
323
    pathlen = curlx_dyn_len(&enc);
1191
323
    path = u->path = curlx_dyn_ptr(&enc);
1192
323
  }
1193
1194
24.8k
  if(pathlen >= (size_t)(1 + !is_file)) {
1195
1.57k
    if(badoctets(path, pathlen, flags))
1196
41
      return CURLUE_BAD_PATH;
1197
1198
    /* paths for file:// scheme can be one byte, others need to be two */
1199
1.52k
    if(!u->path) {
1200
1.36k
      u->path = curlx_memdup0(path, pathlen);
1201
1.36k
      if(!u->path)
1202
0
        return CURLUE_OUT_OF_MEMORY;
1203
1.36k
      path = u->path;
1204
1.36k
    }
1205
163
    else if(flags & CURLU_URLENCODE)
1206
      /* it might have encoded more than the path so cut it */
1207
163
      u->path[pathlen] = 0;
1208
1209
1.52k
    if(!(flags & CURLU_PATH_AS_IS)) {
1210
      /* remove ../ and ./ sequences according to RFC3986 */
1211
1.48k
      char *dedot;
1212
1.48k
      int err = dedotdotify(path, pathlen, &dedot);
1213
1.48k
      if(err)
1214
0
        return CURLUE_OUT_OF_MEMORY;
1215
1.48k
      if(dedot) {
1216
621
        curlx_free(u->path);
1217
621
        u->path = dedot;
1218
621
      }
1219
1.48k
    }
1220
1.52k
  }
1221
24.8k
  return CURLUE_OK;
1222
24.8k
}
1223
1224
static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
1225
25.3k
{
1226
25.3k
  const char *path;
1227
25.3k
  size_t pathlen;
1228
25.3k
  char schemebuf[MAX_SCHEME_LEN + 1];
1229
25.3k
  size_t schemelen = 0;
1230
25.3k
  size_t urllen;
1231
25.3k
  CURLUcode ures = CURLUE_OK;
1232
25.3k
  struct dynbuf host;
1233
25.3k
  bool is_file = FALSE;
1234
1235
25.3k
  DEBUGASSERT(url);
1236
1237
25.3k
  urllen = strlen(url);
1238
25.3k
  if(urllen > CURL_MAX_INPUT_LENGTH)
1239
0
    return CURLUE_MALFORMED_INPUT;
1240
1241
25.3k
  curlx_dyn_init(&host, CURL_MAX_INPUT_LENGTH);
1242
1243
25.3k
  schemelen = Curl_is_absolute_url(url, schemebuf, sizeof(schemebuf),
1244
25.3k
                                   flags & (CURLU_GUESS_SCHEME |
1245
25.3k
                                            CURLU_DEFAULT_SCHEME));
1246
1247
  /* handle the file: scheme */
1248
25.3k
  if(schemelen == 4 && !memcmp(schemebuf, "file", 4)) {
1249
45
    is_file = TRUE;
1250
45
    ures = parse_file(url, urllen, u, &path, &pathlen);
1251
45
  }
1252
25.2k
  else {
1253
25.2k
    const char *hostp = NULL;
1254
25.2k
    const char *p;
1255
25.2k
    size_t hostlen;
1256
25.2k
    ures = parse_scheme(url, u, schemebuf, schemelen, flags, &hostp);
1257
25.2k
    if(ures)
1258
152
      goto fail;
1259
1260
    /* find the end of the hostname + port number */
1261
25.1k
    p = hostp;
1262
344k
    while(*p && *p != '/' && *p != '?' && *p != '#')
1263
319k
      p++;
1264
25.1k
    hostlen = p - hostp;
1265
25.1k
    path = p;
1266
1267
    /* this pathlen also contains the query and the fragment */
1268
25.1k
    pathlen = urllen - (path - url);
1269
25.1k
    if(hostlen) {
1270
25.1k
      ures = parse_authority(u, hostp, hostlen, flags, &host, !!u->scheme);
1271
25.1k
      if(!ures && (flags & CURLU_GUESS_SCHEME) && !u->scheme)
1272
0
        ures = guess_scheme(u, &host);
1273
25.1k
    }
1274
7
    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
7
    else
1280
7
      ures = CURLUE_NO_HOST;
1281
25.1k
  }
1282
25.1k
  if(!ures) {
1283
    /* The path might at this point contain a fragment and/or a query to
1284
       handle */
1285
24.9k
    const char *fragment = memchr(path, '#', pathlen);
1286
24.9k
    if(fragment) {
1287
163
      size_t fraglen = pathlen - (fragment - path);
1288
163
      ures = handle_fragment(u, fragment, fraglen, flags);
1289
      /* after this, pathlen still contains the query */
1290
163
      pathlen -= fraglen;
1291
163
    }
1292
24.9k
  }
1293
25.1k
  if(!ures) {
1294
24.8k
    const char *query = memchr(path, '?', pathlen);
1295
24.8k
    if(query) {
1296
460
      size_t qlen = pathlen - (query - path);
1297
460
      ures = handle_query(u, query, qlen, flags);
1298
460
      pathlen -= qlen;
1299
460
    }
1300
24.8k
  }
1301
25.1k
  if(!ures)
1302
    /* the fragment and query parts are trimmed off from the path */
1303
24.8k
    ures = handle_path(u, path, pathlen, flags, is_file);
1304
25.1k
  if(!ures) {
1305
24.8k
    u->host = curlx_dyn_ptr(&host);
1306
24.8k
    return CURLUE_OK;
1307
24.8k
  }
1308
454
fail:
1309
454
  curlx_dyn_free(&host);
1310
454
  free_urlhandle(u);
1311
454
  return ures;
1312
25.1k
}
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
25.3k
{
1320
25.3k
  CURLUcode ures;
1321
25.3k
  CURLU tmpurl;
1322
25.3k
  memset(&tmpurl, 0, sizeof(tmpurl));
1323
25.3k
  ures = parseurl(url, &tmpurl, flags);
1324
25.3k
  if(!ures) {
1325
24.8k
    free_urlhandle(u);
1326
24.8k
    *u = tmpurl;
1327
24.8k
  }
1328
25.3k
  return ures;
1329
25.3k
}
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
127
{
1337
127
  struct dynbuf urlbuf;
1338
127
  bool host_changed = FALSE;
1339
127
  const char *useurl = relurl;
1340
127
  const char *cutoff = NULL;
1341
127
  size_t prelen;
1342
127
  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
127
  const char *scheme = u->scheme ? u->scheme : DEFAULT_SCHEME;
1346
1347
  /* protsep points to the start of the hostname, after [scheme]:// */
1348
127
  const char *protsep = base + strlen(scheme) + 3;
1349
127
  DEBUGASSERT(base && relurl && u); /* all set here */
1350
127
  if(!base)
1351
0
    return CURLUE_MALFORMED_INPUT; /* should never happen */
1352
1353
  /* handle different relative URL types */
1354
127
  switch(relurl[0]) {
1355
29
  case '/':
1356
29
    if(relurl[1] == '/') {
1357
      /* protocol-relative URL: //example.com/path */
1358
19
      cutoff = protsep;
1359
19
      useurl = &relurl[2];
1360
19
      host_changed = TRUE;
1361
19
    }
1362
10
    else
1363
      /* absolute /path */
1364
10
      cutoff = strchr(protsep, '/');
1365
29
    break;
1366
1367
22
  case '#':
1368
    /* fragment-only change */
1369
22
    if(u->fragment_present)
1370
2
      cutoff = strchr(protsep, '#');
1371
22
    break;
1372
1373
76
  default:
1374
    /* path or query-only change */
1375
76
    if(u->query_present)
1376
      /* remove existing query */
1377
12
      cutoff = strchr(protsep, '?');
1378
64
    else if(u->fragment_present)
1379
      /* Remove existing fragment */
1380
2
      cutoff = strchr(protsep, '#');
1381
1382
76
    if(relurl[0] != '?') {
1383
      /* append a relative path after the last slash */
1384
73
      cutoff = memrchr(protsep, '/',
1385
73
                       cutoff ? (size_t)(cutoff - protsep) : strlen(protsep));
1386
73
      if(cutoff)
1387
73
        cutoff++; /* truncate after last slash */
1388
73
    }
1389
76
    break;
1390
127
  }
1391
1392
127
  prelen = cutoff ? (size_t)(cutoff - base) : strlen(base);
1393
1394
  /* build new URL */
1395
127
  curlx_dyn_init(&urlbuf, CURL_MAX_INPUT_LENGTH);
1396
1397
127
  if(!curlx_dyn_addn(&urlbuf, base, prelen) &&
1398
127
     !urlencode_str(&urlbuf, useurl, strlen(useurl), !host_changed,
1399
127
                    QUERY_NOT_YET)) {
1400
127
    uc = parseurl_and_replace(curlx_dyn_ptr(&urlbuf), u,
1401
127
                              flags & ~U_CURLU_PATH_AS_IS);
1402
127
  }
1403
0
  else
1404
0
    uc = CURLUE_OUT_OF_MEMORY;
1405
1406
127
  curlx_dyn_free(&urlbuf);
1407
127
  return uc;
1408
127
}
1409
1410
/*
1411
 */
1412
CURLU *curl_url(void)
1413
24.9k
{
1414
24.9k
  return curlx_calloc(1, sizeof(struct Curl_URL));
1415
24.9k
}
1416
1417
void curl_url_cleanup(CURLU *u)
1418
55.7k
{
1419
55.7k
  if(u) {
1420
24.9k
    free_urlhandle(u);
1421
24.9k
    curlx_free(u);
1422
24.9k
  }
1423
55.7k
}
1424
1425
#define DUP(dest, src, name)                    \
1426
0
  do {                                          \
1427
0
    if((src)->name) {                           \
1428
0
      (dest)->name = curlx_strdup((src)->name); \
1429
0
      if(!(dest)->name)                         \
1430
0
        goto fail;                              \
1431
0
    }                                           \
1432
0
  } while(0)
1433
1434
CURLU *curl_url_dup(const CURLU *in)
1435
0
{
1436
0
  struct Curl_URL *u = curlx_calloc(1, sizeof(struct Curl_URL));
1437
0
  if(u) {
1438
0
    DUP(u, in, scheme);
1439
0
    DUP(u, in, user);
1440
0
    DUP(u, in, password);
1441
0
    DUP(u, in, options);
1442
0
    DUP(u, in, host);
1443
0
    DUP(u, in, path);
1444
0
    DUP(u, in, query);
1445
0
    DUP(u, in, fragment);
1446
0
    DUP(u, in, zoneid);
1447
0
    u->portnum = in->portnum;
1448
0
    u->port_present = in->port_present;
1449
0
    u->fragment_present = in->fragment_present;
1450
0
    u->query_present = in->query_present;
1451
0
  }
1452
0
  return u;
1453
0
fail:
1454
0
  curl_url_cleanup(u);
1455
0
  return NULL;
1456
0
}
1457
1458
#ifndef USE_IDN
1459
#define host_decode(x, y) CURLUE_LACKS_IDN
1460
#define host_encode(x, y) CURLUE_LACKS_IDN
1461
#else
1462
static CURLUcode host_decode(const char *host, char **allochost)
1463
0
{
1464
0
  CURLcode result = Curl_idn_decode(host, allochost);
1465
0
  if(result)
1466
0
    return (result == CURLE_OUT_OF_MEMORY) ?
1467
0
      CURLUE_OUT_OF_MEMORY : CURLUE_BAD_HOSTNAME;
1468
0
  return CURLUE_OK;
1469
0
}
1470
1471
static CURLUcode host_encode(const char *host, char **allochost)
1472
0
{
1473
0
  CURLcode result = Curl_idn_encode(host, allochost);
1474
0
  if(result)
1475
0
    return (result == CURLE_OUT_OF_MEMORY) ?
1476
0
      CURLUE_OUT_OF_MEMORY : CURLUE_BAD_HOSTNAME;
1477
0
  return CURLUE_OK;
1478
0
}
1479
#endif
1480
1481
static CURLUcode urlget_format(const CURLU *u, CURLUPart what,
1482
                               const char *ptr, char **partp,
1483
                               bool plusdecode, unsigned int flags)
1484
73.1k
{
1485
73.1k
  CURLUcode uc = CURLUE_OK;
1486
73.1k
  size_t partlen = strlen(ptr);
1487
73.1k
  bool urldecode = (flags & CURLU_URLDECODE) ? 1 : 0;
1488
73.1k
  bool urlencode = (flags & CURLU_URLENCODE) ? 1 : 0;
1489
73.1k
  bool punycode = (flags & CURLU_PUNYCODE) && (what == CURLUPART_HOST);
1490
73.1k
  bool depunyfy = (flags & CURLU_PUNY2IDN) && (what == CURLUPART_HOST);
1491
73.1k
  char *part = curlx_memdup0(ptr, partlen);
1492
73.1k
  *partp = NULL;
1493
73.1k
  if(!part)
1494
0
    return CURLUE_OUT_OF_MEMORY;
1495
73.1k
  if(plusdecode) {
1496
    /* convert + to space */
1497
0
    char *plus = part;
1498
0
    size_t i = 0;
1499
0
    for(i = 0; i < partlen; ++plus, i++) {
1500
0
      if(*plus == '+')
1501
0
        *plus = ' ';
1502
0
    }
1503
0
  }
1504
73.1k
  if(urldecode) {
1505
11.9k
    char *decoded;
1506
11.9k
    size_t dlen;
1507
    /* this unconditional rejection of control bytes is documented API
1508
       behavior */
1509
11.9k
    CURLcode result = Curl_urldecode(part, partlen, &decoded, &dlen,
1510
11.9k
                                     REJECT_CTRL);
1511
11.9k
    curlx_free(part);
1512
11.9k
    if(result)
1513
0
      return CURLUE_URLDECODE;
1514
11.9k
    part = decoded;
1515
11.9k
    partlen = dlen;
1516
11.9k
  }
1517
73.1k
  if(urlencode) {
1518
11.9k
    struct dynbuf enc;
1519
11.9k
    curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1520
11.9k
    uc = urlencode_str(&enc, part, partlen, TRUE, what == CURLUPART_QUERY ?
1521
11.9k
                       QUERY_YES : QUERY_NO);
1522
11.9k
    curlx_free(part);
1523
11.9k
    if(uc)
1524
0
      return uc;
1525
11.9k
    part = curlx_dyn_ptr(&enc);
1526
11.9k
  }
1527
61.1k
  else if(punycode) {
1528
0
    if(!Curl_is_ASCII_name(u->host)) {
1529
0
      char *punyversion = NULL;
1530
0
      uc = host_decode(part, &punyversion);
1531
0
      curlx_free(part);
1532
0
      if(uc)
1533
0
        return uc;
1534
0
      part = punyversion;
1535
0
    }
1536
0
  }
1537
61.1k
  else if(depunyfy && Curl_is_ASCII_name(u->host)) {
1538
0
    char *unpunified = NULL;
1539
0
    uc = host_encode(part, &unpunified);
1540
0
    curlx_free(part);
1541
0
    if(uc)
1542
0
      return uc;
1543
0
    part = unpunified;
1544
0
  }
1545
73.1k
  *partp = part;
1546
73.1k
  return CURLUE_OK;
1547
73.1k
}
1548
1549
static CURLUcode file_url(const CURLU *u, char **part,
1550
                          const char *fragmentsep,
1551
                          const char *querysep)
1552
0
{
1553
0
  char *url = curl_maprintf("file://%s%s%s%s%s",
1554
0
                            u->path, querysep, u->query ? u->query : "",
1555
0
                            fragmentsep, u->fragment ? u->fragment : "");
1556
0
  if(!url)
1557
0
    return CURLUE_OUT_OF_MEMORY;
1558
1559
0
  *part = url;
1560
0
  return CURLUE_OK;
1561
0
}
1562
1563
static CURLUcode urlget_url(const CURLU *u, char **part, unsigned int flags)
1564
12.3k
{
1565
12.3k
  char *url;
1566
12.3k
  char *allochost = NULL;
1567
12.3k
  const char *fragmentsep =
1568
12.3k
    (u->fragment || (u->fragment_present && flags & CURLU_GET_EMPTY)) ?
1569
12.2k
    "#" : "";
1570
12.3k
  const char *querysep = ((u->query && u->query[0]) ||
1571
12.2k
                          (u->query_present && flags & CURLU_GET_EMPTY)) ?
1572
11.9k
    "?" : "";
1573
12.3k
  char portbuf[7];
1574
12.3k
  if(curl_strequal("file", u->scheme))
1575
0
    return file_url(u, part, fragmentsep, querysep);
1576
12.3k
  else if(!u->host)
1577
0
    return CURLUE_NO_HOST;
1578
12.3k
  else {
1579
12.3k
    const char *scheme;
1580
12.3k
    char *options = u->options;
1581
12.3k
    char *port = NULL;
1582
12.3k
    const struct Curl_scheme *h = NULL;
1583
12.3k
    char schemebuf[MAX_SCHEME_LEN + 5];
1584
12.3k
    if(u->scheme)
1585
12.3k
      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
12.3k
    if(u->port_present) {
1592
2
      curl_msnprintf(portbuf, sizeof(portbuf), "%u", u->portnum);
1593
2
      port = portbuf;
1594
2
    }
1595
1596
12.3k
    h = Curl_get_scheme(scheme);
1597
12.3k
    if(h) {
1598
12.3k
      if(!u->port_present && (flags & CURLU_DEFAULT_PORT)) {
1599
        /* there is no stored port number, but asked to deliver a default one
1600
           for the scheme */
1601
0
        curl_msnprintf(portbuf, sizeof(portbuf), "%u", h->defport);
1602
0
        port = portbuf;
1603
0
      }
1604
12.3k
      else if(u->port_present && (h->defport == u->portnum) &&
1605
0
              (flags & CURLU_NO_DEFAULT_PORT)) {
1606
        /* there is a stored port number, but asked to inhibit if it matches
1607
           the default port for the scheme */
1608
0
        port = NULL;
1609
0
      }
1610
1611
12.3k
      if(!(h->flags & PROTOPT_URLOPTIONS))
1612
12.3k
        options = NULL;
1613
12.3k
    }
1614
1615
12.3k
    if(u->host[0] == '[') {
1616
0
      if(u->zoneid) {
1617
        /* make it '[ host %25 zoneid ]' */
1618
0
        struct dynbuf enc;
1619
0
        size_t hostlen = strlen(u->host);
1620
0
        curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1621
0
        if(curlx_dyn_addf(&enc, "%.*s%%25%s]", (int)hostlen - 1, u->host,
1622
0
                          u->zoneid))
1623
0
          return CURLUE_OUT_OF_MEMORY;
1624
0
        allochost = curlx_dyn_ptr(&enc);
1625
0
      }
1626
0
    }
1627
12.3k
    else if(flags & CURLU_URLENCODE) {
1628
0
      allochost = curl_easy_escape(NULL, u->host, 0);
1629
0
      if(!allochost)
1630
0
        return CURLUE_OUT_OF_MEMORY;
1631
0
    }
1632
12.3k
    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
12.3k
    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
12.3k
    if(!(flags & CURLU_NO_GUESS_SCHEME) || !u->guessed_scheme)
1648
12.3k
      curl_msnprintf(schemebuf, sizeof(schemebuf), "%s://", scheme);
1649
0
    else
1650
0
      schemebuf[0] = 0;
1651
1652
12.3k
    url = curl_maprintf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
1653
12.3k
                        schemebuf,
1654
12.3k
                        u->user ? u->user : "",
1655
12.3k
                        u->password ? ":" : "",
1656
12.3k
                        u->password ? u->password : "",
1657
12.3k
                        options ? ";" : "",
1658
12.3k
                        options ? options : "",
1659
12.3k
                        (u->user || u->password || options) ? "@" : "",
1660
12.3k
                        allochost ? allochost : u->host,
1661
12.3k
                        port ? ":" : "",
1662
12.3k
                        port ? port : "",
1663
12.3k
                        u->path ? u->path : "/",
1664
12.3k
                        querysep,
1665
12.3k
                        u->query ? u->query : "",
1666
12.3k
                        fragmentsep,
1667
12.3k
                        u->fragment ? u->fragment : "");
1668
12.3k
    curlx_free(allochost);
1669
12.3k
  }
1670
12.3k
  if(!url)
1671
0
    return CURLUE_OUT_OF_MEMORY;
1672
12.3k
  *part = url;
1673
12.3k
  return CURLUE_OK;
1674
12.3k
}
1675
1676
CURLUcode curl_url_get(const CURLU *u, CURLUPart what,
1677
                       char **part, unsigned int flags)
1678
193k
{
1679
193k
  const char *ptr;
1680
193k
  CURLUcode ifmissing = CURLUE_UNKNOWN_PART;
1681
193k
  char portbuf[7];
1682
193k
  bool plusdecode = FALSE;
1683
193k
  if(!u)
1684
0
    return CURLUE_BAD_HANDLE;
1685
193k
  if(!part)
1686
0
    return CURLUE_BAD_PARTPOINTER;
1687
193k
  *part = NULL;
1688
1689
193k
  switch(what) {
1690
36.1k
  case CURLUPART_SCHEME:
1691
36.1k
    ptr = u->scheme;
1692
36.1k
    ifmissing = CURLUE_NO_SCHEME;
1693
36.1k
    flags &= ~U_CURLU_URLDECODE; /* never for schemes */
1694
36.1k
    if((flags & CURLU_NO_GUESS_SCHEME) && u->guessed_scheme)
1695
0
      return CURLUE_NO_SCHEME;
1696
36.1k
    break;
1697
36.1k
  case CURLUPART_USER:
1698
23.8k
    ptr = u->user;
1699
23.8k
    ifmissing = CURLUE_NO_USER;
1700
23.8k
    break;
1701
23.8k
  case CURLUPART_PASSWORD:
1702
23.8k
    ptr = u->password;
1703
23.8k
    ifmissing = CURLUE_NO_PASSWORD;
1704
23.8k
    break;
1705
11.9k
  case CURLUPART_OPTIONS:
1706
11.9k
    ptr = u->options;
1707
11.9k
    ifmissing = CURLUE_NO_OPTIONS;
1708
11.9k
    break;
1709
24.2k
  case CURLUPART_HOST:
1710
24.2k
    ptr = u->host;
1711
24.2k
    ifmissing = CURLUE_NO_HOST;
1712
24.2k
    break;
1713
23.8k
  case CURLUPART_ZONEID:
1714
23.8k
    ptr = u->zoneid;
1715
23.8k
    ifmissing = CURLUE_NO_ZONEID;
1716
23.8k
    break;
1717
12.2k
  case CURLUPART_PORT:
1718
12.2k
    ptr = NULL;
1719
12.2k
    ifmissing = CURLUE_NO_PORT;
1720
12.2k
    flags &= ~U_CURLU_URLDECODE; /* never for port */
1721
12.2k
    if(u->port_present) {
1722
12.0k
      const struct Curl_scheme *h = u->scheme ?
1723
12.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
12.0k
      if(h && (h->defport == u->portnum) &&
1727
11.9k
         (flags & CURLU_NO_DEFAULT_PORT)) {
1728
11.9k
        ptr = NULL;
1729
11.9k
      }
1730
113
      else {
1731
113
        curl_msnprintf(portbuf, sizeof(portbuf), "%u", u->portnum);
1732
113
        ptr = portbuf;
1733
113
      }
1734
12.0k
    }
1735
196
    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
12.2k
    break;
1745
12.2k
  case CURLUPART_PATH:
1746
12.2k
    ptr = u->path;
1747
12.2k
    if(!ptr)
1748
11.1k
      ptr = "/";
1749
12.2k
    break;
1750
12.2k
  case CURLUPART_QUERY:
1751
12.2k
    ptr = u->query;
1752
12.2k
    ifmissing = CURLUE_NO_QUERY;
1753
12.2k
    plusdecode = flags & CURLU_URLDECODE;
1754
12.2k
    if(ptr && !ptr[0] && !(flags & CURLU_GET_EMPTY))
1755
      /* there was a blank query and the user does not ask for it */
1756
14
      ptr = NULL;
1757
12.2k
    break;
1758
0
  case CURLUPART_FRAGMENT:
1759
0
    ptr = u->fragment;
1760
0
    ifmissing = CURLUE_NO_FRAGMENT;
1761
0
    if(!ptr && u->fragment_present && flags & CURLU_GET_EMPTY)
1762
      /* there was a blank fragment and the user asks for it */
1763
0
      ptr = "";
1764
0
    break;
1765
12.3k
  case CURLUPART_URL:
1766
12.3k
    return urlget_url(u, part, flags);
1767
0
  default:
1768
0
    ptr = NULL;
1769
0
    break;
1770
193k
  }
1771
180k
  if(ptr)
1772
73.1k
    return urlget_format(u, what, ptr, part, plusdecode, flags);
1773
1774
107k
  return ifmissing;
1775
180k
}
1776
1777
static CURLUcode set_url_scheme(CURLU *u, const char *scheme,
1778
                                unsigned int flags)
1779
0
{
1780
0
  size_t plen = strlen(scheme);
1781
0
  const struct Curl_scheme *h = NULL;
1782
0
  if((plen > MAX_SCHEME_LEN) || (plen < 1))
1783
    /* too long or too short */
1784
0
    return CURLUE_BAD_SCHEME;
1785
  /* verify that it is a fine scheme */
1786
0
  h = Curl_get_scheme(scheme);
1787
0
  if(!(flags & CURLU_NON_SUPPORT_SCHEME) && (!h || !h->run))
1788
0
    return CURLUE_UNSUPPORTED_SCHEME;
1789
0
  if(!h) {
1790
0
    const char *s = scheme;
1791
0
    if(ISALPHA(*s)) {
1792
      /* ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) */
1793
0
      s++;
1794
0
      while(--plen) {
1795
0
        if(ISALNUM(*s) || (*s == '+') || (*s == '-') || (*s == '.'))
1796
0
          s++; /* fine */
1797
0
        else
1798
0
          return CURLUE_BAD_SCHEME;
1799
0
      }
1800
0
    }
1801
0
    else
1802
0
      return CURLUE_BAD_SCHEME;
1803
0
  }
1804
0
  u->guessed_scheme = FALSE;
1805
0
  return CURLUE_OK;
1806
0
}
1807
1808
static CURLUcode set_url_port(CURLU *u, const char *provided_port)
1809
0
{
1810
0
  curl_off_t port;
1811
0
  if(!ISDIGIT(provided_port[0]))
1812
    /* not a number */
1813
0
    return CURLUE_BAD_PORT_NUMBER;
1814
0
  if(curlx_str_number(&provided_port, &port, 0xffff) || *provided_port)
1815
    /* weirdly provided number, not good! */
1816
0
    return CURLUE_BAD_PORT_NUMBER;
1817
0
  u->portnum = (uint16_t)port;
1818
0
  u->port_present = TRUE;
1819
0
  return CURLUE_OK;
1820
0
}
1821
1822
static CURLUcode set_url(CURLU *u, const char *url, size_t part_size,
1823
                         unsigned int flags)
1824
25.3k
{
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
25.3k
  CURLUcode uc;
1832
25.3k
  char *oldurl = NULL;
1833
1834
25.3k
  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
0
    uc = curl_url_get(u, CURLUPART_URL, &oldurl, flags);
1838
0
    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
0
    if(uc == CURLUE_OUT_OF_MEMORY)
1848
0
      return uc;
1849
0
    return CURLUE_MALFORMED_INPUT;
1850
0
  }
1851
1852
  /* if the new URL is absolute replace the existing with the new. */
1853
25.3k
  if(Curl_is_absolute_url(url, NULL, 0,
1854
25.3k
                          flags & (CURLU_GUESS_SCHEME | CURLU_DEFAULT_SCHEME)))
1855
25.1k
    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
127
  uc = curl_url_get(u, CURLUPART_URL, &oldurl,
1863
127
                    (flags & ~CURLU_NO_GUESS_SCHEME) | CURLU_GET_EMPTY);
1864
127
  if(uc == CURLUE_OUT_OF_MEMORY)
1865
0
    return uc;
1866
127
  else if(uc)
1867
0
    return parseurl_and_replace(url, u, flags);
1868
1869
127
  DEBUGASSERT(oldurl); /* it is set here */
1870
  /* apply the relative part to create a new URL */
1871
127
  uc = redirect_url(oldurl, url, u, flags);
1872
127
  curlx_free(oldurl);
1873
127
  return uc;
1874
127
}
1875
1876
static CURLUcode urlset_clear(CURLU *u, CURLUPart what)
1877
0
{
1878
0
  switch(what) {
1879
0
  case CURLUPART_URL:
1880
0
    free_urlhandle(u);
1881
0
    memset(u, 0, sizeof(struct Curl_URL));
1882
0
    break;
1883
0
  case CURLUPART_SCHEME:
1884
0
    curlx_safefree(u->scheme);
1885
0
    u->guessed_scheme = FALSE;
1886
0
    break;
1887
0
  case CURLUPART_USER:
1888
0
    curlx_safefree(u->user);
1889
0
    break;
1890
0
  case CURLUPART_PASSWORD:
1891
0
    curlx_strzero(u->password);
1892
0
    curlx_safefree(u->password);
1893
0
    break;
1894
0
  case CURLUPART_OPTIONS:
1895
0
    curlx_safefree(u->options);
1896
0
    break;
1897
0
  case CURLUPART_HOST:
1898
0
    curlx_safefree(u->host);
1899
0
    break;
1900
0
  case CURLUPART_ZONEID:
1901
0
    curlx_safefree(u->zoneid);
1902
0
    break;
1903
0
  case CURLUPART_PORT:
1904
0
    u->portnum = 0;
1905
0
    u->port_present = FALSE;
1906
0
    break;
1907
0
  case CURLUPART_PATH:
1908
0
    curlx_safefree(u->path);
1909
0
    break;
1910
0
  case CURLUPART_QUERY:
1911
0
    curlx_safefree(u->query);
1912
0
    u->query_present = FALSE;
1913
0
    break;
1914
0
  case CURLUPART_FRAGMENT:
1915
0
    curlx_safefree(u->fragment);
1916
0
    u->fragment_present = FALSE;
1917
0
    break;
1918
0
  default:
1919
0
    return CURLUE_UNKNOWN_PART;
1920
0
  }
1921
0
  return CURLUE_OK;
1922
0
}
1923
1924
static bool allowed_in_path(unsigned char x)
1925
0
{
1926
0
  switch(x) {
1927
0
  case '!':
1928
0
  case '$':
1929
0
  case '&':
1930
0
  case '\'':
1931
0
  case '(':
1932
0
  case ')':
1933
0
  case '{':
1934
0
  case '}':
1935
0
  case '[':
1936
0
  case ']':
1937
0
  case '*':
1938
0
  case '+':
1939
0
  case ',':
1940
0
  case ';':
1941
0
  case '=':
1942
0
  case ':':
1943
0
  case '@':
1944
0
  case '/':
1945
0
    return TRUE;
1946
0
  }
1947
0
  return FALSE;
1948
0
}
1949
1950
static CURLUcode url_encode_part(struct dynbuf *encp,
1951
                                 const char *part,
1952
                                 bool plusencode,
1953
                                 bool pathmode,
1954
                                 bool equalsencode)
1955
0
{
1956
0
  const unsigned char *i;
1957
1958
0
  for(i = (const unsigned char *)part; *i; i++) {
1959
0
    CURLcode result;
1960
0
    if((*i == ' ') && plusencode)
1961
0
      result = curlx_dyn_addn(encp, "+", 1);
1962
0
    else if(ISUNRESERVED(*i) ||
1963
0
            (pathmode && allowed_in_path(*i)) ||
1964
0
            ((*i == '=') && equalsencode)) {
1965
0
      if((*i == '=') && equalsencode)
1966
        /* only skip the first equals sign */
1967
0
        equalsencode = FALSE;
1968
0
      result = curlx_dyn_addn(encp, i, 1);
1969
0
    }
1970
0
    else {
1971
0
      unsigned char out[3] = { '%' };
1972
0
      Curl_hexbyte(&out[1], *i);
1973
0
      result = curlx_dyn_addn(encp, out, 3);
1974
0
    }
1975
0
    if(result)
1976
0
      return cc2cu(result);
1977
0
  }
1978
0
  return CURLUE_OK;
1979
0
}
1980
1981
static CURLUcode url_uppercasehex_part(struct dynbuf *encp,
1982
                                       const char *part)
1983
0
{
1984
0
  char *p;
1985
0
  CURLcode result = curlx_dyn_add(encp, part);
1986
0
  if(result)
1987
0
    return cc2cu(result);
1988
0
  p = curlx_dyn_ptr(encp);
1989
0
  while(*p) {
1990
    /* make sure percent encoded are upper case */
1991
0
    if((*p == '%') && ISXDIGIT(p[1]) && ISXDIGIT(p[2]) &&
1992
0
       (ISLOWER(p[1]) || ISLOWER(p[2]))) {
1993
0
      p[1] = Curl_raw_toupper(p[1]);
1994
0
      p[2] = Curl_raw_toupper(p[2]);
1995
0
      p += 3;
1996
0
    }
1997
0
    else
1998
0
      p++;
1999
0
  }
2000
0
  return CURLUE_OK;
2001
0
}
2002
2003
static CURLUcode url_append_query(CURLU *u, struct dynbuf *encp)
2004
0
{
2005
  /* Append the 'encp' string onto the old query. Add a '&' separator if none
2006
     is already present at the end of the existing query */
2007
2008
0
  size_t querylen = u->query ? strlen(u->query) : 0;
2009
0
  bool addamperand = querylen && (u->query[querylen - 1] != '&');
2010
0
  if(querylen) {
2011
0
    struct dynbuf qbuf;
2012
0
    CURLcode result;
2013
0
    const char *newp = curlx_dyn_ptr(encp);
2014
0
    curlx_dyn_init(&qbuf, CURL_MAX_INPUT_LENGTH);
2015
2016
    /* add original query */
2017
0
    result = curlx_dyn_addn(&qbuf, u->query, querylen);
2018
0
    if(!result && addamperand)
2019
      /* add ampersand */
2020
0
      result = curlx_dyn_addn(&qbuf, "&", 1);
2021
0
    if(!result)
2022
      /* add new query part */
2023
0
      result = curlx_dyn_add(&qbuf, newp);
2024
0
    if(result)
2025
0
      goto nomem;
2026
0
    curlx_dyn_free(encp);
2027
0
    curlx_free(u->query);
2028
0
    u->query = curlx_dyn_ptr(&qbuf);
2029
0
    return CURLUE_OK;
2030
0
nomem:
2031
0
    curlx_dyn_free(encp);
2032
0
    return cc2cu(result);
2033
0
  }
2034
0
  else {
2035
0
    curlx_free(u->query);
2036
0
    u->query = curlx_dyn_ptr(encp);
2037
0
  }
2038
0
  return CURLUE_OK;
2039
0
}
2040
2041
static CURLUcode url_sethost(CURLU *u, struct dynbuf *encp,
2042
                             bool urlencode,
2043
                             unsigned int flags)
2044
0
{
2045
0
  size_t n = curlx_dyn_len(encp);
2046
0
  bool bad = FALSE;
2047
0
  char *newp = curlx_dyn_ptr(encp);
2048
0
  if(!n)
2049
    /* an empty hostname is okay if told so */
2050
0
    bad = (flags & CURLU_NO_AUTHORITY) ? FALSE : TRUE;
2051
0
  else if(!urlencode) {
2052
    /* if the hostname part was not URL encoded here, it was set already URL
2053
       encoded so we need to decode it to check */
2054
0
    size_t dlen;
2055
0
    char *decoded = NULL;
2056
0
    CURLcode result = Curl_urldecode(newp, n, &decoded, &dlen, REJECT_CTRL);
2057
0
    if(result || hostname_check6(u, decoded, dlen))
2058
0
      bad = TRUE;
2059
0
    curlx_free(decoded);
2060
0
  }
2061
0
  else if(hostname_check6(u, newp, n))
2062
0
    bad = TRUE;
2063
0
  if(bad) {
2064
0
    curlx_dyn_free(encp);
2065
0
    return CURLUE_BAD_HOSTNAME;
2066
0
  }
2067
0
  return CURLUE_OK;
2068
0
}
2069
2070
CURLUcode curl_url_set(CURLU *u, CURLUPart what,
2071
                       const char *part, unsigned int flags)
2072
25.3k
{
2073
25.3k
  char **storep = NULL;
2074
25.3k
  bool urlencode = (flags & CURLU_URLENCODE) ? 1 : 0;
2075
25.3k
  bool plusencode = FALSE;
2076
25.3k
  bool pathmode = FALSE;
2077
25.3k
  bool leadingslash = FALSE;
2078
25.3k
  bool appendquery = FALSE;
2079
25.3k
  bool equalsencode = FALSE;
2080
25.3k
  size_t nalloc;
2081
2082
25.3k
  if(!u)
2083
0
    return CURLUE_BAD_HANDLE;
2084
25.3k
  if(!part)
2085
    /* setting a part to NULL clears it */
2086
0
    return urlset_clear(u, what);
2087
2088
25.3k
  nalloc = strlen(part);
2089
25.3k
  if(nalloc > CURL_MAX_INPUT_LENGTH)
2090
    /* excessive input length */
2091
0
    return CURLUE_MALFORMED_INPUT;
2092
2093
25.3k
  switch(what) {
2094
0
  case CURLUPART_SCHEME: {
2095
0
    CURLUcode status = set_url_scheme(u, part, flags);
2096
0
    if(status)
2097
0
      return status;
2098
0
    storep = &u->scheme;
2099
0
    urlencode = FALSE; /* never */
2100
0
    break;
2101
0
  }
2102
0
  case CURLUPART_USER:
2103
0
    storep = &u->user;
2104
0
    break;
2105
0
  case CURLUPART_PASSWORD:
2106
0
    storep = &u->password;
2107
0
    break;
2108
0
  case CURLUPART_OPTIONS:
2109
0
    storep = &u->options;
2110
0
    break;
2111
0
  case CURLUPART_HOST:
2112
0
    storep = &u->host;
2113
0
    curlx_safefree(u->zoneid);
2114
0
    break;
2115
0
  case CURLUPART_ZONEID:
2116
0
    storep = &u->zoneid;
2117
0
    break;
2118
0
  case CURLUPART_PORT:
2119
0
    return set_url_port(u, part);
2120
0
  case CURLUPART_PATH:
2121
0
    pathmode = TRUE;
2122
0
    leadingslash = TRUE; /* enforce */
2123
0
    storep = &u->path;
2124
0
    break;
2125
0
  case CURLUPART_QUERY:
2126
0
    plusencode = urlencode;
2127
0
    appendquery = (flags & CURLU_APPENDQUERY) ? 1 : 0;
2128
0
    equalsencode = appendquery;
2129
0
    storep = &u->query;
2130
0
    u->query_present = TRUE;
2131
0
    break;
2132
0
  case CURLUPART_FRAGMENT:
2133
0
    storep = &u->fragment;
2134
0
    u->fragment_present = TRUE;
2135
0
    break;
2136
25.3k
  case CURLUPART_URL:
2137
25.3k
    return set_url(u, part, nalloc, flags);
2138
0
  default:
2139
0
    return CURLUE_UNKNOWN_PART;
2140
25.3k
  }
2141
0
  DEBUGASSERT(storep);
2142
0
  {
2143
0
    const char *newp = NULL;
2144
0
    struct dynbuf enc;
2145
0
    CURLUcode status;
2146
0
    curlx_dyn_init(&enc, (nalloc * 3) + 1 + leadingslash);
2147
2148
0
    if(leadingslash && (part[0] != '/')) {
2149
0
      CURLcode result = curlx_dyn_addn(&enc, "/", 1);
2150
0
      if(result)
2151
0
        return cc2cu(result);
2152
0
    }
2153
0
    if(urlencode)
2154
0
      status = url_encode_part(&enc, part, plusencode, pathmode, equalsencode);
2155
0
    else
2156
0
      status = url_uppercasehex_part(&enc, part);
2157
0
    if(!status) {
2158
0
      newp = curlx_dyn_ptr(&enc);
2159
2160
0
      if(appendquery && newp)
2161
0
        return url_append_query(u, &enc);
2162
0
      else if(what == CURLUPART_HOST)
2163
0
        status = url_sethost(u, &enc, urlencode, flags);
2164
0
    }
2165
0
    if(status)
2166
0
      return status;
2167
2168
0
    if(what == CURLUPART_PASSWORD)
2169
0
      curlx_strzero(*storep);
2170
0
    curlx_free(*storep);
2171
0
    *storep = (char *)CURL_UNCONST(newp);
2172
0
  }
2173
0
  return CURLUE_OK;
2174
0
}
2175
2176
bool Curl_url_same_origin(CURLU *base, CURLU *href)
2177
323
{
2178
323
  const struct Curl_scheme *s = NULL;
2179
2180
  /* base must be an absolute URL */
2181
323
  if(!base->scheme || !base->host)
2182
0
    return FALSE;
2183
323
  if(href->scheme && !curl_strequal(base->scheme, href->scheme))
2184
7
    return FALSE;
2185
316
  if(href->host) {
2186
316
    if(!curl_strequal(base->host, href->host))
2187
16
      return FALSE;
2188
2189
300
    if(base->port_present != href->port_present) {
2190
      /* one is present, one is not */
2191
0
      s = Curl_get_scheme(base->scheme);
2192
0
      if(!s) /* Cannot match default port for unknown scheme */
2193
0
        return FALSE;
2194
      /* to match, the present one must be the default port */
2195
0
      if((base->port_present && (base->portnum != s->defport)) ||
2196
0
         (href->port_present && (href->portnum != s->defport)))
2197
0
        return FALSE;
2198
0
    }
2199
300
    else if(base->portnum != href->portnum) /* both present or missing */
2200
0
      return FALSE;
2201
2202
300
    if(!curl_strequal(base->zoneid ? base->zoneid : "",
2203
300
                      href->zoneid ? href->zoneid : ""))
2204
0
      return FALSE;
2205
300
  }
2206
0
  else if(href->port_present) /* no host in href, then there must be no port */
2207
0
    return FALSE;
2208
300
  return TRUE;
2209
316
}
2210
2211
CURLUcode Curl_url_get_port(CURLU *u, uint16_t *pport)
2212
11.9k
{
2213
11.9k
  if(u->port_present) {
2214
0
    *pport = u->portnum;
2215
0
    return CURLUE_OK;
2216
0
  }
2217
11.9k
  else if(u->scheme) {
2218
11.9k
    const struct Curl_scheme *s = Curl_get_scheme(u->scheme);
2219
11.9k
    if(s && s->defport) {
2220
11.9k
      *pport = s->defport;
2221
11.9k
      return CURLUE_OK;
2222
11.9k
    }
2223
11.9k
  }
2224
0
  *pport = 0;
2225
0
  return CURLUE_NO_PORT;
2226
11.9k
}