GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: node_url.cc Lines: 1127 1185 95.1 %
Date: 2022-05-22 04:15:48 Branches: 970 1096 88.5 %

Line Branch Exec Source
1
#include "node_url.h"
2
#include "base_object-inl.h"
3
#include "node_errors.h"
4
#include "node_external_reference.h"
5
#include "node_i18n.h"
6
#include "util-inl.h"
7
8
#include <cmath>
9
#include <cstdio>
10
#include <numeric>
11
#include <string>
12
#include <vector>
13
14
namespace node {
15
16
using errors::TryCatchScope;
17
18
using url::table_data::hex;
19
using url::table_data::C0_CONTROL_ENCODE_SET;
20
using url::table_data::FRAGMENT_ENCODE_SET;
21
using url::table_data::PATH_ENCODE_SET;
22
using url::table_data::USERINFO_ENCODE_SET;
23
using url::table_data::QUERY_ENCODE_SET_NONSPECIAL;
24
using url::table_data::QUERY_ENCODE_SET_SPECIAL;
25
26
using v8::Array;
27
using v8::Context;
28
using v8::Function;
29
using v8::FunctionCallbackInfo;
30
using v8::HandleScope;
31
using v8::Int32;
32
using v8::Integer;
33
using v8::Isolate;
34
using v8::Local;
35
using v8::MaybeLocal;
36
using v8::NewStringType;
37
using v8::Null;
38
using v8::Object;
39
using v8::String;
40
using v8::Undefined;
41
using v8::Value;
42
43
148593
Local<String> Utf8String(Isolate* isolate, const std::string& str) {
44
148593
  return String::NewFromUtf8(isolate,
45
                             str.data(),
46
                             NewStringType::kNormal,
47
148593
                             str.length()).ToLocalChecked();
48
}
49
50
namespace url {
51
namespace {
52
53
// https://url.spec.whatwg.org/#eof-code-point
54
constexpr char kEOL = -1;
55
56
// https://url.spec.whatwg.org/#concept-host
57
class URLHost {
58
 public:
59
  ~URLHost();
60
61
  void ParseIPv4Host(const char* input, size_t length, bool* is_ipv4);
62
  void ParseIPv6Host(const char* input, size_t length);
63
  void ParseOpaqueHost(const char* input, size_t length);
64
  void ParseHost(const char* input,
65
                 size_t length,
66
                 bool is_special,
67
                 bool unicode = false);
68
69
5736
  bool ParsingFailed() const { return type_ == HostType::H_FAILED; }
70
  std::string ToString() const;
71
  // Like ToString(), but avoids a copy in exchange for invalidating `*this`.
72
  std::string ToStringMove();
73
74
 private:
75
  enum class HostType {
76
    H_FAILED,
77
    H_DOMAIN,
78
    H_IPV4,
79
    H_IPV6,
80
    H_OPAQUE,
81
  };
82
83
  union Value {
84
    std::string domain_or_opaque;
85
    uint32_t ipv4;
86
    uint16_t ipv6[8];
87
88
5736
    ~Value() {}
89
5736
    Value() : ipv4(0) {}
90
  };
91
92
  Value value_;
93
  HostType type_ = HostType::H_FAILED;
94
95
15596
  void Reset() {
96
    using string = std::string;
97
15596
    switch (type_) {
98
4637
      case HostType::H_DOMAIN:
99
      case HostType::H_OPAQUE:
100
4637
        value_.domain_or_opaque.~string();
101
4637
        break;
102
10959
      default:
103
10959
        break;
104
    }
105
15596
    type_ = HostType::H_FAILED;
106
15596
  }
107
108
  // Setting the string members of the union with = is brittle because
109
  // it relies on them being initialized to a state that requires no
110
  // destruction of old data.
111
  // For a long time, that worked well enough because ParseIPv6Host() happens
112
  // to zero-fill `value_`, but that really is relying on standard library
113
  // internals too much.
114
  // These helpers are the easiest solution but we might want to consider
115
  // just not forcing strings into an union.
116
458
  void SetOpaque(std::string&& string) {
117
458
    Reset();
118
458
    type_ = HostType::H_OPAQUE;
119
458
    new(&value_.domain_or_opaque) std::string(std::move(string));
120
458
  }
121
122
4179
  void SetDomain(std::string&& string) {
123
4179
    Reset();
124
4179
    type_ = HostType::H_DOMAIN;
125
4179
    new(&value_.domain_or_opaque) std::string(std::move(string));
126
4179
  }
127
};
128
129
5736
URLHost::~URLHost() {
130
5736
  Reset();
131
5736
}
132
133
#define ARGS(XX)                                                              \
134
  XX(ARG_FLAGS)                                                               \
135
  XX(ARG_PROTOCOL)                                                            \
136
  XX(ARG_USERNAME)                                                            \
137
  XX(ARG_PASSWORD)                                                            \
138
  XX(ARG_HOST)                                                                \
139
  XX(ARG_PORT)                                                                \
140
  XX(ARG_PATH)                                                                \
141
  XX(ARG_QUERY)                                                               \
142
  XX(ARG_FRAGMENT)                                                            \
143
  XX(ARG_COUNT)  // This one has to be last.
144
145
enum url_cb_args {
146
#define XX(name) name,
147
  ARGS(XX)
148
#undef XX
149
};
150
151
#define TWO_CHAR_STRING_TEST(bits, name, expr)                                \
152
  template <typename T>                                                       \
153
  bool name(const T ch1, const T ch2) {                                \
154
    static_assert(sizeof(ch1) >= (bits) / 8,                                  \
155
                  "Character must be wider than " #bits " bits");             \
156
    return (expr);                                                            \
157
  }                                                                           \
158
  template <typename T>                                                       \
159
  bool name(const std::basic_string<T>& str) {                         \
160
    static_assert(sizeof(str[0]) >= (bits) / 8,                               \
161
                  "Character must be wider than " #bits " bits");             \
162
    return str.length() >= 2 && name(str[0], str[1]);                         \
163
  }
164
165
// https://infra.spec.whatwg.org/#ascii-tab-or-newline
166

14381947
CHAR_TEST(8, IsASCIITabOrNewline, (ch == '\t' || ch == '\n' || ch == '\r'))
167
168
// https://infra.spec.whatwg.org/#c0-control-or-space
169

302606
CHAR_TEST(8, IsC0ControlOrSpace, (ch >= '\0' && ch <= ' '))
170
171
// https://infra.spec.whatwg.org/#ascii-digit
172

588254
CHAR_TEST(8, IsASCIIDigit, (ch >= '0' && ch <= '9'))
173
174
// https://infra.spec.whatwg.org/#ascii-hex-digit
175


1078
CHAR_TEST(8, IsASCIIHexDigit, (IsASCIIDigit(ch) ||
176
                               (ch >= 'A' && ch <= 'F') ||
177
                               (ch >= 'a' && ch <= 'f')))
178
179
// https://infra.spec.whatwg.org/#ascii-alpha
180


1300250
CHAR_TEST(8, IsASCIIAlpha, ((ch >= 'A' && ch <= 'Z') ||
181
                            (ch >= 'a' && ch <= 'z')))
182
183
// https://infra.spec.whatwg.org/#ascii-alphanumeric
184

570991
CHAR_TEST(8, IsASCIIAlphanumeric, (IsASCIIDigit(ch) || IsASCIIAlpha(ch)))
185
186
// https://infra.spec.whatwg.org/#ascii-lowercase
187
template <typename T>
188
571063
T ASCIILowercase(T ch) {
189
571063
  return IsASCIIAlpha(ch) ? (ch | 0x20) : ch;
190
}
191
192
// https://url.spec.whatwg.org/#forbidden-host-code-point
193









97316
CHAR_TEST(8, IsForbiddenHostCodePoint,
194
          ch == '\0' || ch == '\t' || ch == '\n' || ch == '\r' ||
195
          ch == ' ' || ch == '#' || ch == '%' || ch == '/' ||
196
          ch == ':' || ch == '?' || ch == '@' || ch == '[' ||
197
          ch == '<' || ch == '>' || ch == '\\' || ch == ']' ||
198
          ch == '^' || ch == '|')
199
200
// https://url.spec.whatwg.org/#windows-drive-letter
201

12714
TWO_CHAR_STRING_TEST(8, IsWindowsDriveLetter,
202
                     (IsASCIIAlpha(ch1) && (ch2 == ':' || ch2 == '|')))
203
204
// https://url.spec.whatwg.org/#normalized-windows-drive-letter
205

2744
TWO_CHAR_STRING_TEST(8, IsNormalizedWindowsDriveLetter,
206
                     (IsASCIIAlpha(ch1) && ch2 == ':'))
207
208
#undef TWO_CHAR_STRING_TEST
209
210
12107026
bool BitAt(const uint8_t a[], const uint8_t i) {
211
12107026
  return !!(a[i >> 3] & (1 << (i & 7)));
212
}
213
214
// Appends ch to str. If ch position in encode_set is set, the ch will
215
// be percent-encoded then appended.
216
12107026
void AppendOrEscape(std::string* str,
217
                    const unsigned char ch,
218
                    const uint8_t encode_set[]) {
219
12107026
  if (BitAt(encode_set, ch))
220
1927
    *str += hex + ch * 4;  // "%XX\0" has a length of 4
221
  else
222
12105099
    *str += ch;
223
12107026
}
224
225
850
unsigned hex2bin(const char ch) {
226

850
  if (ch >= '0' && ch <= '9')
227
546
    return ch - '0';
228

304
  if (ch >= 'A' && ch <= 'F')
229
172
    return 10 + (ch - 'A');
230

132
  if (ch >= 'a' && ch <= 'f')
231
132
    return 10 + (ch - 'a');
232
  UNREACHABLE();
233
}
234
235
4874
std::string PercentDecode(const char* input, size_t len) {
236
4874
  std::string dest;
237
4874
  if (len == 0)
238
2
    return dest;
239
4872
  dest.reserve(len);
240
4872
  const char* pointer = input;
241
4872
  const char* end = input + len;
242
243
100738
  while (pointer < end) {
244
95866
    const char ch = pointer[0];
245
95866
    size_t remaining = end - pointer - 1;
246


96303
    if (ch != '%' || remaining < 2 ||
247
437
        (ch == '%' &&
248
437
         (!IsASCIIHexDigit(pointer[1]) ||
249
433
          !IsASCIIHexDigit(pointer[2])))) {
250
95441
      dest += ch;
251
95441
      pointer++;
252
95441
      continue;
253
    } else {
254
425
      unsigned a = hex2bin(pointer[1]);
255
425
      unsigned b = hex2bin(pointer[2]);
256
425
      char c = static_cast<char>(a * 16 + b);
257
425
      dest += c;
258
425
      pointer += 3;
259
    }
260
  }
261
4872
  return dest;
262
}
263
264
#define SPECIALS(XX)                                                          \
265
  XX(ftp, 21, "ftp:")                                                         \
266
  XX(file, -1, "file:")                                                       \
267
  XX(http, 80, "http:")                                                       \
268
  XX(https, 443, "https:")                                                    \
269
  XX(ws, 80, "ws:")                                                           \
270
  XX(wss, 443, "wss:")
271
272
345893
bool IsSpecial(const std::string& scheme) {
273
#define V(_, __, name) if (scheme == name) return true;
274



345893
  SPECIALS(V);
275
#undef V
276
6500
  return false;
277
}
278
279
144922
Local<String> GetSpecial(Environment* env, const std::string& scheme) {
280
#define V(key, _, name) if (scheme == name)                                  \
281
    return env->url_special_##key##_string();
282



144922
  SPECIALS(V)
283
#undef V
284
  UNREACHABLE();
285
}
286
287
137730
int NormalizePort(const std::string& scheme, int p) {
288
#define V(_, port, name) if (scheme == name && p == port) return -1;
289









137730
  SPECIALS(V);
290
#undef V
291
11734
  return p;
292
}
293
294
// https://url.spec.whatwg.org/#start-with-a-windows-drive-letter
295
6816
bool StartsWithWindowsDriveLetter(const char* p, const char* end) {
296
6816
  size_t length = end - p;
297
6113
  return length >= 2 &&
298

12965
    IsWindowsDriveLetter(p[0], p[1]) &&
299
36
    (length == 2 ||
300
36
      p[2] == '/' ||
301
14
      p[2] == '\\' ||
302
6
      p[2] == '?' ||
303
6820
      p[2] == '#');
304
}
305
306
#if defined(NODE_HAVE_I18N_SUPPORT)
307
195
bool ToUnicode(const std::string& input, std::string* output) {
308
390
  MaybeStackBuffer<char> buf;
309
195
  if (i18n::ToUnicode(&buf, input.c_str(), input.length()) < 0)
310
    return false;
311
195
  output->assign(*buf, buf.length());
312
195
  return true;
313
}
314
315
4861
bool ToASCII(const std::string& input, std::string* output) {
316
9722
  MaybeStackBuffer<char> buf;
317
4861
  if (i18n::ToASCII(&buf, input.c_str(), input.length()) < 0)
318
124
    return false;
319
4737
  if (buf.length() == 0)
320
24
    return false;
321
4713
  output->assign(*buf, buf.length());
322
4713
  return true;
323
}
324
#else  // !defined(NODE_HAVE_I18N_SUPPORT)
325
// Intentional non-ops if ICU is not present.
326
bool ToUnicode(const std::string& input, std::string* output) {
327
  *output = input;
328
  return true;
329
}
330
331
bool ToASCII(const std::string& input, std::string* output) {
332
  *output = input;
333
  return true;
334
}
335
#endif  // !defined(NODE_HAVE_I18N_SUPPORT)
336
337
#define NS_IN6ADDRSZ 16
338
339
347
void URLHost::ParseIPv6Host(const char* input, size_t length) {
340
347
  CHECK_EQ(type_, HostType::H_FAILED);
341
342
  unsigned char buf[sizeof(struct in6_addr)];
343
347
  MaybeStackBuffer<char> ipv6(length + 1);
344
347
  *(*ipv6 + length) = 0;
345
347
  memset(buf, 0, sizeof(buf));
346
347
  memcpy(*ipv6, input, sizeof(const char) * length);
347
348
347
  int ret = uv_inet_pton(AF_INET6, *ipv6, buf);
349
350
347
  if (ret != 0) {
351
92
    return;
352
  }
353
354
  // Ref: https://sourceware.org/git/?p=glibc.git;a=blob;f=resolv/inet_ntop.c;h=c4d38c0f951013e51a4fc6eaa8a9b82e146abe5a;hb=HEAD#l119
355
2295
  for (int i = 0; i < NS_IN6ADDRSZ; i += 2) {
356
2040
    value_.ipv6[i >> 1] = (buf[i] << 8) | buf[i + 1];
357
  }
358
359
255
  type_ = HostType::H_IPV6;
360
}
361
362
5489
int64_t ParseNumber(const char* start, const char* end) {
363
5489
  unsigned R = 10;
364

5489
  if (end - start >= 2 && start[0] == '0' && (start[1] | 0x20) == 'x') {
365
48
    start += 2;
366
48
    R = 16;
367
  }
368
5489
  if (end - start == 0) {
369
8
    return 0;
370

5481
  } else if (R == 10 && end - start > 1 && start[0] == '0') {
371
55
    start++;
372
55
    R = 8;
373
  }
374
5481
  const char* p = start;
375
376
8152
  while (p < end) {
377
6834
    const char ch = p[0];
378

6834
    switch (R) {
379
274
      case 8:
380

274
        if (ch < '0' || ch > '7')
381
29
          return -1;
382
245
        break;
383
6352
      case 10:
384
6352
        if (!IsASCIIDigit(ch))
385
4130
          return -1;
386
2222
        break;
387
208
      case 16:
388
208
        if (!IsASCIIHexDigit(ch))
389
4
          return -1;
390
204
        break;
391
    }
392
2671
    p++;
393
  }
394
1318
  return strtoll(start, nullptr, R);
395
}
396
397
4538
void URLHost::ParseIPv4Host(const char* input, size_t length, bool* is_ipv4) {
398
4538
  CHECK_EQ(type_, HostType::H_FAILED);
399
4538
  *is_ipv4 = false;
400
4538
  const char* pointer = input;
401
4538
  const char* mark = input;
402
4538
  const char* end = pointer + length;
403
4538
  int parts = 0;
404
4538
  uint32_t val = 0;
405
  uint64_t numbers[4];
406
4538
  int tooBigNumbers = 0;
407
4538
  if (length == 0)
408
4207
    return;
409
410
42438
  while (pointer <= end) {
411
42083
    const char ch = pointer < end ? pointer[0] : kEOL;
412
42083
    int64_t remaining = end - pointer - 1;
413

42083
    if (ch == '.' || ch == kEOL) {
414
5505
      if (++parts > static_cast<int>(arraysize(numbers))) return;
415
5501
      if (pointer == mark)
416
12
        return;
417
5489
      int64_t n = ParseNumber(mark, pointer);
418
5489
      if (n < 0)
419
4163
        return;
420
421
1326
      if (n > 255) {
422
112
        tooBigNumbers++;
423
      }
424
1326
      numbers[parts - 1] = n;
425
1326
      mark = pointer + 1;
426

1326
      if (ch == '.' && remaining == 0)
427
4
        break;
428
    }
429
37900
    pointer++;
430
  }
431
359
  CHECK_GT(parts, 0);
432
359
  *is_ipv4 = true;
433
434
  // If any but the last item in numbers is greater than 255, return failure.
435
  // If the last item in numbers is greater than or equal to
436
  // 256^(5 - the number of items in numbers), return failure.
437
355
  if (tooBigNumbers > 1 ||
438

774
      (tooBigNumbers == 1 && numbers[parts - 1] <= 255) ||
439
351
      numbers[parts - 1] >= pow(256, static_cast<double>(5 - parts))) {
440
28
    return;
441
  }
442
443
331
  type_ = HostType::H_IPV4;
444
331
  val = static_cast<uint32_t>(numbers[parts - 1]);
445
1200
  for (int n = 0; n < parts - 1; n++) {
446
869
    double b = 3 - n;
447
869
    val +=
448
869
        static_cast<uint32_t>(numbers[n]) * static_cast<uint32_t>(pow(256, b));
449
  }
450
451
331
  value_.ipv4 = val;
452
}
453
454
520
void URLHost::ParseOpaqueHost(const char* input, size_t length) {
455
520
  CHECK_EQ(type_, HostType::H_FAILED);
456
520
  std::string output;
457
520
  output.reserve(length);
458
3053
  for (size_t i = 0; i < length; i++) {
459
2595
    const char ch = input[i];
460

2595
    if (ch != '%' && IsForbiddenHostCodePoint(ch)) {
461
62
      return;
462
    } else {
463
2533
      AppendOrEscape(&output, ch, C0_CONTROL_ENCODE_SET);
464
    }
465
  }
466
467
458
  SetOpaque(std::move(output));
468
}
469
470
5736
void URLHost::ParseHost(const char* input,
471
                        size_t length,
472
                        bool is_special,
473
                        bool unicode) {
474
5736
  CHECK_EQ(type_, HostType::H_FAILED);
475
5736
  const char* pointer = input;
476
477
5736
  if (length == 0)
478
1557
    return;
479
480
5736
  if (pointer[0] == '[') {
481
355
    if (pointer[length - 1] != ']')
482
8
      return;
483
347
    return ParseIPv6Host(++pointer, length - 2);
484
  }
485
486
5381
  if (!is_special)
487
520
    return ParseOpaqueHost(input, length);
488
489
  // First, we have to percent decode
490
4861
  std::string decoded = PercentDecode(input, length);
491
492
  // Then we have to punycode toASCII
493
4861
  if (!ToASCII(decoded, &decoded))
494
148
    return;
495
496
  // If any of the following characters are still present, we have to fail
497
99285
  for (size_t n = 0; n < decoded.size(); n++) {
498
94747
    const char ch = decoded[n];
499
94747
    if (IsForbiddenHostCodePoint(ch)) {
500
175
      return;
501
    }
502
  }
503
504
  // Check to see if it's an IPv4 IP address
505
  bool is_ipv4;
506
4538
  ParseIPv4Host(decoded.c_str(), decoded.length(), &is_ipv4);
507
4538
  if (is_ipv4)
508
359
    return;
509
510
  // If the unicode flag is set, run the result through punycode ToUnicode
511

4179
  if (unicode && !ToUnicode(decoded, &decoded))
512
    return;
513
514
  // It's not an IPv4 or IPv6 address, it must be a domain
515
4179
  SetDomain(std::move(decoded));
516
}
517
518
// Locates the longest sequence of 0 segments in an IPv6 address
519
// in order to use the :: compression when serializing
520
template <typename T>
521
255
T* FindLongestZeroSequence(T* values, size_t len) {
522
255
  T* start = values;
523
255
  T* end = start + len;
524
255
  T* result = nullptr;
525
526
255
  T* current = nullptr;
527
255
  unsigned counter = 0, longest = 1;
528
529
2295
  while (start < end) {
530
2040
    if (*start == 0) {
531
1735
      if (current == nullptr)
532
269
        current = start;
533
1735
      counter++;
534
    } else {
535
305
      if (counter > longest) {
536
247
        longest = counter;
537
247
        result = current;
538
      }
539
305
      counter = 0;
540
305
      current = nullptr;
541
    }
542
2040
    start++;
543
  }
544
255
  if (counter > longest)
545
6
    result = current;
546
255
  return result;
547
}
548
549
5223
std::string URLHost::ToStringMove() {
550
5223
  std::string return_value;
551
5223
  switch (type_) {
552
4637
    case HostType::H_DOMAIN:
553
    case HostType::H_OPAQUE:
554
4637
      return_value = std::move(value_.domain_or_opaque);
555
4637
      break;
556
586
    default:
557
586
      return_value = ToString();
558
586
      break;
559
  }
560
5223
  Reset();
561
5223
  return return_value;
562
}
563
564
586
std::string URLHost::ToString() const {
565
1172
  std::string dest;
566

586
  switch (type_) {
567
    case HostType::H_DOMAIN:
568
    case HostType::H_OPAQUE:
569
      return value_.domain_or_opaque;
570
331
    case HostType::H_IPV4: {
571
331
      dest.reserve(15);
572
331
      uint32_t value = value_.ipv4;
573
1655
      for (int n = 0; n < 4; n++) {
574
1324
        dest.insert(0, std::to_string(value % 256));
575
1324
        if (n < 3)
576
993
          dest.insert(0, 1, '.');
577
1324
        value /= 256;
578
      }
579
331
      break;
580
    }
581
255
    case HostType::H_IPV6: {
582
255
      dest.reserve(41);
583
255
      dest += '[';
584
255
      const uint16_t* start = &value_.ipv6[0];
585
      const uint16_t* compress_pointer =
586
255
          FindLongestZeroSequence(start, 8);
587
255
      bool ignore0 = false;
588
2295
      for (int n = 0; n <= 7; n++) {
589
2040
        const uint16_t* piece = &value_.ipv6[n];
590

2040
        if (ignore0 && *piece == 0)
591
1715
          continue;
592
576
        else if (ignore0)
593
245
          ignore0 = false;
594
576
        if (compress_pointer == piece) {
595
251
          dest += n == 0 ? "::" : ":";
596
251
          ignore0 = true;
597
251
          continue;
598
        }
599
        char buf[5];
600
325
        snprintf(buf, sizeof(buf), "%x", *piece);
601
325
        dest += buf;
602
325
        if (n < 7)
603
76
          dest += ':';
604
      }
605
255
      dest += ']';
606
255
      break;
607
    }
608
    case HostType::H_FAILED:
609
      break;
610
  }
611
586
  return dest;
612
}
613
614
5394
bool ParseHost(const std::string& input,
615
               std::string* output,
616
               bool is_special,
617
               bool unicode = false) {
618
5394
  if (input.empty()) {
619
94
    output->clear();
620
94
    return true;
621
  }
622
10600
  URLHost host;
623
5300
  host.ParseHost(input.c_str(), input.length(), is_special, unicode);
624
5300
  if (host.ParsingFailed())
625
489
    return false;
626
4811
  *output = host.ToStringMove();
627
4811
  return true;
628
}
629
630
9079
std::vector<std::string> FromJSStringArray(Environment* env,
631
                                           Local<Array> array) {
632
9079
  std::vector<std::string> vec;
633
9079
  if (array->Length() > 0)
634
9063
    vec.reserve(array->Length());
635
136284
  for (size_t n = 0; n < array->Length(); n++) {
636
118126
    Local<Value> val = array->Get(env->context(), n).ToLocalChecked();
637
118126
    if (val->IsString()) {
638
59063
      Utf8Value value(env->isolate(), val.As<String>());
639
59063
      vec.emplace_back(*value, value.length());
640
    }
641
  }
642
9079
  return vec;
643
}
644
645
9079
url_data HarvestBase(Environment* env, Local<Object> base_obj) {
646
9079
  url_data base;
647
9079
  Local<Context> context = env->context();
648
649
  Local<Value> flags =
650
27237
      base_obj->Get(env->context(), env->flags_string()).ToLocalChecked();
651
9079
  if (flags->IsInt32())
652
18158
    base.flags = flags->Int32Value(context).FromJust();
653
654
  Local<Value> port =
655
27237
      base_obj->Get(env->context(), env->port_string()).ToLocalChecked();
656
9079
  if (port->IsInt32())
657
76
    base.port = port->Int32Value(context).FromJust();
658
659
  Local<Value> scheme =
660
18158
      base_obj->Get(env->context(), env->scheme_string()).ToLocalChecked();
661
9079
  base.scheme = Utf8Value(env->isolate(), scheme).out();
662
663
  auto GetStr = [&](std::string url_data::*member,
664
                    int flag,
665
                    Local<String> name,
666
45395
                    bool empty_as_present) {
667
90790
    Local<Value> value = base_obj->Get(env->context(), name).ToLocalChecked();
668
90790
    if (value->IsString()) {
669
52234
      Utf8Value utf8value(env->isolate(), value.As<String>());
670
26117
      (base.*member).assign(*utf8value, utf8value.length());
671

44275
      if (empty_as_present || value.As<String>()->Length() != 0) {
672
7981
        base.flags |= flag;
673
      }
674
    }
675
54474
  };
676
9079
  GetStr(&url_data::username,
677
         URL_FLAGS_HAS_USERNAME,
678
         env->username_string(),
679
         false);
680
9079
  GetStr(&url_data::password,
681
         URL_FLAGS_HAS_PASSWORD,
682
         env->password_string(),
683
         false);
684
9079
  GetStr(&url_data::host, URL_FLAGS_HAS_HOST, env->host_string(), true);
685
9079
  GetStr(&url_data::query, URL_FLAGS_HAS_QUERY, env->query_string(), true);
686
9079
  GetStr(&url_data::fragment,
687
         URL_FLAGS_HAS_FRAGMENT,
688
         env->fragment_string(),
689
         true);
690
691
  Local<Value>
692
27237
      path = base_obj->Get(env->context(), env->path_string()).ToLocalChecked();
693
9079
  if (path->IsArray()) {
694
9079
    base.flags |= URL_FLAGS_HAS_PATH;
695
9079
    base.path = FromJSStringArray(env, path.As<Array>());
696
  }
697
9079
  return base;
698
}
699
700
44000
url_data HarvestContext(Environment* env, Local<Object> context_obj) {
701
44000
  url_data context;
702
  Local<Value> flags =
703
132000
      context_obj->Get(env->context(), env->flags_string()).ToLocalChecked();
704
44000
  if (flags->IsInt32()) {
705
    static constexpr int32_t kCopyFlagsMask =
706
        URL_FLAGS_SPECIAL |
707
        URL_FLAGS_CANNOT_BE_BASE |
708
        URL_FLAGS_HAS_USERNAME |
709
        URL_FLAGS_HAS_PASSWORD |
710
        URL_FLAGS_HAS_HOST;
711
44000
    context.flags |= flags.As<Int32>()->Value() & kCopyFlagsMask;
712
  }
713
  Local<Value> scheme =
714
132000
      context_obj->Get(env->context(), env->scheme_string()).ToLocalChecked();
715
88000
  if (scheme->IsString()) {
716
88000
    Utf8Value value(env->isolate(), scheme);
717
44000
    context.scheme.assign(*value, value.length());
718
  }
719
  Local<Value> port =
720
132000
      context_obj->Get(env->context(), env->port_string()).ToLocalChecked();
721
44000
  if (port->IsInt32())
722
243
    context.port = port.As<Int32>()->Value();
723
44000
  if (context.flags & URL_FLAGS_HAS_USERNAME) {
724
    Local<Value> username =
725
221
        context_obj->Get(env->context(),
726
663
                         env->username_string()).ToLocalChecked();
727
442
    CHECK(username->IsString());
728
442
    Utf8Value value(env->isolate(), username);
729
221
    context.username.assign(*value, value.length());
730
  }
731
44000
  if (context.flags & URL_FLAGS_HAS_PASSWORD) {
732
    Local<Value> password =
733
209
        context_obj->Get(env->context(),
734
627
                         env->password_string()).ToLocalChecked();
735
418
    CHECK(password->IsString());
736
418
    Utf8Value value(env->isolate(), password);
737
209
    context.password.assign(*value, value.length());
738
  }
739
  Local<Value> host =
740
44000
      context_obj->Get(env->context(),
741
132000
                       env->host_string()).ToLocalChecked();
742
88000
  if (host->IsString()) {
743
87924
    Utf8Value value(env->isolate(), host);
744
43962
    context.host.assign(*value, value.length());
745
  }
746
44000
  return context;
747
}
748
749
// Single dot segment can be ".", "%2e", or "%2E"
750
2545575
bool IsSingleDotSegment(const std::string& str) {
751
2545575
  switch (str.size()) {
752
7505
    case 1:
753
7505
      return str == ".";
754
148514
    case 3:
755
148514
      return str[0] == '%' &&
756

148560
             str[1] == '2' &&
757
148560
             ASCIILowercase(str[2]) == 'e';
758
2389556
    default:
759
2389556
      return false;
760
  }
761
}
762
763
// Double dot segment can be:
764
//   "..", ".%2e", ".%2E", "%2e.", "%2E.",
765
//   "%2e%2e", "%2E%2E", "%2e%2E", or "%2E%2e"
766
1277723
bool IsDoubleDotSegment(const std::string& str) {
767

1277723
  switch (str.size()) {
768
5143
    case 2:
769
5143
      return str == "..";
770
361649
    case 4:
771

361649
      if (str[0] != '.' && str[0] != '%')
772
361630
        return false;
773
19
      return ((str[0] == '.' &&
774
13
               str[1] == '%' &&
775

8
               str[2] == '2' &&
776
42
               ASCIILowercase(str[3]) == 'e') ||
777
15
              (str[0] == '%' &&
778

12
               str[1] == '2' &&
779
6
               ASCIILowercase(str[2]) == 'e' &&
780
25
               str[3] == '.'));
781
73804
    case 6:
782
73804
      return (str[0] == '%' &&
783

24
              str[1] == '2' &&
784
12
              ASCIILowercase(str[2]) == 'e' &&
785
4
              str[3] == '%' &&
786

73820
              str[4] == '2' &&
787
73808
              ASCIILowercase(str[5]) == 'e');
788
837127
    default:
789
837127
      return false;
790
  }
791
}
792
793
11420
void ShortenUrlPath(struct url_data* url) {
794
11420
  if (url->path.empty()) return;
795


11623
  if (url->path.size() == 1 && url->scheme == "file:" &&
796
608
      IsNormalizedWindowsDriveLetter(url->path[0])) return;
797
11015
  url->path.pop_back();
798
}
799
800
}  // anonymous namespace
801
802
230896
void URL::Parse(const char* input,
803
                size_t len,
804
                enum url_parse_state state_override,
805
                struct url_data* url,
806
                bool has_url,
807
                const struct url_data* base,
808
                bool has_base) {
809
230896
  const char* p = input;
810
230896
  const char* end = input + len;
811
812
230896
  if (!has_url) {
813
151326
    for (const char* ptr = p; ptr < end; ptr++) {
814
151307
      if (IsC0ControlOrSpace(*ptr))
815
56
        p++;
816
      else
817
151251
        break;
818
    }
819
151318
    for (const char* ptr = end - 1; ptr >= p; ptr--) {
820
151299
      if (IsC0ControlOrSpace(*ptr))
821
48
        end--;
822
      else
823
151251
        break;
824
    }
825
151270
    input = p;
826
151270
    len = end - p;
827
  }
828
829
  // The spec says we should strip out any ASCII tabs or newlines.
830
  // In those cases, we create another std::string instance with the filtered
831
  // contents, but in the general case we avoid the overhead.
832
230896
  std::string whitespace_stripped;
833
14611820
  for (const char* ptr = p; ptr < end; ptr++) {
834
14381094
    if (!IsASCIITabOrNewline(*ptr))
835
14380924
      continue;
836
    // Hit tab or newline. Allocate storage, copy what we have until now,
837
    // and then iterate and filter all similar characters out.
838
170
    whitespace_stripped.reserve(len - 1);
839
170
    whitespace_stripped.assign(p, ptr - p);
840
    // 'ptr + 1' skips the current char, which we know to be tab or newline.
841
1023
    for (ptr = ptr + 1; ptr < end; ptr++) {
842
853
      if (!IsASCIITabOrNewline(*ptr))
843
769
        whitespace_stripped += *ptr;
844
    }
845
846
    // Update variables like they should have looked like if the string
847
    // had been stripped of whitespace to begin with.
848
170
    input = whitespace_stripped.c_str();
849
170
    len = whitespace_stripped.size();
850
170
    p = input;
851
170
    end = input + len;
852
170
    break;
853
  }
854
855
230896
  bool atflag = false;  // Set when @ has been seen.
856
230896
  bool square_bracket_flag = false;  // Set inside of [...]
857
230896
  bool password_token_seen_flag = false;  // Set after a : after an username.
858
859
230896
  std::string buffer;
860
861
  // Set the initial parse state.
862
230896
  const bool has_state_override = state_override != kUnknownState;
863
230896
  enum url_parse_state state = has_state_override ? state_override :
864
                                                    kSchemeStart;
865
866

230896
  if (state < kSchemeStart || state > kFragment) {
867
    url->flags |= URL_FLAGS_INVALID_PARSE_STATE;
868
    return;
869
  }
870
871
15151042
  while (p <= end) {
872
14929100
    const char ch = p < end ? p[0] : kEOL;
873
14929100
    bool special = (url->flags & URL_FLAGS_SPECIAL);
874
    bool cannot_be_base;
875

14929100
    bool special_back_slash = (special && ch == '\\');
876
877





14929100
    switch (state) {
878
151353
      case kSchemeStart:
879
151353
        if (IsASCIIAlpha(ch)) {
880
137999
          buffer += ASCIILowercase(ch);
881
137999
          state = kScheme;
882
13354
        } else if (!has_state_override) {
883
13344
          state = kNoScheme;
884
13344
          continue;
885
        } else {
886
10
          url->flags |= URL_FLAGS_FAILED;
887
10
          return;
888
        }
889
137999
        break;
890
570991
      case kScheme:
891


570991
        if (IsASCIIAlphanumeric(ch) || ch == '+' || ch == '-' || ch == '.') {
892
432992
          buffer += ASCIILowercase(ch);
893

137999
        } else if (ch == ':' || (has_state_override && ch == kEOL)) {
894

135966
          if (has_state_override && buffer.size() == 0) {
895
            url->flags |= URL_FLAGS_TERMINATED;
896
            return;
897
          }
898
135966
          buffer += ':';
899
900
135966
          bool new_is_special = IsSpecial(buffer);
901
902
135966
          if (has_state_override) {
903
45
            if ((special != new_is_special) ||
904
45
                ((buffer == "file:") &&
905
6
                 ((url->flags & URL_FLAGS_HAS_USERNAME) ||
906
2
                  (url->flags & URL_FLAGS_HAS_PASSWORD) ||
907


116
                  (url->port != -1))) ||
908
45
                  (url->scheme == "file:" && url->host.empty())) {
909
32
              url->flags |= URL_FLAGS_TERMINATED;
910
32
              return;
911
            }
912
          }
913
914
135934
          url->scheme = std::move(buffer);
915
135934
          url->port = NormalizePort(url->scheme, url->port);
916
135934
          if (new_is_special) {
917
130140
            url->flags |= URL_FLAGS_SPECIAL;
918
130140
            special = true;
919
          } else {
920
5794
            url->flags &= ~URL_FLAGS_SPECIAL;
921
5794
            special = false;
922
          }
923
          // `special_back_slash` equals to `(special && ch == '\\')` and `ch`
924
          // here always not equals to `\\`. So `special_back_slash` here always
925
          // equals to `false`.
926
135934
          special_back_slash = false;
927
135934
          buffer.clear();
928
135934
          if (has_state_override)
929
33
            return;
930
135901
          if (url->scheme == "file:") {
931
125737
            state = kFile;
932
4386
          } else if (special &&
933

14550
                     has_base &&
934
1039
                     url->scheme == base->scheme) {
935
329
            state = kSpecialRelativeOrAuthority;
936
9835
          } else if (special) {
937
4057
            state = kSpecialAuthoritySlashes;
938

5778
          } else if (p + 1 < end && p[1] == '/') {
939
716
            state = kPathOrAuthority;
940
716
            p++;
941
          } else {
942
5062
            url->flags |= URL_FLAGS_CANNOT_BE_BASE;
943
5062
            url->flags |= URL_FLAGS_HAS_PATH;
944
5062
            url->path.emplace_back("");
945
5062
            state = kCannotBeBase;
946
135901
          }
947
2033
        } else if (!has_state_override) {
948
2025
          buffer.clear();
949
2025
          state = kNoScheme;
950
2025
          p = input;
951
2025
          continue;
952
        } else {
953
8
          url->flags |= URL_FLAGS_FAILED;
954
8
          return;
955
        }
956
568893
        break;
957
15369
      case kNoScheme:
958

15369
        cannot_be_base = has_base && (base->flags & URL_FLAGS_CANNOT_BE_BASE);
959

15369
        if (!has_base || (cannot_be_base && ch != '#')) {
960
7970
          url->flags |= URL_FLAGS_FAILED;
961
7970
          return;
962

7399
        } else if (cannot_be_base && ch == '#') {
963
28
          url->scheme = base->scheme;
964
28
          if (IsSpecial(url->scheme)) {
965
            url->flags |= URL_FLAGS_SPECIAL;
966
            special = true;
967
          } else {
968
28
            url->flags &= ~URL_FLAGS_SPECIAL;
969
28
            special = false;
970
          }
971

28
          special_back_slash = (special && ch == '\\');
972
28
          if (base->flags & URL_FLAGS_HAS_PATH) {
973
28
            url->flags |= URL_FLAGS_HAS_PATH;
974
28
            url->path = base->path;
975
          }
976
28
          if (base->flags & URL_FLAGS_HAS_QUERY) {
977
4
            url->flags |= URL_FLAGS_HAS_QUERY;
978
4
            url->query = base->query;
979
          }
980
28
          if (base->flags & URL_FLAGS_HAS_FRAGMENT) {
981
            url->flags |= URL_FLAGS_HAS_FRAGMENT;
982
            url->fragment = base->fragment;
983
          }
984
28
          url->flags |= URL_FLAGS_CANNOT_BE_BASE;
985
28
          state = kFragment;
986

14742
        } else if (has_base &&
987
7371
                   base->scheme != "file:") {
988
534
          state = kRelative;
989
534
          continue;
990
        } else {
991
6837
          url->scheme = "file:";
992
6837
          url->flags |= URL_FLAGS_SPECIAL;
993
6837
          special = true;
994
6837
          state = kFile;
995

6837
          special_back_slash = (special && ch == '\\');
996
6837
          continue;
997
        }
998
28
        break;
999
329
      case kSpecialRelativeOrAuthority:
1000

329
        if (ch == '/' && p + 1 < end && p[1] == '/') {
1001
297
          state = kSpecialAuthorityIgnoreSlashes;
1002
297
          p++;
1003
        } else {
1004
32
          state = kRelative;
1005
32
          continue;
1006
        }
1007
297
        break;
1008
716
      case kPathOrAuthority:
1009
716
        if (ch == '/') {
1010
548
          state = kAuthority;
1011
        } else {
1012
168
          state = kPath;
1013
168
          continue;
1014
        }
1015
548
        break;
1016
566
      case kRelative:
1017
566
        url->scheme = base->scheme;
1018
566
        if (IsSpecial(url->scheme)) {
1019
466
          url->flags |= URL_FLAGS_SPECIAL;
1020
466
          special = true;
1021
        } else {
1022
100
          url->flags &= ~URL_FLAGS_SPECIAL;
1023
100
          special = false;
1024
        }
1025

566
        special_back_slash = (special && ch == '\\');
1026

566
        switch (ch) {
1027
18
          case kEOL:
1028
18
            if (base->flags & URL_FLAGS_HAS_USERNAME) {
1029
4
              url->flags |= URL_FLAGS_HAS_USERNAME;
1030
4
              url->username = base->username;
1031
            }
1032
18
            if (base->flags & URL_FLAGS_HAS_PASSWORD) {
1033
4
              url->flags |= URL_FLAGS_HAS_PASSWORD;
1034
4
              url->password = base->password;
1035
            }
1036
18
            if (base->flags & URL_FLAGS_HAS_HOST) {
1037
16
              url->flags |= URL_FLAGS_HAS_HOST;
1038
16
              url->host = base->host;
1039
            }
1040
18
            if (base->flags & URL_FLAGS_HAS_QUERY) {
1041
              url->flags |= URL_FLAGS_HAS_QUERY;
1042
              url->query = base->query;
1043
            }
1044
18
            if (base->flags & URL_FLAGS_HAS_PATH) {
1045
18
              url->flags |= URL_FLAGS_HAS_PATH;
1046
18
              url->path = base->path;
1047
            }
1048
18
            url->port = base->port;
1049
18
            break;
1050
275
          case '/':
1051
275
            state = kRelativeSlash;
1052
275
            break;
1053
38
          case '?':
1054
38
            if (base->flags & URL_FLAGS_HAS_USERNAME) {
1055
              url->flags |= URL_FLAGS_HAS_USERNAME;
1056
              url->username = base->username;
1057
            }
1058
38
            if (base->flags & URL_FLAGS_HAS_PASSWORD) {
1059
              url->flags |= URL_FLAGS_HAS_PASSWORD;
1060
              url->password = base->password;
1061
            }
1062
38
            if (base->flags & URL_FLAGS_HAS_HOST) {
1063
34
              url->flags |= URL_FLAGS_HAS_HOST;
1064
34
              url->host = base->host;
1065
            }
1066
38
            if (base->flags & URL_FLAGS_HAS_PATH) {
1067
38
              url->flags |= URL_FLAGS_HAS_PATH;
1068
38
              url->path = base->path;
1069
            }
1070
38
            url->port = base->port;
1071
38
            state = kQuery;
1072
38
            break;
1073
38
          case '#':
1074
38
            if (base->flags & URL_FLAGS_HAS_USERNAME) {
1075
              url->flags |= URL_FLAGS_HAS_USERNAME;
1076
              url->username = base->username;
1077
            }
1078
38
            if (base->flags & URL_FLAGS_HAS_PASSWORD) {
1079
              url->flags |= URL_FLAGS_HAS_PASSWORD;
1080
              url->password = base->password;
1081
            }
1082
38
            if (base->flags & URL_FLAGS_HAS_HOST) {
1083
34
              url->flags |= URL_FLAGS_HAS_HOST;
1084
34
              url->host = base->host;
1085
            }
1086
38
            if (base->flags & URL_FLAGS_HAS_QUERY) {
1087
              url->flags |= URL_FLAGS_HAS_QUERY;
1088
              url->query = base->query;
1089
            }
1090
38
            if (base->flags & URL_FLAGS_HAS_PATH) {
1091
38
              url->flags |= URL_FLAGS_HAS_PATH;
1092
38
              url->path = base->path;
1093
            }
1094
38
            url->port = base->port;
1095
38
            state = kFragment;
1096
38
            break;
1097
197
          default:
1098
197
            if (special_back_slash) {
1099
18
              state = kRelativeSlash;
1100
            } else {
1101
179
              if (base->flags & URL_FLAGS_HAS_USERNAME) {
1102
1
                url->flags |= URL_FLAGS_HAS_USERNAME;
1103
1
                url->username = base->username;
1104
              }
1105
179
              if (base->flags & URL_FLAGS_HAS_PASSWORD) {
1106
1
                url->flags |= URL_FLAGS_HAS_PASSWORD;
1107
1
                url->password = base->password;
1108
              }
1109
179
              if (base->flags & URL_FLAGS_HAS_HOST) {
1110
159
                url->flags |= URL_FLAGS_HAS_HOST;
1111
159
                url->host = base->host;
1112
              }
1113
179
              if (base->flags & URL_FLAGS_HAS_PATH) {
1114
179
                url->flags |= URL_FLAGS_HAS_PATH;
1115
179
                url->path = base->path;
1116
179
                ShortenUrlPath(url);
1117
              }
1118
179
              url->port = base->port;
1119
179
              state = kPath;
1120
179
              continue;
1121
            }
1122
        }
1123
387
        break;
1124
293
      case kRelativeSlash:
1125


293
        if (IsSpecial(url->scheme) && (ch == '/' || ch == '\\')) {
1126
22
          state = kSpecialAuthorityIgnoreSlashes;
1127
271
        } else if (ch == '/') {
1128
6
          state = kAuthority;
1129
        } else {
1130
265
          if (base->flags & URL_FLAGS_HAS_USERNAME) {
1131
8
            url->flags |= URL_FLAGS_HAS_USERNAME;
1132
8
            url->username = base->username;
1133
          }
1134
265
          if (base->flags & URL_FLAGS_HAS_PASSWORD) {
1135
4
            url->flags |= URL_FLAGS_HAS_PASSWORD;
1136
4
            url->password = base->password;
1137
          }
1138
265
          if (base->flags & URL_FLAGS_HAS_HOST) {
1139
257
            url->flags |= URL_FLAGS_HAS_HOST;
1140
257
            url->host = base->host;
1141
          }
1142
265
          url->port = base->port;
1143
265
          state = kPath;
1144
265
          continue;
1145
        }
1146
28
        break;
1147
4057
      case kSpecialAuthoritySlashes:
1148
4057
        state = kSpecialAuthorityIgnoreSlashes;
1149

4057
        if (ch == '/' && p + 1 < end && p[1] == '/') {
1150
3908
          p++;
1151
        } else {
1152
149
          continue;
1153
        }
1154
3908
        break;
1155
4453
      case kSpecialAuthorityIgnoreSlashes:
1156

4453
        if (ch != '/' && ch != '\\') {
1157
4376
          state = kAuthority;
1158
4376
          continue;
1159
        }
1160
77
        break;
1161
101759
      case kAuthority:
1162
101759
        if (ch == '@') {
1163
565
          if (atflag) {
1164
41
            buffer.reserve(buffer.size() + 3);
1165
41
            buffer.insert(0, "%40");
1166
          }
1167
565
          atflag = true;
1168
565
          size_t blen = buffer.size();
1169

565
          if (blen > 0 && buffer[0] != ':') {
1170
469
            url->flags |= URL_FLAGS_HAS_USERNAME;
1171
          }
1172
6652
          for (size_t n = 0; n < blen; n++) {
1173
6087
            const char bch = buffer[n];
1174
6087
            if (bch == ':') {
1175
444
              url->flags |= URL_FLAGS_HAS_PASSWORD;
1176
444
              if (!password_token_seen_flag) {
1177
428
                password_token_seen_flag = true;
1178
428
                continue;
1179
              }
1180
            }
1181
5659
            if (password_token_seen_flag) {
1182
2722
              AppendOrEscape(&url->password, bch, USERINFO_ENCODE_SET);
1183
            } else {
1184
2937
              AppendOrEscape(&url->username, bch, USERINFO_ENCODE_SET);
1185
            }
1186
          }
1187
565
          buffer.clear();
1188

101194
        } else if (ch == kEOL ||
1189
96334
                   ch == '/' ||
1190
96302
                   ch == '?' ||
1191
96284
                   ch == '#' ||
1192
                   special_back_slash) {
1193

4930
          if (atflag && buffer.size() == 0) {
1194
52
            url->flags |= URL_FLAGS_FAILED;
1195
52
            return;
1196
          }
1197
4878
          p -= buffer.size() + 1;
1198
4878
          buffer.clear();
1199
4878
          state = kHost;
1200
        } else {
1201
96264
          buffer += ch;
1202
        }
1203
101707
        break;
1204
92360
      case kHost:
1205
      case kHostname:
1206

92360
        if (has_state_override && url->scheme == "file:") {
1207
12
          state = kFileHost;
1208
12
          continue;
1209

92348
        } else if (ch == ':' && !square_bracket_flag) {
1210
1836
          if (buffer.size() == 0) {
1211
24
            url->flags |= URL_FLAGS_FAILED;
1212
24
            return;
1213
          }
1214
1812
          if (state_override == kHostname) {
1215
4
            return;
1216
          }
1217
1808
          url->flags |= URL_FLAGS_HAS_HOST;
1218
1808
          if (!ParseHost(buffer, &url->host, special)) {
1219
5
            url->flags |= URL_FLAGS_FAILED;
1220
5
            return;
1221
          }
1222
1803
          buffer.clear();
1223
1803
          state = kPort;
1224

90512
        } else if (ch == kEOL ||
1225
87180
                   ch == '/' ||
1226
87140
                   ch == '?' ||
1227
87114
                   ch == '#' ||
1228
                   special_back_slash) {
1229
3422
          p--;
1230

3422
          if (special && buffer.size() == 0) {
1231
21
            url->flags |= URL_FLAGS_FAILED;
1232
21
            return;
1233
          }
1234
331
          if (has_state_override &&
1235

3770
              buffer.size() == 0 &&
1236
80
              ((url->username.size() > 0 || url->password.size() > 0) ||
1237
38
               url->port != -1)) {
1238
8
            url->flags |= URL_FLAGS_TERMINATED;
1239
8
            return;
1240
          }
1241
3393
          url->flags |= URL_FLAGS_HAS_HOST;
1242
3393
          if (!ParseHost(buffer, &url->host, special)) {
1243
432
            url->flags |= URL_FLAGS_FAILED;
1244
432
            return;
1245
          }
1246
2961
          buffer.clear();
1247
2961
          state = kPathStart;
1248
2961
          if (has_state_override) {
1249
227
            return;
1250
          }
1251
        } else {
1252
87090
          if (ch == '[')
1253
349
            square_bracket_flag = true;
1254
87090
          if (ch == ']')
1255
345
            square_bracket_flag = false;
1256
87090
          buffer += ch;
1257
        }
1258
91627
        break;
1259
9833
      case kPort:
1260
9833
        if (IsASCIIDigit(ch)) {
1261
7961
          buffer += ch;
1262

1872
        } else if (has_state_override ||
1263
1124
                   ch == kEOL ||
1264
36
                   ch == '/' ||
1265
36
                   ch == '?' ||
1266
36
                   ch == '#' ||
1267
                   special_back_slash) {
1268
1836
          if (buffer.size() > 0) {
1269
1822
            unsigned port = 0;
1270
            // the condition port <= 0xffff prevents integer overflow
1271

9567
            for (size_t i = 0; port <= 0xffff && i < buffer.size(); i++)
1272
7745
              port = port * 10 + buffer[i] - '0';
1273
1822
            if (port > 0xffff) {
1274
              // TODO(TimothyGu): This hack is currently needed for the host
1275
              // setter since it needs access to hostname if it is valid, and
1276
              // if the FAILED flag is set the entire response to JS layer
1277
              // will be empty.
1278
26
              if (state_override == kHost)
1279
2
                url->port = -1;
1280
              else
1281
24
                url->flags |= URL_FLAGS_FAILED;
1282
26
              return;
1283
            }
1284
            // the port is valid
1285
1796
            url->port = NormalizePort(url->scheme, static_cast<int>(port));
1286
1796
            if (url->port == -1)
1287
257
              url->flags |= URL_FLAGS_IS_DEFAULT_SCHEME_PORT;
1288
1796
            buffer.clear();
1289
14
          } else if (has_state_override) {
1290
            // TODO(TimothyGu): Similar case as above.
1291
6
            if (state_override == kHost)
1292
2
              url->port = -1;
1293
            else
1294
4
              url->flags |= URL_FLAGS_TERMINATED;
1295
6
            return;
1296
          }
1297
1804
          state = kPathStart;
1298
1804
          continue;
1299
        } else {
1300
36
          url->flags |= URL_FLAGS_FAILED;
1301
36
          return;
1302
        }
1303
7961
        break;
1304
132574
      case kFile:
1305
132574
        url->scheme = "file:";
1306
132574
        url->host.clear();
1307
132574
        url->flags |= URL_FLAGS_HAS_HOST;
1308

132574
        if (ch == '/' || ch == '\\') {
1309
125855
          state = kFileSlash;
1310

6719
        } else if (has_base && base->scheme == "file:") {
1311

6700
          switch (ch) {
1312
4
            case kEOL:
1313
4
              if (base->flags & URL_FLAGS_HAS_HOST) {
1314
4
                url->host = base->host;
1315
              }
1316
4
              if (base->flags & URL_FLAGS_HAS_PATH) {
1317
4
                url->flags |= URL_FLAGS_HAS_PATH;
1318
4
                url->path = base->path;
1319
              }
1320
4
              if (base->flags & URL_FLAGS_HAS_QUERY) {
1321
4
                url->flags |= URL_FLAGS_HAS_QUERY;
1322
4
                url->query = base->query;
1323
              }
1324
4
              break;
1325
4
            case '?':
1326
4
              if (base->flags & URL_FLAGS_HAS_HOST) {
1327
4
                url->host = base->host;
1328
              }
1329
4
              if (base->flags & URL_FLAGS_HAS_PATH) {
1330
4
                url->flags |= URL_FLAGS_HAS_PATH;
1331
4
                url->path = base->path;
1332
              }
1333
4
              url->flags |= URL_FLAGS_HAS_QUERY;
1334
4
              url->query.clear();
1335
4
              state = kQuery;
1336
4
              break;
1337
4
            case '#':
1338
4
              if (base->flags & URL_FLAGS_HAS_HOST) {
1339
4
                url->host = base->host;
1340
              }
1341
4
              if (base->flags & URL_FLAGS_HAS_PATH) {
1342
4
                url->flags |= URL_FLAGS_HAS_PATH;
1343
4
                url->path = base->path;
1344
              }
1345
4
              if (base->flags & URL_FLAGS_HAS_QUERY) {
1346
4
                url->flags |= URL_FLAGS_HAS_QUERY;
1347
4
                url->query = base->query;
1348
              }
1349
4
              url->flags |= URL_FLAGS_HAS_FRAGMENT;
1350
4
              url->fragment.clear();
1351
4
              state = kFragment;
1352
4
              break;
1353
6688
            default:
1354
6688
              url->query.clear();
1355
6688
              if (base->flags & URL_FLAGS_HAS_HOST) {
1356
6688
                url->host = base->host;
1357
              }
1358
6688
              if (base->flags & URL_FLAGS_HAS_PATH) {
1359
6688
                url->flags |= URL_FLAGS_HAS_PATH;
1360
6688
                url->path = base->path;
1361
              }
1362
6688
              if (!StartsWithWindowsDriveLetter(p, end)) {
1363
6664
                ShortenUrlPath(url);
1364
              } else {
1365
24
                url->path.clear();
1366
              }
1367
6688
              state = kPath;
1368
6688
              continue;
1369
          }
1370
        } else {
1371
19
          state = kPath;
1372
19
          continue;
1373
        }
1374
125867
        break;
1375
125855
      case kFileSlash:
1376

125855
        if (ch == '/' || ch == '\\') {
1377
125713
          state = kFileHost;
1378
        } else {
1379

142
          if (has_base && base->scheme == "file:") {
1380
128
            url->flags |= URL_FLAGS_HAS_HOST;
1381
128
            url->host = base->host;
1382

242
            if (!StartsWithWindowsDriveLetter(p, end) &&
1383
114
                IsNormalizedWindowsDriveLetter(base->path[0])) {
1384
4
              url->flags |= URL_FLAGS_HAS_PATH;
1385
4
              url->path.push_back(base->path[0]);
1386
            }
1387
          }
1388
142
          state = kPath;
1389
142
          continue;
1390
        }
1391
125713
        break;
1392
126830
      case kFileHost:
1393

126830
        if (ch == kEOL ||
1394
1115
            ch == '/' ||
1395
1105
            ch == '\\' ||
1396
1105
            ch == '?' ||
1397
            ch == '#') {
1398
125713
          if (!has_state_override &&
1399

251438
              buffer.size() == 2 &&
1400
22
              IsWindowsDriveLetter(buffer)) {
1401
12
            state = kPath;
1402
125713
          } else if (buffer.size() == 0) {
1403
125520
            url->flags |= URL_FLAGS_HAS_HOST;
1404
125520
            url->host.clear();
1405
125520
            if (has_state_override)
1406
4
              return;
1407
125516
            state = kPathStart;
1408
          } else {
1409
193
            std::string host;
1410
193
            if (!ParseHost(buffer, &host, special)) {
1411
52
              url->flags |= URL_FLAGS_FAILED;
1412
52
              return;
1413
            }
1414
141
            if (host == "localhost")
1415
37
              host.clear();
1416
141
            url->flags |= URL_FLAGS_HAS_HOST;
1417
141
            url->host = host;
1418
141
            if (has_state_override)
1419
4
              return;
1420
137
            buffer.clear();
1421
137
            state = kPathStart;
1422
          }
1423
125665
          continue;
1424
        } else {
1425
1105
          buffer += ch;
1426
        }
1427
1105
        break;
1428
209040
      case kPathStart:
1429
209040
        if (IsSpecial(url->scheme)) {
1430
208492
          state = kPath;
1431

208492
          if (ch != '/' && ch != '\\') {
1432
79934
            continue;
1433
          }
1434

548
        } else if (!has_state_override && ch == '?') {
1435
6
          url->flags |= URL_FLAGS_HAS_QUERY;
1436
6
          url->query.clear();
1437
6
          state = kQuery;
1438

542
        } else if (!has_state_override && ch == '#') {
1439
6
          url->flags |= URL_FLAGS_HAS_FRAGMENT;
1440
6
          url->fragment.clear();
1441
6
          state = kFragment;
1442
536
        } else if (ch != kEOL) {
1443
459
          state = kPath;
1444
459
          if (ch != '/') {
1445
35
            continue;
1446
          }
1447

77
        } else if (has_state_override && !(url->flags & URL_FLAGS_HAS_HOST)) {
1448
2
          url->flags |= URL_FLAGS_HAS_PATH;
1449
2
          url->path.emplace_back("");
1450
        }
1451
129071
        break;
1452
13268714
      case kPath:
1453

13268714
        if (ch == kEOL ||
1454
11992065
            ch == '/' ||
1455
11991995
            special_back_slash ||
1456

11991995
            (!has_state_override && (ch == '?' || ch == '#'))) {
1457
1277723
          if (IsDoubleDotSegment(buffer)) {
1458
4577
            ShortenUrlPath(url);
1459

4577
            if (ch != '/' && !special_back_slash) {
1460
280
              url->flags |= URL_FLAGS_HAS_PATH;
1461
280
              url->path.emplace_back("");
1462
            }
1463
1276287
          } else if (IsSingleDotSegment(buffer) &&
1464

1276287
                     ch != '/' && !special_back_slash) {
1465
717
            url->flags |= URL_FLAGS_HAS_PATH;
1466
717
            url->path.emplace_back("");
1467
1272429
          } else if (!IsSingleDotSegment(buffer)) {
1468
2533008
            if (url->scheme == "file:" &&
1469
1432460
                url->path.empty() &&
1470

2702465
                buffer.size() == 2 &&
1471
100
                IsWindowsDriveLetter(buffer)) {
1472
98
              buffer[1] = ':';
1473
            }
1474
1270005
            url->flags |= URL_FLAGS_HAS_PATH;
1475
1270005
            url->path.emplace_back(std::move(buffer));
1476
          }
1477
1277723
          buffer.clear();
1478
2555446
          if (ch == '?') {
1479
948
            url->flags |= URL_FLAGS_HAS_QUERY;
1480
948
            url->query.clear();
1481
948
            state = kQuery;
1482
1276775
          } else if (ch == '#') {
1483
56
            url->flags |= URL_FLAGS_HAS_FRAGMENT;
1484
56
            url->fragment.clear();
1485
56
            state = kFragment;
1486
          }
1487
        } else {
1488
11990991
          AppendOrEscape(&buffer, ch, PATH_ENCODE_SET);
1489
        }
1490
13268714
        break;
1491
50114
      case kCannotBeBase:
1492
50114
        switch (ch) {
1493
6
          case '?':
1494
6
            state = kQuery;
1495
6
            break;
1496
10
          case '#':
1497
10
            state = kFragment;
1498
10
            break;
1499
50098
          default:
1500
50098
            if (url->path.empty())
1501
              url->path.emplace_back("");
1502
50098
            else if (ch != kEOL)
1503
45052
              AppendOrEscape(&url->path[0], ch, C0_CONTROL_ENCODE_SET);
1504
        }
1505
50114
        break;
1506
59629
      case kQuery:
1507

59629
        if (ch == kEOL || (!has_state_override && ch == '#')) {
1508
1161
          url->flags |= URL_FLAGS_HAS_QUERY;
1509
1161
          url->query = std::move(buffer);
1510
1161
          buffer.clear();
1511
1551
          if (ch == '#')
1512
390
            state = kFragment;
1513
        } else {
1514
58468
          AppendOrEscape(&buffer, ch, special ? QUERY_ENCODE_SET_SPECIAL :
1515
                                                QUERY_ENCODE_SET_NONSPECIAL);
1516
        }
1517
59629
        break;
1518
4265
      case kFragment:
1519
4265
        switch (ch) {
1520
606
          case kEOL:
1521
606
            url->flags |= URL_FLAGS_HAS_FRAGMENT;
1522
606
            url->fragment = std::move(buffer);
1523
606
            break;
1524
3659
          default:
1525
3659
            AppendOrEscape(&buffer, ch, FRAGMENT_ENCODE_SET);
1526
        }
1527
4265
        break;
1528
      default:
1529
        url->flags |= URL_FLAGS_INVALID_PARSE_STATE;
1530
        return;
1531
    }
1532
1533
14677938
    p++;
1534
  }
1535
}  // NOLINT(readability/fn_size)
1536
1537
// https://url.spec.whatwg.org/#url-serializing
1538
35626
std::string URL::SerializeURL(const url_data& url,
1539
                              bool exclude = false) {
1540
35626
  std::string output;
1541
35626
  output.reserve(
1542
    10 +  // We generally insert < 10 separator characters between URL parts
1543
35626
    url.scheme.size() +
1544
35626
    url.username.size() +
1545
35626
    url.password.size() +
1546
35626
    url.host.size() +
1547
35626
    url.query.size() +
1548
35626
    url.fragment.size() +
1549
35626
    url.href.size() +
1550
35626
    std::accumulate(
1551
        url.path.begin(),
1552
        url.path.end(),
1553
        0,
1554
376300
        [](size_t sum, const auto& str) { return sum + str.size(); }));
1555
1556
35626
  output += url.scheme;
1557
35626
  if (url.flags & URL_FLAGS_HAS_HOST) {
1558
35626
    output += "//";
1559
35626
    if (url.flags & URL_FLAGS_HAS_USERNAME ||
1560
35626
        url.flags & URL_FLAGS_HAS_PASSWORD) {
1561
      if (url.flags & URL_FLAGS_HAS_USERNAME) {
1562
        output += url.username;
1563
      }
1564
      if (url.flags & URL_FLAGS_HAS_PASSWORD) {
1565
        output += ":" + url.password;
1566
      }
1567
      output += "@";
1568
    }
1569
35626
    output += url.host;
1570
35626
    if (url.port != -1) {
1571
      output += ":" + std::to_string(url.port);
1572
    }
1573
  }
1574
35626
  if (url.flags & URL_FLAGS_CANNOT_BE_BASE) {
1575
    output += url.path[0];
1576
  } else {
1577
    if (!(url.flags & URL_FLAGS_HAS_HOST) &&
1578

35626
          url.path.size() > 1 &&
1579
          url.path[0].empty()) {
1580
      output += "/.";
1581
    }
1582
376300
    for (size_t i = 1; i < url.path.size(); i++) {
1583
340674
      output += "/" + url.path[i];
1584
    }
1585
  }
1586
35626
  if (url.flags & URL_FLAGS_HAS_QUERY) {
1587
    output += "?" + url.query;
1588
  }
1589

35626
  if (!exclude && (url.flags & URL_FLAGS_HAS_FRAGMENT)) {
1590
    output += "#" + url.fragment;
1591
  }
1592
35626
  output.shrink_to_fit();
1593
35626
  return output;
1594
}
1595
1596
namespace {
1597
150941
void SetArgs(Environment* env,
1598
             Local<Value> argv[ARG_COUNT],
1599
             const struct url_data& url) {
1600
150941
  Isolate* isolate = env->isolate();
1601
150941
  argv[ARG_FLAGS] = Integer::NewFromUnsigned(isolate, url.flags);
1602
301882
  argv[ARG_PROTOCOL] =
1603
150941
      url.flags & URL_FLAGS_SPECIAL ?
1604
144922
          GetSpecial(env, url.scheme) :
1605
6019
          OneByteString(isolate, url.scheme.c_str());
1606
150941
  if (url.flags & URL_FLAGS_HAS_USERNAME)
1607
1224
    argv[ARG_USERNAME] = Utf8String(isolate, url.username);
1608
150941
  if (url.flags & URL_FLAGS_HAS_PASSWORD)
1609
1184
    argv[ARG_PASSWORD] = Utf8String(isolate, url.password);
1610
150941
  if (url.flags & URL_FLAGS_HAS_HOST)
1611
291236
    argv[ARG_HOST] = Utf8String(isolate, url.host);
1612
150941
  if (url.flags & URL_FLAGS_HAS_QUERY)
1613
2338
    argv[ARG_QUERY] = Utf8String(isolate, url.query);
1614
150941
  if (url.flags & URL_FLAGS_HAS_FRAGMENT)
1615
1204
    argv[ARG_FRAGMENT] = Utf8String(isolate, url.fragment);
1616
150941
  if (url.port > -1)
1617
3452
    argv[ARG_PORT] = Integer::New(isolate, url.port);
1618
150941
  if (url.flags & URL_FLAGS_HAS_PATH)
1619
300690
    argv[ARG_PATH] = ToV8Value(env->context(), url.path).ToLocalChecked();
1620
150941
}
1621
1622
159615
void Parse(Environment* env,
1623
           Local<Value> recv,
1624
           const char* input,
1625
           size_t len,
1626
           enum url_parse_state state_override,
1627
           Local<Value> base_obj,
1628
           Local<Value> context_obj,
1629
           Local<Function> cb,
1630
           Local<Value> error_cb) {
1631
159615
  Isolate* isolate = env->isolate();
1632
159615
  Local<Context> context = env->context();
1633
159615
  HandleScope handle_scope(isolate);
1634
159615
  Context::Scope context_scope(context);
1635
1636
159615
  const bool has_context = context_obj->IsObject();
1637
159615
  const bool has_base = base_obj->IsObject();
1638
1639
159615
  url_data base;
1640
159615
  url_data url;
1641
159615
  if (has_context)
1642
44000
    url = HarvestContext(env, context_obj.As<Object>());
1643
159615
  if (has_base)
1644
9079
    base = HarvestBase(env, base_obj.As<Object>());
1645
1646
159615
  URL::Parse(input, len, state_override, &url, has_context, &base, has_base);
1647

159615
  if ((url.flags & URL_FLAGS_INVALID_PARSE_STATE) ||
1648
44000
      ((state_override != kUnknownState) &&
1649
44000
       (url.flags & URL_FLAGS_TERMINATED)))
1650
44
    return;
1651
1652
  // Define the return value placeholders
1653
159571
  const Local<Value> undef = Undefined(isolate);
1654
159571
  const Local<Value> null = Null(isolate);
1655
159571
  if (!(url.flags & URL_FLAGS_FAILED)) {
1656
    Local<Value> argv[] = {
1657
      undef,
1658
      undef,
1659
      undef,
1660
      undef,
1661
      null,  // host defaults to null
1662
      null,  // port defaults to null
1663
      undef,
1664
      null,  // query defaults to null
1665
      null,  // fragment defaults to null
1666
150941
    };
1667
150941
    SetArgs(env, argv, url);
1668
150941
    USE(cb->Call(context, recv, arraysize(argv), argv));
1669
8630
  } else if (error_cb->IsFunction()) {
1670
17000
    Local<Value> flags = Integer::NewFromUnsigned(isolate, url.flags);
1671
8500
    USE(error_cb.As<Function>()->Call(context, recv, 1, &flags));
1672
  }
1673
}
1674
1675
159615
void Parse(const FunctionCallbackInfo<Value>& args) {
1676
159615
  Environment* env = Environment::GetCurrent(args);
1677
159615
  CHECK_GE(args.Length(), 5);
1678
319230
  CHECK(args[0]->IsString());  // input
1679


434467
  CHECK(args[2]->IsUndefined() ||  // base context
1680
        args[2]->IsNull() ||
1681
        args[2]->IsObject());
1682


451230
  CHECK(args[3]->IsUndefined() ||  // context
1683
        args[3]->IsNull() ||
1684
        args[3]->IsObject());
1685
159615
  CHECK(args[4]->IsFunction());  // complete callback
1686

434845
  CHECK(args[5]->IsUndefined() || args[5]->IsFunction());  // error callback
1687
1688
159615
  Utf8Value input(env->isolate(), args[0]);
1689
159615
  enum url_parse_state state_override = kUnknownState;
1690
159615
  if (args[1]->IsNumber()) {
1691
159615
    state_override = static_cast<enum url_parse_state>(
1692
319230
        args[1]->Uint32Value(env->context()).FromJust());
1693
  }
1694
1695
319230
  Parse(env, args.This(),
1696
159615
        *input, input.length(),
1697
        state_override,
1698
        args[2],
1699
        args[3],
1700
319230
        args[4].As<Function>(),
1701
        args[5]);
1702
159615
}
1703
1704
92
void EncodeAuthSet(const FunctionCallbackInfo<Value>& args) {
1705
92
  Environment* env = Environment::GetCurrent(args);
1706
92
  CHECK_GE(args.Length(), 1);
1707
184
  CHECK(args[0]->IsString());
1708
184
  Utf8Value value(env->isolate(), args[0]);
1709
92
  std::string output;
1710
92
  size_t len = value.length();
1711
92
  output.reserve(len);
1712
756
  for (size_t n = 0; n < len; n++) {
1713
664
    const char ch = (*value)[n];
1714
664
    AppendOrEscape(&output, ch, USERINFO_ENCODE_SET);
1715
  }
1716
276
  args.GetReturnValue().Set(
1717
184
      String::NewFromUtf8(env->isolate(), output.c_str()).ToLocalChecked());
1718
92
}
1719
1720
229
void DomainToASCII(const FunctionCallbackInfo<Value>& args) {
1721
229
  Environment* env = Environment::GetCurrent(args);
1722
229
  CHECK_GE(args.Length(), 1);
1723
458
  CHECK(args[0]->IsString());
1724
229
  Utf8Value value(env->isolate(), args[0]);
1725
1726
229
  URLHost host;
1727
  // Assuming the host is used for a special scheme.
1728
229
  host.ParseHost(*value, value.length(), true);
1729
229
  if (host.ParsingFailed()) {
1730
12
    args.GetReturnValue().Set(FIXED_ONE_BYTE_STRING(env->isolate(), ""));
1731
12
    return;
1732
  }
1733
217
  std::string out = host.ToStringMove();
1734
651
  args.GetReturnValue().Set(
1735
434
      String::NewFromUtf8(env->isolate(), out.c_str()).ToLocalChecked());
1736
}
1737
1738
207
void DomainToUnicode(const FunctionCallbackInfo<Value>& args) {
1739
207
  Environment* env = Environment::GetCurrent(args);
1740
207
  CHECK_GE(args.Length(), 1);
1741
414
  CHECK(args[0]->IsString());
1742
207
  Utf8Value value(env->isolate(), args[0]);
1743
1744
207
  URLHost host;
1745
  // Assuming the host is used for a special scheme.
1746
207
  host.ParseHost(*value, value.length(), true, true);
1747
207
  if (host.ParsingFailed()) {
1748
12
    args.GetReturnValue().Set(FIXED_ONE_BYTE_STRING(env->isolate(), ""));
1749
12
    return;
1750
  }
1751
195
  std::string out = host.ToStringMove();
1752
585
  args.GetReturnValue().Set(
1753
390
      String::NewFromUtf8(env->isolate(), out.c_str()).ToLocalChecked());
1754
}
1755
1756
848
void SetURLConstructor(const FunctionCallbackInfo<Value>& args) {
1757
848
  Environment* env = Environment::GetCurrent(args);
1758
848
  CHECK_EQ(args.Length(), 1);
1759
848
  CHECK(args[0]->IsFunction());
1760
1696
  env->set_url_constructor_function(args[0].As<Function>());
1761
848
}
1762
1763
848
void Initialize(Local<Object> target,
1764
                Local<Value> unused,
1765
                Local<Context> context,
1766
                void* priv) {
1767
848
  Environment* env = Environment::GetCurrent(context);
1768
848
  env->SetMethod(target, "parse", Parse);
1769
848
  env->SetMethodNoSideEffect(target, "encodeAuth", EncodeAuthSet);
1770
848
  env->SetMethodNoSideEffect(target, "domainToASCII", DomainToASCII);
1771
848
  env->SetMethodNoSideEffect(target, "domainToUnicode", DomainToUnicode);
1772
848
  env->SetMethod(target, "setURLConstructor", SetURLConstructor);
1773
1774
#define XX(name, _) NODE_DEFINE_CONSTANT(target, name);
1775
22896
  FLAGS(XX)
1776
#undef XX
1777
1778
#define XX(name) NODE_DEFINE_CONSTANT(target, name);
1779
35616
  PARSESTATES(XX)
1780
#undef XX
1781
848
}
1782
}  // namespace
1783
1784
5184
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
1785
5184
  registry->Register(Parse);
1786
5184
  registry->Register(EncodeAuthSet);
1787
5184
  registry->Register(DomainToASCII);
1788
5184
  registry->Register(DomainToUnicode);
1789
5184
  registry->Register(SetURLConstructor);
1790
5184
}
1791
1792
8
std::string URL::ToFilePath() const {
1793
8
  if (context_.scheme != "file:") {
1794
1
    return "";
1795
  }
1796
1797
#ifdef _WIN32
1798
  const char* slash = "\\";
1799
  auto is_slash = [] (char ch) {
1800
    return ch == '/' || ch == '\\';
1801
  };
1802
#else
1803
7
  const char* slash = "/";
1804
46
  auto is_slash = [] (char ch) {
1805
46
    return ch == '/';
1806
  };
1807

14
  if ((context_.flags & URL_FLAGS_HAS_HOST) &&
1808
7
      context_.host.length() > 0) {
1809
1
    return "";
1810
  }
1811
#endif
1812
12
  std::string decoded_path;
1813
18
  for (const std::string& part : context_.path) {
1814
13
    std::string decoded = PercentDecode(part.c_str(), part.length());
1815
58
    for (char& ch : decoded) {
1816
46
      if (is_slash(ch)) {
1817
1
        return "";
1818
      }
1819
    }
1820
12
    decoded_path += slash + decoded;
1821
  }
1822
1823
#ifdef _WIN32
1824
  // TODO(TimothyGu): Use "\\?\" long paths on Windows.
1825
1826
  // If hostname is set, then we have a UNC path. Pass the hostname through
1827
  // ToUnicode just in case it is an IDN using punycode encoding. We do not
1828
  // need to worry about percent encoding because the URL parser will have
1829
  // already taken care of that for us. Note that this only causes IDNs with an
1830
  // appropriate `xn--` prefix to be decoded.
1831
  if ((context_.flags & URL_FLAGS_HAS_HOST) &&
1832
      context_.host.length() > 0) {
1833
    std::string unicode_host;
1834
    if (!ToUnicode(context_.host, &unicode_host)) {
1835
      return "";
1836
    }
1837
    return "\\\\" + unicode_host + decoded_path;
1838
  }
1839
  // Otherwise, it's a local path that requires a drive letter.
1840
  if (decoded_path.length() < 3) {
1841
    return "";
1842
  }
1843
  if (decoded_path[2] != ':' ||
1844
      !IsASCIIAlpha(decoded_path[1])) {
1845
    return "";
1846
  }
1847
  // Strip out the leading '\'.
1848
  return decoded_path.substr(1);
1849
#else
1850
5
  return decoded_path;
1851
#endif
1852
}
1853
1854
35626
URL URL::FromFilePath(const std::string& file_path) {
1855
71252
  URL url("file://");
1856
71252
  std::string escaped_file_path;
1857
3781583
  for (size_t i = 0; i < file_path.length(); ++i) {
1858
3745957
    escaped_file_path += file_path[i];
1859
3745957
    if (file_path[i] == '%')
1860
12
      escaped_file_path += "25";
1861
  }
1862
35626
  URL::Parse(escaped_file_path.c_str(), escaped_file_path.length(), kPathStart,
1863
             &url.context_, true, nullptr, false);
1864
35626
  return url;
1865
}
1866
1867
// This function works by calling out to a JS function that creates and
1868
// returns the JS URL object. Be mindful of the JS<->Native boundary
1869
// crossing that is required.
1870
MaybeLocal<Value> URL::ToObject(Environment* env) const {
1871
  Isolate* isolate = env->isolate();
1872
  Local<Context> context = env->context();
1873
  Context::Scope context_scope(context);
1874
1875
  const Local<Value> undef = Undefined(isolate);
1876
  const Local<Value> null = Null(isolate);
1877
1878
  if (context_.flags & URL_FLAGS_FAILED)
1879
    return Local<Value>();
1880
1881
  Local<Value> argv[] = {
1882
    undef,
1883
    undef,
1884
    undef,
1885
    undef,
1886
    null,  // host defaults to null
1887
    null,  // port defaults to null
1888
    undef,
1889
    null,  // query defaults to null
1890
    null,  // fragment defaults to null
1891
  };
1892
  SetArgs(env, argv, context_);
1893
1894
  MaybeLocal<Value> ret;
1895
  {
1896
    TryCatchScope try_catch(env, TryCatchScope::CatchMode::kFatal);
1897
1898
    // The SetURLConstructor method must have been called already to
1899
    // set the constructor function used below. SetURLConstructor is
1900
    // called automatically when the internal/url.js module is loaded
1901
    // during the internal/bootstrap/node.js processing.
1902
    ret = env->url_constructor_function()
1903
        ->Call(env->context(), undef, arraysize(argv), argv);
1904
  }
1905
1906
  return ret;
1907
}
1908
1909
}  // namespace url
1910
}  // namespace node
1911
1912
5252
NODE_MODULE_CONTEXT_AWARE_INTERNAL(url, node::url::Initialize)
1913
5184
NODE_MODULE_EXTERNAL_REFERENCE(url, node::url::RegisterExternalReferences)