Coverage Report

Created: 2026-10-28 06:38

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