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