• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "util-inl.h"
8 #include "v8.h"
9 
10 #include <cstdint>
11 #include <cstdio>
12 #include <numeric>
13 
14 namespace node {
15 namespace url {
16 
17 using v8::Context;
18 using v8::FunctionCallbackInfo;
19 using v8::HandleScope;
20 using v8::Isolate;
21 using v8::Local;
22 using v8::NewStringType;
23 using v8::Object;
24 using v8::String;
25 using v8::Value;
26 
MemoryInfo(MemoryTracker * tracker) const27 void BindingData::MemoryInfo(MemoryTracker* tracker) const {
28   tracker->TrackField("url_components_buffer", url_components_buffer_);
29 }
30 
BindingData(Realm * realm,v8::Local<v8::Object> object)31 BindingData::BindingData(Realm* realm, v8::Local<v8::Object> object)
32     : SnapshotableObject(realm, object, type_int),
33       url_components_buffer_(realm->isolate(), kURLComponentsLength) {
34   object
35       ->Set(realm->context(),
36             FIXED_ONE_BYTE_STRING(realm->isolate(), "urlComponents"),
37             url_components_buffer_.GetJSArray())
38       .Check();
39 }
40 
PrepareForSerialization(v8::Local<v8::Context> context,v8::SnapshotCreator * creator)41 bool BindingData::PrepareForSerialization(v8::Local<v8::Context> context,
42                                           v8::SnapshotCreator* creator) {
43   // We'll just re-initialize the buffers in the constructor since their
44   // contents can be thrown away once consumed in the previous call.
45   url_components_buffer_.Release();
46   // Return true because we need to maintain the reference to the binding from
47   // JS land.
48   return true;
49 }
50 
Serialize(int index)51 InternalFieldInfoBase* BindingData::Serialize(int index) {
52   DCHECK_EQ(index, BaseObject::kEmbedderType);
53   InternalFieldInfo* info =
54       InternalFieldInfoBase::New<InternalFieldInfo>(type());
55   return info;
56 }
57 
Deserialize(v8::Local<v8::Context> context,v8::Local<v8::Object> holder,int index,InternalFieldInfoBase * info)58 void BindingData::Deserialize(v8::Local<v8::Context> context,
59                               v8::Local<v8::Object> holder,
60                               int index,
61                               InternalFieldInfoBase* info) {
62   DCHECK_EQ(index, BaseObject::kEmbedderType);
63   v8::HandleScope scope(context->GetIsolate());
64   Realm* realm = Realm::GetCurrent(context);
65   BindingData* binding = realm->AddBindingData<BindingData>(context, holder);
66   CHECK_NOT_NULL(binding);
67 }
68 
DomainToASCII(const FunctionCallbackInfo<Value> & args)69 void BindingData::DomainToASCII(const FunctionCallbackInfo<Value>& args) {
70   Environment* env = Environment::GetCurrent(args);
71   CHECK_GE(args.Length(), 1);
72   CHECK(args[0]->IsString());
73 
74   std::string input = Utf8Value(env->isolate(), args[0]).ToString();
75   if (input.empty()) {
76     return args.GetReturnValue().Set(FIXED_ONE_BYTE_STRING(env->isolate(), ""));
77   }
78 
79   // It is important to have an initial value that contains a special scheme.
80   // Since it will change the implementation of `set_hostname` according to URL
81   // spec.
82   auto out = ada::parse<ada::url>("ws://x");
83   DCHECK(out);
84   if (!out->set_hostname(input)) {
85     return args.GetReturnValue().Set(FIXED_ONE_BYTE_STRING(env->isolate(), ""));
86   }
87   std::string host = out->get_hostname();
88   args.GetReturnValue().Set(
89       String::NewFromUtf8(env->isolate(), host.c_str()).ToLocalChecked());
90 }
91 
DomainToUnicode(const FunctionCallbackInfo<Value> & args)92 void BindingData::DomainToUnicode(const FunctionCallbackInfo<Value>& args) {
93   Environment* env = Environment::GetCurrent(args);
94   CHECK_GE(args.Length(), 1);
95   CHECK(args[0]->IsString());
96 
97   std::string input = Utf8Value(env->isolate(), args[0]).ToString();
98   if (input.empty()) {
99     return args.GetReturnValue().Set(
100         String::NewFromUtf8(env->isolate(), "").ToLocalChecked());
101   }
102 
103   // It is important to have an initial value that contains a special scheme.
104   // Since it will change the implementation of `set_hostname` according to URL
105   // spec.
106   auto out = ada::parse<ada::url>("ws://x");
107   DCHECK(out);
108   if (!out->set_hostname(input)) {
109     return args.GetReturnValue().Set(
110         String::NewFromUtf8(env->isolate(), "").ToLocalChecked());
111   }
112   std::string result = ada::unicode::to_unicode(out->get_hostname());
113 
114   args.GetReturnValue().Set(String::NewFromUtf8(env->isolate(),
115                                                 result.c_str(),
116                                                 NewStringType::kNormal,
117                                                 result.length())
118                                 .ToLocalChecked());
119 }
120 
121 // TODO(@anonrig): Add V8 Fast API for CanParse method
CanParse(const FunctionCallbackInfo<Value> & args)122 void BindingData::CanParse(const FunctionCallbackInfo<Value>& args) {
123   CHECK_GE(args.Length(), 1);
124   CHECK(args[0]->IsString());  // input
125   // args[1] // base url
126 
127   Environment* env = Environment::GetCurrent(args);
128   HandleScope handle_scope(env->isolate());
129   Context::Scope context_scope(env->context());
130 
131   Utf8Value input(env->isolate(), args[0]);
132   ada::result<ada::url_aggregator> base;
133   ada::url_aggregator* base_pointer = nullptr;
134   if (args[1]->IsString()) {
135     base = ada::parse<ada::url_aggregator>(
136         Utf8Value(env->isolate(), args[1]).ToString());
137     if (!base) {
138       return args.GetReturnValue().Set(false);
139     }
140     base_pointer = &base.value();
141   }
142   auto out =
143       ada::parse<ada::url_aggregator>(input.ToStringView(), base_pointer);
144 
145   args.GetReturnValue().Set(out.has_value());
146 }
147 
Format(const FunctionCallbackInfo<Value> & args)148 void BindingData::Format(const FunctionCallbackInfo<Value>& args) {
149   CHECK_GT(args.Length(), 4);
150   CHECK(args[0]->IsString());  // url href
151 
152   Environment* env = Environment::GetCurrent(args);
153   Isolate* isolate = env->isolate();
154 
155   Utf8Value href(isolate, args[0].As<String>());
156   const bool hash = args[1]->IsTrue();
157   const bool unicode = args[2]->IsTrue();
158   const bool search = args[3]->IsTrue();
159   const bool auth = args[4]->IsTrue();
160 
161   // ada::url provides a faster alternative to ada::url_aggregator if we
162   // directly want to manipulate the url components without using the respective
163   // setters. therefore we are using ada::url here.
164   auto out = ada::parse<ada::url>(href.ToStringView());
165   CHECK(out);
166 
167   if (!hash) {
168     out->hash = std::nullopt;
169   }
170 
171   if (unicode && out->has_hostname()) {
172     out->host = ada::idna::to_unicode(out->get_hostname());
173   }
174 
175   if (!search) {
176     out->query = std::nullopt;
177   }
178 
179   if (!auth) {
180     out->username = "";
181     out->password = "";
182   }
183 
184   std::string result = out->get_href();
185   args.GetReturnValue().Set(String::NewFromUtf8(env->isolate(),
186                                                 result.data(),
187                                                 NewStringType::kNormal,
188                                                 result.length())
189                                 .ToLocalChecked());
190 }
191 
Parse(const FunctionCallbackInfo<Value> & args)192 void BindingData::Parse(const FunctionCallbackInfo<Value>& args) {
193   CHECK_GE(args.Length(), 1);
194   CHECK(args[0]->IsString());  // input
195   // args[1] // base url
196 
197   BindingData* binding_data = Realm::GetBindingData<BindingData>(args);
198   Environment* env = Environment::GetCurrent(args);
199   HandleScope handle_scope(env->isolate());
200   Context::Scope context_scope(env->context());
201 
202   Utf8Value input(env->isolate(), args[0]);
203   ada::result<ada::url_aggregator> base;
204   ada::url_aggregator* base_pointer = nullptr;
205   if (args[1]->IsString()) {
206     base = ada::parse<ada::url_aggregator>(
207         Utf8Value(env->isolate(), args[1]).ToString());
208     if (!base) {
209       return args.GetReturnValue().Set(false);
210     }
211     base_pointer = &base.value();
212   }
213   auto out =
214       ada::parse<ada::url_aggregator>(input.ToStringView(), base_pointer);
215 
216   if (!out) {
217     return args.GetReturnValue().Set(false);
218   }
219 
220   binding_data->UpdateComponents(out->get_components(), out->type);
221 
222   args.GetReturnValue().Set(
223       ToV8Value(env->context(), out->get_href(), env->isolate())
224           .ToLocalChecked());
225 }
226 
Update(const FunctionCallbackInfo<Value> & args)227 void BindingData::Update(const FunctionCallbackInfo<Value>& args) {
228   CHECK(args[0]->IsString());    // href
229   CHECK(args[1]->IsNumber());    // action type
230   CHECK(args[2]->IsString());    // new value
231 
232   BindingData* binding_data = Realm::GetBindingData<BindingData>(args);
233   Environment* env = Environment::GetCurrent(args);
234   Isolate* isolate = env->isolate();
235 
236   enum url_update_action action = static_cast<enum url_update_action>(
237       args[1]->Uint32Value(env->context()).FromJust());
238   Utf8Value input(isolate, args[0].As<String>());
239   Utf8Value new_value(isolate, args[2].As<String>());
240 
241   std::string_view new_value_view = new_value.ToStringView();
242   auto out = ada::parse<ada::url_aggregator>(input.ToStringView());
243   CHECK(out);
244 
245   bool result{true};
246 
247   switch (action) {
248     case kPathname: {
249       result = out->set_pathname(new_value_view);
250       break;
251     }
252     case kHash: {
253       out->set_hash(new_value_view);
254       break;
255     }
256     case kHost: {
257       result = out->set_host(new_value_view);
258       break;
259     }
260     case kHostname: {
261       result = out->set_hostname(new_value_view);
262       break;
263     }
264     case kHref: {
265       result = out->set_href(new_value_view);
266       break;
267     }
268     case kPassword: {
269       result = out->set_password(new_value_view);
270       break;
271     }
272     case kPort: {
273       result = out->set_port(new_value_view);
274       break;
275     }
276     case kProtocol: {
277       result = out->set_protocol(new_value_view);
278       break;
279     }
280     case kSearch: {
281       out->set_search(new_value_view);
282       break;
283     }
284     case kUsername: {
285       result = out->set_username(new_value_view);
286       break;
287     }
288     default:
289       UNREACHABLE("Unsupported URL update action");
290   }
291 
292   if (!result) {
293     return args.GetReturnValue().Set(false);
294   }
295 
296   binding_data->UpdateComponents(out->get_components(), out->type);
297   args.GetReturnValue().Set(
298       ToV8Value(env->context(), out->get_href(), env->isolate())
299           .ToLocalChecked());
300 }
301 
ToASCII(const v8::FunctionCallbackInfo<v8::Value> & args)302 void BindingData::ToASCII(const v8::FunctionCallbackInfo<v8::Value>& args) {
303   Environment* env = Environment::GetCurrent(args);
304   CHECK_GE(args.Length(), 1);
305   CHECK(args[0]->IsString());
306 
307   Utf8Value input(env->isolate(), args[0]);
308   auto out = ada::idna::to_ascii(input.ToStringView());
309   args.GetReturnValue().Set(
310       String::NewFromUtf8(env->isolate(), out.c_str()).ToLocalChecked());
311 }
312 
ToUnicode(const v8::FunctionCallbackInfo<v8::Value> & args)313 void BindingData::ToUnicode(const v8::FunctionCallbackInfo<v8::Value>& args) {
314   Environment* env = Environment::GetCurrent(args);
315   CHECK_GE(args.Length(), 1);
316   CHECK(args[0]->IsString());
317 
318   Utf8Value input(env->isolate(), args[0]);
319   auto out = ada::idna::to_unicode(input.ToStringView());
320   args.GetReturnValue().Set(
321       String::NewFromUtf8(env->isolate(), out.c_str()).ToLocalChecked());
322 }
323 
UpdateComponents(const ada::url_components & components,const ada::scheme::type type)324 void BindingData::UpdateComponents(const ada::url_components& components,
325                                    const ada::scheme::type type) {
326   url_components_buffer_[0] = components.protocol_end;
327   url_components_buffer_[1] = components.username_end;
328   url_components_buffer_[2] = components.host_start;
329   url_components_buffer_[3] = components.host_end;
330   url_components_buffer_[4] = components.port;
331   url_components_buffer_[5] = components.pathname_start;
332   url_components_buffer_[6] = components.search_start;
333   url_components_buffer_[7] = components.hash_start;
334   url_components_buffer_[8] = type;
335   static_assert(kURLComponentsLength == 9,
336                 "kURLComponentsLength should be up-to-date");
337 }
338 
Initialize(Local<Object> target,Local<Value> unused,Local<Context> context,void * priv)339 void BindingData::Initialize(Local<Object> target,
340                              Local<Value> unused,
341                              Local<Context> context,
342                              void* priv) {
343   Realm* realm = Realm::GetCurrent(context);
344   BindingData* const binding_data =
345       realm->AddBindingData<BindingData>(context, target);
346   if (binding_data == nullptr) return;
347 
348   SetMethodNoSideEffect(context, target, "toASCII", ToASCII);
349   SetMethodNoSideEffect(context, target, "toUnicode", ToUnicode);
350   SetMethodNoSideEffect(context, target, "domainToASCII", DomainToASCII);
351   SetMethodNoSideEffect(context, target, "domainToUnicode", DomainToUnicode);
352   SetMethodNoSideEffect(context, target, "canParse", CanParse);
353   SetMethodNoSideEffect(context, target, "format", Format);
354   SetMethod(context, target, "parse", Parse);
355   SetMethod(context, target, "update", Update);
356 }
357 
RegisterExternalReferences(ExternalReferenceRegistry * registry)358 void BindingData::RegisterExternalReferences(
359     ExternalReferenceRegistry* registry) {
360   registry->Register(ToASCII);
361   registry->Register(ToUnicode);
362   registry->Register(DomainToASCII);
363   registry->Register(DomainToUnicode);
364   registry->Register(CanParse);
365   registry->Register(Format);
366   registry->Register(Parse);
367   registry->Register(Update);
368 }
369 
FromFilePath(const std::string_view file_path)370 std::string FromFilePath(const std::string_view file_path) {
371   std::string escaped_file_path;
372   for (size_t i = 0; i < file_path.length(); ++i) {
373     escaped_file_path += file_path[i];
374     if (file_path[i] == '%') escaped_file_path += "25";
375   }
376 
377   return ada::href_from_file(escaped_file_path);
378 }
379 
380 }  // namespace url
381 
382 }  // namespace node
383 
384 NODE_BINDING_CONTEXT_AWARE_INTERNAL(url, node::url::BindingData::Initialize)
385 NODE_BINDING_EXTERNAL_REFERENCE(
386     url, node::url::BindingData::RegisterExternalReferences)
387