xref: /third_party/node/src/node_url.cc (revision 1cb0ef41)
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
14namespace node {
15namespace url {
16
17using v8::Context;
18using v8::FunctionCallbackInfo;
19using v8::HandleScope;
20using v8::Isolate;
21using v8::Local;
22using v8::NewStringType;
23using v8::Object;
24using v8::String;
25using v8::Value;
26
27void BindingData::MemoryInfo(MemoryTracker* tracker) const {
28  tracker->TrackField("url_components_buffer", url_components_buffer_);
29}
30
31BindingData::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
41bool 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
51InternalFieldInfoBase* BindingData::Serialize(int index) {
52  DCHECK_EQ(index, BaseObject::kEmbedderType);
53  InternalFieldInfo* info =
54      InternalFieldInfoBase::New<InternalFieldInfo>(type());
55  return info;
56}
57
58void 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
69void 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
92void 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
122void 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
148void 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
192void 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
227void 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
302void 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
313void 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
324void 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
339void 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
358void 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
370std::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
384NODE_BINDING_CONTEXT_AWARE_INTERNAL(url, node::url::BindingData::Initialize)
385NODE_BINDING_EXTERNAL_REFERENCE(
386    url, node::url::BindingData::RegisterExternalReferences)
387