/src/node/src/node_url.cc
Line | Count | Source |
1 | | #include "node_url.h" |
2 | | #include "ada.h" |
3 | | #include "base_object-inl.h" |
4 | | #include "node_debug.h" |
5 | | #include "node_errors.h" |
6 | | #include "node_external_reference.h" |
7 | | #include "node_i18n.h" |
8 | | #include "node_metadata.h" |
9 | | #include "node_process-inl.h" |
10 | | #include "path.h" |
11 | | #include "util-inl.h" |
12 | | #include "v8-fast-api-calls.h" |
13 | | #include "v8-local-handle.h" |
14 | | #include "v8.h" |
15 | | |
16 | | #include <cstdint> |
17 | | #include <cstdio> |
18 | | #include <numeric> |
19 | | |
20 | | namespace node { |
21 | | namespace url { |
22 | | |
23 | | using v8::CFunction; |
24 | | using v8::Context; |
25 | | using v8::FastApiCallbackOptions; |
26 | | using v8::FunctionCallbackInfo; |
27 | | using v8::HandleScope; |
28 | | using v8::Isolate; |
29 | | using v8::Local; |
30 | | using v8::Object; |
31 | | using v8::ObjectTemplate; |
32 | | using v8::SnapshotCreator; |
33 | | using v8::String; |
34 | | using v8::Value; |
35 | | |
36 | 0 | void BindingData::MemoryInfo(MemoryTracker* tracker) const { |
37 | 0 | tracker->TrackField("url_components_buffer", url_components_buffer_); |
38 | 0 | } |
39 | | |
40 | | BindingData::BindingData(Realm* realm, Local<Object> object) |
41 | 35 | : SnapshotableObject(realm, object, type_int), |
42 | 35 | url_components_buffer_(realm->isolate(), kURLComponentsLength) { |
43 | 35 | object |
44 | 35 | ->Set(realm->context(), |
45 | 35 | FIXED_ONE_BYTE_STRING(realm->isolate(), "urlComponents"), |
46 | 35 | url_components_buffer_.GetJSArray()) |
47 | 35 | .Check(); |
48 | 35 | url_components_buffer_.MakeWeak(); |
49 | 35 | } |
50 | | |
51 | | bool BindingData::PrepareForSerialization(Local<Context> context, |
52 | 0 | SnapshotCreator* creator) { |
53 | | // We'll just re-initialize the buffers in the constructor since their |
54 | | // contents can be thrown away once consumed in the previous call. |
55 | 0 | url_components_buffer_.Release(); |
56 | | // Return true because we need to maintain the reference to the binding from |
57 | | // JS land. |
58 | 0 | return true; |
59 | 0 | } |
60 | | |
61 | 0 | InternalFieldInfoBase* BindingData::Serialize(int index) { |
62 | 0 | DCHECK_IS_SNAPSHOT_SLOT(index); |
63 | 0 | InternalFieldInfo* info = |
64 | 0 | InternalFieldInfoBase::New<InternalFieldInfo>(type()); |
65 | 0 | return info; |
66 | 0 | } |
67 | | |
68 | | void BindingData::Deserialize(Local<Context> context, |
69 | | Local<Object> holder, |
70 | | int index, |
71 | 0 | InternalFieldInfoBase* info) { |
72 | 0 | DCHECK_IS_SNAPSHOT_SLOT(index); |
73 | 0 | HandleScope scope(Isolate::GetCurrent()); |
74 | 0 | Realm* realm = Realm::GetCurrent(context); |
75 | 0 | BindingData* binding = realm->AddBindingData<BindingData>(holder); |
76 | 0 | CHECK_NOT_NULL(binding); |
77 | 0 | } |
78 | | |
79 | | #ifndef LARGEST_ASCII_CHAR_CODE_TO_ENCODE |
80 | 0 | #define LARGEST_ASCII_CHAR_CODE_TO_ENCODE '~' |
81 | | #endif |
82 | | |
83 | | // RFC1738 defines the following chars as "unsafe" for URLs |
84 | | // @see https://www.ietf.org/rfc/rfc1738.txt 2.2. URL Character Encoding Issues |
85 | | constexpr auto lookup_table = []() consteval { |
86 | | // Each entry is an array that can hold up to 3 chars + null terminator |
87 | | std::array<std::array<char, 4>, LARGEST_ASCII_CHAR_CODE_TO_ENCODE + 1> |
88 | | result{}; |
89 | | |
90 | | for (uint8_t i = 0; i <= LARGEST_ASCII_CHAR_CODE_TO_ENCODE; i++) { |
91 | | switch (i) { |
92 | | #define ENCODE_CHAR(CHAR, HEX_DIGIT_2, HEX_DIGIT_1) \ |
93 | | case CHAR: \ |
94 | | result[i] = {{'%', HEX_DIGIT_2, HEX_DIGIT_1, 0}}; \ |
95 | | break; |
96 | | |
97 | | ENCODE_CHAR('\0', '0', '0') // '\0' == 0x00 |
98 | | ENCODE_CHAR('\t', '0', '9') // '\t' == 0x09 |
99 | | ENCODE_CHAR('\n', '0', 'A') // '\n' == 0x0A |
100 | | ENCODE_CHAR('\r', '0', 'D') // '\r' == 0x0D |
101 | | ENCODE_CHAR(' ', '2', '0') // ' ' == 0x20 |
102 | | ENCODE_CHAR('"', '2', '2') // '"' == 0x22 |
103 | | ENCODE_CHAR('#', '2', '3') // '#' == 0x23 |
104 | | ENCODE_CHAR('%', '2', '5') // '%' == 0x25 |
105 | | ENCODE_CHAR('?', '3', 'F') // '?' == 0x3F |
106 | | ENCODE_CHAR('[', '5', 'B') // '[' == 0x5B |
107 | | ENCODE_CHAR('\\', '5', 'C') // '\\' == 0x5C |
108 | | ENCODE_CHAR(']', '5', 'D') // ']' == 0x5D |
109 | | ENCODE_CHAR('^', '5', 'E') // '^' == 0x5E |
110 | | ENCODE_CHAR('|', '7', 'C') // '|' == 0x7C |
111 | | ENCODE_CHAR('~', '7', 'E') // '~' == 0x7E |
112 | | #undef ENCODE_CHAR |
113 | | |
114 | | default: |
115 | | result[i] = {{static_cast<char>(i), '\0', '\0', '\0'}}; |
116 | | break; |
117 | | } |
118 | | } |
119 | | |
120 | | return result; |
121 | | } |
122 | | (); |
123 | | |
124 | | enum class OS { WINDOWS, POSIX }; |
125 | | |
126 | 0 | std::string EncodePathChars(std::string_view input_str, OS operating_system) { |
127 | 0 | std::string encoded = "file://"; |
128 | 0 | encoded.reserve(input_str.size() + |
129 | 0 | 7); // Reserve space for "file://" and input_str |
130 | 0 | for (size_t i : input_str) { |
131 | 0 | if (i > LARGEST_ASCII_CHAR_CODE_TO_ENCODE) [[unlikely]] { |
132 | 0 | encoded.push_back(i); |
133 | 0 | continue; |
134 | 0 | } |
135 | 0 | if (operating_system == OS::WINDOWS) { |
136 | 0 | if (i == '\\') { |
137 | 0 | encoded.push_back('/'); |
138 | 0 | continue; |
139 | 0 | } |
140 | 0 | } |
141 | 0 | encoded.append(lookup_table[i].data()); |
142 | 0 | } |
143 | |
|
144 | 0 | return encoded; |
145 | 0 | } |
146 | | |
147 | 0 | void BindingData::PathToFileURL(const FunctionCallbackInfo<Value>& args) { |
148 | 0 | CHECK_GE(args.Length(), 2); // input |
149 | 0 | CHECK(args[0]->IsString()); |
150 | 0 | CHECK(args[1]->IsBoolean()); |
151 | | |
152 | 0 | Realm* realm = Realm::GetCurrent(args); |
153 | 0 | BindingData* binding_data = realm->GetBindingData<BindingData>(); |
154 | 0 | Isolate* isolate = realm->isolate(); |
155 | 0 | OS os = args[1]->IsTrue() ? OS::WINDOWS : OS::POSIX; |
156 | |
|
157 | 0 | Utf8Value input(isolate, args[0]); |
158 | 0 | auto input_str = input.ToStringView(); |
159 | 0 | CHECK(!input_str.empty()); |
160 | | |
161 | 0 | auto out = |
162 | 0 | ada::parse<ada::url_aggregator>(EncodePathChars(input_str, os), nullptr); |
163 | |
|
164 | 0 | if (!out) { |
165 | 0 | return ThrowInvalidURL(realm->env(), input.ToStringView(), std::nullopt); |
166 | 0 | } |
167 | | |
168 | 0 | if (os == OS::WINDOWS && args.Length() > 2 && !args[2]->IsUndefined()) |
169 | 0 | [[unlikely]] { |
170 | 0 | CHECK(args[2]->IsString()); |
171 | 0 | Utf8Value hostname(isolate, args[2]); |
172 | 0 | CHECK(out->set_hostname(hostname.ToStringView())); |
173 | 0 | } |
174 | | |
175 | 0 | binding_data->UpdateComponents(out->get_components(), out->type); |
176 | |
|
177 | 0 | Local<Value> ret; |
178 | 0 | if (ToV8Value(realm->context(), out->get_href(), isolate).ToLocal(&ret)) |
179 | 0 | [[likely]] { |
180 | 0 | args.GetReturnValue().Set(ret); |
181 | 0 | } |
182 | 0 | } |
183 | | |
184 | 0 | void BindingData::DomainToASCII(const FunctionCallbackInfo<Value>& args) { |
185 | 0 | Environment* env = Environment::GetCurrent(args); |
186 | 0 | CHECK_GE(args.Length(), 1); // input |
187 | 0 | CHECK(args[0]->IsString()); |
188 | | |
189 | 0 | Utf8Value input(env->isolate(), args[0]); |
190 | 0 | if (input.ToStringView().empty()) { |
191 | 0 | return args.GetReturnValue().SetEmptyString(); |
192 | 0 | } |
193 | | |
194 | | // It is important to have an initial value that contains a special scheme. |
195 | | // Since it will change the implementation of `set_hostname` according to URL |
196 | | // spec. |
197 | 0 | auto out = ada::parse<ada::url>("ws://x"); |
198 | 0 | DCHECK(out); |
199 | 0 | if (!out->set_hostname(input.ToStringView())) { |
200 | 0 | return args.GetReturnValue().Set(String::Empty(env->isolate())); |
201 | 0 | } |
202 | 0 | std::string host = out->get_hostname(); |
203 | |
|
204 | 0 | Local<Value> ret; |
205 | 0 | if (ToV8Value(env->context(), host, env->isolate()).ToLocal(&ret)) |
206 | 0 | [[likely]] { |
207 | 0 | args.GetReturnValue().Set(ret); |
208 | 0 | } |
209 | 0 | } |
210 | | |
211 | 0 | void BindingData::DomainToUnicode(const FunctionCallbackInfo<Value>& args) { |
212 | 0 | Environment* env = Environment::GetCurrent(args); |
213 | 0 | CHECK_GE(args.Length(), 1); // input |
214 | 0 | CHECK(args[0]->IsString()); |
215 | | |
216 | 0 | Utf8Value input(env->isolate(), args[0]); |
217 | 0 | if (input.ToStringView().empty()) { |
218 | 0 | return args.GetReturnValue().SetEmptyString(); |
219 | 0 | } |
220 | | |
221 | | // It is important to have an initial value that contains a special scheme. |
222 | | // Since it will change the implementation of `set_hostname` according to URL |
223 | | // spec. |
224 | 0 | auto out = ada::parse<ada::url>("ws://x"); |
225 | 0 | DCHECK(out); |
226 | 0 | if (!out->set_hostname(input.ToStringView())) { |
227 | 0 | return args.GetReturnValue().Set(String::Empty(env->isolate())); |
228 | 0 | } |
229 | 0 | std::string result = ada::idna::to_unicode(out->get_hostname()); |
230 | |
|
231 | 0 | Local<Value> ret; |
232 | 0 | if (ToV8Value(env->context(), result, env->isolate()).ToLocal(&ret)) |
233 | 0 | [[likely]] { |
234 | 0 | args.GetReturnValue().Set(ret); |
235 | 0 | } |
236 | 0 | } |
237 | | |
238 | 0 | void BindingData::GetOrigin(const FunctionCallbackInfo<Value>& args) { |
239 | 0 | CHECK_GE(args.Length(), 1); |
240 | 0 | CHECK(args[0]->IsString()); // input |
241 | | |
242 | 0 | Environment* env = Environment::GetCurrent(args); |
243 | 0 | HandleScope handle_scope(env->isolate()); |
244 | |
|
245 | 0 | Utf8Value input(env->isolate(), args[0]); |
246 | 0 | std::string_view input_view = input.ToStringView(); |
247 | 0 | auto out = ada::parse<ada::url_aggregator>(input_view); |
248 | |
|
249 | 0 | if (!out) { |
250 | 0 | THROW_ERR_INVALID_URL(env, "Invalid URL"); |
251 | 0 | return; |
252 | 0 | } |
253 | | |
254 | 0 | std::string origin = out->get_origin(); |
255 | |
|
256 | 0 | Local<Value> ret; |
257 | 0 | if (ToV8Value(env->context(), origin, env->isolate()).ToLocal(&ret)) |
258 | 0 | [[likely]] { |
259 | 0 | args.GetReturnValue().Set(ret); |
260 | 0 | } |
261 | 0 | } |
262 | | |
263 | 0 | void BindingData::CanParse(const FunctionCallbackInfo<Value>& args) { |
264 | 0 | CHECK_GE(args.Length(), 1); |
265 | 0 | CHECK(args[0]->IsString()); // input |
266 | | // args[1] // base url |
267 | | |
268 | 0 | Environment* env = Environment::GetCurrent(args); |
269 | 0 | HandleScope handle_scope(env->isolate()); |
270 | |
|
271 | 0 | Utf8Value input(env->isolate(), args[0]); |
272 | 0 | std::string_view input_view = input.ToStringView(); |
273 | |
|
274 | 0 | bool can_parse{}; |
275 | 0 | if (args[1]->IsString()) { |
276 | 0 | Utf8Value base(env->isolate(), args[1]); |
277 | 0 | std::string_view base_view = base.ToStringView(); |
278 | 0 | can_parse = ada::can_parse(input_view, &base_view); |
279 | 0 | } else { |
280 | 0 | can_parse = ada::can_parse(input_view); |
281 | 0 | } |
282 | |
|
283 | 0 | args.GetReturnValue().Set(can_parse); |
284 | 0 | } |
285 | | |
286 | | bool BindingData::FastCanParse( |
287 | | Local<Value> receiver, |
288 | | Local<Value> input, |
289 | | // NOLINTNEXTLINE(runtime/references) This is V8 api. |
290 | 0 | FastApiCallbackOptions& options) { |
291 | 0 | TRACK_V8_FAST_API_CALL("url.canParse"); |
292 | 0 | auto isolate = options.isolate; |
293 | 0 | HandleScope handleScope(isolate); |
294 | 0 | Local<String> str; |
295 | 0 | if (!input->ToString(isolate->GetCurrentContext()).ToLocal(&str)) { |
296 | 0 | return false; |
297 | 0 | } |
298 | 0 | Utf8Value utf8(isolate, str); |
299 | 0 | return ada::can_parse(utf8.ToStringView()); |
300 | 0 | } |
301 | | |
302 | | bool BindingData::FastCanParseWithBase( |
303 | | Local<Value> receiver, |
304 | | Local<Value> input, |
305 | | Local<Value> base, |
306 | | // NOLINTNEXTLINE(runtime/references) This is V8 api. |
307 | 0 | FastApiCallbackOptions& options) { |
308 | 0 | TRACK_V8_FAST_API_CALL("url.canParse.withBase"); |
309 | 0 | auto isolate = options.isolate; |
310 | 0 | HandleScope handleScope(isolate); |
311 | 0 | auto context = isolate->GetCurrentContext(); |
312 | 0 | Local<String> input_str; |
313 | 0 | if (!input->ToString(context).ToLocal(&input_str)) { |
314 | 0 | return false; |
315 | 0 | } |
316 | 0 | Local<String> base_str; |
317 | 0 | if (!base->ToString(context).ToLocal(&base_str)) { |
318 | 0 | return false; |
319 | 0 | } |
320 | 0 | Utf8Value input_utf8(isolate, input_str); |
321 | 0 | Utf8Value base_utf8(isolate, base_str); |
322 | |
|
323 | 0 | auto base_view = base_utf8.ToStringView(); |
324 | 0 | return ada::can_parse(input_utf8.ToStringView(), &base_view); |
325 | 0 | } |
326 | | |
327 | | CFunction BindingData::fast_can_parse_methods_[] = { |
328 | | CFunction::Make(FastCanParse), CFunction::Make(FastCanParseWithBase)}; |
329 | | |
330 | 0 | void BindingData::Format(const FunctionCallbackInfo<Value>& args) { |
331 | 0 | CHECK_GT(args.Length(), 4); |
332 | 0 | CHECK(args[0]->IsString()); // url href |
333 | | |
334 | 0 | Environment* env = Environment::GetCurrent(args); |
335 | 0 | Isolate* isolate = env->isolate(); |
336 | |
|
337 | 0 | Utf8Value href(isolate, args[0].As<String>()); |
338 | 0 | const bool hash = args[1]->IsTrue(); |
339 | 0 | const bool unicode = args[2]->IsTrue(); |
340 | 0 | const bool search = args[3]->IsTrue(); |
341 | 0 | const bool auth = args[4]->IsTrue(); |
342 | | |
343 | | // ada::url provides a faster alternative to ada::url_aggregator if we |
344 | | // directly want to manipulate the url components without using the respective |
345 | | // setters. therefore we are using ada::url here. |
346 | 0 | auto out = ada::parse<ada::url>(href.ToStringView()); |
347 | 0 | CHECK(out); |
348 | | |
349 | 0 | if (!hash) { |
350 | 0 | out->hash = std::nullopt; |
351 | 0 | } |
352 | |
|
353 | 0 | if (unicode && out->has_hostname()) { |
354 | 0 | out->host = ada::idna::to_unicode(out->get_hostname()); |
355 | 0 | } |
356 | |
|
357 | 0 | if (!search) { |
358 | 0 | out->query = std::nullopt; |
359 | 0 | } |
360 | |
|
361 | 0 | if (!auth) { |
362 | 0 | out->username = ""; |
363 | 0 | out->password = ""; |
364 | 0 | } |
365 | |
|
366 | 0 | std::string result = out->get_href(); |
367 | |
|
368 | 0 | Local<Value> ret; |
369 | 0 | if (ToV8Value(env->context(), result, env->isolate()).ToLocal(&ret)) |
370 | 0 | [[likely]] { |
371 | 0 | args.GetReturnValue().Set(ret); |
372 | 0 | } |
373 | 0 | } |
374 | | |
375 | 0 | void BindingData::Parse(const FunctionCallbackInfo<Value>& args) { |
376 | 0 | CHECK_GE(args.Length(), 1); |
377 | 0 | CHECK(args[0]->IsString()); // input |
378 | | // args[1] // base url |
379 | | // args[2] // raise Exception |
380 | | |
381 | 0 | const bool raise_exception = args.Length() > 2 && args[2]->IsTrue(); |
382 | |
|
383 | 0 | Realm* realm = Realm::GetCurrent(args); |
384 | 0 | BindingData* binding_data = realm->GetBindingData<BindingData>(); |
385 | 0 | Isolate* isolate = realm->isolate(); |
386 | 0 | std::optional<std::string> base_{}; |
387 | |
|
388 | 0 | Utf8Value input(isolate, args[0]); |
389 | 0 | ada::result<ada::url_aggregator> base; |
390 | 0 | ada::url_aggregator* base_pointer = nullptr; |
391 | 0 | if (args[1]->IsString()) { |
392 | 0 | base_ = Utf8Value(isolate, args[1]).ToString(); |
393 | 0 | base = ada::parse<ada::url_aggregator>(*base_); |
394 | 0 | if (!base && raise_exception) { |
395 | 0 | return ThrowInvalidURL(realm->env(), input.ToStringView(), base_); |
396 | 0 | } else if (!base) { |
397 | 0 | return; |
398 | 0 | } |
399 | 0 | base_pointer = &base.value(); |
400 | 0 | } |
401 | 0 | auto out = |
402 | 0 | ada::parse<ada::url_aggregator>(input.ToStringView(), base_pointer); |
403 | |
|
404 | 0 | if (!out && raise_exception) { |
405 | 0 | return ThrowInvalidURL(realm->env(), input.ToStringView(), base_); |
406 | 0 | } else if (!out) { |
407 | 0 | return; |
408 | 0 | } |
409 | | |
410 | 0 | binding_data->UpdateComponents(out->get_components(), out->type); |
411 | |
|
412 | 0 | Local<Value> ret; |
413 | 0 | if (ToV8Value(realm->context(), out->get_href(), isolate).ToLocal(&ret)) |
414 | 0 | [[likely]] { |
415 | 0 | args.GetReturnValue().Set(ret); |
416 | 0 | } |
417 | 0 | } |
418 | | |
419 | 0 | void BindingData::Update(const FunctionCallbackInfo<Value>& args) { |
420 | 0 | CHECK(args[0]->IsString()); // href |
421 | 0 | CHECK(args[1]->IsNumber()); // action type |
422 | 0 | CHECK(args[2]->IsString()); // new value |
423 | | |
424 | 0 | Realm* realm = Realm::GetCurrent(args); |
425 | 0 | BindingData* binding_data = realm->GetBindingData<BindingData>(); |
426 | 0 | Isolate* isolate = realm->isolate(); |
427 | |
|
428 | 0 | uint32_t val; |
429 | 0 | if (!args[1]->Uint32Value(realm->context()).To(&val)) { |
430 | 0 | return; |
431 | 0 | } |
432 | 0 | enum url_update_action action = static_cast<enum url_update_action>(val); |
433 | 0 | Utf8Value input(isolate, args[0].As<String>()); |
434 | 0 | Utf8Value new_value(isolate, args[2].As<String>()); |
435 | |
|
436 | 0 | std::string_view new_value_view = new_value.ToStringView(); |
437 | 0 | auto out = ada::parse<ada::url_aggregator>(input.ToStringView()); |
438 | 0 | CHECK(out); |
439 | | |
440 | 0 | bool result{true}; |
441 | |
|
442 | 0 | switch (action) { |
443 | 0 | case kPathname: { |
444 | 0 | result = out->set_pathname(new_value_view); |
445 | 0 | break; |
446 | 0 | } |
447 | 0 | case kHash: { |
448 | 0 | out->set_hash(new_value_view); |
449 | 0 | break; |
450 | 0 | } |
451 | 0 | case kHost: { |
452 | 0 | result = out->set_host(new_value_view); |
453 | 0 | break; |
454 | 0 | } |
455 | 0 | case kHostname: { |
456 | 0 | result = out->set_hostname(new_value_view); |
457 | 0 | break; |
458 | 0 | } |
459 | 0 | case kHref: { |
460 | 0 | result = out->set_href(new_value_view); |
461 | 0 | break; |
462 | 0 | } |
463 | 0 | case kPassword: { |
464 | 0 | result = out->set_password(new_value_view); |
465 | 0 | break; |
466 | 0 | } |
467 | 0 | case kPort: { |
468 | 0 | result = out->set_port(new_value_view); |
469 | 0 | break; |
470 | 0 | } |
471 | 0 | case kProtocol: { |
472 | 0 | result = out->set_protocol(new_value_view); |
473 | 0 | break; |
474 | 0 | } |
475 | 0 | case kSearch: { |
476 | 0 | out->set_search(new_value_view); |
477 | 0 | break; |
478 | 0 | } |
479 | 0 | case kUsername: { |
480 | 0 | result = out->set_username(new_value_view); |
481 | 0 | break; |
482 | 0 | } |
483 | 0 | default: |
484 | 0 | UNREACHABLE("Unsupported URL update action"); |
485 | 0 | } |
486 | | |
487 | 0 | if (!result) { |
488 | 0 | return args.GetReturnValue().Set(false); |
489 | 0 | } |
490 | | |
491 | 0 | binding_data->UpdateComponents(out->get_components(), out->type); |
492 | |
|
493 | 0 | Local<Value> ret; |
494 | 0 | if (ToV8Value(realm->context(), out->get_href(), isolate).ToLocal(&ret)) |
495 | 0 | [[likely]] { |
496 | 0 | args.GetReturnValue().Set(ret); |
497 | 0 | } |
498 | 0 | } |
499 | | |
500 | | void BindingData::UpdateComponents(const ada::url_components& components, |
501 | 0 | const ada::scheme::type type) { |
502 | 0 | url_components_buffer_[0] = components.protocol_end; |
503 | 0 | url_components_buffer_[1] = components.username_end; |
504 | 0 | url_components_buffer_[2] = components.host_start; |
505 | 0 | url_components_buffer_[3] = components.host_end; |
506 | 0 | url_components_buffer_[4] = components.port; |
507 | 0 | url_components_buffer_[5] = components.pathname_start; |
508 | 0 | url_components_buffer_[6] = components.search_start; |
509 | 0 | url_components_buffer_[7] = components.hash_start; |
510 | 0 | url_components_buffer_[8] = type; |
511 | 0 | static_assert(kURLComponentsLength == 9, |
512 | 0 | "kURLComponentsLength should be up-to-date"); |
513 | 0 | } |
514 | | |
515 | | void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data, |
516 | 35 | Local<ObjectTemplate> target) { |
517 | 35 | Isolate* isolate = isolate_data->isolate(); |
518 | 35 | SetMethodNoSideEffect(isolate, target, "domainToASCII", DomainToASCII); |
519 | 35 | SetMethodNoSideEffect(isolate, target, "domainToUnicode", DomainToUnicode); |
520 | 35 | SetMethodNoSideEffect(isolate, target, "format", Format); |
521 | 35 | SetMethodNoSideEffect(isolate, target, "getOrigin", GetOrigin); |
522 | 35 | SetMethod(isolate, target, "parse", Parse); |
523 | 35 | SetMethod(isolate, target, "pathToFileURL", PathToFileURL); |
524 | 35 | SetMethod(isolate, target, "update", Update); |
525 | 35 | SetFastMethodNoSideEffect( |
526 | 35 | isolate, target, "canParse", CanParse, {fast_can_parse_methods_, 2}); |
527 | 35 | } |
528 | | |
529 | | void BindingData::CreatePerContextProperties(Local<Object> target, |
530 | | Local<Value> unused, |
531 | | Local<Context> context, |
532 | 35 | void* priv) { |
533 | 35 | Realm* realm = Realm::GetCurrent(context); |
534 | 35 | realm->AddBindingData<BindingData>(target); |
535 | 35 | } |
536 | | |
537 | | void BindingData::RegisterExternalReferences( |
538 | 0 | ExternalReferenceRegistry* registry) { |
539 | 0 | registry->Register(DomainToASCII); |
540 | 0 | registry->Register(DomainToUnicode); |
541 | 0 | registry->Register(Format); |
542 | 0 | registry->Register(GetOrigin); |
543 | 0 | registry->Register(Parse); |
544 | 0 | registry->Register(PathToFileURL); |
545 | 0 | registry->Register(Update); |
546 | 0 | registry->Register(CanParse); |
547 | 0 | for (const CFunction& method : fast_can_parse_methods_) { |
548 | 0 | registry->Register(method); |
549 | 0 | } |
550 | 0 | } |
551 | | |
552 | | void ThrowInvalidURL(node::Environment* env, |
553 | | std::string_view input, |
554 | 0 | std::optional<std::string> base) { |
555 | 0 | Local<Value> err = ERR_INVALID_URL(env->isolate(), "Invalid URL"); |
556 | 0 | DCHECK(err->IsObject()); |
557 | |
|
558 | 0 | auto err_object = err.As<Object>(); |
559 | |
|
560 | 0 | Local<Value> tmp; |
561 | 0 | if (!ToV8Value(env->context(), input, env->isolate()).ToLocal(&tmp) || |
562 | 0 | err_object->Set(env->context(), env->input_string(), tmp).IsNothing()) |
563 | 0 | [[unlikely]] { |
564 | | // A superseding error has been thrown. |
565 | 0 | return; |
566 | 0 | } |
567 | | |
568 | 0 | if (base.has_value()) { |
569 | 0 | if (!ToV8Value(env->context(), base.value(), env->isolate()) |
570 | 0 | .ToLocal(&tmp) || |
571 | 0 | err_object->Set(env->context(), env->base_string(), tmp).IsNothing()) |
572 | 0 | [[unlikely]] { |
573 | 0 | return; |
574 | 0 | } |
575 | 0 | } |
576 | | |
577 | 0 | env->isolate()->ThrowException(err); |
578 | 0 | } |
579 | | |
580 | 0 | std::string FromFilePath(std::string_view file_path) { |
581 | | // Avoid unnecessary allocations. |
582 | 0 | size_t pos = file_path.empty() ? std::string_view::npos : file_path.find('%'); |
583 | 0 | if (pos == std::string_view::npos) { |
584 | 0 | return ada::href_from_file(file_path); |
585 | 0 | } |
586 | | // Escape '%' characters to a temporary string. |
587 | 0 | std::string escaped_file_path; |
588 | 0 | do { |
589 | 0 | escaped_file_path += file_path.substr(0, pos + 1); |
590 | 0 | escaped_file_path += "25"; |
591 | 0 | file_path = file_path.substr(pos + 1); |
592 | 0 | pos = file_path.empty() ? std::string_view::npos : file_path.find('%'); |
593 | 0 | } while (pos != std::string_view::npos); |
594 | 0 | escaped_file_path += file_path; |
595 | 0 | return ada::href_from_file(escaped_file_path); |
596 | 0 | } |
597 | | |
598 | | std::optional<std::string> FileURLToPath(Environment* env, |
599 | 0 | const ada::url_aggregator& file_url) { |
600 | 0 | if (file_url.type != ada::scheme::FILE) { |
601 | 0 | THROW_ERR_INVALID_URL_SCHEME(env->isolate()); |
602 | 0 | return std::nullopt; |
603 | 0 | } |
604 | | |
605 | 0 | std::string_view pathname = file_url.get_pathname(); |
606 | | #ifdef _WIN32 |
607 | | size_t first_percent = std::string::npos; |
608 | | size_t pathname_size = pathname.size(); |
609 | | std::string pathname_escaped_slash; |
610 | | |
611 | | for (size_t i = 0; i < pathname_size; i++) { |
612 | | if (pathname[i] == '/') { |
613 | | pathname_escaped_slash += '\\'; |
614 | | } else { |
615 | | pathname_escaped_slash += pathname[i]; |
616 | | } |
617 | | |
618 | | if (pathname[i] != '%') continue; |
619 | | |
620 | | if (first_percent == std::string::npos) { |
621 | | first_percent = i; |
622 | | } |
623 | | |
624 | | // just safe-guard against access the pathname |
625 | | // outside the bounds |
626 | | if ((i + 2) >= pathname_size) continue; |
627 | | |
628 | | char third = pathname[i + 2] | 0x20; |
629 | | |
630 | | bool is_slash = pathname[i + 1] == '2' && third == 102; |
631 | | bool is_forward_slash = pathname[i + 1] == '5' && third == 99; |
632 | | |
633 | | if (!is_slash && !is_forward_slash) continue; |
634 | | |
635 | | THROW_ERR_INVALID_FILE_URL_PATH( |
636 | | env->isolate(), |
637 | | "File URL path must not include encoded \\ or / characters"); |
638 | | return std::nullopt; |
639 | | } |
640 | | |
641 | | std::string_view hostname = file_url.get_hostname(); |
642 | | std::string decoded_pathname = ada::unicode::percent_decode( |
643 | | std::string_view(pathname_escaped_slash), first_percent); |
644 | | |
645 | | if (hostname.size() > 0) { |
646 | | // If hostname is set, then we have a UNC path |
647 | | // Pass the hostname through domainToUnicode just in case |
648 | | // it is an IDN using punycode encoding. We do not need to worry |
649 | | // about percent encoding because the URL parser will have |
650 | | // already taken care of that for us. Note that this only |
651 | | // causes IDNs with an appropriate `xn--` prefix to be decoded. |
652 | | return "\\\\" + ada::idna::to_unicode(hostname) + decoded_pathname; |
653 | | } |
654 | | |
655 | | char letter = decoded_pathname[1] | 0x20; |
656 | | char sep = decoded_pathname[2]; |
657 | | |
658 | | // a..z A..Z |
659 | | if (letter < 'a' || letter > 'z' || sep != ':') { |
660 | | THROW_ERR_INVALID_FILE_URL_PATH(env->isolate(), |
661 | | "File URL path must be absolute"); |
662 | | return std::nullopt; |
663 | | } |
664 | | |
665 | | return decoded_pathname.substr(1); |
666 | | #else // _WIN32 |
667 | 0 | std::string_view hostname = file_url.get_hostname(); |
668 | |
|
669 | 0 | if (hostname.size() > 0) { |
670 | 0 | THROW_ERR_INVALID_FILE_URL_HOST( |
671 | 0 | env->isolate(), |
672 | 0 | "File URL host must be \"localhost\" or empty on ", |
673 | 0 | std::string(per_process::metadata.platform)); |
674 | 0 | return std::nullopt; |
675 | 0 | } |
676 | | |
677 | 0 | size_t first_percent = std::string::npos; |
678 | 0 | for (size_t i = 0; (i + 2) < pathname.size(); i++) { |
679 | 0 | if (pathname[i] != '%') continue; |
680 | | |
681 | 0 | if (first_percent == std::string::npos) { |
682 | 0 | first_percent = i; |
683 | 0 | } |
684 | |
|
685 | 0 | if (pathname[i + 1] == '2' && (pathname[i + 2] | 0x20) == 102) { |
686 | 0 | THROW_ERR_INVALID_FILE_URL_PATH( |
687 | 0 | env->isolate(), |
688 | 0 | "File URL path must not include encoded / characters"); |
689 | 0 | return std::nullopt; |
690 | 0 | } |
691 | 0 | } |
692 | | |
693 | 0 | return ada::unicode::percent_decode(pathname, first_percent); |
694 | 0 | #endif // _WIN32 |
695 | 0 | } |
696 | | |
697 | | } // namespace url |
698 | | |
699 | | } // namespace node |
700 | | |
701 | | NODE_BINDING_CONTEXT_AWARE_INTERNAL( |
702 | | url, node::url::BindingData::CreatePerContextProperties) |
703 | | NODE_BINDING_PER_ISOLATE_INIT( |
704 | | url, node::url::BindingData::CreatePerIsolateProperties) |
705 | | NODE_BINDING_EXTERNAL_REFERENCE( |
706 | | url, node::url::BindingData::RegisterExternalReferences) |