GCC Code Coverage Report | |||||||||||||||||||||
|
|||||||||||||||||||||
Line | Branch | Exec | Source |
1 |
#include "node_url.h" |
||
2 |
#include "node.h" |
||
3 |
#include "node_internals.h" |
||
4 |
#include "env.h" |
||
5 |
#include "env-inl.h" |
||
6 |
#include "util.h" |
||
7 |
#include "util-inl.h" |
||
8 |
#include "v8.h" |
||
9 |
#include "base-object.h" |
||
10 |
#include "base-object-inl.h" |
||
11 |
#include "node_i18n.h" |
||
12 |
|||
13 |
#include <string> |
||
14 |
#include <vector> |
||
15 |
#include <stdio.h> |
||
16 |
#include <cmath> |
||
17 |
|||
18 |
#if defined(NODE_HAVE_I18N_SUPPORT) |
||
19 |
#include <unicode/utf8.h> |
||
20 |
#include <unicode/utf.h> |
||
21 |
#endif |
||
22 |
|||
23 |
namespace node { |
||
24 |
|||
25 |
using v8::Array; |
||
26 |
using v8::Context; |
||
27 |
using v8::Function; |
||
28 |
using v8::FunctionCallbackInfo; |
||
29 |
using v8::HandleScope; |
||
30 |
using v8::Integer; |
||
31 |
using v8::Isolate; |
||
32 |
using v8::Local; |
||
33 |
using v8::Null; |
||
34 |
using v8::Object; |
||
35 |
using v8::String; |
||
36 |
using v8::Undefined; |
||
37 |
using v8::Value; |
||
38 |
|||
39 |
#define GET(env, obj, name) \ |
||
40 |
obj->Get(env->context(), \ |
||
41 |
OneByteString(env->isolate(), name)).ToLocalChecked() |
||
42 |
|||
43 |
#define GET_AND_SET(env, obj, name, data, flag) \ |
||
44 |
{ \ |
||
45 |
Local<Value> val = GET(env, obj, #name); \ |
||
46 |
if (val->IsString()) { \ |
||
47 |
Utf8Value value(env->isolate(), val.As<String>()); \ |
||
48 |
data->name = *value; \ |
||
49 |
data->flags |= flag; \ |
||
50 |
} \ |
||
51 |
} |
||
52 |
|||
53 |
#define CANNOT_BE_BASE() url.flags |= URL_FLAGS_CANNOT_BE_BASE; |
||
54 |
#define INVALID_PARSE_STATE() url.flags |= URL_FLAGS_INVALID_PARSE_STATE; |
||
55 |
#define SPECIAL() \ |
||
56 |
{ \ |
||
57 |
url.flags |= URL_FLAGS_SPECIAL; \ |
||
58 |
special = true; \ |
||
59 |
} |
||
60 |
#define TERMINATE() \ |
||
61 |
{ \ |
||
62 |
url.flags |= URL_FLAGS_TERMINATED; \ |
||
63 |
goto done; \ |
||
64 |
} |
||
65 |
#define FAILED() \ |
||
66 |
{ \ |
||
67 |
url.flags |= URL_FLAGS_FAILED; \ |
||
68 |
goto done; \ |
||
69 |
} |
||
70 |
|||
71 |
#define CHECK_FLAG(flags, name) (flags & URL_FLAGS_##name) /* NOLINT */ |
||
72 |
|||
73 |
#define IS_CANNOT_BE_BASE(flags) CHECK_FLAG(flags, CANNOT_BE_BASE) |
||
74 |
#define IS_FAILED(flags) CHECK_FLAG(flags, FAILED) |
||
75 |
|||
76 |
#define DOES_HAVE_SCHEME(url) CHECK_FLAG(url.flags, HAS_SCHEME) |
||
77 |
#define DOES_HAVE_USERNAME(url) CHECK_FLAG(url.flags, HAS_USERNAME) |
||
78 |
#define DOES_HAVE_PASSWORD(url) CHECK_FLAG(url.flags, HAS_PASSWORD) |
||
79 |
#define DOES_HAVE_HOST(url) CHECK_FLAG(url.flags, HAS_HOST) |
||
80 |
#define DOES_HAVE_PATH(url) CHECK_FLAG(url.flags, HAS_PATH) |
||
81 |
#define DOES_HAVE_QUERY(url) CHECK_FLAG(url.flags, HAS_QUERY) |
||
82 |
#define DOES_HAVE_FRAGMENT(url) CHECK_FLAG(url.flags, HAS_FRAGMENT) |
||
83 |
|||
84 |
#define SET_HAVE_SCHEME() url.flags |= URL_FLAGS_HAS_SCHEME; |
||
85 |
#define SET_HAVE_USERNAME() url.flags |= URL_FLAGS_HAS_USERNAME; |
||
86 |
#define SET_HAVE_PASSWORD() url.flags |= URL_FLAGS_HAS_PASSWORD; |
||
87 |
#define SET_HAVE_HOST() url.flags |= URL_FLAGS_HAS_HOST; |
||
88 |
#define SET_HAVE_PATH() url.flags |= URL_FLAGS_HAS_PATH; |
||
89 |
#define SET_HAVE_QUERY() url.flags |= URL_FLAGS_HAS_QUERY; |
||
90 |
#define SET_HAVE_FRAGMENT() url.flags |= URL_FLAGS_HAS_FRAGMENT; |
||
91 |
|||
92 |
#define UTF8STRING(isolate, str) \ |
||
93 |
String::NewFromUtf8(isolate, str.c_str(), v8::NewStringType::kNormal) \ |
||
94 |
.ToLocalChecked() |
||
95 |
|||
96 |
namespace url { |
||
97 |
|||
98 |
#if defined(NODE_HAVE_I18N_SUPPORT) |
||
99 |
static int ToUnicode(std::string* input, std::string* output) { |
||
100 |
MaybeStackBuffer<char> buf; |
||
101 |
if (i18n::ToUnicode(&buf, input->c_str(), input->length()) < 0) |
||
102 |
return -1; |
||
103 |
output->assign(*buf, buf.length()); |
||
104 |
return 0; |
||
105 |
} |
||
106 |
|||
107 |
443 |
static int ToASCII(std::string* input, std::string* output) { |
|
108 |
886 |
MaybeStackBuffer<char> buf; |
|
109 |
✓✗ | 886 |
if (i18n::ToASCII(&buf, input->c_str(), input->length()) < 0) |
110 |
return -1; |
||
111 |
886 |
output->assign(*buf, buf.length()); |
|
112 |
443 |
return 0; |
|
113 |
} |
||
114 |
|||
115 |
// Unfortunately there's not really a better way to do this. |
||
116 |
// Iterate through each encoded codepoint and verify that |
||
117 |
// it is a valid unicode codepoint. |
||
118 |
445 |
static int IsValidUTF8(std::string* input) { |
|
119 |
445 |
const char* p = input->c_str(); |
|
120 |
445 |
int32_t len = input->length(); |
|
121 |
✓✓ | 445 |
for (int32_t i = 0; i < len;) { |
122 |
UChar32 c; |
||
123 |
✓✓✓✓ ✓✗ |
4459 |
U8_NEXT_UNSAFE(p, i, c); |
124 |
✓✓✓✗ ✗✓✓✓ ✓✗✗✓ |
4459 |
if (!U_IS_UNICODE_CHAR(c)) |
125 |
return -1; |
||
126 |
} |
||
127 |
return 0; |
||
128 |
} |
||
129 |
#else |
||
130 |
// Intentional non-ops if ICU is not present. |
||
131 |
static int ToUnicode(std::string* input, std::string* output) { |
||
132 |
output->reserve(input->length()); |
||
133 |
*output = input->c_str(); |
||
134 |
} |
||
135 |
|||
136 |
static int ToASCII(std::string* input, std::string* output) { |
||
137 |
output->reserve(input->length()); |
||
138 |
*output = input->c_str(); |
||
139 |
} |
||
140 |
|||
141 |
static int IsValidUTF8(std::string* input) { |
||
142 |
return 0; |
||
143 |
} |
||
144 |
#endif |
||
145 |
|||
146 |
9 |
static url_host_type ParseIPv6Host(url_host* host, |
|
147 |
const char* input, |
||
148 |
size_t length) { |
||
149 |
9 |
url_host_type type = HOST_TYPE_FAILED; |
|
150 |
✓✓ | 81 |
for (unsigned n = 0; n < 8; n++) |
151 |
72 |
host->value.ipv6[n] = 0; |
|
152 |
9 |
uint16_t* piece_pointer = &host->value.ipv6[0]; |
|
153 |
9 |
uint16_t* last_piece = piece_pointer + 8; |
|
154 |
9 |
uint16_t* compress_pointer = nullptr; |
|
155 |
9 |
const char* pointer = input; |
|
156 |
9 |
const char* end = pointer + length; |
|
157 |
unsigned value, len, swaps, dots; |
||
158 |
✓✓ | 9 |
char ch = pointer < end ? pointer[0] : kEOL; |
159 |
✓✓ | 9 |
if (ch == ':') { |
160 |
✓✓✓✗ |
3 |
if (length < 2 || pointer[1] != ':') |
161 |
goto end; |
||
162 |
2 |
pointer += 2; |
|
163 |
✓✗ | 2 |
ch = pointer < end ? pointer[0] : kEOL; |
164 |
2 |
piece_pointer++; |
|
165 |
2 |
compress_pointer = piece_pointer; |
|
166 |
} |
||
167 |
✓✓ | 21 |
while (ch != kEOL) { |
168 |
✓✗ | 15 |
if (piece_pointer > last_piece) |
169 |
goto end; |
||
170 |
✓✓ | 15 |
if (ch == ':') { |
171 |
✓✗ | 3 |
if (compress_pointer != nullptr) |
172 |
goto end; |
||
173 |
3 |
pointer++; |
|
174 |
✓✗ | 3 |
ch = pointer < end ? pointer[0] : kEOL; |
175 |
3 |
piece_pointer++; |
|
176 |
3 |
compress_pointer = piece_pointer; |
|
177 |
3 |
continue; |
|
178 |
} |
||
179 |
value = 0; |
||
180 |
len = 0; |
||
181 |
✓✓✓✓ ✗✓ |
48 |
while (len < 4 && ASCII_HEX_DIGIT(ch)) { |
182 |
36 |
value = value * 0x10 + hex2bin(ch); |
|
183 |
18 |
pointer++; |
|
184 |
✓✓ | 18 |
ch = pointer < end ? pointer[0] : kEOL; |
185 |
18 |
len++; |
|
186 |
} |
||
187 |
✗✓✓✓ |
12 |
switch (ch) { |
188 |
case '.': |
||
189 |
if (len == 0) |
||
190 |
goto end; |
||
191 |
pointer -= len; |
||
192 |
ch = pointer < end ? pointer[0] : kEOL; |
||
193 |
if (piece_pointer > last_piece - 2) |
||
194 |
goto end; |
||
195 |
dots = 0; |
||
196 |
while (ch != kEOL) { |
||
197 |
value = 0xffffffff; |
||
198 |
if (!ASCII_DIGIT(ch)) |
||
199 |
goto end; |
||
200 |
while (ASCII_DIGIT(ch)) { |
||
201 |
unsigned number = ch - '0'; |
||
202 |
if (value == 0xffffffff) { |
||
203 |
value = number; |
||
204 |
} else if (value == 0) { |
||
205 |
goto end; |
||
206 |
} else { |
||
207 |
value = value * 10 + number; |
||
208 |
} |
||
209 |
if (value > 255) |
||
210 |
goto end; |
||
211 |
pointer++; |
||
212 |
ch = pointer < end ? pointer[0] : kEOL; |
||
213 |
} |
||
214 |
if (dots < 3 && ch != '.') |
||
215 |
goto end; |
||
216 |
*piece_pointer = *piece_pointer * 0x100 + value; |
||
217 |
if (dots & 0x1) |
||
218 |
piece_pointer++; |
||
219 |
if (ch != kEOL) { |
||
220 |
pointer++; |
||
221 |
ch = pointer < end ? pointer[0] : kEOL; |
||
222 |
} |
||
223 |
if (dots == 3 && ch != kEOL) |
||
224 |
goto end; |
||
225 |
dots++; |
||
226 |
} |
||
227 |
continue; |
||
228 |
case ':': |
||
229 |
5 |
pointer++; |
|
230 |
✓✗ | 5 |
ch = pointer < end ? pointer[0] : kEOL; |
231 |
✓✗ | 5 |
if (ch == kEOL) |
232 |
goto end; |
||
233 |
break; |
||
234 |
case kEOL: |
||
235 |
break; |
||
236 |
default: |
||
237 |
goto end; |
||
238 |
} |
||
239 |
10 |
*piece_pointer = value; |
|
240 |
10 |
piece_pointer++; |
|
241 |
} |
||
242 |
|||
243 |
✓✓ | 6 |
if (compress_pointer != nullptr) { |
244 |
5 |
swaps = piece_pointer - compress_pointer; |
|
245 |
5 |
piece_pointer = last_piece - 1; |
|
246 |
✓✗✓✓ |
19 |
while (piece_pointer != &host->value.ipv6[0] && swaps > 0) { |
247 |
7 |
uint16_t temp = *piece_pointer; |
|
248 |
7 |
uint16_t* swap_piece = compress_pointer + swaps - 1; |
|
249 |
7 |
*piece_pointer = *swap_piece; |
|
250 |
7 |
*swap_piece = temp; |
|
251 |
7 |
piece_pointer--; |
|
252 |
7 |
swaps--; |
|
253 |
} |
||
254 |
✗✓ | 2 |
} else if (compress_pointer == nullptr && |
255 |
1 |
piece_pointer != last_piece) { |
|
256 |
goto end; |
||
257 |
} |
||
258 |
type = HOST_TYPE_IPV6; |
||
259 |
end: |
||
260 |
9 |
host->type = type; |
|
261 |
9 |
return type; |
|
262 |
} |
||
263 |
|||
264 |
445 |
static inline int ParseNumber(const char* start, const char* end) { |
|
265 |
445 |
unsigned R = 10; |
|
266 |
✓✓✓✓ ✓✓ |
445 |
if (end - start >= 2 && start[0] == '0' && (start[1] | 0x20) == 'x') { |
267 |
6 |
start += 2; |
|
268 |
6 |
R = 16; |
|
269 |
} |
||
270 |
✓✗ | 445 |
if (end - start == 0) { |
271 |
return 0; |
||
272 |
✓✓✓✓ ✓✓ |
445 |
} else if (R == 10 && end - start > 1 && start[0] == '0') { |
273 |
6 |
start++; |
|
274 |
6 |
R = 8; |
|
275 |
} |
||
276 |
445 |
const char* p = start; |
|
277 |
|||
278 |
✓✓ | 607 |
while (p < end) { |
279 |
498 |
const char ch = p[0]; |
|
280 |
✓✓✓✗ |
498 |
switch (R) { |
281 |
case 8: |
||
282 |
✓✗ | 12 |
if (ch < '0' || ch > '7') |
283 |
return -1; |
||
284 |
break; |
||
285 |
case 10: |
||
286 |
✓✓ | 456 |
if (!ASCII_DIGIT(ch)) |
287 |
return -1; |
||
288 |
break; |
||
289 |
case 16: |
||
290 |
✓✓✓✗ |
30 |
if (!ASCII_HEX_DIGIT(ch)) |
291 |
return -1; |
||
292 |
break; |
||
293 |
} |
||
294 |
81 |
p++; |
|
295 |
} |
||
296 |
28 |
return strtol(start, NULL, R); |
|
297 |
} |
||
298 |
|||
299 |
429 |
static url_host_type ParseIPv4Host(url_host* host, |
|
300 |
const char* input, |
||
301 |
size_t length) { |
||
302 |
429 |
url_host_type type = HOST_TYPE_DOMAIN; |
|
303 |
429 |
const char* pointer = input; |
|
304 |
429 |
const char* mark = input; |
|
305 |
429 |
const char* end = pointer + length; |
|
306 |
429 |
int parts = 0; |
|
307 |
429 |
uint32_t val = 0; |
|
308 |
unsigned numbers[4]; |
||
309 |
✓✗ | 429 |
if (length == 0) |
310 |
goto end; |
||
311 |
|||
312 |
✓✓ | 3039 |
while (pointer <= end) { |
313 |
✓✓ | 3035 |
const char ch = pointer < end ? pointer[0] : kEOL; |
314 |
3035 |
const int remaining = end - pointer - 1; |
|
315 |
✓✓ | 3035 |
if (ch == '.' || ch == kEOL) { |
316 |
✓✗✓✗ ✓✗ |
445 |
if (++parts > 4 || pointer - mark == 0) |
317 |
break; |
||
318 |
445 |
int n = ParseNumber(mark, pointer); |
|
319 |
✓✓ | 445 |
if (n < 0) { |
320 |
type = HOST_TYPE_DOMAIN; |
||
321 |
goto end; |
||
322 |
} |
||
323 |
✓✓ | 28 |
if (pointer - mark == 10) { |
324 |
3 |
numbers[parts - 1] = n; |
|
325 |
break; |
||
326 |
} |
||
327 |
✓✓ | 25 |
if (n > 255) { |
328 |
type = HOST_TYPE_FAILED; |
||
329 |
goto end; |
||
330 |
} |
||
331 |
21 |
numbers[parts - 1] = n; |
|
332 |
21 |
mark = pointer + 1; |
|
333 |
✓✓ | 21 |
if (ch == '.' && remaining == 0) |
334 |
break; |
||
335 |
} |
||
336 |
2610 |
pointer++; |
|
337 |
} |
||
338 |
|||
339 |
8 |
type = HOST_TYPE_IPV4; |
|
340 |
✓✗ | 8 |
if (parts > 0) { |
341 |
8 |
val = numbers[parts - 1]; |
|
342 |
✓✓ | 21 |
for (int n = 0; n < parts - 1; n++) { |
343 |
13 |
double b = 3-n; |
|
344 |
13 |
val += numbers[n] * pow(256, b); |
|
345 |
} |
||
346 |
} |
||
347 |
|||
348 |
8 |
host->value.ipv4 = val; |
|
349 |
end: |
||
350 |
429 |
host->type = type; |
|
351 |
429 |
return type; |
|
352 |
} |
||
353 |
|||
354 |
454 |
static url_host_type ParseHost(url_host* host, |
|
355 |
const char* input, |
||
356 |
size_t length, |
||
357 |
bool unicode = false) { |
||
358 |
454 |
url_host_type type = HOST_TYPE_FAILED; |
|
359 |
454 |
const char* pointer = input; |
|
360 |
908 |
std::string decoded; |
|
361 |
|||
362 |
✓✗ | 454 |
if (length == 0) |
363 |
goto end; |
||
364 |
|||
365 |
✓✓ | 454 |
if (pointer[0] == '[') { |
366 |
✓✗ | 9 |
if (pointer[length - 1] != ']') |
367 |
goto end; |
||
368 |
9 |
return ParseIPv6Host(host, ++pointer, length - 2); |
|
369 |
} |
||
370 |
|||
371 |
// First, we have to percent decode |
||
372 |
✓✗ | 445 |
if (PercentDecode(input, length, &decoded) < 0) |
373 |
goto end; |
||
374 |
|||
375 |
// If there are any invalid UTF8 byte sequences, we have to fail. |
||
376 |
// Unfortunately this means iterating through the string and checking |
||
377 |
// each decoded codepoint. |
||
378 |
✓✓ | 445 |
if (IsValidUTF8(&decoded) < 0) |
379 |
goto end; |
||
380 |
|||
381 |
// Then we have to punycode toASCII |
||
382 |
✓✗ | 443 |
if (ToASCII(&decoded, &decoded) < 0) |
383 |
goto end; |
||
384 |
|||
385 |
// If any of the following characters are still present, we have to fail |
||
386 |
✓✓ | 9173 |
for (size_t n = 0; n < decoded.size(); n++) { |
387 |
4379 |
const char ch = decoded[n]; |
|
388 |
✓✓✓✗ |
4379 |
if (ch == 0x00 || ch == 0x09 || ch == 0x0a || ch == 0x0d || |
389 |
✓✓✓✓ |
4378 |
ch == 0x20 || ch == '#' || ch == '%' || ch == '/' || |
390 |
✓✗✓✓ ✓✗ |
4367 |
ch == '?' || ch == '@' || ch == '[' || ch == '\\' || |
391 |
ch == ']') { |
||
392 |
goto end; |
||
393 |
} |
||
394 |
} |
||
395 |
|||
396 |
// Check to see if it's an IPv4 IP address |
||
397 |
858 |
type = ParseIPv4Host(host, decoded.c_str(), decoded.length()); |
|
398 |
✓✓ | 429 |
if (type == HOST_TYPE_IPV4 || type == HOST_TYPE_FAILED) |
399 |
goto end; |
||
400 |
|||
401 |
// If the unicode flag is set, run the result through punycode ToUnicode |
||
402 |
✗✓✗✗ ✓✗ |
417 |
if (unicode && ToUnicode(&decoded, &decoded) < 0) |
403 |
goto end; |
||
404 |
|||
405 |
// It's not an IPv4 or IPv6 address, it must be a domain |
||
406 |
417 |
type = HOST_TYPE_DOMAIN; |
|
407 |
417 |
host->value.domain = decoded; |
|
408 |
|||
409 |
end: |
||
410 |
445 |
host->type = type; |
|
411 |
445 |
return type; |
|
412 |
} |
||
413 |
|||
414 |
// Locates the longest sequence of 0 segments in an IPv6 address |
||
415 |
// in order to use the :: compression when serializing |
||
416 |
static inline uint16_t* FindLongestZeroSequence(uint16_t* values, |
||
417 |
size_t len) { |
||
418 |
5 |
uint16_t* start = values; |
|
419 |
5 |
uint16_t* end = start + len; |
|
420 |
5 |
uint16_t* result = nullptr; |
|
421 |
|||
422 |
5 |
uint16_t* current = nullptr; |
|
423 |
5 |
unsigned counter = 0, longest = 1; |
|
424 |
|||
425 |
✓✓ | 45 |
while (start < end) { |
426 |
✓✓ | 40 |
if (*start == 0) { |
427 |
✓✓ | 32 |
if (current == nullptr) |
428 |
5 |
current = start; |
|
429 |
32 |
counter++; |
|
430 |
} else { |
||
431 |
✓✓ | 8 |
if (counter > longest) { |
432 |
5 |
longest = counter; |
|
433 |
5 |
result = current; |
|
434 |
} |
||
435 |
counter = 0; |
||
436 |
current = nullptr; |
||
437 |
} |
||
438 |
40 |
start++; |
|
439 |
} |
||
440 |
✗✓ | 5 |
if (counter > longest) |
441 |
result = current; |
||
442 |
return result; |
||
443 |
} |
||
444 |
|||
445 |
430 |
static url_host_type WriteHost(url_host* host, std::string* dest) { |
|
446 |
430 |
dest->clear(); |
|
447 |
✓✓✓✗ |
430 |
switch (host->type) { |
448 |
case HOST_TYPE_DOMAIN: |
||
449 |
417 |
*dest = host->value.domain; |
|
450 |
break; |
||
451 |
case HOST_TYPE_IPV4: { |
||
452 |
8 |
dest->reserve(15); |
|
453 |
8 |
uint32_t value = host->value.ipv4; |
|
454 |
✓✓ | 40 |
for (int n = 0; n < 4; n++) { |
455 |
char buf[4]; |
||
456 |
32 |
char* buffer = buf; |
|
457 |
64 |
snprintf(buffer, sizeof(buf), "%d", value % 256); |
|
458 |
32 |
dest->insert(0, buf); |
|
459 |
✓✓ | 32 |
if (n < 3) |
460 |
24 |
dest->insert(0, 1, '.'); |
|
461 |
32 |
value /= 256; |
|
462 |
} |
||
463 |
break; |
||
464 |
} |
||
465 |
case HOST_TYPE_IPV6: { |
||
466 |
5 |
dest->reserve(41); |
|
467 |
5 |
*dest+= '['; |
|
468 |
5 |
uint16_t* start = &host->value.ipv6[0]; |
|
469 |
uint16_t* compress_pointer = |
||
470 |
5 |
FindLongestZeroSequence(start, 8); |
|
471 |
✓✓ | 13 |
for (int n = 0; n <= 7; n++) { |
472 |
8 |
uint16_t* piece = &host->value.ipv6[n]; |
|
473 |
✓✓ | 8 |
if (compress_pointer == piece) { |
474 |
✓✓ | 5 |
*dest += n == 0 ? "::" : ":"; |
475 |
✓✓✗✓ ✓✓ |
69 |
while (*piece == 0 && ++n < 8) |
476 |
32 |
piece = &host->value.ipv6[n]; |
|
477 |
✓✗ | 5 |
if (n == 8) |
478 |
break; |
||
479 |
} |
||
480 |
char buf[5]; |
||
481 |
8 |
char* buffer = buf; |
|
482 |
16 |
snprintf(buffer, sizeof(buf), "%x", *piece); |
|
483 |
8 |
*dest += buf; |
|
484 |
✓✓ | 8 |
if (n < 7) |
485 |
*dest += ':'; |
||
486 |
} |
||
487 |
*dest += ']'; |
||
488 |
break; |
||
489 |
} |
||
490 |
case HOST_TYPE_FAILED: |
||
491 |
break; |
||
492 |
} |
||
493 |
430 |
return host->type; |
|
494 |
} |
||
495 |
|||
496 |
463 |
static int ParseHost(std::string* input, |
|
497 |
std::string* output, |
||
498 |
bool unicode = false) { |
||
499 |
✓✓ | 463 |
if (input->length() == 0) |
500 |
return 0; |
||
501 |
908 |
url_host host{{""}, HOST_TYPE_DOMAIN}; |
|
502 |
908 |
ParseHost(&host, input->c_str(), input->length(), unicode); |
|
503 |
✓✓ | 454 |
if (host.type == HOST_TYPE_FAILED) |
504 |
return -1; |
||
505 |
430 |
WriteHost(&host, output); |
|
506 |
430 |
return 0; |
|
507 |
} |
||
508 |
|||
509 |
315 |
static inline void Copy(Isolate* isolate, |
|
510 |
Local<Array> ary, |
||
511 |
std::vector<std::string>* vec) { |
||
512 |
315 |
const int32_t len = ary->Length(); |
|
513 |
✓✗ | 315 |
if (len == 0) |
514 |
return; // nothing to copy |
||
515 |
315 |
vec->reserve(len); |
|
516 |
✓✓ | 770 |
for (int32_t n = 0; n < len; n++) { |
517 |
455 |
Local<Value> val = ary->Get(n); |
|
518 |
✓✗ | 910 |
if (val->IsString()) { |
519 |
1365 |
Utf8Value value(isolate, val.As<String>()); |
|
520 |
2730 |
vec->push_back(std::string(*value, value.length())); |
|
521 |
} |
||
522 |
} |
||
523 |
} |
||
524 |
|||
525 |
740 |
static inline Local<Array> Copy(Isolate* isolate, |
|
526 |
std::vector<std::string> vec) { |
||
527 |
1480 |
Local<Array> ary = Array::New(isolate, vec.size()); |
|
528 |
✓✓ | 3512 |
for (size_t n = 0; n < vec.size(); n++) |
529 |
5080 |
ary->Set(n, UTF8STRING(isolate, vec[n])); |
|
530 |
740 |
return ary; |
|
531 |
} |
||
532 |
|||
533 |
315 |
static inline void HarvestBase(Environment* env, |
|
534 |
struct url_data* base, |
||
535 |
Local<Object> base_obj) { |
||
536 |
1260 |
Local<Value> flags = GET(env, base_obj, "flags"); |
|
537 |
✓✗ | 315 |
if (flags->IsInt32()) |
538 |
315 |
base->flags = flags->Int32Value(); |
|
539 |
|||
540 |
✓✗ | 2835 |
GET_AND_SET(env, base_obj, scheme, base, URL_FLAGS_HAS_SCHEME); |
541 |
✓✓ | 1587 |
GET_AND_SET(env, base_obj, username, base, URL_FLAGS_HAS_USERNAME); |
542 |
✓✓ | 1583 |
GET_AND_SET(env, base_obj, password, base, URL_FLAGS_HAS_PASSWORD); |
543 |
✓✓ | 2175 |
GET_AND_SET(env, base_obj, host, base, URL_FLAGS_HAS_HOST); |
544 |
✓✓ | 1579 |
GET_AND_SET(env, base_obj, query, base, URL_FLAGS_HAS_QUERY); |
545 |
✗✓ | 1575 |
GET_AND_SET(env, base_obj, fragment, base, URL_FLAGS_HAS_FRAGMENT); |
546 |
1260 |
Local<Value> port = GET(env, base_obj, "port"); |
|
547 |
✓✓ | 315 |
if (port->IsInt32()) |
548 |
2 |
base->port = port->Int32Value(); |
|
549 |
1260 |
Local<Value> path = GET(env, base_obj, "path"); |
|
550 |
✓✗ | 315 |
if (path->IsArray()) { |
551 |
315 |
base->flags |= URL_FLAGS_HAS_PATH; |
|
552 |
630 |
Copy(env->isolate(), path.As<Array>(), &(base->path)); |
|
553 |
} |
||
554 |
315 |
} |
|
555 |
|||
556 |
91 |
static inline void HarvestContext(Environment* env, |
|
557 |
struct url_data* context, |
||
558 |
Local<Object> context_obj) { |
||
559 |
364 |
Local<Value> flags = GET(env, context_obj, "flags"); |
|
560 |
✓✗ | 91 |
if (flags->IsInt32()) { |
561 |
91 |
int32_t _flags = flags->Int32Value(); |
|
562 |
✓✓ | 91 |
if (_flags & URL_FLAGS_SPECIAL) |
563 |
69 |
context->flags |= URL_FLAGS_SPECIAL; |
|
564 |
✓✓ | 91 |
if (_flags & URL_FLAGS_CANNOT_BE_BASE) |
565 |
2 |
context->flags |= URL_FLAGS_CANNOT_BE_BASE; |
|
566 |
} |
||
567 |
364 |
Local<Value> scheme = GET(env, context_obj, "scheme"); |
|
568 |
✓✗ | 182 |
if (scheme->IsString()) { |
569 |
182 |
Utf8Value value(env->isolate(), scheme); |
|
570 |
182 |
context->scheme.assign(*value, value.length()); |
|
571 |
} |
||
572 |
364 |
Local<Value> port = GET(env, context_obj, "port"); |
|
573 |
✓✓ | 91 |
if (port->IsInt32()) |
574 |
8 |
context->port = port->Int32Value(); |
|
575 |
91 |
} |
|
576 |
|||
577 |
// Single dot segment can be ".", "%2e", or "%2E" |
||
578 |
809 |
static inline bool IsSingleDotSegment(std::string str) { |
|
579 |
✓✓✓ | 809 |
switch (str.size()) { |
580 |
case 1: |
||
581 |
44 |
return str == "."; |
|
582 |
case 3: |
||
583 |
✓✗ | 275 |
return str[0] == '%' && |
584 |
✓✓✓✓ ✗✓ |
278 |
str[1] == '2' && |
585 |
✓✗✓✓ ✓✗✗✓ |
8 |
TO_LOWER(str[2]) == 'e'; |
586 |
default: |
||
587 |
return false; |
||
588 |
} |
||
589 |
} |
||
590 |
|||
591 |
// Double dot segment can be: |
||
592 |
// "..", ".%2e", ".%2E", "%2e.", "%2E.", |
||
593 |
// "%2e%2e", "%2E%2E", "%2e%2E", or "%2E%2e" |
||
594 |
845 |
static inline bool IsDoubleDotSegment(std::string str) { |
|
595 |
✓✓✓✓ |
845 |
switch (str.size()) { |
596 |
case 2: |
||
597 |
72 |
return str == ".."; |
|
598 |
case 4: |
||
599 |
✓✓✗✓ ✓✓ |
223 |
if (str[0] != '.' && str[0] != '%') |
600 |
return false; |
||
601 |
✗✓ | 2 |
return ((str[0] == '.' && |
602 |
✗✗ | 1 |
str[1] == '%' && |
603 |
str[2] == '2' && |
||
604 |
✓✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✓✗✗ |
2 |
TO_LOWER(str[3]) == 'e') || |
605 |
✗✗ | 1 |
(str[0] == '%' && |
606 |
str[1] == '2' && |
||
607 |
TO_LOWER(str[2]) == 'e' && |
||
608 |
str[3] == '.')); |
||
609 |
case 6: |
||
610 |
✓✗ | 9 |
return (str[0] == '%' && |
611 |
✗✗ | 1 |
str[1] == '2' && |
612 |
✗✗✗✓ ✗✗✗✓ ✗✗ |
3 |
TO_LOWER(str[2]) == 'e' && |
613 |
str[3] == '%' && |
||
614 |
✓✓✗✓ ✗✗✗✗ |
9 |
str[4] == '2' && |
615 |
TO_LOWER(str[5]) == 'e'); |
||
616 |
default: |
||
617 |
return false; |
||
618 |
} |
||
619 |
} |
||
620 |
|||
621 |
846 |
static void Parse(Environment* env, |
|
622 |
Local<Value> recv, |
||
623 |
const char* input, |
||
624 |
const size_t len, |
||
625 |
enum url_parse_state override, |
||
626 |
Local<Object> base_obj, |
||
627 |
Local<Object> context_obj, |
||
628 |
Local<Function> cb) { |
||
629 |
846 |
Isolate* isolate = env->isolate(); |
|
630 |
846 |
Local<Context> context = env->context(); |
|
631 |
1692 |
HandleScope handle_scope(isolate); |
|
632 |
1692 |
Context::Scope context_scope(context); |
|
633 |
|||
634 |
846 |
const bool has_base = base_obj->IsObject(); |
|
635 |
846 |
bool atflag = false; |
|
636 |
846 |
bool sbflag = false; |
|
637 |
846 |
bool uflag = false; |
|
638 |
846 |
bool base_is_file = false; |
|
639 |
846 |
int wskip = 0; |
|
640 |
|||
641 |
1692 |
struct url_data base; |
|
642 |
1692 |
struct url_data url; |
|
643 |
✓✓ | 846 |
if (context_obj->IsObject()) |
644 |
91 |
HarvestContext(env, &url, context_obj); |
|
645 |
✓✓ | 846 |
if (has_base) |
646 |
315 |
HarvestBase(env, &base, base_obj); |
|
647 |
|||
648 |
1692 |
std::string buffer; |
|
649 |
846 |
url.scheme.reserve(len); |
|
650 |
846 |
url.username.reserve(len); |
|
651 |
846 |
url.password.reserve(len); |
|
652 |
846 |
url.host.reserve(len); |
|
653 |
846 |
url.path.reserve(len); |
|
654 |
846 |
url.query.reserve(len); |
|
655 |
846 |
url.fragment.reserve(len); |
|
656 |
846 |
buffer.reserve(len); |
|
657 |
|||
658 |
// Set the initial parse state. |
||
659 |
846 |
const bool state_override = override != kUnknownState; |
|
660 |
✓✓ | 846 |
enum url_parse_state state = state_override ? override : kSchemeStart; |
661 |
|||
662 |
846 |
const char* p = input; |
|
663 |
846 |
const char* end = input + len; |
|
664 |
|||
665 |
✓✗ | 846 |
if (state < kSchemeStart || state > kFragment) { |
666 |
INVALID_PARSE_STATE(); |
||
667 |
goto done; |
||
668 |
} |
||
669 |
|||
670 |
✓✓ | 22123 |
while (p <= end) { |
671 |
✓✓ | 21359 |
const char ch = p < end ? p[0] : kEOL; |
672 |
|||
673 |
✓✓ | 21359 |
if (TAB_AND_NEWLINE(ch)) { |
674 |
✓✓ | 42 |
if (state == kAuthority) { |
675 |
// It's necessary to keep track of how much whitespace |
||
676 |
// is being ignored when in kAuthority state because of |
||
677 |
// how the buffer is managed. TODO: See if there's a better |
||
678 |
// way |
||
679 |
9 |
wskip++; |
|
680 |
} |
||
681 |
42 |
p++; |
|
682 |
42 |
continue; |
|
683 |
} |
||
684 |
|||
685 |
21317 |
bool special = url.flags & URL_FLAGS_SPECIAL; |
|
686 |
21317 |
const bool special_back_slash = (special && ch == '\\'); |
|
687 |
✓✓✓✓ ✓✓✓✓ ✓✓✓✓ ✓✓✓✓ ✓✓✓✓ ✗ |
21317 |
switch (state) { |
688 |
case kSchemeStart: |
||
689 |
✓✓ | 768 |
if (ASCII_ALPHA(ch)) { |
690 |
✓✗ | 1384 |
buffer += TO_LOWER(ch); |
691 |
692 |
state = kScheme; |
|
692 |
✓✓ | 76 |
} else if (!state_override) { |
693 |
state = kNoScheme; |
||
694 |
continue; |
||
695 |
} else { |
||
696 |
3 |
TERMINATE() |
|
697 |
} |
||
698 |
692 |
break; |
|
699 |
case kScheme: |
||
700 |
✓✓✓✓ ✓✓✓✓ |
2963 |
if (SCHEME_CHAR(ch)) { |
701 |
✓✓ | 4542 |
buffer += TO_LOWER(ch); |
702 |
2271 |
p++; |
|
703 |
2271 |
continue; |
|
704 |
✓✓✓✓ |
692 |
} else if (ch == ':' || (state_override && ch == kEOL)) { |
705 |
681 |
buffer += ':'; |
|
706 |
✓✗ | 681 |
if (buffer.size() > 0) { |
707 |
681 |
SET_HAVE_SCHEME() |
|
708 |
url.scheme = buffer; |
||
709 |
} |
||
710 |
✓✓ | 1362 |
if (IsSpecial(url.scheme)) { |
711 |
437 |
SPECIAL() |
|
712 |
} else { |
||
713 |
244 |
url.flags &= ~URL_FLAGS_SPECIAL; |
|
714 |
} |
||
715 |
✓✓ | 681 |
if (state_override) |
716 |
goto done; |
||
717 |
673 |
buffer.clear(); |
|
718 |
✓✓ | 673 |
if (url.scheme == "file:") { |
719 |
state = kFile; |
||
720 |
✓✓ | 1276 |
} else if (special && |
721 |
✓✗ | 182 |
has_base && |
722 |
✓✓✓✓ |
820 |
DOES_HAVE_SCHEME(base) && |
723 |
182 |
url.scheme == base.scheme) { |
|
724 |
state = kSpecialRelativeOrAuthority; |
||
725 |
✓✓ | 575 |
} else if (special) { |
726 |
state = kSpecialAuthoritySlashes; |
||
727 |
✓✓ | 239 |
} else if (p[1] == '/') { |
728 |
73 |
state = kPathOrAuthority; |
|
729 |
73 |
p++; |
|
730 |
} else { |
||
731 |
166 |
CANNOT_BE_BASE() |
|
732 |
166 |
SET_HAVE_PATH() |
|
733 |
664 |
url.path.push_back(""); |
|
734 |
166 |
state = kCannotBeBase; |
|
735 |
} |
||
736 |
✓✓ | 11 |
} else if (!state_override) { |
737 |
9 |
buffer.clear(); |
|
738 |
9 |
state = kNoScheme; |
|
739 |
9 |
p = input; |
|
740 |
9 |
continue; |
|
741 |
} else { |
||
742 |
2 |
TERMINATE() |
|
743 |
} |
||
744 |
break; |
||
745 |
case kNoScheme: |
||
746 |
✓✗✓✓ ✓✓ |
82 |
if (!has_base || (IS_CANNOT_BE_BASE(base.flags) && ch != '#')) { |
747 |
8 |
FAILED() |
|
748 |
✓✓✓✗ |
74 |
} else if (IS_CANNOT_BE_BASE(base.flags) && ch == '#') { |
749 |
7 |
SET_HAVE_SCHEME() |
|
750 |
7 |
url.scheme = base.scheme; |
|
751 |
✗✓ | 14 |
if (IsSpecial(url.scheme)) { |
752 |
SPECIAL() |
||
753 |
} else { |
||
754 |
7 |
url.flags &= ~URL_FLAGS_SPECIAL; |
|
755 |
} |
||
756 |
✓✗ | 7 |
if (DOES_HAVE_PATH(base)) { |
757 |
7 |
SET_HAVE_PATH() |
|
758 |
7 |
url.path = base.path; |
|
759 |
} |
||
760 |
✓✓ | 7 |
if (DOES_HAVE_QUERY(base)) { |
761 |
1 |
SET_HAVE_QUERY() |
|
762 |
url.query = base.query; |
||
763 |
} |
||
764 |
✗✓ | 7 |
if (DOES_HAVE_FRAGMENT(base)) { |
765 |
SET_HAVE_FRAGMENT() |
||
766 |
url.fragment = base.fragment; |
||
767 |
} |
||
768 |
7 |
CANNOT_BE_BASE() |
|
769 |
7 |
state = kFragment; |
|
770 |
✓✗✓✓ |
134 |
} else if (has_base && |
771 |
✓✗✓✓ |
134 |
DOES_HAVE_SCHEME(base) && |
772 |
67 |
base.scheme != "file:") { |
|
773 |
state = kRelative; |
||
774 |
continue; |
||
775 |
} else { |
||
776 |
10 |
SET_HAVE_SCHEME() |
|
777 |
10 |
url.scheme = "file:"; |
|
778 |
10 |
SPECIAL() |
|
779 |
10 |
state = kFile; |
|
780 |
10 |
continue; |
|
781 |
} |
||
782 |
7 |
break; |
|
783 |
case kSpecialRelativeOrAuthority: |
||
784 |
✓✓✓✓ |
63 |
if (ch == '/' && p[1] == '/') { |
785 |
56 |
state = kSpecialAuthorityIgnoreSlashes; |
|
786 |
56 |
p++; |
|
787 |
} else { |
||
788 |
state = kRelative; |
||
789 |
continue; |
||
790 |
} |
||
791 |
56 |
break; |
|
792 |
case kPathOrAuthority: |
||
793 |
✓✓ | 73 |
if (ch == '/') { |
794 |
state = kAuthority; |
||
795 |
} else { |
||
796 |
state = kPath; |
||
797 |
continue; |
||
798 |
} |
||
799 |
break; |
||
800 |
case kRelative: |
||
801 |
64 |
SET_HAVE_SCHEME() |
|
802 |
64 |
url.scheme = base.scheme; |
|
803 |
✓✓ | 128 |
if (IsSpecial(url.scheme)) { |
804 |
49 |
SPECIAL() |
|
805 |
} else { |
||
806 |
15 |
url.flags &= ~URL_FLAGS_SPECIAL; |
|
807 |
} |
||
808 |
✓✓✓✓ ✓ |
64 |
switch (ch) { |
809 |
case kEOL: |
||
810 |
✓✓ | 3 |
if (DOES_HAVE_USERNAME(base)) { |
811 |
1 |
SET_HAVE_USERNAME() |
|
812 |
url.username = base.username; |
||
813 |
} |
||
814 |
✓✓ | 3 |
if (DOES_HAVE_PASSWORD(base)) { |
815 |
1 |
SET_HAVE_PASSWORD() |
|
816 |
url.password = base.password; |
||
817 |
} |
||
818 |
✓✗ | 3 |
if (DOES_HAVE_HOST(base)) { |
819 |
3 |
SET_HAVE_HOST() |
|
820 |
url.host = base.host; |
||
821 |
} |
||
822 |
✗✓ | 3 |
if (DOES_HAVE_QUERY(base)) { |
823 |
SET_HAVE_QUERY() |
||
824 |
url.query = base.query; |
||
825 |
} |
||
826 |
✓✗ | 3 |
if (DOES_HAVE_PATH(base)) { |
827 |
3 |
SET_HAVE_PATH() |
|
828 |
3 |
url.path = base.path; |
|
829 |
} |
||
830 |
3 |
url.port = base.port; |
|
831 |
3 |
break; |
|
832 |
case '/': |
||
833 |
state = kRelativeSlash; |
||
834 |
break; |
||
835 |
case '?': |
||
836 |
✗✓ | 4 |
if (DOES_HAVE_USERNAME(base)) { |
837 |
SET_HAVE_USERNAME() |
||
838 |
url.username = base.username; |
||
839 |
} |
||
840 |
✗✓ | 4 |
if (DOES_HAVE_PASSWORD(base)) { |
841 |
SET_HAVE_PASSWORD() |
||
842 |
url.password = base.password; |
||
843 |
} |
||
844 |
✓✓ | 4 |
if (DOES_HAVE_HOST(base)) { |
845 |
3 |
SET_HAVE_HOST() |
|
846 |
url.host = base.host; |
||
847 |
} |
||
848 |
✓✗ | 4 |
if (DOES_HAVE_PATH(base)) { |
849 |
4 |
SET_HAVE_PATH() |
|
850 |
4 |
url.path = base.path; |
|
851 |
} |
||
852 |
4 |
url.port = base.port; |
|
853 |
4 |
state = kQuery; |
|
854 |
4 |
break; |
|
855 |
case '#': |
||
856 |
✗✓ | 8 |
if (DOES_HAVE_USERNAME(base)) { |
857 |
SET_HAVE_USERNAME() |
||
858 |
url.username = base.username; |
||
859 |
} |
||
860 |
✗✓ | 8 |
if (DOES_HAVE_PASSWORD(base)) { |
861 |
SET_HAVE_PASSWORD() |
||
862 |
url.password = base.password; |
||
863 |
} |
||
864 |
✓✓ | 8 |
if (DOES_HAVE_HOST(base)) { |
865 |
7 |
SET_HAVE_HOST() |
|
866 |
url.host = base.host; |
||
867 |
} |
||
868 |
✗✓ | 8 |
if (DOES_HAVE_QUERY(base)) { |
869 |
SET_HAVE_QUERY() |
||
870 |
url.query = base.query; |
||
871 |
} |
||
872 |
✓✗ | 8 |
if (DOES_HAVE_PATH(base)) { |
873 |
8 |
SET_HAVE_PATH() |
|
874 |
8 |
url.path = base.path; |
|
875 |
} |
||
876 |
8 |
url.port = base.port; |
|
877 |
8 |
state = kFragment; |
|
878 |
8 |
break; |
|
879 |
default: |
||
880 |
✓✓ | 33 |
if (special_back_slash) { |
881 |
state = kRelativeSlash; |
||
882 |
} else { |
||
883 |
✗✓ | 31 |
if (DOES_HAVE_USERNAME(base)) { |
884 |
SET_HAVE_USERNAME() |
||
885 |
url.username = base.username; |
||
886 |
} |
||
887 |
✗✓ | 31 |
if (DOES_HAVE_PASSWORD(base)) { |
888 |
SET_HAVE_PASSWORD() |
||
889 |
url.password = base.password; |
||
890 |
} |
||
891 |
✓✓ | 31 |
if (DOES_HAVE_HOST(base)) { |
892 |
29 |
SET_HAVE_HOST() |
|
893 |
url.host = base.host; |
||
894 |
} |
||
895 |
✓✗ | 31 |
if (DOES_HAVE_PATH(base)) { |
896 |
31 |
SET_HAVE_PATH() |
|
897 |
31 |
url.path = base.path; |
|
898 |
✓✗ | 31 |
if (!url.path.empty()) |
899 |
31 |
url.path.pop_back(); |
|
900 |
} |
||
901 |
31 |
url.port = base.port; |
|
902 |
31 |
state = kPath; |
|
903 |
31 |
continue; |
|
904 |
} |
||
905 |
} |
||
906 |
break; |
||
907 |
case kRelativeSlash: |
||
908 |
✓✓ | 18 |
if (ch == '/' || special_back_slash) { |
909 |
state = kSpecialAuthorityIgnoreSlashes; |
||
910 |
} else { |
||
911 |
✓✓ | 14 |
if (DOES_HAVE_USERNAME(base)) { |
912 |
2 |
SET_HAVE_USERNAME() |
|
913 |
url.username = base.username; |
||
914 |
} |
||
915 |
✓✓ | 14 |
if (DOES_HAVE_PASSWORD(base)) { |
916 |
1 |
SET_HAVE_PASSWORD() |
|
917 |
url.password = base.password; |
||
918 |
} |
||
919 |
✓✓ | 14 |
if (DOES_HAVE_HOST(base)) { |
920 |
13 |
SET_HAVE_HOST() |
|
921 |
url.host = base.host; |
||
922 |
} |
||
923 |
14 |
url.port = base.port; |
|
924 |
14 |
state = kPath; |
|
925 |
14 |
continue; |
|
926 |
} |
||
927 |
break; |
||
928 |
case kSpecialAuthoritySlashes: |
||
929 |
336 |
state = kSpecialAuthorityIgnoreSlashes; |
|
930 |
✓✓✓✓ |
336 |
if (ch == '/' && p[1] == '/') { |
931 |
296 |
p++; |
|
932 |
} else { |
||
933 |
continue; |
||
934 |
} |
||
935 |
296 |
break; |
|
936 |
case kSpecialAuthorityIgnoreSlashes: |
||
937 |
✓✓ | 417 |
if (ch != '/' && ch != '\\') { |
938 |
state = kAuthority; |
||
939 |
continue; |
||
940 |
} |
||
941 |
break; |
||
942 |
case kAuthority: |
||
943 |
✓✓ | 5342 |
if (ch == '@') { |
944 |
✓✓ | 57 |
if (atflag) { |
945 |
5 |
buffer.reserve(buffer.size() + 3); |
|
946 |
5 |
buffer.insert(0, "%40"); |
|
947 |
} |
||
948 |
57 |
atflag = true; |
|
949 |
57 |
const size_t blen = buffer.size(); |
|
950 |
✓✓✓✓ ✓✓ |
101 |
if (blen > 0 && buffer[0] != ':') { |
951 |
35 |
SET_HAVE_USERNAME() |
|
952 |
} |
||
953 |
✓✓ | 491 |
for (size_t n = 0; n < blen; n++) { |
954 |
217 |
const char bch = buffer[n]; |
|
955 |
✓✓ | 217 |
if (bch == ':') { |
956 |
33 |
SET_HAVE_PASSWORD() |
|
957 |
✓✓ | 33 |
if (!uflag) { |
958 |
uflag = true; |
||
959 |
continue; |
||
960 |
} |
||
961 |
} |
||
962 |
✓✓ | 185 |
if (uflag) { |
963 |
83 |
AppendOrEscape(&url.password, bch, UserinfoEncodeSet); |
|
964 |
} else { |
||
965 |
102 |
AppendOrEscape(&url.username, bch, UserinfoEncodeSet); |
|
966 |
} |
||
967 |
} |
||
968 |
buffer.clear(); |
||
969 |
✓✓ | 10570 |
} else if (ch == kEOL || |
970 |
5285 |
ch == '/' || |
|
971 |
✓✓ | 9724 |
ch == '?' || |
972 |
✓✓ | 9712 |
ch == '#' || |
973 |
special_back_slash) { |
||
974 |
439 |
p -= buffer.size() + 1 + wskip; |
|
975 |
439 |
buffer.clear(); |
|
976 |
439 |
state = kHost; |
|
977 |
} else { |
||
978 |
4846 |
buffer += ch; |
|
979 |
} |
||
980 |
break; |
||
981 |
case kHost: |
||
982 |
case kHostname: |
||
983 |
✓✓ | 5181 |
if (ch == ':' && !sbflag) { |
984 |
✓✓✓✓ ✓✓ |
81 |
if (special && buffer.size() == 0) |
985 |
3 |
FAILED() |
|
986 |
78 |
SET_HAVE_HOST() |
|
987 |
✓✓ | 78 |
if (ParseHost(&buffer, &url.host) < 0) |
988 |
3 |
FAILED() |
|
989 |
75 |
buffer.clear(); |
|
990 |
75 |
state = kPort; |
|
991 |
✓✓ | 75 |
if (override == kHostname) |
992 |
2 |
TERMINATE() |
|
993 |
✓✓ | 10200 |
} else if (ch == kEOL || |
994 |
5100 |
ch == '/' || |
|
995 |
✓✓ | 9460 |
ch == '?' || |
996 |
✓✓ | 9444 |
ch == '#' || |
997 |
special_back_slash) { |
||
998 |
392 |
p--; |
|
999 |
✓✓✓✓ ✓✓ |
392 |
if (special && buffer.size() == 0) |
1000 |
11 |
FAILED() |
|
1001 |
381 |
SET_HAVE_HOST() |
|
1002 |
✓✓ | 381 |
if (ParseHost(&buffer, &url.host) < 0) |
1003 |
21 |
FAILED() |
|
1004 |
360 |
buffer.clear(); |
|
1005 |
360 |
state = kPathStart; |
|
1006 |
✓✓ | 360 |
if (state_override) |
1007 |
14 |
TERMINATE() |
|
1008 |
} else { |
||
1009 |
✓✓ | 4708 |
if (ch == '[') |
1010 |
9 |
sbflag = true; |
|
1011 |
✓✓ | 4708 |
if (ch == ']') |
1012 |
9 |
sbflag = false; |
|
1013 |
✓✓ | 4708 |
buffer += TO_LOWER(ch); |
1014 |
} |
||
1015 |
break; |
||
1016 |
case kPort: |
||
1017 |
✓✓ | 364 |
if (ASCII_DIGIT(ch)) { |
1018 |
278 |
buffer += ch; |
|
1019 |
✓✓ | 86 |
} else if (state_override || |
1020 |
86 |
ch == kEOL || |
|
1021 |
✓✓ | 96 |
ch == '/' || |
1022 |
48 |
ch == '?' || |
|
1023 |
✗✓ | 6 |
ch == '#' || |
1024 |
special_back_slash) { |
||
1025 |
✓✓ | 80 |
if (buffer.size() > 0) { |
1026 |
int port = 0; |
||
1027 |
✓✓ | 631 |
for (size_t i = 0; i < buffer.size(); i++) |
1028 |
554 |
port = port * 10 + buffer[i] - '0'; |
|
1029 |
✓✓ | 77 |
if (port >= 0 && port <= 0xffff) { |
1030 |
148 |
url.port = NormalizePort(url.scheme, port); |
|
1031 |
✓✓ | 3 |
} else if (!state_override) { |
1032 |
1 |
FAILED() |
|
1033 |
} |
||
1034 |
buffer.clear(); |
||
1035 |
} |
||
1036 |
state = kPathStart; |
||
1037 |
continue; |
||
1038 |
} else { |
||
1039 |
6 |
FAILED(); |
|
1040 |
} |
||
1041 |
break; |
||
1042 |
case kFile: |
||
1043 |
base_is_file = ( |
||
1044 |
✓✗ | 24 |
has_base && |
1045 |
✓✓✓✓ |
69 |
DOES_HAVE_SCHEME(base) && |
1046 |
69 |
base.scheme == "file:"); |
|
1047 |
✗✗✗✓ ✓ |
45 |
switch (ch) { |
1048 |
case kEOL: |
||
1049 |
if (base_is_file) { |
||
1050 |
if (DOES_HAVE_HOST(base)) { |
||
1051 |
SET_HAVE_HOST() |
||
1052 |
url.host = base.host; |
||
1053 |
} |
||
1054 |
if (DOES_HAVE_PATH(base)) { |
||
1055 |
SET_HAVE_PATH() |
||
1056 |
url.path = base.path; |
||
1057 |
} |
||
1058 |
if (DOES_HAVE_QUERY(base)) { |
||
1059 |
SET_HAVE_QUERY() |
||
1060 |
url.query = base.query; |
||
1061 |
} |
||
1062 |
} |
||
1063 |
break; |
||
1064 |
case '\\': |
||
1065 |
case '/': |
||
1066 |
state = kFileSlash; |
||
1067 |
break; |
||
1068 |
case '?': |
||
1069 |
if (base_is_file) { |
||
1070 |
if (DOES_HAVE_HOST(base)) { |
||
1071 |
SET_HAVE_HOST() |
||
1072 |
url.host = base.host; |
||
1073 |
} |
||
1074 |
if (DOES_HAVE_PATH(base)) { |
||
1075 |
SET_HAVE_PATH() |
||
1076 |
url.path = base.path; |
||
1077 |
} |
||
1078 |
SET_HAVE_QUERY() |
||
1079 |
state = kQuery; |
||
1080 |
} |
||
1081 |
break; |
||
1082 |
case '#': |
||
1083 |
if (base_is_file) { |
||
1084 |
if (DOES_HAVE_HOST(base)) { |
||
1085 |
SET_HAVE_HOST() |
||
1086 |
url.host = base.host; |
||
1087 |
} |
||
1088 |
if (DOES_HAVE_PATH(base)) { |
||
1089 |
SET_HAVE_PATH() |
||
1090 |
url.path = base.path; |
||
1091 |
} |
||
1092 |
if (DOES_HAVE_QUERY(base)) { |
||
1093 |
SET_HAVE_QUERY() |
||
1094 |
url.query = base.query; |
||
1095 |
} |
||
1096 |
state = kFragment; |
||
1097 |
} |
||
1098 |
break; |
||
1099 |
default: |
||
1100 |
✓✓✓✗ |
13 |
if (base_is_file && |
1101 |
✓✓✓✗ |
13 |
(!WINDOWS_DRIVE_LETTER(ch, p[1]) || |
1102 |
✓✗ | 6 |
end - p == 1 || |
1103 |
3 |
(p[2] != '/' && |
|
1104 |
p[2] != '\\' && |
||
1105 |
p[2] != '?' && |
||
1106 |
p[2] != '#'))) { |
||
1107 |
✗✓ | 2 |
if (DOES_HAVE_HOST(base)) { |
1108 |
SET_HAVE_HOST() |
||
1109 |
url.host = base.host; |
||
1110 |
} |
||
1111 |
✓✗ | 2 |
if (DOES_HAVE_PATH(base)) { |
1112 |
2 |
SET_HAVE_PATH() |
|
1113 |
2 |
url.path = base.path; |
|
1114 |
} |
||
1115 |
✗✓ | 2 |
if (!url.path.empty()) |
1116 |
2 |
url.path.pop_back(); |
|
1117 |
} |
||
1118 |
state = kPath; |
||
1119 |
continue; |
||
1120 |
} |
||
1121 |
break; |
||
1122 |
case kFileSlash: |
||
1123 |
✓✓ | 37 |
if (ch == '/' || ch == '\\') { |
1124 |
state = kFileHost; |
||
1125 |
} else { |
||
1126 |
✓✗✓✗ |
6 |
if (has_base && |
1127 |
✓✓ | 6 |
DOES_HAVE_SCHEME(base) && |
1128 |
✓✗ | 4 |
base.scheme == "file:" && |
1129 |
✓✗ | 2 |
DOES_HAVE_PATH(base) && |
1130 |
✓✗✗✓ |
5 |
base.path.size() > 0 && |
1131 |
✗✗✗✗ ✗✗✗✗ ✗✗ |
1 |
NORMALIZED_WINDOWS_DRIVE_LETTER(base.path[0])) { |
1132 |
SET_HAVE_PATH() |
||
1133 |
url.path.push_back(base.path[0]); |
||
1134 |
} |
||
1135 |
state = kPath; |
||
1136 |
continue; |
||
1137 |
} |
||
1138 |
break; |
||
1139 |
case kFileHost: |
||
1140 |
✓✓ | 170 |
if (ch == kEOL || |
1141 |
85 |
ch == '/' || |
|
1142 |
✓✓ | 52 |
ch == '\\' || |
1143 |
✗✓ | 103 |
ch == '?' || |
1144 |
ch == '#') { |
||
1145 |
✓✓✓✗ ✓✓ |
69 |
if (buffer.size() == 2 && |
1146 |
✗✓✗✗ ✗✗✓✗ ✓✗ |
4 |
WINDOWS_DRIVE_LETTER(buffer[0], buffer[1])) { |
1147 |
state = kPath; |
||
1148 |
✓✓ | 33 |
} else if (buffer.size() == 0) { |
1149 |
state = kPathStart; |
||
1150 |
} else { |
||
1151 |
✓✓ | 7 |
if (buffer != "localhost") { |
1152 |
4 |
SET_HAVE_HOST() |
|
1153 |
✗✓ | 4 |
if (ParseHost(&buffer, &url.host) < 0) |
1154 |
FAILED() |
||
1155 |
} |
||
1156 |
7 |
buffer.clear(); |
|
1157 |
7 |
state = kPathStart; |
|
1158 |
} |
||
1159 |
continue; |
||
1160 |
} else { |
||
1161 |
51 |
buffer += ch; |
|
1162 |
} |
||
1163 |
break; |
||
1164 |
case kPathStart: |
||
1165 |
465 |
state = kPath; |
|
1166 |
✓✓ | 465 |
if (ch != '/' && !special_back_slash) |
1167 |
continue; |
||
1168 |
break; |
||
1169 |
case kPath: |
||
1170 |
✓✓ | 6242 |
if (ch == kEOL || |
1171 |
✓✓ | 5443 |
ch == '/' || |
1172 |
✓✓ | 2305 |
special_back_slash || |
1173 |
✓✓ | 2124 |
(!state_override && (ch == '?' || ch == '#'))) { |
1174 |
✓✓ | 1690 |
if (IsDoubleDotSegment(buffer)) { |
1175 |
✓✓ | 36 |
if (!url.path.empty()) |
1176 |
18 |
url.path.pop_back(); |
|
1177 |
✓✓ | 36 |
if (ch != '/' && !special_back_slash) { |
1178 |
7 |
SET_HAVE_PATH() |
|
1179 |
28 |
url.path.push_back(""); |
|
1180 |
} |
||
1181 |
✓✓ | 1618 |
} else if (IsSingleDotSegment(buffer)) { |
1182 |
✓✓ | 10 |
if (ch != '/' && !special_back_slash) { |
1183 |
3 |
SET_HAVE_PATH(); |
|
1184 |
12 |
url.path.push_back(""); |
|
1185 |
} |
||
1186 |
} else { |
||
1187 |
✓✓✓✓ |
2355 |
if (DOES_HAVE_SCHEME(url) && |
1188 |
✓✓ | 860 |
url.scheme == "file:" && |
1189 |
✓✓ | 145 |
url.path.empty() && |
1190 |
✓✓✓✗ |
846 |
buffer.size() == 2 && |
1191 |
✓✓✓✗ ✓✗✓✓ ✓✗ |
23 |
WINDOWS_DRIVE_LETTER(buffer[0], buffer[1])) { |
1192 |
5 |
url.flags &= ~URL_FLAGS_HAS_HOST; |
|
1193 |
5 |
buffer[1] = ':'; |
|
1194 |
} |
||
1195 |
799 |
SET_HAVE_PATH() |
|
1196 |
3995 |
std::string segment(buffer.c_str(), buffer.size()); |
|
1197 |
799 |
url.path.push_back(segment); |
|
1198 |
} |
||
1199 |
845 |
buffer.clear(); |
|
1200 |
✓✓ | 845 |
if (ch == '?') { |
1201 |
23 |
SET_HAVE_QUERY() |
|
1202 |
23 |
state = kQuery; |
|
1203 |
✓✓ | 822 |
} else if (ch == '#') { |
1204 |
6 |
state = kFragment; |
|
1205 |
} |
||
1206 |
} else { |
||
1207 |
✓✓✓✓ ✓✓✓✓ ✗✓ |
2276 |
if (ch == '%' && p[1] == '2' && TO_LOWER(p[2]) == 'e') { |
1208 |
15 |
buffer += '.'; |
|
1209 |
15 |
p += 2; |
|
1210 |
} else { |
||
1211 |
2261 |
AppendOrEscape(&buffer, ch, DefaultEncodeSet); |
|
1212 |
} |
||
1213 |
} |
||
1214 |
break; |
||
1215 |
case kCannotBeBase: |
||
1216 |
✓✓✓ | 1177 |
switch (ch) { |
1217 |
case '?': |
||
1218 |
state = kQuery; |
||
1219 |
break; |
||
1220 |
case '#': |
||
1221 |
2 |
state = kFragment; |
|
1222 |
2 |
break; |
|
1223 |
default: |
||
1224 |
✗✓ | 2348 |
if (url.path.size() == 0) |
1225 |
url.path.push_back(""); |
||
1226 |
✓✗✓✓ ✓✓ |
2348 |
if (url.path.size() > 0 && ch != kEOL) |
1227 |
1011 |
AppendOrEscape(&url.path[0], ch, SimpleEncodeSet); |
|
1228 |
} |
||
1229 |
break; |
||
1230 |
case kQuery: |
||
1231 |
✓✓✓✓ |
520 |
if (ch == kEOL || (!state_override && ch == '#')) { |
1232 |
46 |
SET_HAVE_QUERY() |
|
1233 |
46 |
url.query = buffer; |
|
1234 |
46 |
buffer.clear(); |
|
1235 |
✓✓ | 46 |
if (ch == '#') |
1236 |
14 |
state = kFragment; |
|
1237 |
} else { |
||
1238 |
474 |
AppendOrEscape(&buffer, ch, QueryEncodeSet); |
|
1239 |
} |
||
1240 |
break; |
||
1241 |
case kFragment: |
||
1242 |
✓✓✓ | 196 |
switch (ch) { |
1243 |
case kEOL: |
||
1244 |
43 |
SET_HAVE_FRAGMENT() |
|
1245 |
url.fragment = buffer; |
||
1246 |
break; |
||
1247 |
case 0: |
||
1248 |
break; |
||
1249 |
default: |
||
1250 |
152 |
buffer += ch; |
|
1251 |
} |
||
1252 |
break; |
||
1253 |
default: |
||
1254 |
INVALID_PARSE_STATE() |
||
1255 |
goto done; |
||
1256 |
} |
||
1257 |
|||
1258 |
18047 |
p++; |
|
1259 |
} |
||
1260 |
|||
1261 |
done: |
||
1262 |
|||
1263 |
// Define the return value placeholders |
||
1264 |
1692 |
const Local<Value> undef = Undefined(isolate); |
|
1265 |
Local<Value> argv[9] = { |
||
1266 |
undef, |
||
1267 |
undef, |
||
1268 |
undef, |
||
1269 |
undef, |
||
1270 |
undef, |
||
1271 |
undef, |
||
1272 |
undef, |
||
1273 |
undef, |
||
1274 |
undef, |
||
1275 |
846 |
}; |
|
1276 |
|||
1277 |
1692 |
argv[ARG_FLAGS] = Integer::NewFromUnsigned(isolate, url.flags); |
|
1278 |
✓✓ | 846 |
if (!IS_FAILED(url.flags)) { |
1279 |
✓✓ | 793 |
if (DOES_HAVE_SCHEME(url)) |
1280 |
1424 |
argv[ARG_PROTOCOL] = OneByteString(isolate, url.scheme.c_str()); |
|
1281 |
✓✓ | 793 |
if (DOES_HAVE_USERNAME(url)) |
1282 |
93 |
argv[ARG_USERNAME] = UTF8STRING(isolate, url.username); |
|
1283 |
✓✓ | 793 |
if (DOES_HAVE_PASSWORD(url)) |
1284 |
84 |
argv[ARG_PASSWORD] = UTF8STRING(isolate, url.password); |
|
1285 |
✓✓ | 793 |
if (DOES_HAVE_HOST(url)) |
1286 |
1461 |
argv[ARG_HOST] = UTF8STRING(isolate, url.host); |
|
1287 |
✓✓ | 793 |
if (DOES_HAVE_QUERY(url)) |
1288 |
141 |
argv[ARG_QUERY] = UTF8STRING(isolate, url.query); |
|
1289 |
✓✓ | 793 |
if (DOES_HAVE_FRAGMENT(url)) |
1290 |
129 |
argv[ARG_FRAGMENT] = UTF8STRING(isolate, url.fragment); |
|
1291 |
✓✓ | 793 |
if (url.port > -1) |
1292 |
140 |
argv[ARG_PORT] = Integer::New(isolate, url.port); |
|
1293 |
✓✓ | 793 |
if (DOES_HAVE_PATH(url)) |
1294 |
1480 |
argv[ARG_PATH] = Copy(isolate, url.path); |
|
1295 |
} |
||
1296 |
|||
1297 |
846 |
cb->Call(context, recv, 9, argv); |
|
1298 |
846 |
} |
|
1299 |
|||
1300 |
846 |
static void Parse(const FunctionCallbackInfo<Value>& args) { |
|
1301 |
846 |
Environment* env = Environment::GetCurrent(args); |
|
1302 |
✗✓ | 846 |
CHECK_GE(args.Length(), 5); |
1303 |
✗✓ | 1692 |
CHECK(args[0]->IsString()); |
1304 |
✓✓✓✓ ✓✓✓✗ ✗✓ |
3665 |
CHECK(args[2]->IsUndefined() || |
1305 |
args[2]->IsNull() || |
||
1306 |
args[2]->IsObject()); |
||
1307 |
✓✓✗✓ ✓✓✓✗ ✗✓ |
2811 |
CHECK(args[3]->IsUndefined() || |
1308 |
args[3]->IsNull() || |
||
1309 |
args[3]->IsObject()); |
||
1310 |
✗✓ | 846 |
CHECK(args[4]->IsFunction()); |
1311 |
1692 |
Utf8Value input(env->isolate(), args[0]); |
|
1312 |
846 |
enum url_parse_state override = kUnknownState; |
|
1313 |
✓✗ | 846 |
if (args[1]->IsNumber()) |
1314 |
846 |
override = (enum url_parse_state)(args[1]->Uint32Value()); |
|
1315 |
|||
1316 |
7614 |
Parse(env, args.This(), |
|
1317 |
846 |
*input, input.length(), |
|
1318 |
override, |
||
1319 |
args[2].As<Object>(), |
||
1320 |
args[3].As<Object>(), |
||
1321 |
846 |
args[4].As<Function>()); |
|
1322 |
846 |
} |
|
1323 |
|||
1324 |
8 |
static void EncodeAuthSet(const FunctionCallbackInfo<Value>& args) { |
|
1325 |
8 |
Environment* env = Environment::GetCurrent(args); |
|
1326 |
✗✓ | 8 |
CHECK_GE(args.Length(), 1); |
1327 |
✗✓ | 16 |
CHECK(args[0]->IsString()); |
1328 |
16 |
Utf8Value value(env->isolate(), args[0]); |
|
1329 |
16 |
std::string output; |
|
1330 |
8 |
const size_t len = value.length(); |
|
1331 |
8 |
output.reserve(len); |
|
1332 |
✓✓ | 150 |
for (size_t n = 0; n < len; n++) { |
1333 |
142 |
const char ch = (*value)[n]; |
|
1334 |
142 |
AppendOrEscape(&output, ch, UserinfoEncodeSet); |
|
1335 |
} |
||
1336 |
16 |
args.GetReturnValue().Set( |
|
1337 |
16 |
String::NewFromUtf8(env->isolate(), |
|
1338 |
output.c_str(), |
||
1339 |
16 |
v8::NewStringType::kNormal).ToLocalChecked()); |
|
1340 |
8 |
} |
|
1341 |
|||
1342 |
static void DomainToASCII(const FunctionCallbackInfo<Value>& args) { |
||
1343 |
Environment* env = Environment::GetCurrent(args); |
||
1344 |
CHECK_GE(args.Length(), 1); |
||
1345 |
CHECK(args[0]->IsString()); |
||
1346 |
Utf8Value value(env->isolate(), args[0]); |
||
1347 |
|||
1348 |
url_host host{{""}, HOST_TYPE_DOMAIN}; |
||
1349 |
ParseHost(&host, *value, value.length()); |
||
1350 |
if (host.type == HOST_TYPE_FAILED) { |
||
1351 |
args.GetReturnValue().Set(FIXED_ONE_BYTE_STRING(env->isolate(), "")); |
||
1352 |
return; |
||
1353 |
} |
||
1354 |
std::string out; |
||
1355 |
WriteHost(&host, &out); |
||
1356 |
args.GetReturnValue().Set( |
||
1357 |
String::NewFromUtf8(env->isolate(), |
||
1358 |
out.c_str(), |
||
1359 |
v8::NewStringType::kNormal).ToLocalChecked()); |
||
1360 |
} |
||
1361 |
|||
1362 |
static void DomainToUnicode(const FunctionCallbackInfo<Value>& args) { |
||
1363 |
Environment* env = Environment::GetCurrent(args); |
||
1364 |
CHECK_GE(args.Length(), 1); |
||
1365 |
CHECK(args[0]->IsString()); |
||
1366 |
Utf8Value value(env->isolate(), args[0]); |
||
1367 |
|||
1368 |
url_host host{{""}, HOST_TYPE_DOMAIN}; |
||
1369 |
ParseHost(&host, *value, value.length(), true); |
||
1370 |
if (host.type == HOST_TYPE_FAILED) { |
||
1371 |
args.GetReturnValue().Set(FIXED_ONE_BYTE_STRING(env->isolate(), "")); |
||
1372 |
return; |
||
1373 |
} |
||
1374 |
std::string out; |
||
1375 |
WriteHost(&host, &out); |
||
1376 |
args.GetReturnValue().Set( |
||
1377 |
String::NewFromUtf8(env->isolate(), |
||
1378 |
out.c_str(), |
||
1379 |
v8::NewStringType::kNormal).ToLocalChecked()); |
||
1380 |
} |
||
1381 |
|||
1382 |
387 |
static void Init(Local<Object> target, |
|
1383 |
Local<Value> unused, |
||
1384 |
Local<Context> context, |
||
1385 |
void* priv) { |
||
1386 |
387 |
Environment* env = Environment::GetCurrent(context); |
|
1387 |
387 |
env->SetMethod(target, "parse", Parse); |
|
1388 |
387 |
env->SetMethod(target, "encodeAuth", EncodeAuthSet); |
|
1389 |
387 |
env->SetMethod(target, "domainToASCII", DomainToASCII); |
|
1390 |
387 |
env->SetMethod(target, "domainToUnicode", DomainToUnicode); |
|
1391 |
|||
1392 |
#define XX(name, _) NODE_DEFINE_CONSTANT(target, name); |
||
1393 |
15480 |
FLAGS(XX) |
|
1394 |
#undef XX |
||
1395 |
|||
1396 |
#define XX(name) NODE_DEFINE_CONSTANT(target, name); |
||
1397 |
10836 |
ARGS(XX) |
|
1398 |
24768 |
PARSESTATES(XX) |
|
1399 |
#undef XX |
||
1400 |
387 |
} |
|
1401 |
} // namespace url |
||
1402 |
} // namespace node |
||
1403 |
|||
1404 |
1705 |
NODE_MODULE_CONTEXT_AWARE_BUILTIN(url, node::url::Init) |
Generated by: GCOVR (Version 3.3) |