Coverage Report

Created: 2026-10-15 06:18

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
275
  ((('a' <= (str)[0] && (str)[0] <= 'z') ||                \
50
275
    ('A' <= (str)[0] && (str)[0] <= 'Z')) &&               \
51
275
   ((str)[1] == ':' || (str)[1] == '|') &&                 \
52
275
   ((str)[2] == '/' || (str)[2] == '\\' || (str)[2] == 0))
53
54
/* scheme is not URL encoded, the longest libcurl supported ones are... */
55
551k
#define MAX_SCHEME_LEN 40
56
525
#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
173k
{
71
173k
  curlx_free(u->scheme);
72
173k
  curlx_free(u->user);
73
173k
  curlx_strzero(u->password);
74
173k
  curlx_free(u->password);
75
173k
  curlx_free(u->options);
76
173k
  curlx_free(u->host);
77
173k
  curlx_free(u->zoneid);
78
173k
  curlx_free(u->path);
79
173k
  curlx_free(u->query);
80
173k
  curlx_free(u->fragment);
81
173k
}
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
62
{
89
  /* Find the start of the hostname */
90
62
  const char *sep = strstr(url, "//");
91
62
  if(!sep)
92
45
    sep = url;
93
17
  else
94
17
    sep += 2;
95
96
  /* Find first / or ? */
97
388
  while(*sep && *sep != '/' && *sep != '?')
98
326
    sep++;
99
100
62
  return sep;
101
62
}
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
60.8k
{
131
  /* we must add this with whitespace-replacing */
132
60.8k
  const unsigned char *iptr;
133
60.8k
  const unsigned char *host_sep = (const unsigned char *)url;
134
60.8k
  CURLcode result = CURLE_OK;
135
136
60.8k
  DEBUGASSERT((query >= QUERY_NO) && (query <= QUERY_YES));
137
138
60.8k
  if(!relative) {
139
62
    size_t n;
140
62
    host_sep = (const unsigned char *)find_host_sep(url);
141
142
    /* output the first piece as-is */
143
62
    n = (const char *)host_sep - url;
144
62
    result = curlx_dyn_addn(o, url, n);
145
62
    len -= n;
146
62
  }
147
148
10.6M
  for(iptr = host_sep; len && !result;) {
149
10.6M
    if(*iptr == ' ') {
150
924
      if(query != QUERY_YES)
151
673
        result = curlx_dyn_addn(o, "%20", 3);
152
251
      else
153
251
        result = curlx_dyn_addn(o, "+", 1);
154
924
      iptr++;
155
924
      len--;
156
924
    }
157
10.6M
    else if((*iptr < ' ') || (*iptr >= 0x7f)) {
158
10.5M
      unsigned char out[3] = { '%' };
159
10.5M
      Curl_hexbyte(&out[1], *iptr);
160
10.5M
      result = curlx_dyn_addn(o, out, 3);
161
10.5M
      iptr++;
162
10.5M
      len--;
163
10.5M
    }
164
117k
    else if(*iptr == '%' && (len >= 3) &&
165
14.9k
            ISXDIGIT(iptr[1]) && ISXDIGIT(iptr[2]) &&
166
7.50k
            (ISLOWER(iptr[1]) || ISLOWER(iptr[2]))) {
167
      /* uppercase it */
168
6.47k
      unsigned char hex = (unsigned char)((curlx_hexval(iptr[1]) << 4) |
169
6.47k
                                          curlx_hexval(iptr[2]));
170
6.47k
      unsigned char out[3] = { '%' };
171
6.47k
      Curl_hexbyte(&out[1], hex);
172
6.47k
      result = curlx_dyn_addn(o, out, 3);
173
6.47k
      iptr += 3;
174
6.47k
      len -= 3;
175
6.47k
    }
176
111k
    else {
177
111k
      const unsigned char *start = iptr;
178
14.5M
      while(len) {
179
14.5M
        if(*iptr == ' ' || *iptr < ' ' || *iptr >= 0x7f)
180
45.9k
          break;
181
14.4M
        if(*iptr == '%' && (len >= 3) &&
182
4.59M
           ISXDIGIT(iptr[1]) && ISXDIGIT(iptr[2]) &&
183
3.98M
           (ISLOWER(iptr[1]) || ISLOWER(iptr[2])))
184
5.92k
          break;
185
14.4M
        if(*iptr == '?') {
186
1.25k
          if(query == QUERY_NOT_YET) {
187
144
            iptr++;
188
144
            len--;
189
144
            query = QUERY_YES;
190
144
            break;
191
144
          }
192
1.25k
        }
193
14.4M
        iptr++;
194
14.4M
        len--;
195
14.4M
      }
196
111k
      result = curlx_dyn_addn(o, (const char *)start, (size_t)(iptr - start));
197
111k
    }
198
10.6M
  }
199
200
60.8k
  if(result)
201
0
    return cc2cu(result);
202
60.8k
  return CURLUE_OK;
203
60.8k
}
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
173k
{
216
173k
  size_t i = 0;
217
173k
  DEBUGASSERT(!buf || (buflen > MAX_SCHEME_LEN));
218
173k
  (void)buflen; /* only used in debug-builds */
219
173k
  if(buf)
220
81.6k
    buf[0] = 0; /* always leave a defined value in buf */
221
#ifdef _WIN32
222
  if(guess_scheme && STARTS_WITH_DRIVE_PREFIX(url))
223
    return 0;
224
#endif
225
173k
  if(ISALPHA(url[0])) {
226
159k
    if(buf)
227
80.8k
      buf[0] = Curl_raw_tolower(url[0]);
228
551k
    for(i = 1; i < MAX_SCHEME_LEN; ++i) {
229
550k
      char s = url[i];
230
550k
      if(s && (ISALNUM(s) || (s == '+') || (s == '-') || (s == '.'))) {
231
391k
        if(buf)
232
197k
          buf[i] = Curl_raw_tolower(s);
233
391k
      }
234
159k
      else {
235
159k
        break;
236
159k
      }
237
550k
    }
238
159k
  }
239
173k
  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
148k
    size_t len = i;
246
148k
    if(buf)
247
75.7k
      buf[i] = 0;
248
148k
    return len;
249
148k
  }
250
24.7k
  if(buf)
251
5.93k
    buf[0] = 0;
252
24.7k
  return 0;
253
173k
}
254
255
/* scan for byte values <= 31, 127 and maybe space */
256
static bool badoctets(const char *input, size_t n, int flags)
257
47.3k
{
258
47.3k
  const uint8_t *p = (const unsigned char *)input;
259
47.3k
  const uint8_t control = flags & CURLU_ALLOW_SPACE ? 0x1f : 0x20;
260
25.4M
  while(n--) {
261
25.4M
    if(*p <= control || *p == 127)
262
85
      return TRUE;
263
25.4M
    p++;
264
25.4M
  }
265
47.2k
  return FALSE;
266
47.3k
}
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
81.4k
{
287
81.4k
  CURLUcode ures = CURLUE_OK;
288
81.4k
  CURLcode result;
289
81.4k
  char *userp = NULL;
290
81.4k
  char *passwdp = NULL;
291
81.4k
  char *optionsp = NULL;
292
81.4k
  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
81.4k
  const char *ptr;
302
303
81.4k
  DEBUGASSERT(login);
304
305
81.4k
  *hostname_offset = 0;
306
81.4k
  ptr = memchr(login, '@', len);
307
81.4k
  if(!ptr)
308
70.5k
    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
10.9k
  ptr++;
314
315
  /* if this is a known scheme, get some details */
316
10.9k
  if(u->scheme)
317
10.5k
    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
10.9k
  result = Curl_parse_login_details(login, ptr - login - 1,
322
10.9k
                                    &userp, &passwdp,
323
10.9k
                                    (h && (h->flags & PROTOPT_URLOPTIONS)) ?
324
10.9k
                                    &optionsp : NULL);
325
10.9k
  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
10.9k
  if(userp) {
333
10.9k
    if(flags & CURLU_DISALLOW_USER) {
334
      /* Option DISALLOW_USER is set and URL contains username. */
335
2
      ures = CURLUE_USER_NOT_ALLOWED;
336
2
      goto out;
337
2
    }
338
10.8k
    curlx_free(u->user);
339
10.8k
    u->user = userp;
340
10.8k
  }
341
342
10.8k
  if(passwdp) {
343
5.87k
    curlx_strzero(u->password);
344
5.87k
    curlx_free(u->password);
345
5.87k
    u->password = passwdp;
346
5.87k
  }
347
348
10.8k
  if(optionsp) {
349
46
    curlx_free(u->options);
350
46
    u->options = optionsp;
351
46
  }
352
353
10.8k
  if(userp && badoctets(userp, strlen(userp), flags))
354
14
    ures = CURLUE_BAD_USER;
355
10.8k
  else if(passwdp && badoctets(passwdp, strlen(passwdp), flags))
356
9
    ures = CURLUE_BAD_PASSWORD;
357
10.8k
  else if(optionsp && badoctets(optionsp, strlen(optionsp), flags))
358
20
    ures = CURLUE_MALFORMED_INPUT;
359
360
10.8k
  userp = passwdp = optionsp = NULL;
361
362
10.8k
  if(!ures) {
363
    /* the hostname starts at this offset */
364
10.8k
    *hostname_offset = ptr - login;
365
10.8k
    return CURLUE_OK;
366
10.8k
  }
367
368
70.5k
out:
369
370
70.5k
  curlx_free(userp);
371
70.5k
  curlx_strzero(passwdp);
372
70.5k
  curlx_free(passwdp);
373
70.5k
  curlx_free(optionsp);
374
70.5k
  curlx_safefree(u->user);
375
70.5k
  curlx_strzero(u->password);
376
70.5k
  curlx_safefree(u->password);
377
70.5k
  curlx_safefree(u->options);
378
379
70.5k
  return ures;
380
10.8k
}
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
81.3k
{
388
81.3k
  const char *portptr;
389
81.3k
  const char *hostname = curlx_dyn_ptr(host);
390
  /*
391
   * Find the end of an IPv6 address on the ']' ending bracket.
392
   */
393
81.3k
  u->portnum = 0;
394
81.3k
  u->port_present = FALSE;
395
81.3k
  if(hostname[0] == '[') {
396
736
    portptr = memchr(hostname + 1, ']', curlx_dyn_len(host) - 1);
397
736
    if(!portptr)
398
19
      return CURLUE_BAD_IPV6;
399
717
    portptr++;
400
    /* this is a RFC2732-style specified IP-address */
401
717
    if(*portptr) {
402
24
      if(*portptr != ':')
403
8
        return CURLUE_BAD_PORT_NUMBER;
404
24
    }
405
693
    else
406
693
      portptr = NULL;
407
717
  }
408
80.6k
  else
409
80.6k
    portptr = memchr(hostname, ':', curlx_dyn_len(host));
410
411
81.3k
  if(portptr) {
412
4.31k
    curl_off_t port;
413
4.31k
    size_t keep = portptr - hostname;
414
4.31k
    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
4.31k
    curlx_dyn_setlen(host, keep);
423
4.31k
    portptr++;
424
4.31k
    if(!*portptr)
425
275
      return has_scheme ? CURLUE_OK : CURLUE_BAD_PORT_NUMBER;
426
4.04k
    if(*portptr == '\\')
427
7
      return CURLUE_BACKSLASH;
428
4.03k
    rc = curlx_str_number(&portptr, &port, 0xffff);
429
4.03k
    if(rc)
430
38
      return CURLUE_BAD_PORT_NUMBER;
431
3.99k
    else if(*portptr == '\\')
432
7
      return CURLUE_BACKSLASH;
433
3.99k
    else if(*portptr)
434
9
      return CURLUE_BAD_PORT_NUMBER;
435
436
3.98k
    u->portnum = (uint16_t)port;
437
3.98k
    u->port_present = TRUE;
438
3.98k
  }
439
440
81.0k
  return CURLUE_OK;
441
81.3k
}
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
700
{
467
700
  size_t len;
468
700
  DEBUGASSERT(*hostname == '[');
469
700
  if(hlen < 4) /* '[::]' is the shortest possible valid string */
470
8
    return CURLUE_BAD_IPV6;
471
692
  hostname++;
472
692
  hlen -= 2;
473
474
  /* only valid IPv6 letters are ok */
475
692
  len = strspn(hostname, "0123456789abcdefABCDEF:.");
476
477
692
  if(hlen != len) {
478
225
    hlen = len;
479
225
    if(hostname[len] == '%') {
480
      /* this could now be '%[zone id]' */
481
214
      char zoneid[MAX_ZONEID_LEN];
482
214
      int i = 0;
483
214
      char *h = &hostname[len + 1];
484
      /* pass '25' if present and is a URL encoded percent sign */
485
214
      if(!strncmp(h, "25", 2) && h[2] && (h[2] != ']'))
486
1
        h += 2;
487
736
      while(!invalid_host_char[(unsigned char)*h] && (*h != ']') &&
488
525
            (i < (MAX_ZONEID_LEN - 1)))
489
522
        zoneid[i++] = *h++;
490
214
      if(!i || (']' != *h))
491
19
        return CURLUE_BAD_IPV6;
492
195
      zoneid[i] = 0;
493
195
      u->zoneid = curlx_strdup(zoneid);
494
195
      if(!u->zoneid)
495
0
        return CURLUE_OUT_OF_MEMORY;
496
195
      hostname[len] = ']'; /* insert end bracket */
497
195
      hostname[len + 1] = 0; /* terminate the hostname */
498
195
    }
499
11
    else
500
11
      return CURLUE_BAD_IPV6;
501
    /* hostname is fine */
502
225
  }
503
504
  /* Normalize the IPv6 address */
505
662
  {
506
662
    char dest[16]; /* fits a binary IPv6 address */
507
662
    hostname[hlen] = 0; /* end the address there */
508
662
    if(curlx_inet_pton(AF_INET6, hostname, dest) != 1)
509
52
      return CURLUE_BAD_IPV6;
510
610
    if(!curlx_inet_ntop(AF_INET6, dest, hostname, hlen + 1)) {
511
493
      hlen = strlen(hostname); /* might be shorter now */
512
493
      hostname[hlen + 1] = 0;
513
493
    }
514
610
    hostname[hlen] = ']'; /* restore ending bracket */
515
610
  }
516
0
  return CURLUE_OK;
517
662
}
518
519
/* the input is a confirmed hostname, never an IPv6 address */
520
static CURLUcode hostname_check(char *hostname, size_t hlen)
521
8.15k
{
522
8.15k
  size_t i;
523
252k
  for(i = 0; i < hlen; i++) {
524
244k
    if(invalid_host_char[(unsigned char)hostname[i]])
525
140
      return CURLUE_BAD_HOSTNAME;
526
244k
  }
527
8.01k
  if((hlen >= 2) &&
528
5.58k
     (hostname[hlen - 1] == '.') && (hostname[hlen - 2] == '.'))
529
    /* more than one trailing dot is not allowed */
530
13
    return CURLUE_BAD_HOSTNAME;
531
8.00k
  else if((hlen == 1) && (hostname[0] == '.'))
532
    /* a single dot alone is not allowed */
533
19
    return CURLUE_BAD_HOSTNAME;
534
7.98k
  return CURLUE_OK;
535
8.01k
}
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
80.4k
{
571
80.4k
  bool done = FALSE;
572
80.4k
  int n = 0;
573
80.4k
  const char *c = curlx_dyn_ptr(host);
574
80.4k
  unsigned int parts[4] = { 0, 0, 0, 0 };
575
80.4k
  CURLcode result = CURLE_OK;
576
577
80.4k
  if(!ISDIGIT(*c))
578
7.55k
    return HOST_NAME;
579
580
354k
  while(!done) {
581
282k
    int rc;
582
282k
    curl_off_t l;
583
282k
    if(*c == '0') {
584
94.0k
      if((c[1] | 0x20) == 'x') {
585
30
        c += 2; /* skip the prefix */
586
30
        rc = curlx_str_hex(&c, &l, UINT_MAX);
587
30
        if(rc)
588
11
          return HOST_NAME;
589
30
      }
590
94.0k
      else
591
94.0k
        rc = curlx_str_octal(&c, &l, UINT_MAX);
592
94.0k
    }
593
188k
    else
594
188k
      rc = curlx_str_number(&c, &l, UINT_MAX);
595
596
282k
    if(rc) {
597
225
      if(!n || (rc != STRE_NO_NUM) || *c)
598
130
        return HOST_NAME;
599
95
      n--;
600
95
    }
601
281k
    else
602
281k
      parts[n] = (unsigned int)l;
603
604
281k
    switch(*c) {
605
209k
    case '.':
606
209k
      if(n == 3) {
607
46
        if(c[1])
608
          /* something follows this dot */
609
23
          return HOST_NAME;
610
23
        done = TRUE;
611
23
      }
612
209k
      else {
613
209k
        n++;
614
209k
        c++;
615
209k
      }
616
209k
      break;
617
618
209k
    case '\0':
619
72.5k
      done = TRUE;
620
72.5k
      break;
621
622
192
    default:
623
192
      return HOST_NAME;
624
281k
    }
625
281k
  }
626
627
72.5k
  switch(n) {
628
2.77k
  case 0: /* a -- 32 bits */
629
2.77k
    curlx_dyn_reset(host);
630
631
2.77k
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
632
2.77k
                            (parts[0] >> 24),
633
2.77k
                            ((parts[0] >> 16) & 0xff),
634
2.77k
                            ((parts[0] >> 8) & 0xff),
635
2.77k
                            (parts[0] & 0xff));
636
2.77k
    break;
637
232
  case 1: /* a.b -- 8.24 bits */
638
232
    if((parts[0] > 0xff) || (parts[1] > 0xffffff))
639
83
      return HOST_NAME;
640
149
    curlx_dyn_reset(host);
641
149
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
642
149
                            parts[0],
643
149
                            ((parts[1] >> 16) & 0xff),
644
149
                            ((parts[1] >> 8) & 0xff),
645
149
                            (parts[1] & 0xff));
646
149
    break;
647
125
  case 2: /* a.b.c -- 8.8.16 bits */
648
125
    if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xffff))
649
73
      return HOST_NAME;
650
52
    curlx_dyn_reset(host);
651
52
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
652
52
                            parts[0],
653
52
                            parts[1],
654
52
                            ((parts[2] >> 8) & 0xff),
655
52
                            (parts[2] & 0xff));
656
52
    break;
657
69.4k
  case 3: /* a.b.c.d -- 8.8.8.8 bits */
658
69.4k
    if((parts[0] > 0xff) || (parts[1] > 0xff) || (parts[2] > 0xff) ||
659
69.3k
       (parts[3] > 0xff))
660
89
      return HOST_NAME;
661
69.3k
    curlx_dyn_reset(host);
662
69.3k
    result = curlx_dyn_addf(host, "%u.%u.%u.%u",
663
69.3k
                            parts[0],
664
69.3k
                            parts[1],
665
69.3k
                            parts[2],
666
69.3k
                            parts[3]);
667
69.3k
    break;
668
72.5k
  }
669
72.3k
  if(result)
670
0
    return HOST_ERROR;
671
72.3k
  return HOST_IPV4;
672
72.3k
}
673
674
/* if necessary, replace the host content with a URL decoded version */
675
static CURLUcode urldecode_host(struct dynbuf *host)
676
81.1k
{
677
81.1k
  const char *per;
678
81.1k
  const char *hostname = curlx_dyn_ptr(host);
679
81.1k
  per = memchr(hostname, '%', curlx_dyn_len(host));
680
81.1k
  if(!per)
681
    /* nothing to decode */
682
80.9k
    return CURLUE_OK;
683
277
  else {
684
    /* encoded */
685
277
    size_t dlen;
686
277
    char *decoded;
687
277
    CURLcode result = Curl_urldecode(hostname, 0, &decoded, &dlen,
688
277
                                     REJECT_CTRL);
689
277
    if(result)
690
8
      return CURLUE_BAD_HOSTNAME;
691
269
    curlx_dyn_reset(host);
692
269
    result = curlx_dyn_addn(host, decoded, dlen);
693
269
    curlx_free(decoded);
694
269
    if(result)
695
0
      return cc2cu(result);
696
269
  }
697
698
269
  return CURLUE_OK;
699
81.1k
}
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
81.4k
{
707
81.4k
  size_t offset;
708
81.4k
  CURLUcode uc;
709
81.4k
  CURLcode result;
710
711
  /*
712
   * Parse the login details and strip them out of the hostname.
713
   */
714
81.4k
  uc = parse_hostname_login(u, auth, authlen, flags, &offset);
715
81.4k
  if(uc)
716
45
    return uc;
717
718
81.3k
  result = curlx_dyn_addn(host, auth + offset, authlen - offset);
719
81.3k
  if(result) {
720
0
    uc = cc2cu(result);
721
0
    return uc;
722
0
  }
723
724
  /* parse_port() also sets the hostname length correctly */
725
81.3k
  uc = parse_port(u, host, has_scheme);
726
727
81.3k
  if(!curlx_dyn_len(host))
728
    /* this makes no-host errors override port number problems */
729
108
    uc = CURLUE_NO_HOST;
730
81.3k
  if(!uc)
731
81.1k
    uc = urldecode_host(host);
732
81.3k
  if(uc)
733
212
    ;
734
81.1k
  else if(auth[offset] == '[')
735
700
    uc = ipv6_parse(u, curlx_dyn_ptr(host), curlx_dyn_len(host));
736
80.4k
  else {
737
    /* ipv4_normalize() returns *NAME, *IPV4 or *ERROR */
738
80.4k
    int type = ipv4_normalize(host);
739
740
80.4k
    if(type == HOST_NAME)
741
8.15k
      uc = hostname_check(curlx_dyn_ptr(host), curlx_dyn_len(host));
742
72.3k
    else if(type == HOST_ERROR)
743
0
      uc = CURLUE_OUT_OF_MEMORY;
744
80.4k
  }
745
746
81.3k
  return uc;
747
81.3k
}
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.55M
{
776
6.55M
  const char *p = *str;
777
6.55M
  if(*p == '.') {
778
36.0k
    (*str)++;
779
36.0k
    (*clen)--;
780
36.0k
    return TRUE;
781
36.0k
  }
782
6.51M
  else if((*clen >= 3) &&
783
6.47M
          (p[0] == '%') && (p[1] == '2') && ((p[2] | 0x20) == 'e')) {
784
9.14k
    *str += 3;
785
9.14k
    *clen -= 3;
786
9.14k
    return TRUE;
787
9.14k
  }
788
6.50M
  return FALSE;
789
6.55M
}
790
791
21.7M
#define ISSLASH(x) ((x) == '/')
792
793
/* prescan the string to see if it needs work */
794
static bool needs_dedotdot(const char *p, size_t pn)
795
25.1k
{
796
  /* a single byte path cannot be cleaned up */
797
25.1k
  if(pn < 2)
798
15
    return FALSE;
799
25.1k
  if(!memchr(p, '.', pn) && !memchr(p, '%', pn))
800
5.55k
    return FALSE;
801
6.50M
  while(pn) {
802
6.48M
    if(is_dot(&p, &pn)) {
803
      /* "./" or dot before end of string */
804
19.9k
      if(!pn || ISSLASH(*p))
805
852
        return TRUE;
806
      /* "../" or ".." before end of string */
807
19.1k
      else if(is_dot(&p, &pn) && (!pn || ISSLASH(*p)))
808
342
        return TRUE;
809
19.9k
    }
810
6.46M
    else {
811
6.46M
      p++;
812
6.46M
      pn--;
813
6.46M
    }
814
6.48M
  }
815
18.4k
  return FALSE;
816
19.5k
}
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
25.1k
{
837
25.1k
  struct dynbuf out;
838
25.1k
  CURLcode result = CURLE_OK;
839
840
  /* variables for leading dot checks */
841
25.1k
  const char *dinput = input;
842
25.1k
  size_t dlen = clen;
843
844
25.1k
  *outp = NULL;
845
25.1k
  if(!needs_dedotdot(input, clen))
846
23.9k
    return 0;
847
848
1.19k
  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
1.19k
  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
21.1M
  while(clen && !result) { /* until end of path content */
874
21.1M
    if(ISSLASH(*input)) {
875
33.8k
      const char *p = &input[1];
876
33.8k
      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
33.8k
      if(is_dot(&p, &blen)) {
881
14.7k
        if(!blen) { /* /. */
882
77
          result = curlx_dyn_addn(&out, "/", 1);
883
77
          break;
884
77
        }
885
14.7k
        else if(ISSLASH(*p)) { /* /./ */
886
3.79k
          input = p;
887
3.79k
          clen = blen;
888
3.79k
          continue;
889
3.79k
        }
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
10.9k
        else if(is_dot(&p, &blen) && (ISSLASH(*p) || !blen)) {
896
          /* remove the last segment from the output buffer */
897
1.92k
          size_t len = curlx_dyn_len(&out);
898
1.92k
          if(len) {
899
1.51k
            const char *ptr = curlx_dyn_ptr(&out);
900
1.51k
            const char *last = memrchr(ptr, '/', len);
901
1.51k
            if(last)
902
              /* trim the output at the slash */
903
1.51k
              curlx_dyn_setlen(&out, last - ptr);
904
1.51k
          }
905
906
1.92k
          if(blen) { /* /../ */
907
1.88k
            input = p;
908
1.88k
            clen = blen;
909
1.88k
            continue;
910
1.88k
          }
911
38
          result = curlx_dyn_addn(&out, "/", 1);
912
38
          break;
913
1.92k
        }
914
14.7k
      }
915
33.8k
    }
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
21.1M
    result = curlx_dyn_addn(&out, input, 1);
923
21.1M
    input++;
924
21.1M
    clen--;
925
21.1M
  }
926
1.19k
end:
927
1.19k
  if(!result) {
928
1.19k
    if(curlx_dyn_len(&out))
929
1.19k
      *outp = curlx_dyn_ptr(&out);
930
0
    else {
931
0
      *outp = curlx_strdup("");
932
0
      if(!*outp)
933
0
        return 1;
934
0
    }
935
1.19k
  }
936
1.19k
  return result ? 1 : 0; /* success */
937
1.19k
}
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
191
{
947
191
  const char *path;
948
191
  size_t pathlen;
949
950
191
  *pathp = NULL;
951
191
  *pathlenp = 0;
952
191
  if(urllen <= 6)
953
    /* file:/ is not enough to actually be a complete file: URL */
954
7
    return CURLUE_BAD_FILE_URL;
955
956
  /* path has been allocated large enough to hold this */
957
184
  path = &url[5];
958
184
  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
184
  if(path[0] != '/')
964
1
    return CURLUE_BAD_FILE_URL;
965
966
  /* Extra handling URLs with an authority component (i.e. that start with
967
   * "file://")
968
   *
969
   * We allow omitted hostname (e.g. file:/<path>) -- valid according to
970
   * RFC 8089, but not the (current) WHAT-WG URL spec.
971
   */
972
183
  if(path[1] == '/') {
973
    /* swallow the two slashes */
974
107
    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
107
    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
73
      if(checkprefix("localhost/", ptr) ||
996
72
         checkprefix("127.0.0.1/", ptr)) {
997
2
        ptr += 9; /* now points to the slash after the host */
998
2
      }
999
71
      else
1000
        /* Invalid file://hostname/, expected localhost or 127.0.0.1 or
1001
           none */
1002
71
        return CURLUE_BAD_FILE_URL;
1003
73
    }
1004
1005
36
    path = ptr;
1006
36
    pathlen = urllen - (ptr - url);
1007
36
  }
1008
1009
112
#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
112
  if(('/' == path[0] && STARTS_WITH_URL_DRIVE_PREFIX(&path[1])) ||
1013
90
     STARTS_WITH_URL_DRIVE_PREFIX(path)) {
1014
    /* File drive letters are only accepted in MS-DOS/Windows */
1015
48
    return CURLUE_BAD_FILE_URL;
1016
48
  }
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
64
  u->scheme = curlx_strdup("file");
1026
64
  if(!u->scheme)
1027
0
    return CURLUE_OUT_OF_MEMORY;
1028
1029
64
  *pathp = path;
1030
64
  *pathlenp = pathlen;
1031
64
  return CURLUE_OK;
1032
64
}
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
81.4k
{
1038
  /* clear path */
1039
81.4k
  const char *schemep = NULL;
1040
1041
81.4k
  if(schemelen) {
1042
75.5k
    int num_slashes = 0;
1043
75.5k
    const char *p = &url[schemelen + 1];
1044
75.5k
    if(!Curl_getn_scheme(schemebuf, schemelen) &&
1045
690
       !(flags & CURLU_NON_SUPPORT_SCHEME))
1046
1
      return CURLUE_UNSUPPORTED_SCHEME;
1047
1048
75.5k
    if(!ISSLASH(*p))
1049
      /* less than one */
1050
3
      return CURLUE_BAD_SLASHES;
1051
75.5k
    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
75.5k
    else {
1058
211k
      while(ISSLASH(*p) && (num_slashes < 4)) {
1059
135k
        p++;
1060
135k
        num_slashes++;
1061
135k
      }
1062
75.5k
      if(num_slashes > 3)
1063
7
        return CURLUE_BAD_SLASHES;
1064
75.5k
    }
1065
1066
75.5k
    schemep = schemebuf;
1067
75.5k
    *hostpp = p; /* hostname starts here */
1068
75.5k
  }
1069
5.93k
  else {
1070
    /* no scheme! */
1071
1072
5.93k
    if(!(flags & (CURLU_DEFAULT_SCHEME | CURLU_GUESS_SCHEME)))
1073
0
      return CURLUE_BAD_SCHEME;
1074
1075
5.93k
    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
5.93k
    *hostpp = url;
1082
5.93k
  }
1083
1084
81.4k
  if(schemep) {
1085
75.5k
    u->scheme = curlx_strdup(schemep);
1086
75.5k
    if(!u->scheme)
1087
0
      return CURLUE_OUT_OF_MEMORY;
1088
75.5k
  }
1089
81.4k
  return CURLUE_OK;
1090
81.4k
}
1091
1092
static CURLUcode guess_scheme(CURLU *u, struct dynbuf *host)
1093
5.54k
{
1094
5.54k
  const char *hostname = curlx_dyn_ptr(host);
1095
5.54k
  const char *schemep = NULL;
1096
  /* legacy curl-style guess based on hostname */
1097
5.54k
  if(checkprefix("ftp.", hostname))
1098
4.12k
    schemep = "ftp";
1099
1.42k
  else if(checkprefix("dict.", hostname))
1100
1
    schemep = "dict";
1101
1.41k
  else if(checkprefix("ldap.", hostname))
1102
1
    schemep = "ldap";
1103
1.41k
  else if(checkprefix("imap.", hostname))
1104
1
    schemep = "imap";
1105
1.41k
  else if(checkprefix("smtp.", hostname))
1106
3
    schemep = "smtp";
1107
1.41k
  else if(checkprefix("pop3.", hostname))
1108
1
    schemep = "pop3";
1109
1.41k
  else
1110
1.41k
    schemep = "http";
1111
1112
5.54k
  u->scheme = curlx_strdup(schemep);
1113
5.54k
  if(!u->scheme)
1114
0
    return CURLUE_OUT_OF_MEMORY;
1115
1116
5.54k
  u->guessed_scheme = TRUE;
1117
5.54k
  return CURLUE_OK;
1118
5.54k
}
1119
1120
static CURLUcode handle_fragment(CURLU *u, const char *fragment,
1121
                                 size_t fraglen, unsigned int flags)
1122
9.58k
{
1123
9.58k
  CURLUcode ures;
1124
9.58k
  u->fragment_present = TRUE;
1125
9.58k
  if(fraglen > 1) {
1126
    /* skip the leading '#' in the copy but include the null-terminator */
1127
4.74k
    if(flags & CURLU_URLENCODE) {
1128
1.93k
      struct dynbuf enc;
1129
1.93k
      curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1130
1.93k
      ures = urlencode_str(&enc, fragment + 1, fraglen - 1, TRUE, QUERY_NO);
1131
1.93k
      if(ures)
1132
0
        return ures;
1133
1.93k
      u->fragment = curlx_dyn_ptr(&enc);
1134
1.93k
    }
1135
2.80k
    else {
1136
2.80k
      if(badoctets(fragment, fraglen, flags))
1137
10
        return CURLUE_BAD_FRAGMENT;
1138
2.79k
      u->fragment = curlx_memdup0(fragment + 1, fraglen - 1);
1139
2.79k
      if(!u->fragment)
1140
0
        return CURLUE_OUT_OF_MEMORY;
1141
2.79k
    }
1142
4.74k
  }
1143
9.57k
  return CURLUE_OK;
1144
9.58k
}
1145
1146
static CURLUcode handle_query(CURLU *u, const char *query,
1147
                              size_t qlen, unsigned int flags)
1148
4.08k
{
1149
4.08k
  u->query_present = TRUE;
1150
4.08k
  if(qlen > 1) {
1151
3.68k
    if(flags & CURLU_URLENCODE) {
1152
1.33k
      struct dynbuf enc;
1153
1.33k
      CURLUcode ures;
1154
1.33k
      curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1155
      /* skip the leading question mark */
1156
1.33k
      ures = urlencode_str(&enc, query + 1, qlen - 1, TRUE, QUERY_YES);
1157
1.33k
      if(ures)
1158
0
        return ures;
1159
1.33k
      u->query = curlx_dyn_ptr(&enc);
1160
1.33k
    }
1161
2.35k
    else {
1162
2.35k
      if(badoctets(query, qlen, flags))
1163
12
        return CURLUE_BAD_QUERY;
1164
1165
2.34k
      u->query = curlx_memdup0(query + 1, qlen - 1);
1166
2.34k
      if(!u->query)
1167
0
        return CURLUE_OUT_OF_MEMORY;
1168
2.34k
    }
1169
3.68k
  }
1170
396
  else {
1171
    /* single byte query */
1172
396
    u->query = curlx_strdup("");
1173
396
    if(!u->query)
1174
0
      return CURLUE_OUT_OF_MEMORY;
1175
396
  }
1176
4.06k
  return CURLUE_OK;
1177
4.08k
}
1178
1179
static CURLUcode handle_path(CURLU *u, const char *path,
1180
                             size_t pathlen, unsigned int flags,
1181
                             bool is_file)
1182
80.9k
{
1183
80.9k
  CURLUcode ures;
1184
80.9k
  if(pathlen && (flags & CURLU_URLENCODE)) {
1185
19.1k
    struct dynbuf enc;
1186
19.1k
    curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1187
19.1k
    ures = urlencode_str(&enc, path, pathlen, TRUE, QUERY_NO);
1188
19.1k
    if(ures)
1189
0
      return ures;
1190
19.1k
    pathlen = curlx_dyn_len(&enc);
1191
19.1k
    path = u->path = curlx_dyn_ptr(&enc);
1192
19.1k
  }
1193
1194
80.9k
  if(pathlen >= (size_t)(1 + !is_file)) {
1195
25.3k
    if(badoctets(path, pathlen, flags))
1196
20
      return CURLUE_BAD_PATH;
1197
1198
    /* paths for file:// scheme can be one byte, others need to be two */
1199
25.3k
    if(!u->path) {
1200
13.2k
      u->path = curlx_memdup0(path, pathlen);
1201
13.2k
      if(!u->path)
1202
0
        return CURLUE_OUT_OF_MEMORY;
1203
13.2k
      path = u->path;
1204
13.2k
    }
1205
12.1k
    else if(flags & CURLU_URLENCODE)
1206
      /* it might have encoded more than the path so cut it */
1207
12.1k
      u->path[pathlen] = 0;
1208
1209
25.3k
    if(!(flags & CURLU_PATH_AS_IS)) {
1210
      /* remove ../ and ./ sequences according to RFC3986 */
1211
25.1k
      char *dedot;
1212
25.1k
      int err = dedotdotify(path, pathlen, &dedot);
1213
25.1k
      if(err)
1214
0
        return CURLUE_OUT_OF_MEMORY;
1215
25.1k
      if(dedot) {
1216
1.19k
        curlx_free(u->path);
1217
1.19k
        u->path = dedot;
1218
1.19k
      }
1219
25.1k
    }
1220
25.3k
  }
1221
80.9k
  return CURLUE_OK;
1222
80.9k
}
1223
1224
static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
1225
81.6k
{
1226
81.6k
  const char *path;
1227
81.6k
  size_t pathlen;
1228
81.6k
  char schemebuf[MAX_SCHEME_LEN + 1];
1229
81.6k
  size_t schemelen = 0;
1230
81.6k
  size_t urllen;
1231
81.6k
  CURLUcode ures = CURLUE_OK;
1232
81.6k
  struct dynbuf host;
1233
81.6k
  bool is_file = FALSE;
1234
1235
81.6k
  DEBUGASSERT(url);
1236
1237
81.6k
  urllen = strlen(url);
1238
81.6k
  if(urllen > CURL_MAX_INPUT_LENGTH)
1239
0
    return CURLUE_MALFORMED_INPUT;
1240
1241
81.6k
  curlx_dyn_init(&host, CURL_MAX_INPUT_LENGTH);
1242
1243
81.6k
  schemelen = Curl_is_absolute_url(url, schemebuf, sizeof(schemebuf),
1244
81.6k
                                   flags & (CURLU_GUESS_SCHEME |
1245
81.6k
                                            CURLU_DEFAULT_SCHEME));
1246
1247
  /* handle the file: scheme */
1248
81.6k
  if(schemelen == 4 && !memcmp(schemebuf, "file", 4)) {
1249
191
    is_file = TRUE;
1250
191
    ures = parse_file(url, urllen, u, &path, &pathlen);
1251
191
  }
1252
81.4k
  else {
1253
81.4k
    const char *hostp = NULL;
1254
81.4k
    const char *p;
1255
81.4k
    size_t hostlen;
1256
81.4k
    ures = parse_scheme(url, u, schemebuf, schemelen, flags, &hostp);
1257
81.4k
    if(ures)
1258
11
      goto fail;
1259
1260
    /* find the end of the hostname + port number */
1261
81.4k
    p = hostp;
1262
1.30M
    while(*p && *p != '/' && *p != '?' && *p != '#')
1263
1.22M
      p++;
1264
81.4k
    hostlen = p - hostp;
1265
81.4k
    path = p;
1266
1267
    /* this pathlen also contains the query and the fragment */
1268
81.4k
    pathlen = urllen - (path - url);
1269
81.4k
    if(hostlen) {
1270
81.4k
      ures = parse_authority(u, hostp, hostlen, flags, &host, !!u->scheme);
1271
81.4k
      if(!ures && (flags & CURLU_GUESS_SCHEME) && !u->scheme)
1272
5.54k
        ures = guess_scheme(u, &host);
1273
81.4k
    }
1274
25
    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
25
    else
1280
25
      ures = CURLUE_NO_HOST;
1281
81.4k
  }
1282
81.6k
  if(!ures) {
1283
    /* The path might at this point contain a fragment and/or a query to
1284
       handle */
1285
80.9k
    const char *fragment = memchr(path, '#', pathlen);
1286
80.9k
    if(fragment) {
1287
9.58k
      size_t fraglen = pathlen - (fragment - path);
1288
9.58k
      ures = handle_fragment(u, fragment, fraglen, flags);
1289
      /* after this, pathlen still contains the query */
1290
9.58k
      pathlen -= fraglen;
1291
9.58k
    }
1292
80.9k
  }
1293
81.6k
  if(!ures) {
1294
80.9k
    const char *query = memchr(path, '?', pathlen);
1295
80.9k
    if(query) {
1296
4.08k
      size_t qlen = pathlen - (query - path);
1297
4.08k
      ures = handle_query(u, query, qlen, flags);
1298
4.08k
      pathlen -= qlen;
1299
4.08k
    }
1300
80.9k
  }
1301
81.6k
  if(!ures)
1302
    /* the fragment and query parts are trimmed off from the path */
1303
80.9k
    ures = handle_path(u, path, pathlen, flags, is_file);
1304
81.6k
  if(!ures) {
1305
80.9k
    u->host = curlx_dyn_ptr(&host);
1306
80.9k
    return CURLUE_OK;
1307
80.9k
  }
1308
724
fail:
1309
724
  curlx_dyn_free(&host);
1310
724
  free_urlhandle(u);
1311
724
  return ures;
1312
81.6k
}
1313
1314
/*
1315
 * Parse the URL and, if successful, replace everything in the Curl_URL struct.
1316
 */
1317
static CURLUcode parseurl_and_replace(const char *url, CURLU *u,
1318
                                      unsigned int flags)
1319
81.6k
{
1320
81.6k
  CURLUcode ures;
1321
81.6k
  CURLU tmpurl;
1322
81.6k
  memset(&tmpurl, 0, sizeof(tmpurl));
1323
81.6k
  ures = parseurl(url, &tmpurl, flags);
1324
81.6k
  if(!ures) {
1325
80.9k
    free_urlhandle(u);
1326
80.9k
    *u = tmpurl;
1327
80.9k
  }
1328
81.6k
  return ures;
1329
81.6k
}
1330
1331
/*
1332
 * Concatenate a relative URL onto a base URL making it absolute.
1333
 */
1334
static CURLUcode redirect_url(const char *base, const char *relurl,
1335
                              CURLU *u, unsigned int flags)
1336
6.47k
{
1337
6.47k
  struct dynbuf urlbuf;
1338
6.47k
  bool host_changed = FALSE;
1339
6.47k
  const char *useurl = relurl;
1340
6.47k
  const char *cutoff = NULL;
1341
6.47k
  size_t prelen;
1342
6.47k
  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
6.47k
  const char *scheme = u->scheme ? u->scheme : DEFAULT_SCHEME;
1346
1347
  /* protsep points to the start of the hostname, after [scheme]:// */
1348
6.47k
  const char *protsep = base + strlen(scheme) + 3;
1349
6.47k
  DEBUGASSERT(base && relurl && u); /* all set here */
1350
6.47k
  if(!base)
1351
0
    return CURLUE_MALFORMED_INPUT; /* should never happen */
1352
1353
  /* handle different relative URL types */
1354
6.47k
  switch(relurl[0]) {
1355
76
  case '/':
1356
76
    if(relurl[1] == '/') {
1357
      /* protocol-relative URL: //example.com/path */
1358
62
      cutoff = protsep;
1359
62
      useurl = &relurl[2];
1360
62
      host_changed = TRUE;
1361
62
    }
1362
14
    else
1363
      /* absolute /path */
1364
14
      cutoff = strchr(protsep, '/');
1365
76
    break;
1366
1367
977
  case '#':
1368
    /* fragment-only change */
1369
977
    if(u->fragment_present)
1370
98
      cutoff = strchr(protsep, '#');
1371
977
    break;
1372
1373
5.41k
  default:
1374
    /* path or query-only change */
1375
5.41k
    if(u->query_present)
1376
      /* remove existing query */
1377
219
      cutoff = strchr(protsep, '?');
1378
5.19k
    else if(u->fragment_present)
1379
      /* Remove existing fragment */
1380
181
      cutoff = strchr(protsep, '#');
1381
1382
5.41k
    if(relurl[0] != '?') {
1383
      /* append a relative path after the last slash */
1384
5.35k
      cutoff = memrchr(protsep, '/',
1385
5.35k
                       cutoff ? (size_t)(cutoff - protsep) : strlen(protsep));
1386
5.35k
      if(cutoff)
1387
5.35k
        cutoff++; /* truncate after last slash */
1388
5.35k
    }
1389
5.41k
    break;
1390
6.47k
  }
1391
1392
6.47k
  prelen = cutoff ? (size_t)(cutoff - base) : strlen(base);
1393
1394
  /* build new URL */
1395
6.47k
  curlx_dyn_init(&urlbuf, CURL_MAX_INPUT_LENGTH);
1396
1397
6.47k
  if(!curlx_dyn_addn(&urlbuf, base, prelen) &&
1398
6.47k
     !urlencode_str(&urlbuf, useurl, strlen(useurl), !host_changed,
1399
6.47k
                    QUERY_NOT_YET)) {
1400
6.47k
    uc = parseurl_and_replace(curlx_dyn_ptr(&urlbuf), u,
1401
6.47k
                              flags & ~U_CURLU_PATH_AS_IS);
1402
6.47k
  }
1403
0
  else
1404
0
    uc = CURLUE_OUT_OF_MEMORY;
1405
1406
6.47k
  curlx_dyn_free(&urlbuf);
1407
6.47k
  return uc;
1408
6.47k
}
1409
1410
/*
1411
 */
1412
CURLU *curl_url(void)
1413
70.8k
{
1414
70.8k
  return curlx_calloc(1, sizeof(struct Curl_URL));
1415
70.8k
}
1416
1417
void curl_url_cleanup(CURLU *u)
1418
143k
{
1419
143k
  if(u) {
1420
91.4k
    free_urlhandle(u);
1421
91.4k
    curlx_free(u);
1422
91.4k
  }
1423
143k
}
1424
1425
#define DUP(dest, src, name)                    \
1426
185k
  do {                                          \
1427
185k
    if((src)->name) {                           \
1428
56.7k
      (dest)->name = curlx_strdup((src)->name); \
1429
56.7k
      if(!(dest)->name)                         \
1430
56.7k
        goto fail;                              \
1431
56.7k
    }                                           \
1432
185k
  } while(0)
1433
1434
CURLU *curl_url_dup(const CURLU *in)
1435
20.6k
{
1436
20.6k
  struct Curl_URL *u;
1437
1438
20.6k
  if(!in)
1439
0
    return NULL;
1440
1441
20.6k
  u = curlx_calloc(1, sizeof(struct Curl_URL));
1442
20.6k
  if(u) {
1443
20.6k
    DUP(u, in, scheme);
1444
20.6k
    DUP(u, in, user);
1445
20.6k
    DUP(u, in, password);
1446
20.6k
    DUP(u, in, options);
1447
20.6k
    DUP(u, in, host);
1448
20.6k
    DUP(u, in, path);
1449
20.6k
    DUP(u, in, query);
1450
20.6k
    DUP(u, in, fragment);
1451
20.6k
    DUP(u, in, zoneid);
1452
20.6k
    u->portnum = in->portnum;
1453
20.6k
    u->port_present = in->port_present;
1454
20.6k
    u->fragment_present = in->fragment_present;
1455
20.6k
    u->query_present = in->query_present;
1456
20.6k
    u->guessed_scheme = in->guessed_scheme;
1457
20.6k
  }
1458
20.6k
  return u;
1459
0
fail:
1460
0
  curl_url_cleanup(u);
1461
0
  return NULL;
1462
20.6k
}
1463
1464
#ifndef USE_IDN
1465
#define host_decode(x, y) CURLUE_LACKS_IDN
1466
#define host_encode(x, y) CURLUE_LACKS_IDN
1467
#else
1468
static CURLUcode host_decode(const char *host, char **allochost)
1469
0
{
1470
0
  CURLcode result = Curl_idn_decode(host, allochost);
1471
0
  if(result)
1472
0
    return (result == CURLE_OUT_OF_MEMORY) ?
1473
0
      CURLUE_OUT_OF_MEMORY : CURLUE_BAD_HOSTNAME;
1474
0
  return CURLUE_OK;
1475
0
}
1476
1477
static CURLUcode host_encode(const char *host, char **allochost)
1478
0
{
1479
0
  CURLcode result = Curl_idn_encode(host, allochost);
1480
0
  if(result)
1481
0
    return (result == CURLE_OUT_OF_MEMORY) ?
1482
0
      CURLUE_OUT_OF_MEMORY : CURLUE_BAD_HOSTNAME;
1483
0
  return CURLUE_OK;
1484
0
}
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
181k
{
1491
181k
  CURLUcode uc = CURLUE_OK;
1492
181k
  size_t partlen = strlen(ptr);
1493
181k
  bool urldecode = (flags & CURLU_URLDECODE) ? 1 : 0;
1494
181k
  bool urlencode = (flags & CURLU_URLENCODE) ? 1 : 0;
1495
181k
  bool punycode = (flags & CURLU_PUNYCODE) && (what == CURLUPART_HOST);
1496
181k
  bool depunyfy = (flags & CURLU_PUNY2IDN) && (what == CURLUPART_HOST);
1497
181k
  char *part = curlx_memdup0(ptr, partlen);
1498
181k
  *partp = NULL;
1499
181k
  if(!part)
1500
0
    return CURLUE_OUT_OF_MEMORY;
1501
181k
  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
181k
  if(urldecode) {
1511
23.2k
    char *decoded;
1512
23.2k
    size_t dlen;
1513
    /* this unconditional rejection of control bytes is documented API
1514
       behavior */
1515
23.2k
    CURLcode result = Curl_urldecode(part, partlen, &decoded, &dlen,
1516
23.2k
                                     REJECT_CTRL);
1517
23.2k
    curlx_free(part);
1518
23.2k
    if(result)
1519
7
      return CURLUE_URLDECODE;
1520
23.2k
    part = decoded;
1521
23.2k
    partlen = dlen;
1522
23.2k
  }
1523
181k
  if(urlencode) {
1524
31.9k
    struct dynbuf enc;
1525
31.9k
    curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1526
31.9k
    uc = urlencode_str(&enc, part, partlen, TRUE, what == CURLUPART_QUERY ?
1527
31.9k
                       QUERY_YES : QUERY_NO);
1528
31.9k
    curlx_free(part);
1529
31.9k
    if(uc)
1530
0
      return uc;
1531
31.9k
    part = curlx_dyn_ptr(&enc);
1532
31.9k
  }
1533
149k
  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
149k
  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
181k
  *partp = part;
1552
181k
  return CURLUE_OK;
1553
181k
}
1554
1555
static CURLUcode file_url(const CURLU *u, char **part,
1556
                          const char *fragmentsep,
1557
                          const char *querysep)
1558
48
{
1559
48
  char *url = curl_maprintf("file://%s%s%s%s%s",
1560
48
                            u->path, querysep, u->query ? u->query : "",
1561
48
                            fragmentsep, u->fragment ? u->fragment : "");
1562
48
  if(!url)
1563
0
    return CURLUE_OUT_OF_MEMORY;
1564
1565
48
  *part = url;
1566
48
  return CURLUE_OK;
1567
48
}
1568
1569
static CURLUcode urlget_url(const CURLU *u, char **part, unsigned int flags)
1570
78.2k
{
1571
78.2k
  char *url;
1572
78.2k
  char *allochost = NULL;
1573
78.2k
  const char *fragmentsep =
1574
78.2k
    (u->fragment || (u->fragment_present && flags & CURLU_GET_EMPTY)) ?
1575
74.2k
    "#" : "";
1576
78.2k
  const char *querysep = ((u->query && u->query[0]) ||
1577
74.0k
                          (u->query_present && flags & CURLU_GET_EMPTY)) ?
1578
73.8k
    "?" : "";
1579
78.2k
  char portbuf[7];
1580
78.2k
  if(curl_strequal("file", u->scheme))
1581
48
    return file_url(u, part, fragmentsep, querysep);
1582
78.1k
  else if(!u->host)
1583
6.87k
    return CURLUE_NO_HOST;
1584
71.2k
  else {
1585
71.2k
    const char *scheme;
1586
71.2k
    char *options = u->options;
1587
71.2k
    char *port = NULL;
1588
71.2k
    const struct Curl_scheme *h = NULL;
1589
71.2k
    char schemebuf[MAX_SCHEME_LEN + 5];
1590
71.2k
    if(u->scheme)
1591
71.2k
      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
71.2k
    if(u->port_present) {
1598
5.19k
      curl_msnprintf(portbuf, sizeof(portbuf), "%u", u->portnum);
1599
5.19k
      port = portbuf;
1600
5.19k
    }
1601
1602
71.2k
    h = Curl_get_scheme(scheme);
1603
71.2k
    if(h) {
1604
70.6k
      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
70.6k
      else if(u->port_present && (h->defport == u->portnum) &&
1611
1.14k
              (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
331
        port = NULL;
1615
331
      }
1616
1617
70.6k
      if(!(h->flags & PROTOPT_URLOPTIONS))
1618
70.6k
        options = NULL;
1619
70.6k
    }
1620
1621
71.2k
    if(u->host[0] == '[') {
1622
468
      if(u->zoneid) {
1623
        /* make it '[ host %25 zoneid ]' */
1624
111
        struct dynbuf enc;
1625
111
        size_t hostlen = strlen(u->host);
1626
111
        curlx_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
1627
111
        if(curlx_dyn_addf(&enc, "%.*s%%25%s]", (int)hostlen - 1, u->host,
1628
111
                          u->zoneid))
1629
0
          return CURLUE_OUT_OF_MEMORY;
1630
111
        allochost = curlx_dyn_ptr(&enc);
1631
111
      }
1632
468
    }
1633
70.8k
    else if(flags & CURLU_URLENCODE) {
1634
6.16k
      allochost = curl_easy_escape(NULL, u->host, 0);
1635
6.16k
      if(!allochost)
1636
0
        return CURLUE_OUT_OF_MEMORY;
1637
6.16k
    }
1638
64.6k
    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
64.6k
    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
71.2k
    if(!(flags & CURLU_NO_GUESS_SCHEME) || !u->guessed_scheme)
1654
71.2k
      curl_msnprintf(schemebuf, sizeof(schemebuf), "%s://", scheme);
1655
0
    else
1656
0
      schemebuf[0] = 0;
1657
1658
71.2k
    url = curl_maprintf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",
1659
71.2k
                        schemebuf,
1660
71.2k
                        u->user ? u->user : "",
1661
71.2k
                        u->password ? ":" : "",
1662
71.2k
                        u->password ? u->password : "",
1663
71.2k
                        options ? ";" : "",
1664
71.2k
                        options ? options : "",
1665
71.2k
                        (u->user || u->password || options) ? "@" : "",
1666
71.2k
                        allochost ? allochost : u->host,
1667
71.2k
                        port ? ":" : "",
1668
71.2k
                        port ? port : "",
1669
71.2k
                        u->path ? u->path : "/",
1670
71.2k
                        querysep,
1671
71.2k
                        u->query ? u->query : "",
1672
71.2k
                        fragmentsep,
1673
71.2k
                        u->fragment ? u->fragment : "");
1674
71.2k
    curlx_free(allochost);
1675
71.2k
  }
1676
71.2k
  if(!url)
1677
0
    return CURLUE_OUT_OF_MEMORY;
1678
71.2k
  *part = url;
1679
71.2k
  return CURLUE_OK;
1680
71.2k
}
1681
1682
CURLUcode curl_url_get(const CURLU *u, CURLUPart what,
1683
                       char **part, unsigned int flags)
1684
500k
{
1685
500k
  const char *ptr;
1686
500k
  CURLUcode ifmissing = CURLUE_UNKNOWN_PART;
1687
500k
  char portbuf[7];
1688
500k
  bool plusdecode = FALSE;
1689
500k
  if(!u)
1690
0
    return CURLUE_BAD_HANDLE;
1691
500k
  if(!part)
1692
0
    return CURLUE_BAD_PARTPOINTER;
1693
500k
  *part = NULL;
1694
1695
500k
  switch(what) {
1696
82.6k
  case CURLUPART_SCHEME:
1697
82.6k
    ptr = u->scheme;
1698
82.6k
    ifmissing = CURLUE_NO_SCHEME;
1699
82.6k
    flags &= ~U_CURLU_URLDECODE; /* never for schemes */
1700
82.6k
    if((flags & CURLU_NO_GUESS_SCHEME) && u->guessed_scheme)
1701
798
      return CURLUE_NO_SCHEME;
1702
81.8k
    break;
1703
81.8k
  case CURLUPART_USER:
1704
51.7k
    ptr = u->user;
1705
51.7k
    ifmissing = CURLUE_NO_USER;
1706
51.7k
    break;
1707
51.7k
  case CURLUPART_PASSWORD:
1708
51.7k
    ptr = u->password;
1709
51.7k
    ifmissing = CURLUE_NO_PASSWORD;
1710
51.7k
    break;
1711
31.9k
  case CURLUPART_OPTIONS:
1712
31.9k
    ptr = u->options;
1713
31.9k
    ifmissing = CURLUE_NO_OPTIONS;
1714
31.9k
    break;
1715
57.0k
  case CURLUPART_HOST:
1716
57.0k
    ptr = u->host;
1717
57.0k
    ifmissing = CURLUE_NO_HOST;
1718
57.0k
    break;
1719
55.1k
  case CURLUPART_ZONEID:
1720
55.1k
    ptr = u->zoneid;
1721
55.1k
    ifmissing = CURLUE_NO_ZONEID;
1722
55.1k
    break;
1723
24.9k
  case CURLUPART_PORT:
1724
24.9k
    ptr = NULL;
1725
24.9k
    ifmissing = CURLUE_NO_PORT;
1726
24.9k
    flags &= ~U_CURLU_URLDECODE; /* never for port */
1727
24.9k
    if(u->port_present) {
1728
33
      const struct Curl_scheme *h = u->scheme ?
1729
33
                                    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
33
      if(h && (h->defport == u->portnum) &&
1733
0
         (flags & CURLU_NO_DEFAULT_PORT)) {
1734
0
        ptr = NULL;
1735
0
      }
1736
33
      else {
1737
33
        curl_msnprintf(portbuf, sizeof(portbuf), "%u", u->portnum);
1738
33
        ptr = portbuf;
1739
33
      }
1740
33
    }
1741
24.8k
    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
24.9k
    break;
1751
33.6k
  case CURLUPART_PATH:
1752
33.6k
    ptr = u->path;
1753
33.6k
    if(!ptr)
1754
23.4k
      ptr = "/";
1755
33.6k
    break;
1756
33.6k
  case CURLUPART_QUERY:
1757
33.6k
    ptr = u->query;
1758
33.6k
    ifmissing = CURLUE_NO_QUERY;
1759
33.6k
    plusdecode = flags & CURLU_URLDECODE;
1760
33.6k
    if(ptr && !ptr[0] && !(flags & CURLU_GET_EMPTY))
1761
      /* there was a blank query and the user does not ask for it */
1762
1
      ptr = NULL;
1763
33.6k
    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
78.2k
  case CURLUPART_URL:
1772
78.2k
    return urlget_url(u, part, flags);
1773
0
  default:
1774
0
    ptr = NULL;
1775
0
    break;
1776
500k
  }
1777
421k
  if(ptr)
1778
181k
    return urlget_format(u, what, ptr, part, plusdecode, flags);
1779
1780
240k
  return ifmissing;
1781
421k
}
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
702
{
1816
702
  curl_off_t port;
1817
702
  if(!ISDIGIT(provided_port[0]))
1818
    /* not a number */
1819
0
    return CURLUE_BAD_PORT_NUMBER;
1820
702
  if(curlx_str_number(&provided_port, &port, 0xffff) || *provided_port)
1821
    /* weirdly provided number, not good! */
1822
0
    return CURLUE_BAD_PORT_NUMBER;
1823
702
  u->portnum = (uint16_t)port;
1824
702
  u->port_present = TRUE;
1825
702
  return CURLUE_OK;
1826
702
}
1827
1828
static CURLUcode set_url(CURLU *u, const char *url, size_t part_size,
1829
                         unsigned int flags)
1830
82.6k
{
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
82.6k
  CURLUcode uc;
1838
82.6k
  char *oldurl = NULL;
1839
1840
82.6k
  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
941
    uc = curl_url_get(u, CURLUPART_URL, &oldurl, flags);
1844
941
    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
941
    if(uc == CURLUE_OUT_OF_MEMORY)
1854
0
      return uc;
1855
941
    return CURLUE_MALFORMED_INPUT;
1856
941
  }
1857
1858
  /* if the new URL is absolute replace the existing with the new. */
1859
81.6k
  if(Curl_is_absolute_url(url, NULL, 0,
1860
81.6k
                          flags & (CURLU_GUESS_SCHEME | CURLU_DEFAULT_SCHEME)))
1861
69.2k
    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
12.4k
  uc = curl_url_get(u, CURLUPART_URL, &oldurl,
1869
12.4k
                    (flags & ~CURLU_NO_GUESS_SCHEME) | CURLU_GET_EMPTY);
1870
12.4k
  if(uc == CURLUE_OUT_OF_MEMORY)
1871
0
    return uc;
1872
12.4k
  else if(uc)
1873
5.93k
    return parseurl_and_replace(url, u, flags);
1874
1875
6.47k
  DEBUGASSERT(oldurl); /* it is set here */
1876
  /* apply the relative part to create a new URL */
1877
6.47k
  uc = redirect_url(oldurl, url, u, flags);
1878
6.47k
  curlx_free(oldurl);
1879
6.47k
  return uc;
1880
6.47k
}
1881
1882
static CURLUcode urlset_clear(CURLU *u, CURLUPart what)
1883
22.0k
{
1884
22.0k
  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
521
  case CURLUPART_USER:
1894
521
    curlx_safefree(u->user);
1895
521
    break;
1896
521
  case CURLUPART_PASSWORD:
1897
521
    curlx_strzero(u->password);
1898
521
    curlx_safefree(u->password);
1899
521
    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
21.0k
  case CURLUPART_FRAGMENT:
1921
21.0k
    curlx_safefree(u->fragment);
1922
21.0k
    u->fragment_present = FALSE;
1923
21.0k
    break;
1924
0
  default:
1925
0
    return CURLUE_UNKNOWN_PART;
1926
22.0k
  }
1927
22.0k
  return CURLUE_OK;
1928
22.0k
}
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
6.40k
{
1962
6.40k
  const unsigned char *i;
1963
1964
45.4M
  for(i = (const unsigned char *)part; *i; i++) {
1965
45.4M
    CURLcode result;
1966
45.4M
    if((*i == ' ') && plusencode)
1967
0
      result = curlx_dyn_addn(encp, "+", 1);
1968
45.4M
    else if(ISUNRESERVED(*i) ||
1969
41.2M
            (pathmode && allowed_in_path(*i)) ||
1970
41.2M
            ((*i == '=') && equalsencode)) {
1971
4.17M
      if((*i == '=') && equalsencode)
1972
        /* only skip the first equals sign */
1973
0
        equalsencode = FALSE;
1974
4.17M
      result = curlx_dyn_addn(encp, i, 1);
1975
4.17M
    }
1976
41.2M
    else {
1977
41.2M
      unsigned char out[3] = { '%' };
1978
41.2M
      Curl_hexbyte(&out[1], *i);
1979
41.2M
      result = curlx_dyn_addn(encp, out, 3);
1980
41.2M
    }
1981
45.4M
    if(result)
1982
0
      return cc2cu(result);
1983
45.4M
  }
1984
6.40k
  return CURLUE_OK;
1985
6.40k
}
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
111k
{
2093
111k
  char **storep = NULL;
2094
111k
  bool urlencode = (flags & CURLU_URLENCODE) ? 1 : 0;
2095
111k
  bool plusencode = FALSE;
2096
111k
  bool pathmode = FALSE;
2097
111k
  bool leadingslash = FALSE;
2098
111k
  bool appendquery = FALSE;
2099
111k
  bool equalsencode = FALSE;
2100
111k
  size_t nalloc;
2101
2102
111k
  if(!u)
2103
0
    return CURLUE_BAD_HANDLE;
2104
111k
  if(!part)
2105
    /* setting a part to NULL clears it */
2106
22.0k
    return urlset_clear(u, what);
2107
2108
89.7k
  nalloc = strlen(part);
2109
89.7k
  if(nalloc > CURL_MAX_INPUT_LENGTH)
2110
    /* excessive input length */
2111
0
    return CURLUE_MALFORMED_INPUT;
2112
2113
89.7k
  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
3.20k
  case CURLUPART_USER:
2123
3.20k
    storep = &u->user;
2124
3.20k
    break;
2125
3.20k
  case CURLUPART_PASSWORD:
2126
3.20k
    storep = &u->password;
2127
3.20k
    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
702
  case CURLUPART_PORT:
2139
702
    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
82.6k
  case CURLUPART_URL:
2157
82.6k
    return set_url(u, part, nalloc, flags);
2158
0
  default:
2159
0
    return CURLUE_UNKNOWN_PART;
2160
89.7k
  }
2161
6.40k
  DEBUGASSERT(storep);
2162
6.40k
  {
2163
6.40k
    const char *newp = NULL;
2164
6.40k
    struct dynbuf enc;
2165
6.40k
    CURLUcode status;
2166
6.40k
    curlx_dyn_init(&enc, (what == CURLUPART_HOST) ?
2167
0
                   CURLMAX((nalloc * 3) + 1 + leadingslash, 16) :
2168
6.40k
                   (nalloc * 3) + 1 + leadingslash);
2169
2170
6.40k
    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
6.40k
    if(urlencode)
2176
6.40k
      status = url_encode_part(&enc, part, plusencode, pathmode, equalsencode);
2177
0
    else
2178
0
      status = url_uppercasehex_part(&enc, part);
2179
6.40k
    if(!status) {
2180
6.40k
      newp = curlx_dyn_ptr(&enc);
2181
2182
6.40k
      if(appendquery && newp)
2183
0
        return url_append_query(u, &enc);
2184
6.40k
      else if(what == CURLUPART_HOST)
2185
0
        status = url_sethost(u, &enc, urlencode, flags);
2186
6.40k
    }
2187
6.40k
    if(status)
2188
0
      return status;
2189
2190
6.40k
    if(what == CURLUPART_PASSWORD)
2191
3.20k
      curlx_strzero(*storep);
2192
6.40k
    curlx_free(*storep);
2193
6.40k
    *storep = (char *)CURL_UNCONST(newp);
2194
6.40k
  }
2195
0
  return CURLUE_OK;
2196
6.40k
}
2197
2198
bool Curl_url_same_origin(CURLU *base, CURLU *href)
2199
10.9k
{
2200
10.9k
  const struct Curl_scheme *s = NULL;
2201
2202
  /* base must be an absolute URL */
2203
10.9k
  if(!base->scheme || !base->host)
2204
0
    return FALSE;
2205
10.9k
  if(href->scheme && !curl_strequal(base->scheme, href->scheme))
2206
1
    return FALSE;
2207
10.9k
  if(href->host) {
2208
10.9k
    if(!curl_strequal(base->host, href->host))
2209
42
      return FALSE;
2210
2211
10.9k
    if(base->port_present != href->port_present) {
2212
      /* one is present, one is not */
2213
51
      s = Curl_get_scheme(base->scheme);
2214
51
      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
51
      if((base->port_present && (base->portnum != s->defport)) ||
2218
51
         (href->port_present && (href->portnum != s->defport)))
2219
50
        return FALSE;
2220
51
    }
2221
10.8k
    else if(base->portnum != href->portnum) /* both present or missing */
2222
13
      return FALSE;
2223
2224
10.8k
    if(!curl_strequal(base->zoneid ? base->zoneid : "",
2225
10.8k
                      href->zoneid ? href->zoneid : ""))
2226
0
      return FALSE;
2227
10.8k
  }
2228
0
  else if(href->port_present) /* no host in href, then there must be no port */
2229
0
    return FALSE;
2230
10.8k
  return TRUE;
2231
10.9k
}
2232
2233
CURLUcode Curl_url_get_port(CURLU *u, uint16_t *pport)
2234
31.2k
{
2235
31.2k
  if(u->port_present) {
2236
1.11k
    *pport = u->portnum;
2237
1.11k
    return CURLUE_OK;
2238
1.11k
  }
2239
30.1k
  else if(u->scheme) {
2240
30.1k
    const struct Curl_scheme *s = Curl_get_scheme(u->scheme);
2241
30.1k
    if(s && s->defport) {
2242
30.1k
      *pport = s->defport;
2243
30.1k
      return CURLUE_OK;
2244
30.1k
    }
2245
30.1k
  }
2246
47
  *pport = 0;
2247
47
  return CURLUE_NO_PORT;
2248
31.2k
}