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