/src/node/src/node_url.cc
Line | Count | Source (jump to first uncovered line) |
1 | | #include "node_url.h" |
2 | | #include "ada.h" |
3 | | #include "base_object-inl.h" |
4 | | #include "node_errors.h" |
5 | | #include "node_external_reference.h" |
6 | | #include "node_i18n.h" |
7 | | #include "node_metadata.h" |
8 | | #include "node_process-inl.h" |
9 | | #include "util-inl.h" |
10 | | #include "v8-fast-api-calls.h" |
11 | | #include "v8.h" |
12 | | |
13 | | #include <cstdint> |
14 | | #include <cstdio> |
15 | | #include <numeric> |
16 | | |
17 | | namespace node { |
18 | | namespace url { |
19 | | |
20 | | using v8::CFunction; |
21 | | using v8::Context; |
22 | | using v8::FastOneByteString; |
23 | | using v8::FunctionCallbackInfo; |
24 | | using v8::HandleScope; |
25 | | using v8::Isolate; |
26 | | using v8::Local; |
27 | | using v8::NewStringType; |
28 | | using v8::Object; |
29 | | using v8::ObjectTemplate; |
30 | | using v8::String; |
31 | | using v8::Value; |
32 | | |
33 | 0 | void BindingData::MemoryInfo(MemoryTracker* tracker) const { |
34 | 0 | tracker->TrackField("url_components_buffer", url_components_buffer_); |
35 | 0 | } |
36 | | |
37 | | BindingData::BindingData(Realm* realm, v8::Local<v8::Object> object) |
38 | 122k | : SnapshotableObject(realm, object, type_int), |
39 | 122k | url_components_buffer_(realm->isolate(), kURLComponentsLength) { |
40 | 122k | object |
41 | 122k | ->Set(realm->context(), |
42 | 122k | FIXED_ONE_BYTE_STRING(realm->isolate(), "urlComponents"), |
43 | 122k | url_components_buffer_.GetJSArray()) |
44 | 122k | .Check(); |
45 | 122k | url_components_buffer_.MakeWeak(); |
46 | 122k | } |
47 | | |
48 | | bool BindingData::PrepareForSerialization(v8::Local<v8::Context> context, |
49 | 0 | v8::SnapshotCreator* creator) { |
50 | | // We'll just re-initialize the buffers in the constructor since their |
51 | | // contents can be thrown away once consumed in the previous call. |
52 | 0 | url_components_buffer_.Release(); |
53 | | // Return true because we need to maintain the reference to the binding from |
54 | | // JS land. |
55 | 0 | return true; |
56 | 0 | } |
57 | | |
58 | 0 | InternalFieldInfoBase* BindingData::Serialize(int index) { |
59 | 0 | DCHECK_IS_SNAPSHOT_SLOT(index); |
60 | 0 | InternalFieldInfo* info = |
61 | 0 | InternalFieldInfoBase::New<InternalFieldInfo>(type()); |
62 | 0 | return info; |
63 | 0 | } |
64 | | |
65 | | void BindingData::Deserialize(v8::Local<v8::Context> context, |
66 | | v8::Local<v8::Object> holder, |
67 | | int index, |
68 | 0 | InternalFieldInfoBase* info) { |
69 | 0 | DCHECK_IS_SNAPSHOT_SLOT(index); |
70 | 0 | v8::HandleScope scope(context->GetIsolate()); |
71 | 0 | Realm* realm = Realm::GetCurrent(context); |
72 | 0 | BindingData* binding = realm->AddBindingData<BindingData>(holder); |
73 | 0 | CHECK_NOT_NULL(binding); |
74 | 0 | } |
75 | | |
76 | 0 | void BindingData::DomainToASCII(const FunctionCallbackInfo<Value>& args) { |
77 | 0 | Environment* env = Environment::GetCurrent(args); |
78 | 0 | CHECK_GE(args.Length(), 1); |
79 | 0 | CHECK(args[0]->IsString()); |
80 | | |
81 | 0 | std::string input = Utf8Value(env->isolate(), args[0]).ToString(); |
82 | 0 | if (input.empty()) { |
83 | 0 | return args.GetReturnValue().Set(String::Empty(env->isolate())); |
84 | 0 | } |
85 | | |
86 | | // It is important to have an initial value that contains a special scheme. |
87 | | // Since it will change the implementation of `set_hostname` according to URL |
88 | | // spec. |
89 | 0 | auto out = ada::parse<ada::url>("ws://x"); |
90 | 0 | DCHECK(out); |
91 | 0 | if (!out->set_hostname(input)) { |
92 | 0 | return args.GetReturnValue().Set(String::Empty(env->isolate())); |
93 | 0 | } |
94 | 0 | std::string host = out->get_hostname(); |
95 | 0 | args.GetReturnValue().Set( |
96 | 0 | String::NewFromUtf8(env->isolate(), host.c_str()).ToLocalChecked()); |
97 | 0 | } |
98 | | |
99 | 0 | void BindingData::DomainToUnicode(const FunctionCallbackInfo<Value>& args) { |
100 | 0 | Environment* env = Environment::GetCurrent(args); |
101 | 0 | CHECK_GE(args.Length(), 1); |
102 | 0 | CHECK(args[0]->IsString()); |
103 | | |
104 | 0 | std::string input = Utf8Value(env->isolate(), args[0]).ToString(); |
105 | 0 | if (input.empty()) { |
106 | 0 | return args.GetReturnValue().Set(String::Empty(env->isolate())); |
107 | 0 | } |
108 | | |
109 | | // It is important to have an initial value that contains a special scheme. |
110 | | // Since it will change the implementation of `set_hostname` according to URL |
111 | | // spec. |
112 | 0 | auto out = ada::parse<ada::url>("ws://x"); |
113 | 0 | DCHECK(out); |
114 | 0 | if (!out->set_hostname(input)) { |
115 | 0 | return args.GetReturnValue().Set(String::Empty(env->isolate())); |
116 | 0 | } |
117 | 0 | std::string result = ada::unicode::to_unicode(out->get_hostname()); |
118 | |
|
119 | 0 | args.GetReturnValue().Set(String::NewFromUtf8(env->isolate(), |
120 | 0 | result.c_str(), |
121 | 0 | NewStringType::kNormal, |
122 | 0 | result.length()) |
123 | 0 | .ToLocalChecked()); |
124 | 0 | } |
125 | | |
126 | 0 | void BindingData::GetOrigin(const v8::FunctionCallbackInfo<Value>& args) { |
127 | 0 | CHECK_GE(args.Length(), 1); |
128 | 0 | CHECK(args[0]->IsString()); // input |
129 | | |
130 | 0 | Environment* env = Environment::GetCurrent(args); |
131 | 0 | HandleScope handle_scope(env->isolate()); |
132 | |
|
133 | 0 | Utf8Value input(env->isolate(), args[0]); |
134 | 0 | std::string_view input_view = input.ToStringView(); |
135 | 0 | auto out = ada::parse<ada::url_aggregator>(input_view); |
136 | |
|
137 | 0 | if (!out) { |
138 | 0 | THROW_ERR_INVALID_URL(env, "Invalid URL"); |
139 | 0 | return; |
140 | 0 | } |
141 | | |
142 | 0 | std::string origin = out->get_origin(); |
143 | 0 | args.GetReturnValue().Set(String::NewFromUtf8(env->isolate(), |
144 | 0 | origin.data(), |
145 | 0 | NewStringType::kNormal, |
146 | 0 | origin.length()) |
147 | 0 | .ToLocalChecked()); |
148 | 0 | } |
149 | | |
150 | 0 | void BindingData::CanParse(const FunctionCallbackInfo<Value>& args) { |
151 | 0 | CHECK_GE(args.Length(), 1); |
152 | 0 | CHECK(args[0]->IsString()); // input |
153 | | // args[1] // base url |
154 | | |
155 | 0 | Environment* env = Environment::GetCurrent(args); |
156 | 0 | HandleScope handle_scope(env->isolate()); |
157 | |
|
158 | 0 | Utf8Value input(env->isolate(), args[0]); |
159 | 0 | std::string_view input_view = input.ToStringView(); |
160 | |
|
161 | 0 | bool can_parse{}; |
162 | 0 | if (args[1]->IsString()) { |
163 | 0 | Utf8Value base(env->isolate(), args[1]); |
164 | 0 | std::string_view base_view = base.ToStringView(); |
165 | 0 | can_parse = ada::can_parse(input_view, &base_view); |
166 | 0 | } else { |
167 | 0 | can_parse = ada::can_parse(input_view); |
168 | 0 | } |
169 | |
|
170 | 0 | args.GetReturnValue().Set(can_parse); |
171 | 0 | } |
172 | | |
173 | | bool BindingData::FastCanParse(Local<Value> receiver, |
174 | 0 | const FastOneByteString& input) { |
175 | 0 | return ada::can_parse(std::string_view(input.data, input.length)); |
176 | 0 | } |
177 | | |
178 | | bool BindingData::FastCanParseWithBase(Local<Value> receiver, |
179 | | const FastOneByteString& input, |
180 | 0 | const FastOneByteString& base) { |
181 | 0 | auto base_view = std::string_view(base.data, base.length); |
182 | 0 | return ada::can_parse(std::string_view(input.data, input.length), &base_view); |
183 | 0 | } |
184 | | |
185 | | CFunction BindingData::fast_can_parse_methods_[] = { |
186 | | CFunction::Make(FastCanParse), CFunction::Make(FastCanParseWithBase)}; |
187 | | |
188 | 0 | void BindingData::Format(const FunctionCallbackInfo<Value>& args) { |
189 | 0 | CHECK_GT(args.Length(), 4); |
190 | 0 | CHECK(args[0]->IsString()); // url href |
191 | | |
192 | 0 | Environment* env = Environment::GetCurrent(args); |
193 | 0 | Isolate* isolate = env->isolate(); |
194 | |
|
195 | 0 | Utf8Value href(isolate, args[0].As<String>()); |
196 | 0 | const bool hash = args[1]->IsTrue(); |
197 | 0 | const bool unicode = args[2]->IsTrue(); |
198 | 0 | const bool search = args[3]->IsTrue(); |
199 | 0 | const bool auth = args[4]->IsTrue(); |
200 | | |
201 | | // ada::url provides a faster alternative to ada::url_aggregator if we |
202 | | // directly want to manipulate the url components without using the respective |
203 | | // setters. therefore we are using ada::url here. |
204 | 0 | auto out = ada::parse<ada::url>(href.ToStringView()); |
205 | 0 | CHECK(out); |
206 | | |
207 | 0 | if (!hash) { |
208 | 0 | out->hash = std::nullopt; |
209 | 0 | } |
210 | |
|
211 | 0 | if (unicode && out->has_hostname()) { |
212 | 0 | out->host = ada::idna::to_unicode(out->get_hostname()); |
213 | 0 | } |
214 | |
|
215 | 0 | if (!search) { |
216 | 0 | out->query = std::nullopt; |
217 | 0 | } |
218 | |
|
219 | 0 | if (!auth) { |
220 | 0 | out->username = ""; |
221 | 0 | out->password = ""; |
222 | 0 | } |
223 | |
|
224 | 0 | std::string result = out->get_href(); |
225 | 0 | args.GetReturnValue().Set(String::NewFromUtf8(env->isolate(), |
226 | 0 | result.data(), |
227 | 0 | NewStringType::kNormal, |
228 | 0 | result.length()) |
229 | 0 | .ToLocalChecked()); |
230 | 0 | } |
231 | | |
232 | 3 | void BindingData::Parse(const FunctionCallbackInfo<Value>& args) { |
233 | 3 | CHECK_GE(args.Length(), 1); |
234 | 3 | CHECK(args[0]->IsString()); // input |
235 | | // args[1] // base url |
236 | | |
237 | 3 | Realm* realm = Realm::GetCurrent(args); |
238 | 3 | BindingData* binding_data = realm->GetBindingData<BindingData>(); |
239 | 3 | Isolate* isolate = realm->isolate(); |
240 | 3 | std::optional<std::string> base_{}; |
241 | | |
242 | 3 | Utf8Value input(isolate, args[0]); |
243 | 3 | ada::result<ada::url_aggregator> base; |
244 | 3 | ada::url_aggregator* base_pointer = nullptr; |
245 | 3 | if (args[1]->IsString()) { |
246 | 0 | base_ = Utf8Value(isolate, args[1]).ToString(); |
247 | 0 | base = ada::parse<ada::url_aggregator>(*base_); |
248 | 0 | if (!base) { |
249 | 0 | return ThrowInvalidURL(realm->env(), input.ToStringView(), base_); |
250 | 0 | } |
251 | 0 | base_pointer = &base.value(); |
252 | 0 | } |
253 | 3 | auto out = |
254 | 3 | ada::parse<ada::url_aggregator>(input.ToStringView(), base_pointer); |
255 | | |
256 | 3 | if (!out) { |
257 | 0 | return ThrowInvalidURL(realm->env(), input.ToStringView(), base_); |
258 | 0 | } |
259 | | |
260 | 3 | binding_data->UpdateComponents(out->get_components(), out->type); |
261 | | |
262 | 3 | args.GetReturnValue().Set( |
263 | 3 | ToV8Value(realm->context(), out->get_href(), isolate).ToLocalChecked()); |
264 | 3 | } |
265 | | |
266 | 3 | void BindingData::Update(const FunctionCallbackInfo<Value>& args) { |
267 | 3 | CHECK(args[0]->IsString()); // href |
268 | 3 | CHECK(args[1]->IsNumber()); // action type |
269 | 3 | CHECK(args[2]->IsString()); // new value |
270 | | |
271 | 3 | Realm* realm = Realm::GetCurrent(args); |
272 | 3 | BindingData* binding_data = realm->GetBindingData<BindingData>(); |
273 | 3 | Isolate* isolate = realm->isolate(); |
274 | | |
275 | 3 | enum url_update_action action = static_cast<enum url_update_action>( |
276 | 3 | args[1]->Uint32Value(realm->context()).FromJust()); |
277 | 3 | Utf8Value input(isolate, args[0].As<String>()); |
278 | 3 | Utf8Value new_value(isolate, args[2].As<String>()); |
279 | | |
280 | 3 | std::string_view new_value_view = new_value.ToStringView(); |
281 | 3 | auto out = ada::parse<ada::url_aggregator>(input.ToStringView()); |
282 | 3 | CHECK(out); |
283 | | |
284 | 3 | bool result{true}; |
285 | | |
286 | 3 | switch (action) { |
287 | 1 | case kPathname: { |
288 | 1 | result = out->set_pathname(new_value_view); |
289 | 1 | break; |
290 | 0 | } |
291 | 1 | case kHash: { |
292 | 1 | out->set_hash(new_value_view); |
293 | 1 | break; |
294 | 0 | } |
295 | 0 | case kHost: { |
296 | 0 | result = out->set_host(new_value_view); |
297 | 0 | break; |
298 | 0 | } |
299 | 0 | case kHostname: { |
300 | 0 | result = out->set_hostname(new_value_view); |
301 | 0 | break; |
302 | 0 | } |
303 | 0 | case kHref: { |
304 | 0 | result = out->set_href(new_value_view); |
305 | 0 | break; |
306 | 0 | } |
307 | 0 | case kPassword: { |
308 | 0 | result = out->set_password(new_value_view); |
309 | 0 | break; |
310 | 0 | } |
311 | 0 | case kPort: { |
312 | 0 | result = out->set_port(new_value_view); |
313 | 0 | break; |
314 | 0 | } |
315 | 0 | case kProtocol: { |
316 | 0 | result = out->set_protocol(new_value_view); |
317 | 0 | break; |
318 | 0 | } |
319 | 1 | case kSearch: { |
320 | 1 | out->set_search(new_value_view); |
321 | 1 | break; |
322 | 0 | } |
323 | 0 | case kUsername: { |
324 | 0 | result = out->set_username(new_value_view); |
325 | 0 | break; |
326 | 0 | } |
327 | 0 | default: |
328 | 0 | UNREACHABLE("Unsupported URL update action"); |
329 | 3 | } |
330 | | |
331 | 3 | if (!result) { |
332 | 0 | return args.GetReturnValue().Set(false); |
333 | 0 | } |
334 | | |
335 | 3 | binding_data->UpdateComponents(out->get_components(), out->type); |
336 | 3 | args.GetReturnValue().Set( |
337 | 3 | ToV8Value(realm->context(), out->get_href(), isolate).ToLocalChecked()); |
338 | 3 | } |
339 | | |
340 | | void BindingData::UpdateComponents(const ada::url_components& components, |
341 | 6 | const ada::scheme::type type) { |
342 | 6 | url_components_buffer_[0] = components.protocol_end; |
343 | 6 | url_components_buffer_[1] = components.username_end; |
344 | 6 | url_components_buffer_[2] = components.host_start; |
345 | 6 | url_components_buffer_[3] = components.host_end; |
346 | 6 | url_components_buffer_[4] = components.port; |
347 | 6 | url_components_buffer_[5] = components.pathname_start; |
348 | 6 | url_components_buffer_[6] = components.search_start; |
349 | 6 | url_components_buffer_[7] = components.hash_start; |
350 | 6 | url_components_buffer_[8] = type; |
351 | 6 | static_assert(kURLComponentsLength == 9, |
352 | 6 | "kURLComponentsLength should be up-to-date"); |
353 | 6 | } |
354 | | |
355 | | void BindingData::CreatePerIsolateProperties(IsolateData* isolate_data, |
356 | 122k | Local<ObjectTemplate> target) { |
357 | 122k | Isolate* isolate = isolate_data->isolate(); |
358 | 122k | SetMethodNoSideEffect(isolate, target, "domainToASCII", DomainToASCII); |
359 | 122k | SetMethodNoSideEffect(isolate, target, "domainToUnicode", DomainToUnicode); |
360 | 122k | SetMethodNoSideEffect(isolate, target, "format", Format); |
361 | 122k | SetMethodNoSideEffect(isolate, target, "getOrigin", GetOrigin); |
362 | 122k | SetMethod(isolate, target, "parse", Parse); |
363 | 122k | SetMethod(isolate, target, "update", Update); |
364 | 122k | SetFastMethodNoSideEffect( |
365 | 122k | isolate, target, "canParse", CanParse, {fast_can_parse_methods_, 2}); |
366 | 122k | } |
367 | | |
368 | | void BindingData::CreatePerContextProperties(Local<Object> target, |
369 | | Local<Value> unused, |
370 | | Local<Context> context, |
371 | 122k | void* priv) { |
372 | 122k | Realm* realm = Realm::GetCurrent(context); |
373 | 122k | realm->AddBindingData<BindingData>(target); |
374 | 122k | } |
375 | | |
376 | | void BindingData::RegisterExternalReferences( |
377 | 0 | ExternalReferenceRegistry* registry) { |
378 | 0 | registry->Register(DomainToASCII); |
379 | 0 | registry->Register(DomainToUnicode); |
380 | 0 | registry->Register(Format); |
381 | 0 | registry->Register(GetOrigin); |
382 | 0 | registry->Register(Parse); |
383 | 0 | registry->Register(Update); |
384 | 0 | registry->Register(CanParse); |
385 | 0 | registry->Register(FastCanParse); |
386 | 0 | registry->Register(FastCanParseWithBase); |
387 | |
|
388 | 0 | for (const CFunction& method : fast_can_parse_methods_) { |
389 | 0 | registry->Register(method.GetTypeInfo()); |
390 | 0 | } |
391 | 0 | } |
392 | | |
393 | | void ThrowInvalidURL(node::Environment* env, |
394 | | std::string_view input, |
395 | 0 | std::optional<std::string> base) { |
396 | 0 | Local<Value> err = ERR_INVALID_URL(env->isolate(), "Invalid URL"); |
397 | 0 | DCHECK(err->IsObject()); |
398 | |
|
399 | 0 | auto err_object = err.As<Object>(); |
400 | |
|
401 | 0 | USE(err_object->Set(env->context(), |
402 | 0 | env->input_string(), |
403 | 0 | v8::String::NewFromUtf8(env->isolate(), |
404 | 0 | input.data(), |
405 | 0 | v8::NewStringType::kNormal, |
406 | 0 | input.size()) |
407 | 0 | .ToLocalChecked())); |
408 | |
|
409 | 0 | if (base.has_value()) { |
410 | 0 | USE(err_object->Set(env->context(), |
411 | 0 | env->base_string(), |
412 | 0 | v8::String::NewFromUtf8(env->isolate(), |
413 | 0 | base.value().c_str(), |
414 | 0 | v8::NewStringType::kNormal, |
415 | 0 | base.value().size()) |
416 | 0 | .ToLocalChecked())); |
417 | 0 | } |
418 | |
|
419 | 0 | env->isolate()->ThrowException(err); |
420 | 0 | } |
421 | | |
422 | 0 | std::string FromFilePath(std::string_view file_path) { |
423 | | // Avoid unnecessary allocations. |
424 | 0 | size_t pos = file_path.empty() ? std::string_view::npos : file_path.find('%'); |
425 | 0 | if (pos == std::string_view::npos) { |
426 | 0 | return ada::href_from_file(file_path); |
427 | 0 | } |
428 | | // Escape '%' characters to a temporary string. |
429 | 0 | std::string escaped_file_path; |
430 | 0 | do { |
431 | 0 | escaped_file_path += file_path.substr(0, pos + 1); |
432 | 0 | escaped_file_path += "25"; |
433 | 0 | file_path = file_path.substr(pos + 1); |
434 | 0 | pos = file_path.empty() ? std::string_view::npos : file_path.find('%'); |
435 | 0 | } while (pos != std::string_view::npos); |
436 | 0 | escaped_file_path += file_path; |
437 | 0 | return ada::href_from_file(escaped_file_path); |
438 | 0 | } |
439 | | |
440 | | std::optional<std::string> FileURLToPath(Environment* env, |
441 | 0 | const ada::url_aggregator& file_url) { |
442 | 0 | if (file_url.type != ada::scheme::FILE) { |
443 | 0 | THROW_ERR_INVALID_URL_SCHEME(env->isolate()); |
444 | 0 | return std::nullopt; |
445 | 0 | } |
446 | | |
447 | 0 | std::string_view pathname = file_url.get_pathname(); |
448 | | #ifdef _WIN32 |
449 | | size_t first_percent = std::string::npos; |
450 | | size_t pathname_size = pathname.size(); |
451 | | std::string pathname_escaped_slash; |
452 | | |
453 | | for (size_t i = 0; i < pathname_size; i++) { |
454 | | if (pathname[i] == '/') { |
455 | | pathname_escaped_slash += '\\'; |
456 | | } else { |
457 | | pathname_escaped_slash += pathname[i]; |
458 | | } |
459 | | |
460 | | if (pathname[i] != '%') continue; |
461 | | |
462 | | if (first_percent == std::string::npos) { |
463 | | first_percent = i; |
464 | | } |
465 | | |
466 | | // just safe-guard against access the pathname |
467 | | // outside the bounds |
468 | | if ((i + 2) >= pathname_size) continue; |
469 | | |
470 | | char third = pathname[i + 2] | 0x20; |
471 | | |
472 | | bool is_slash = pathname[i + 1] == '2' && third == 102; |
473 | | bool is_forward_slash = pathname[i + 1] == '5' && third == 99; |
474 | | |
475 | | if (!is_slash && !is_forward_slash) continue; |
476 | | |
477 | | THROW_ERR_INVALID_FILE_URL_PATH( |
478 | | env->isolate(), |
479 | | "File URL path must not include encoded \\ or / characters"); |
480 | | return std::nullopt; |
481 | | } |
482 | | |
483 | | std::string_view hostname = file_url.get_hostname(); |
484 | | std::string decoded_pathname = ada::unicode::percent_decode( |
485 | | std::string_view(pathname_escaped_slash), first_percent); |
486 | | |
487 | | if (hostname.size() > 0) { |
488 | | // If hostname is set, then we have a UNC path |
489 | | // Pass the hostname through domainToUnicode just in case |
490 | | // it is an IDN using punycode encoding. We do not need to worry |
491 | | // about percent encoding because the URL parser will have |
492 | | // already taken care of that for us. Note that this only |
493 | | // causes IDNs with an appropriate `xn--` prefix to be decoded. |
494 | | return "\\\\" + ada::unicode::to_unicode(hostname) + decoded_pathname; |
495 | | } |
496 | | |
497 | | char letter = decoded_pathname[1] | 0x20; |
498 | | char sep = decoded_pathname[2]; |
499 | | |
500 | | // a..z A..Z |
501 | | if (letter < 'a' || letter > 'z' || sep != ':') { |
502 | | THROW_ERR_INVALID_FILE_URL_PATH(env->isolate(), |
503 | | "File URL path must be absolute"); |
504 | | return std::nullopt; |
505 | | } |
506 | | |
507 | | return decoded_pathname.substr(1); |
508 | | #else // _WIN32 |
509 | 0 | std::string_view hostname = file_url.get_hostname(); |
510 | |
|
511 | 0 | if (hostname.size() > 0) { |
512 | 0 | THROW_ERR_INVALID_FILE_URL_HOST( |
513 | 0 | env->isolate(), |
514 | 0 | "File URL host must be \"localhost\" or empty on ", |
515 | 0 | std::string(per_process::metadata.platform)); |
516 | 0 | return std::nullopt; |
517 | 0 | } |
518 | | |
519 | 0 | size_t first_percent = std::string::npos; |
520 | 0 | for (size_t i = 0; (i + 2) < pathname.size(); i++) { |
521 | 0 | if (pathname[i] != '%') continue; |
522 | | |
523 | 0 | if (first_percent == std::string::npos) { |
524 | 0 | first_percent = i; |
525 | 0 | } |
526 | |
|
527 | 0 | if (pathname[i + 1] == '2' && (pathname[i + 2] | 0x20) == 102) { |
528 | 0 | THROW_ERR_INVALID_FILE_URL_PATH( |
529 | 0 | env->isolate(), |
530 | 0 | "File URL path must not include encoded / characters"); |
531 | 0 | return std::nullopt; |
532 | 0 | } |
533 | 0 | } |
534 | | |
535 | 0 | return ada::unicode::percent_decode(pathname, first_percent); |
536 | 0 | #endif // _WIN32 |
537 | 0 | } |
538 | | |
539 | | // Reverse the logic applied by path.toNamespacedPath() to create a |
540 | | // namespace-prefixed path. |
541 | 0 | void FromNamespacedPath(std::string* path) { |
542 | | #ifdef _WIN32 |
543 | | if (path->compare(0, 8, "\\\\?\\UNC\\", 8) == 0) { |
544 | | *path = path->substr(8); |
545 | | path->insert(0, "\\\\"); |
546 | | } else if (path->compare(0, 4, "\\\\?\\", 4) == 0) { |
547 | | *path = path->substr(4); |
548 | | } |
549 | | #endif |
550 | 0 | } |
551 | | |
552 | | } // namespace url |
553 | | |
554 | | } // namespace node |
555 | | |
556 | | NODE_BINDING_CONTEXT_AWARE_INTERNAL( |
557 | | url, node::url::BindingData::CreatePerContextProperties) |
558 | | NODE_BINDING_PER_ISOLATE_INIT( |
559 | | url, node::url::BindingData::CreatePerIsolateProperties) |
560 | | NODE_BINDING_EXTERNAL_REFERENCE( |
561 | | url, node::url::BindingData::RegisterExternalReferences) |