• 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   // It is important to have an initial value that contains a special scheme.
99   // Since it will change the implementation of `set_hostname` according to URL
100   // spec.
101   auto out = ada::parse<ada::url>("ws://x");
102   DCHECK(out);
103   if (!out->set_hostname(input)) {
104     return args.GetReturnValue().Set(
105         String::NewFromUtf8(env->isolate(), "").ToLocalChecked());
106   }
107   std::string result = ada::unicode::to_unicode(out->get_hostname());
108 
109   args.GetReturnValue().Set(String::NewFromUtf8(env->isolate(),
110                                                 result.c_str(),
111                                                 NewStringType::kNormal,
112                                                 result.length())
113                                 .ToLocalChecked());
114 }
115 
116 // TODO(@anonrig): Add V8 Fast API for CanParse method
CanParse(const FunctionCallbackInfo<Value> & args)117 void BindingData::CanParse(const FunctionCallbackInfo<Value>& args) {
118   CHECK_GE(args.Length(), 1);
119   CHECK(args[0]->IsString());  // input
120   // args[1] // base url
121 
122   Environment* env = Environment::GetCurrent(args);
123   HandleScope handle_scope(env->isolate());
124   Context::Scope context_scope(env->context());
125 
126   Utf8Value input(env->isolate(), args[0]);
127   ada::result<ada::url_aggregator> base;
128   ada::url_aggregator* base_pointer = nullptr;
129   if (args[1]->IsString()) {
130     base = ada::parse<ada::url_aggregator>(
131         Utf8Value(env->isolate(), args[1]).ToString());
132     if (!base) {
133       return args.GetReturnValue().Set(false);
134     }
135     base_pointer = &base.value();
136   }
137   auto out =
138       ada::parse<ada::url_aggregator>(input.ToStringView(), base_pointer);
139 
140   args.GetReturnValue().Set(out.has_value());
141 }
142 
Format(const FunctionCallbackInfo<Value> & args)143 void BindingData::Format(const FunctionCallbackInfo<Value>& args) {
144   CHECK_GT(args.Length(), 4);
145   CHECK(args[0]->IsString());  // url href
146 
147   Environment* env = Environment::GetCurrent(args);
148   Isolate* isolate = env->isolate();
149 
150   Utf8Value href(isolate, args[0].As<String>());
151   const bool hash = args[1]->IsTrue();
152   const bool unicode = args[2]->IsTrue();
153   const bool search = args[3]->IsTrue();
154   const bool auth = args[4]->IsTrue();
155 
156   // ada::url provides a faster alternative to ada::url_aggregator if we
157   // directly want to manipulate the url components without using the respective
158   // setters. therefore we are using ada::url here.
159   auto out = ada::parse<ada::url>(href.ToStringView());
160   CHECK(out);
161 
162   if (!hash) {
163     out->hash = std::nullopt;
164   }
165 
166   if (unicode) {
167     out->host = ada::idna::to_unicode(out->get_hostname());
168   }
169 
170   if (!search) {
171     out->query = std::nullopt;
172   }
173 
174   if (!auth) {
175     out->username = "";
176     out->password = "";
177   }
178 
179   std::string result = out->get_href();
180   args.GetReturnValue().Set(String::NewFromUtf8(env->isolate(),
181                                                 result.data(),
182                                                 NewStringType::kNormal,
183                                                 result.length())
184                                 .ToLocalChecked());
185 }
186 
Parse(const FunctionCallbackInfo<Value> & args)187 void BindingData::Parse(const FunctionCallbackInfo<Value>& args) {
188   CHECK_GE(args.Length(), 1);
189   CHECK(args[0]->IsString());  // input
190   // args[1] // base url
191 
192   BindingData* binding_data = Realm::GetBindingData<BindingData>(args);
193   Environment* env = Environment::GetCurrent(args);
194   HandleScope handle_scope(env->isolate());
195   Context::Scope context_scope(env->context());
196 
197   Utf8Value input(env->isolate(), args[0]);
198   ada::result<ada::url_aggregator> base;
199   ada::url_aggregator* base_pointer = nullptr;
200   if (args[1]->IsString()) {
201     base = ada::parse<ada::url_aggregator>(
202         Utf8Value(env->isolate(), args[1]).ToString());
203     if (!base) {
204       return args.GetReturnValue().Set(false);
205     }
206     base_pointer = &base.value();
207   }
208   auto out =
209       ada::parse<ada::url_aggregator>(input.ToStringView(), base_pointer);
210 
211   if (!out) {
212     return args.GetReturnValue().Set(false);
213   }
214 
215   binding_data->UpdateComponents(out->get_components(), out->type);
216 
217   args.GetReturnValue().Set(
218       ToV8Value(env->context(), out->get_href(), env->isolate())
219           .ToLocalChecked());
220 }
221 
Update(const FunctionCallbackInfo<Value> & args)222 void BindingData::Update(const FunctionCallbackInfo<Value>& args) {
223   CHECK(args[0]->IsString());    // href
224   CHECK(args[1]->IsNumber());    // action type
225   CHECK(args[2]->IsString());    // new value
226 
227   BindingData* binding_data = Realm::GetBindingData<BindingData>(args);
228   Environment* env = Environment::GetCurrent(args);
229   Isolate* isolate = env->isolate();
230 
231   enum url_update_action action = static_cast<enum url_update_action>(
232       args[1]->Uint32Value(env->context()).FromJust());
233   Utf8Value input(isolate, args[0].As<String>());
234   Utf8Value new_value(isolate, args[2].As<String>());
235 
236   std::string_view new_value_view = new_value.ToStringView();
237   auto out = ada::parse<ada::url_aggregator>(input.ToStringView());
238   CHECK(out);
239 
240   bool result{true};
241 
242   switch (action) {
243     case kPathname: {
244       result = out->set_pathname(new_value_view);
245       break;
246     }
247     case kHash: {
248       out->set_hash(new_value_view);
249       break;
250     }
251     case kHost: {
252       result = out->set_host(new_value_view);
253       break;
254     }
255     case kHostname: {
256       result = out->set_hostname(new_value_view);
257       break;
258     }
259     case kHref: {
260       result = out->set_href(new_value_view);
261       break;
262     }
263     case kPassword: {
264       result = out->set_password(new_value_view);
265       break;
266     }
267     case kPort: {
268       result = out->set_port(new_value_view);
269       break;
270     }
271     case kProtocol: {
272       result = out->set_protocol(new_value_view);
273       break;
274     }
275     case kSearch: {
276       out->set_search(new_value_view);
277       break;
278     }
279     case kUsername: {
280       result = out->set_username(new_value_view);
281       break;
282     }
283     default:
284       UNREACHABLE("Unsupported URL update action");
285   }
286 
287   if (!result) {
288     return args.GetReturnValue().Set(false);
289   }
290 
291   binding_data->UpdateComponents(out->get_components(), out->type);
292   args.GetReturnValue().Set(
293       ToV8Value(env->context(), out->get_href(), env->isolate())
294           .ToLocalChecked());
295 }
296 
ToASCII(const v8::FunctionCallbackInfo<v8::Value> & args)297 void BindingData::ToASCII(const v8::FunctionCallbackInfo<v8::Value>& args) {
298   Environment* env = Environment::GetCurrent(args);
299   CHECK_GE(args.Length(), 1);
300   CHECK(args[0]->IsString());
301 
302   Utf8Value input(env->isolate(), args[0]);
303   auto out = ada::idna::to_ascii(input.ToStringView());
304   args.GetReturnValue().Set(
305       String::NewFromUtf8(env->isolate(), out.c_str()).ToLocalChecked());
306 }
307 
ToUnicode(const v8::FunctionCallbackInfo<v8::Value> & args)308 void BindingData::ToUnicode(const v8::FunctionCallbackInfo<v8::Value>& args) {
309   Environment* env = Environment::GetCurrent(args);
310   CHECK_GE(args.Length(), 1);
311   CHECK(args[0]->IsString());
312 
313   Utf8Value input(env->isolate(), args[0]);
314   auto out = ada::idna::to_unicode(input.ToStringView());
315   args.GetReturnValue().Set(
316       String::NewFromUtf8(env->isolate(), out.c_str()).ToLocalChecked());
317 }
318 
UpdateComponents(const ada::url_components & components,const ada::scheme::type type)319 void BindingData::UpdateComponents(const ada::url_components& components,
320                                    const ada::scheme::type type) {
321   url_components_buffer_[0] = components.protocol_end;
322   url_components_buffer_[1] = components.username_end;
323   url_components_buffer_[2] = components.host_start;
324   url_components_buffer_[3] = components.host_end;
325   url_components_buffer_[4] = components.port;
326   url_components_buffer_[5] = components.pathname_start;
327   url_components_buffer_[6] = components.search_start;
328   url_components_buffer_[7] = components.hash_start;
329   url_components_buffer_[8] = type;
330   static_assert(kURLComponentsLength == 9,
331                 "kURLComponentsLength should be up-to-date");
332 }
333 
Initialize(Local<Object> target,Local<Value> unused,Local<Context> context,void * priv)334 void BindingData::Initialize(Local<Object> target,
335                              Local<Value> unused,
336                              Local<Context> context,
337                              void* priv) {
338   Realm* realm = Realm::GetCurrent(context);
339   BindingData* const binding_data =
340       realm->AddBindingData<BindingData>(context, target);
341   if (binding_data == nullptr) return;
342 
343   SetMethodNoSideEffect(context, target, "toASCII", ToASCII);
344   SetMethodNoSideEffect(context, target, "toUnicode", ToUnicode);
345   SetMethodNoSideEffect(context, target, "domainToASCII", DomainToASCII);
346   SetMethodNoSideEffect(context, target, "domainToUnicode", DomainToUnicode);
347   SetMethodNoSideEffect(context, target, "canParse", CanParse);
348   SetMethodNoSideEffect(context, target, "format", Format);
349   SetMethod(context, target, "parse", Parse);
350   SetMethod(context, target, "update", Update);
351 }
352 
RegisterExternalReferences(ExternalReferenceRegistry * registry)353 void BindingData::RegisterExternalReferences(
354     ExternalReferenceRegistry* registry) {
355   registry->Register(ToASCII);
356   registry->Register(ToUnicode);
357   registry->Register(DomainToASCII);
358   registry->Register(DomainToUnicode);
359   registry->Register(CanParse);
360   registry->Register(Format);
361   registry->Register(Parse);
362   registry->Register(Update);
363 }
364 
FromFilePath(const std::string_view file_path)365 std::string FromFilePath(const std::string_view file_path) {
366   std::string escaped_file_path;
367   for (size_t i = 0; i < file_path.length(); ++i) {
368     escaped_file_path += file_path[i];
369     if (file_path[i] == '%') escaped_file_path += "25";
370   }
371 
372   return ada::href_from_file(escaped_file_path);
373 }
374 
375 }  // namespace url
376 
377 }  // namespace node
378 
379 NODE_BINDING_CONTEXT_AWARE_INTERNAL(url, node::url::BindingData::Initialize)
380 NODE_BINDING_EXTERNAL_REFERENCE(
381     url, node::url::BindingData::RegisterExternalReferences)
382