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