1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "services/proxy_resolver/proxy_resolver_v8.h"
12 #include "base/auto_reset.h"
13 #include "base/check_op.h"
14 #include "base/compiler_specific.h"
15 #include "base/debug/leak_annotations.h"
16 #include "base/lazy_instance.h"
17 #include "base/memory/raw_ptr.h"
18 #include "base/notreached.h"
19 #include "base/strings/string_tokenizer.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/utf_string_conversions.h"
22 #include "base/synchronization/lock.h"
23 #include "base/threading/thread_task_runner_handle.h"
24 #include "gin/array_buffer.h"
25 #include "gin/public/isolate_holder.h"
26 #include "gin/v8_initializer.h"
27 #include "net/base/ip_address.h"
28 #include "net/base/net_errors.h"
29 #include "net/proxy_resolution/pac_file_data.h"
30 #include "net/proxy_resolution/proxy_info.h"
31 #include "services/proxy_resolver/pac_js_library.h"
33 #include "url/url_canon.h"
34 #include "v8/include/v8.h"
36 // Notes on the javascript environment:
38 // For the majority of the PAC utility functions, we use the same code
39 // as Firefox. See the javascript library that pac_js_library.h pulls in.
41 // In addition, we implement a subset of Microsoft's extensions to PAC.
46 // - sortIpAddressList()
48 // It is worth noting that the original PAC specification does not describe
49 // the return values on failure. Consequently, there are compatibility
50 // differences between browsers on what to return on failure, which are
53 // --------------------+-------------+-------------------+--------------
54 // | Firefox3 | InternetExplorer8 | --> Us <---
55 // --------------------+-------------+-------------------+--------------
56 // myIpAddress() | "127.0.0.1" | ??? | "127.0.0.1"
57 // dnsResolve() | null | false | null
58 // myIpAddressEx() | N/A | "" | ""
59 // sortIpAddressList() | N/A | false | false
60 // dnsResolveEx() | N/A | "" | ""
61 // isInNetEx() | N/A | false | false
62 // --------------------+-------------+-------------------+--------------
64 // TODO(eroman): The cell above reading ??? means I didn't test it.
66 // Another difference is in how dnsResolve() and myIpAddress() are
67 // implemented -- whether they should restrict to IPv4 results, or
68 // include both IPv4 and IPv6. The following table illustrates the
71 // --------------------+-------------+-------------------+--------------
72 // | Firefox3 | InternetExplorer8 | --> Us <---
73 // --------------------+-------------+-------------------+--------------
74 // myIpAddress() | IPv4/IPv6 | IPv4 | IPv4/IPv6
75 // dnsResolve() | IPv4/IPv6 | IPv4 | IPv4
76 // isResolvable() | IPv4/IPv6 | IPv4 | IPv4
77 // myIpAddressEx() | N/A | IPv4/IPv6 | IPv4/IPv6
78 // dnsResolveEx() | N/A | IPv4/IPv6 | IPv4/IPv6
79 // sortIpAddressList() | N/A | IPv4/IPv6 | IPv4/IPv6
80 // isResolvableEx() | N/A | IPv4/IPv6 | IPv4/IPv6
81 // isInNetEx() | N/A | IPv4/IPv6 | IPv4/IPv6
82 // -----------------+-------------+-------------------+--------------
84 namespace proxy_resolver {
88 // Pseudo-name for the PAC script.
89 const char kPacResourceName[] = "proxy-pac-script.js";
90 // Pseudo-name for the PAC utility script.
91 const char kPacUtilityResourceName[] = "proxy-pac-utility-script.js";
93 // External string wrapper so V8 can access the UTF16 string wrapped by
95 class V8ExternalStringFromScriptData
96 : public v8::String::ExternalStringResource {
98 explicit V8ExternalStringFromScriptData(
99 const scoped_refptr<net::PacFileData>& script_data)
100 : script_data_(script_data) {}
102 V8ExternalStringFromScriptData(const V8ExternalStringFromScriptData&) =
104 V8ExternalStringFromScriptData& operator=(
105 const V8ExternalStringFromScriptData&) = delete;
107 const uint16_t* data() const override {
108 return reinterpret_cast<const uint16_t*>(script_data_->utf16().data());
111 size_t length() const override { return script_data_->utf16().size(); }
114 const scoped_refptr<net::PacFileData> script_data_;
117 // External string wrapper so V8 can access a string literal.
118 class V8ExternalASCIILiteral
119 : public v8::String::ExternalOneByteStringResource {
121 // |ascii| must be a NULL-terminated C string, and must remain valid
122 // throughout this object's lifetime.
123 V8ExternalASCIILiteral(const char* ascii, size_t length)
124 : ascii_(ascii), length_(length) {
125 DCHECK(base::IsStringASCII(ascii));
128 V8ExternalASCIILiteral(const V8ExternalASCIILiteral&) = delete;
129 V8ExternalASCIILiteral& operator=(const V8ExternalASCIILiteral&) = delete;
131 const char* data() const override { return ascii_; }
133 size_t length() const override { return length_; }
140 // When creating a v8::String from a C++ string we have two choices: create
141 // a copy, or create a wrapper that shares the same underlying storage.
142 // For small strings it is better to just make a copy, whereas for large
143 // strings there are savings by sharing the storage. This number identifies
144 // the cutoff length for when to start wrapping rather than creating copies.
145 const size_t kMaxStringBytesForCopy = 256;
147 // Converts a V8 String to a UTF8 std::string.
148 std::string V8StringToUTF8(v8::Isolate* isolate, v8::Local<v8::String> s) {
149 int len = s->Length();
152 s->WriteUtf8(isolate, base::WriteInto(&result, len + 1));
156 // Converts a V8 String to a UTF16 std::u16string.
157 std::u16string V8StringToUTF16(v8::Isolate* isolate, v8::Local<v8::String> s) {
158 int len = s->Length();
159 std::u16string result;
160 // Note that the reinterpret cast is because on Windows string16 is an alias
161 // to wstring, and hence has character type wchar_t not uint16_t.
164 reinterpret_cast<uint16_t*>(base::WriteInto(&result, len + 1)), 0,
170 // Converts an ASCII std::string to a V8 string.
171 v8::Local<v8::String> ASCIIStringToV8String(v8::Isolate* isolate,
172 const std::string& s) {
173 DCHECK(base::IsStringASCII(s));
174 return v8::String::NewFromUtf8(isolate, s.data(), v8::NewStringType::kNormal,
179 // Converts a UTF16 std::u16string (wrapped by a net::PacFileData) to a
181 v8::Local<v8::String> ScriptDataToV8String(
182 v8::Isolate* isolate,
183 const scoped_refptr<net::PacFileData>& s) {
184 if (s->utf16().size() * 2 <= kMaxStringBytesForCopy) {
185 return v8::String::NewFromTwoByte(
186 isolate, reinterpret_cast<const uint16_t*>(s->utf16().data()),
187 v8::NewStringType::kNormal, s->utf16().size())
190 return v8::String::NewExternalTwoByte(isolate,
191 new V8ExternalStringFromScriptData(s))
195 // Converts an ASCII string literal to a V8 string.
196 v8::Local<v8::String> ASCIILiteralToV8String(v8::Isolate* isolate,
198 DCHECK(base::IsStringASCII(ascii));
199 size_t length = strlen(ascii);
200 if (length <= kMaxStringBytesForCopy)
201 return v8::String::NewFromUtf8(isolate, ascii, v8::NewStringType::kNormal,
204 return v8::String::NewExternalOneByte(
205 isolate, new V8ExternalASCIILiteral(ascii, length))
209 // Stringizes a V8 object by calling its toString() method. Returns true
210 // on success. This may fail if the toString() throws an exception.
211 bool V8ObjectToUTF16String(v8::Local<v8::Value> object,
212 std::u16string* utf16_result,
213 v8::Isolate* isolate) {
214 if (object.IsEmpty())
217 v8::HandleScope scope(isolate);
218 v8::Local<v8::String> str_object;
219 if (!object->ToString(isolate->GetCurrentContext()).ToLocal(&str_object))
221 *utf16_result = V8StringToUTF16(isolate, str_object);
225 // Extracts an hostname argument from |args|. On success returns true
226 // and fills |*hostname| with the result.
227 bool GetHostnameArgument(const v8::FunctionCallbackInfo<v8::Value>& args,
228 std::string* hostname) {
229 // The first argument should be a string.
230 if (args.Length() == 0 || args[0].IsEmpty() || !args[0]->IsString())
233 const std::u16string hostname_utf16 =
234 V8StringToUTF16(args.GetIsolate(), v8::Local<v8::String>::Cast(args[0]));
236 // If the hostname is already in ASCII, simply return it as is.
237 if (base::IsStringASCII(hostname_utf16)) {
238 *hostname = base::UTF16ToASCII(hostname_utf16);
242 // Otherwise try to convert it from IDN to punycode.
243 const int kInitialBufferSize = 256;
244 url::RawCanonOutputT<char16_t, kInitialBufferSize> punycode_output;
245 if (!url::IDNToASCII(hostname_utf16.data(), hostname_utf16.length(),
250 // |punycode_output| should now be ASCII; convert it to a std::string.
251 // (We could use UTF16ToASCII() instead, but that requires an extra string
252 // copy. Since ASCII is a subset of UTF8 the following is equivalent).
253 bool success = base::UTF16ToUTF8(punycode_output.data(),
254 punycode_output.length(), hostname);
256 DCHECK(base::IsStringASCII(*hostname));
260 // Wrapper around an IP address that stores the original string as well as a
261 // corresponding parsed net::IPAddress.
263 // This struct is used as a helper for sorting IP address strings - the IP
264 // literal is parsed just once and used as the sorting key, while also
265 // preserving the original IP literal string.
266 struct IPAddressSortingEntry {
267 IPAddressSortingEntry(const std::string& ip_string,
268 const net::IPAddress& ip_address)
269 : string_value(ip_string), ip_address(ip_address) {}
271 // Used for sorting IP addresses in ascending order in SortIpAddressList().
272 // IPv6 addresses are placed ahead of IPv4 addresses.
273 bool operator<(const IPAddressSortingEntry& rhs) const {
274 const net::IPAddress& ip1 = this->ip_address;
275 const net::IPAddress& ip2 = rhs.ip_address;
276 if (ip1.size() != ip2.size())
277 return ip1.size() > ip2.size(); // IPv6 before IPv4.
278 return ip1 < ip2; // Ascending order.
281 std::string string_value;
282 net::IPAddress ip_address;
285 // Handler for "sortIpAddressList(IpAddressList)". |ip_address_list| is a
286 // semi-colon delimited string containing IP addresses.
287 // |sorted_ip_address_list| is the resulting list of sorted semi-colon delimited
288 // IP addresses or an empty string if unable to sort the IP address list.
289 // Returns 'true' if the sorting was successful, and 'false' if the input was an
290 // empty string, a string of separators (";" in this case), or if any of the IP
291 // addresses in the input list failed to parse.
292 bool SortIpAddressList(const std::string& ip_address_list,
293 std::string* sorted_ip_address_list) {
294 sorted_ip_address_list->clear();
296 // Strip all whitespace (mimics IE behavior).
297 std::string cleaned_ip_address_list;
298 base::RemoveChars(ip_address_list, " \t", &cleaned_ip_address_list);
299 if (cleaned_ip_address_list.empty())
302 // Split-up IP addresses and store them in a vector.
303 std::vector<IPAddressSortingEntry> ip_vector;
304 net::IPAddress ip_address;
305 base::StringTokenizer str_tok(cleaned_ip_address_list, ";");
306 while (str_tok.GetNext()) {
307 if (!ip_address.AssignFromIPLiteral(str_tok.token()))
309 ip_vector.push_back(IPAddressSortingEntry(str_tok.token(), ip_address));
312 if (ip_vector.empty()) // Can happen if we have something like
313 return false; // sortIpAddressList(";") or sortIpAddressList("; ;")
315 DCHECK(!ip_vector.empty());
317 // Sort lists according to ascending numeric value.
318 if (ip_vector.size() > 1)
319 std::stable_sort(ip_vector.begin(), ip_vector.end());
321 // Return a semi-colon delimited list of sorted addresses (IPv6 followed by
323 for (size_t i = 0; i < ip_vector.size(); ++i) {
325 *sorted_ip_address_list += ";";
326 *sorted_ip_address_list += ip_vector[i].string_value;
331 // Handler for "isInNetEx(ip_address, ip_prefix)". |ip_address| is a string
332 // containing an IPv4/IPv6 address, and |ip_prefix| is a string containg a
333 // slash-delimited IP prefix with the top 'n' bits specified in the bit
334 // field. This returns 'true' if the address is in the same subnet, and
335 // 'false' otherwise. Also returns 'false' if the prefix is in an incorrect
336 // format. If the address types of |ip_address| and |ip_prefix| don't match,
337 // will promote the IPv4 literal to an IPv4 mapped IPv6 literal and
338 // proceed with the comparison.
339 bool IsInNetEx(const std::string& ip_address, const std::string& ip_prefix) {
340 net::IPAddress address;
341 if (!address.AssignFromIPLiteral(ip_address))
344 net::IPAddress prefix;
345 size_t prefix_length_in_bits;
346 if (!ParseCIDRBlock(ip_prefix, &prefix, &prefix_length_in_bits))
349 return IPAddressMatchesPrefix(address, prefix, prefix_length_in_bits);
352 // Consider only single component domains like 'foo' as plain host names.
353 bool IsPlainHostName(const std::string& hostname_utf8) {
354 if (hostname_utf8.find('.') != std::string::npos)
357 // IPv6 literals might not contain any periods, however are not considered
359 net::IPAddress unused;
360 return !unused.AssignFromIPLiteral(hostname_utf8);
363 // All instances of ProxyResolverV8 share the same v8::Isolate. This isolate is
364 // created lazily the first time it is needed and lives until process shutdown.
365 // This creation might happen from any thread, as ProxyResolverV8 is typically
366 // run in a threadpool.
368 // TODO(eroman): The lazily created isolate is never freed. Instead it should be
369 // disposed once there are no longer any ProxyResolverV8 referencing it.
370 class SharedIsolateFactory {
372 SharedIsolateFactory() : has_initialized_v8_(false) {}
374 SharedIsolateFactory(const SharedIsolateFactory&) = delete;
375 SharedIsolateFactory& operator=(const SharedIsolateFactory&) = delete;
377 // Lazily creates a v8::Isolate, or returns the already created instance.
378 v8::Isolate* GetSharedIsolate() {
379 base::AutoLock lock(lock_);
382 // Do one-time initialization for V8.
383 if (!has_initialized_v8_) {
384 #if defined(V8_USE_EXTERNAL_STARTUP_DATA)
385 #if defined(USE_V8_CONTEXT_SNAPSHOT)
386 gin::V8Initializer::LoadV8Snapshot(
387 gin::V8SnapshotFileType::kWithAdditionalContext);
389 gin::V8Initializer::LoadV8Snapshot();
393 // The performance of the proxy resolver is limited by DNS resolution,
394 // and not V8, so tune down V8 to use as little memory as possible.
395 static const char kOptimizeForSize[] = "--optimize_for_size";
396 v8::V8::SetFlagsFromString(kOptimizeForSize, strlen(kOptimizeForSize));
398 // Running v8 in jitless mode allows dynamic code to be disabled in the
400 static const char kJitless[] = "--jitless";
401 v8::V8::SetFlagsFromString(kJitless, strlen(kJitless));
403 // WebAssembly isn't encountered during resolution, so reduce the
404 // potential attack surface.
405 static const char kNoExposeWasm[] = "--no-expose-wasm";
406 v8::V8::SetFlagsFromString(kNoExposeWasm, strlen(kNoExposeWasm));
408 gin::IsolateHolder::Initialize(
409 gin::IsolateHolder::kNonStrictMode,
410 gin::ArrayBufferAllocator::SharedInstance());
412 has_initialized_v8_ = true;
415 holder_ = std::make_unique<gin::IsolateHolder>(
416 base::ThreadTaskRunnerHandle::Get(), gin::IsolateHolder::kUseLocker,
417 gin::IsolateHolder::IsolateType::kUtility);
420 return holder_->isolate();
423 v8::Isolate* GetSharedIsolateWithoutCreating() {
424 base::AutoLock lock(lock_);
425 return holder_ ? holder_->isolate() : nullptr;
430 std::unique_ptr<gin::IsolateHolder> holder_;
431 bool has_initialized_v8_;
434 base::LazyInstance<SharedIsolateFactory>::Leaky g_isolate_factory =
435 LAZY_INSTANCE_INITIALIZER;
439 // ProxyResolverV8::Context ---------------------------------------------------
441 class ProxyResolverV8::Context {
443 explicit Context(v8::Isolate* isolate)
444 : js_bindings_(nullptr), isolate_(isolate) {
449 v8::Locker locked(isolate_);
450 v8::Isolate::Scope isolate_scope(isolate_);
456 JSBindings* js_bindings() { return js_bindings_; }
458 int ResolveProxy(const GURL& query_url,
459 net::ProxyInfo* results,
460 JSBindings* bindings) {
462 base::AutoReset<JSBindings*> bindings_reset(&js_bindings_, bindings);
463 v8::Locker locked(isolate_);
464 v8::Isolate::Scope isolate_scope(isolate_);
465 v8::Isolate::SafeForTerminationScope safe_for_termination(isolate_);
466 v8::HandleScope scope(isolate_);
468 v8::Local<v8::Context> context =
469 v8::Local<v8::Context>::New(isolate_, v8_context_);
470 v8::Context::Scope function_scope(context);
472 v8::Local<v8::Value> function;
473 int rv = GetFindProxyForURL(&function);
477 v8::Local<v8::Value> argv[] = {
478 ASCIIStringToV8String(isolate_, query_url.spec()),
479 ASCIIStringToV8String(isolate_, query_url.HostNoBrackets()),
482 v8::TryCatch try_catch(isolate_);
483 v8::Local<v8::Value> ret;
484 if (!v8::Function::Cast(*function)
485 ->Call(context, context->Global(), std::size(argv), argv)
487 DCHECK(try_catch.HasCaught());
488 HandleError(try_catch.Message());
489 return net::ERR_PAC_SCRIPT_FAILED;
492 if (!ret->IsString()) {
493 js_bindings()->OnError(-1, u"FindProxyForURL() did not return a string.");
494 return net::ERR_PAC_SCRIPT_FAILED;
497 std::u16string ret_str =
498 V8StringToUTF16(isolate_, v8::Local<v8::String>::Cast(ret));
500 if (!base::IsStringASCII(ret_str)) {
501 // TODO(eroman): Rather than failing when a wide string is returned, we
502 // could extend the parsing to handle IDNA hostnames by
503 // converting them to ASCII punycode.
505 std::u16string error_message =
506 u"FindProxyForURL() returned a non-ASCII string (crbug.com/47234): " +
508 js_bindings()->OnError(-1, error_message);
509 return net::ERR_PAC_SCRIPT_FAILED;
512 results->UsePacString(base::UTF16ToASCII(ret_str));
516 int InitV8(const scoped_refptr<net::PacFileData>& pac_script,
517 JSBindings* bindings) {
518 base::AutoReset<JSBindings*> bindings_reset(&js_bindings_, bindings);
519 v8::Locker locked(isolate_);
520 v8::Isolate::Scope isolate_scope(isolate_);
521 v8::HandleScope scope(isolate_);
523 v8_this_.Reset(isolate_, v8::External::New(isolate_, this));
524 v8::Local<v8::External> v8_this =
525 v8::Local<v8::External>::New(isolate_, v8_this_);
526 v8::Local<v8::ObjectTemplate> global_template =
527 v8::ObjectTemplate::New(isolate_);
529 // Attach the javascript bindings.
530 v8::Local<v8::FunctionTemplate> alert_template =
531 v8::FunctionTemplate::New(isolate_, &AlertCallback, v8_this);
532 alert_template->RemovePrototype();
533 global_template->Set(ASCIILiteralToV8String(isolate_, "alert"),
536 v8::Local<v8::FunctionTemplate> my_ip_address_template =
537 v8::FunctionTemplate::New(isolate_, &MyIpAddressCallback, v8_this);
538 my_ip_address_template->RemovePrototype();
539 global_template->Set(ASCIILiteralToV8String(isolate_, "myIpAddress"),
540 my_ip_address_template);
542 v8::Local<v8::FunctionTemplate> dns_resolve_template =
543 v8::FunctionTemplate::New(isolate_, &DnsResolveCallback, v8_this);
544 dns_resolve_template->RemovePrototype();
545 global_template->Set(ASCIILiteralToV8String(isolate_, "dnsResolve"),
546 dns_resolve_template);
548 v8::Local<v8::FunctionTemplate> is_plain_host_name_template =
549 v8::FunctionTemplate::New(isolate_, &IsPlainHostNameCallback, v8_this);
550 is_plain_host_name_template->RemovePrototype();
551 global_template->Set(ASCIILiteralToV8String(isolate_, "isPlainHostName"),
552 is_plain_host_name_template);
554 // Microsoft's PAC extensions:
556 v8::Local<v8::FunctionTemplate> dns_resolve_ex_template =
557 v8::FunctionTemplate::New(isolate_, &DnsResolveExCallback, v8_this);
558 dns_resolve_ex_template->RemovePrototype();
559 global_template->Set(ASCIILiteralToV8String(isolate_, "dnsResolveEx"),
560 dns_resolve_ex_template);
562 v8::Local<v8::FunctionTemplate> my_ip_address_ex_template =
563 v8::FunctionTemplate::New(isolate_, &MyIpAddressExCallback, v8_this);
564 my_ip_address_ex_template->RemovePrototype();
565 global_template->Set(ASCIILiteralToV8String(isolate_, "myIpAddressEx"),
566 my_ip_address_ex_template);
568 v8::Local<v8::FunctionTemplate> sort_ip_address_list_template =
569 v8::FunctionTemplate::New(isolate_, &SortIpAddressListCallback,
571 sort_ip_address_list_template->RemovePrototype();
572 global_template->Set(ASCIILiteralToV8String(isolate_, "sortIpAddressList"),
573 sort_ip_address_list_template);
575 v8::Local<v8::FunctionTemplate> is_in_net_ex_template =
576 v8::FunctionTemplate::New(isolate_, &IsInNetExCallback, v8_this);
577 is_in_net_ex_template->RemovePrototype();
578 global_template->Set(ASCIILiteralToV8String(isolate_, "isInNetEx"),
579 is_in_net_ex_template);
581 v8_context_.Reset(isolate_,
582 v8::Context::New(isolate_, nullptr, global_template));
584 v8::Local<v8::Context> context =
585 v8::Local<v8::Context>::New(isolate_, v8_context_);
586 v8::Context::Scope ctx(context);
588 // Add the PAC utility functions to the environment.
589 // (This script should never fail, as it is a string literal!)
590 // Note that the two string literals are concatenated.
592 ASCIILiteralToV8String(isolate_, PAC_JS_LIBRARY PAC_JS_LIBRARY_EX),
593 kPacUtilityResourceName);
599 // Add the user's PAC code to the environment.
601 RunScript(ScriptDataToV8String(isolate_, pac_script), kPacResourceName);
605 // At a minimum, the FindProxyForURL() function must be defined for this
606 // to be a legitimiate PAC script.
607 v8::Local<v8::Value> function;
608 return GetFindProxyForURL(&function);
612 int GetFindProxyForURL(v8::Local<v8::Value>* function) {
613 v8::Local<v8::Context> context =
614 v8::Local<v8::Context>::New(isolate_, v8_context_);
616 v8::TryCatch try_catch(isolate_);
618 if (!context->Global()
619 ->Get(context, ASCIILiteralToV8String(isolate_, "FindProxyForURL"))
620 .ToLocal(function)) {
621 DCHECK(try_catch.HasCaught());
622 HandleError(try_catch.Message());
625 // The value should only be empty if an exception was thrown. Code
626 // defensively just in case.
627 DCHECK_EQ(function->IsEmpty(), try_catch.HasCaught());
628 if (function->IsEmpty() || try_catch.HasCaught()) {
629 js_bindings()->OnError(-1,
630 u"Accessing FindProxyForURL threw an exception.");
631 return net::ERR_PAC_SCRIPT_FAILED;
634 if (!(*function)->IsFunction()) {
635 js_bindings()->OnError(
636 -1, u"FindProxyForURL is undefined or not a function.");
637 return net::ERR_PAC_SCRIPT_FAILED;
643 // Handle an exception thrown by V8.
644 void HandleError(v8::Local<v8::Message> message) {
645 v8::Local<v8::Context> context =
646 v8::Local<v8::Context>::New(isolate_, v8_context_);
647 std::u16string error_message;
648 int line_number = -1;
650 if (!message.IsEmpty()) {
651 auto maybe = message->GetLineNumber(context);
653 line_number = maybe.FromJust();
654 V8ObjectToUTF16String(message->Get(), &error_message, isolate_);
657 js_bindings()->OnError(line_number, error_message);
660 // Compiles and runs |script| in the current V8 context.
661 // Returns net::OK on success, otherwise an error code.
662 int RunScript(v8::Local<v8::String> script, const char* script_name) {
663 v8::Local<v8::Context> context =
664 v8::Local<v8::Context>::New(isolate_, v8_context_);
665 v8::TryCatch try_catch(isolate_);
667 // Compile the script.
668 v8::ScriptOrigin origin = v8::ScriptOrigin(
669 isolate_, ASCIILiteralToV8String(isolate_, script_name));
670 v8::ScriptCompiler::Source script_source(script, origin);
671 v8::Local<v8::Script> code;
672 if (!v8::ScriptCompiler::Compile(
673 context, &script_source, v8::ScriptCompiler::kNoCompileOptions,
674 v8::ScriptCompiler::NoCacheReason::kNoCacheBecausePacScript)
676 DCHECK(try_catch.HasCaught());
677 HandleError(try_catch.Message());
678 return net::ERR_PAC_SCRIPT_FAILED;
682 auto result = code->Run(context);
683 if (result.IsEmpty()) {
684 DCHECK(try_catch.HasCaught());
685 HandleError(try_catch.Message());
686 return net::ERR_PAC_SCRIPT_FAILED;
692 // V8 callback for when "alert()" is invoked by the PAC script.
693 static void AlertCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
695 static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
697 // Like firefox we assume "undefined" if no argument was specified, and
698 // disregard any arguments beyond the first.
699 std::u16string message;
700 if (args.Length() == 0) {
701 message = u"undefined";
703 if (!V8ObjectToUTF16String(args[0], &message, args.GetIsolate()))
704 return; // toString() threw an exception.
707 context->js_bindings()->Alert(message);
710 // V8 callback for when "myIpAddress()" is invoked by the PAC script.
711 static void MyIpAddressCallback(
712 const v8::FunctionCallbackInfo<v8::Value>& args) {
713 DnsResolveCallbackHelper(args,
714 net::ProxyResolveDnsOperation::MY_IP_ADDRESS);
717 // V8 callback for when "myIpAddressEx()" is invoked by the PAC script.
718 static void MyIpAddressExCallback(
719 const v8::FunctionCallbackInfo<v8::Value>& args) {
720 DnsResolveCallbackHelper(args,
721 net::ProxyResolveDnsOperation::MY_IP_ADDRESS_EX);
724 // V8 callback for when "dnsResolve()" is invoked by the PAC script.
725 static void DnsResolveCallback(
726 const v8::FunctionCallbackInfo<v8::Value>& args) {
727 DnsResolveCallbackHelper(args, net::ProxyResolveDnsOperation::DNS_RESOLVE);
730 // V8 callback for when "dnsResolveEx()" is invoked by the PAC script.
731 static void DnsResolveExCallback(
732 const v8::FunctionCallbackInfo<v8::Value>& args) {
733 DnsResolveCallbackHelper(args,
734 net::ProxyResolveDnsOperation::DNS_RESOLVE_EX);
737 // Shared code for implementing:
738 // - myIpAddress(), myIpAddressEx(), dnsResolve(), dnsResolveEx().
739 static void DnsResolveCallbackHelper(
740 const v8::FunctionCallbackInfo<v8::Value>& args,
741 net::ProxyResolveDnsOperation op) {
743 static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
745 std::string hostname;
747 // dnsResolve() and dnsResolveEx() need at least 1 argument.
748 if (op == net::ProxyResolveDnsOperation::DNS_RESOLVE ||
749 op == net::ProxyResolveDnsOperation::DNS_RESOLVE_EX) {
750 if (!GetHostnameArgument(args, &hostname)) {
751 if (op == net::ProxyResolveDnsOperation::DNS_RESOLVE)
752 args.GetReturnValue().SetNull();
759 bool terminate = false;
762 v8::Unlocker unlocker(args.GetIsolate());
764 context->js_bindings()->ResolveDns(hostname, op, &result, &terminate);
768 args.GetIsolate()->TerminateExecution();
771 args.GetReturnValue().Set(
772 ASCIIStringToV8String(args.GetIsolate(), result));
776 // Each function handles resolution errors differently.
778 case net::ProxyResolveDnsOperation::DNS_RESOLVE:
779 args.GetReturnValue().SetNull();
781 case net::ProxyResolveDnsOperation::DNS_RESOLVE_EX:
782 args.GetReturnValue().SetEmptyString();
784 case net::ProxyResolveDnsOperation::MY_IP_ADDRESS:
785 args.GetReturnValue().Set(
786 ASCIILiteralToV8String(args.GetIsolate(), "127.0.0.1"));
788 case net::ProxyResolveDnsOperation::MY_IP_ADDRESS_EX:
789 args.GetReturnValue().SetEmptyString();
796 // V8 callback for when "sortIpAddressList()" is invoked by the PAC script.
797 static void SortIpAddressListCallback(
798 const v8::FunctionCallbackInfo<v8::Value>& args) {
799 // We need at least one string argument.
800 if (args.Length() == 0 || args[0].IsEmpty() || !args[0]->IsString()) {
801 args.GetReturnValue().SetNull();
805 std::string ip_address_list =
806 V8StringToUTF8(args.GetIsolate(), v8::Local<v8::String>::Cast(args[0]));
807 if (!base::IsStringASCII(ip_address_list)) {
808 args.GetReturnValue().SetNull();
811 std::string sorted_ip_address_list;
812 bool success = SortIpAddressList(ip_address_list, &sorted_ip_address_list);
814 args.GetReturnValue().Set(false);
817 args.GetReturnValue().Set(
818 ASCIIStringToV8String(args.GetIsolate(), sorted_ip_address_list));
821 // V8 callback for when "isInNetEx()" is invoked by the PAC script.
822 static void IsInNetExCallback(
823 const v8::FunctionCallbackInfo<v8::Value>& args) {
824 // We need at least 2 string arguments.
825 if (args.Length() < 2 || args[0].IsEmpty() || !args[0]->IsString() ||
826 args[1].IsEmpty() || !args[1]->IsString()) {
827 args.GetReturnValue().SetNull();
831 std::string ip_address =
832 V8StringToUTF8(args.GetIsolate(), v8::Local<v8::String>::Cast(args[0]));
833 if (!base::IsStringASCII(ip_address)) {
834 args.GetReturnValue().Set(false);
837 std::string ip_prefix =
838 V8StringToUTF8(args.GetIsolate(), v8::Local<v8::String>::Cast(args[1]));
839 if (!base::IsStringASCII(ip_prefix)) {
840 args.GetReturnValue().Set(false);
843 args.GetReturnValue().Set(IsInNetEx(ip_address, ip_prefix));
846 // V8 callback for when "isPlainHostName()" is invoked by the PAC script.
847 static void IsPlainHostNameCallback(
848 const v8::FunctionCallbackInfo<v8::Value>& args) {
849 // Need at least 1 string arguments.
850 if (args.Length() < 1 || args[0].IsEmpty() || !args[0]->IsString()) {
851 args.GetIsolate()->ThrowException(
852 v8::Exception::TypeError(ASCIIStringToV8String(
853 args.GetIsolate(), "Requires 1 string parameter")));
857 std::string hostname_utf8 =
858 V8StringToUTF8(args.GetIsolate(), v8::Local<v8::String>::Cast(args[0]));
859 args.GetReturnValue().Set(IsPlainHostName(hostname_utf8));
862 mutable base::Lock lock_;
863 ProxyResolverV8::JSBindings* js_bindings_;
864 raw_ptr<v8::Isolate> isolate_;
865 v8::Persistent<v8::External> v8_this_;
866 v8::Persistent<v8::Context> v8_context_;
869 // ProxyResolverV8 ------------------------------------------------------------
871 ProxyResolverV8::ProxyResolverV8(std::unique_ptr<Context> context)
872 : context_(std::move(context)) {
876 ProxyResolverV8::~ProxyResolverV8() = default;
878 int ProxyResolverV8::GetProxyForURL(const GURL& query_url,
879 net::ProxyInfo* results,
880 ProxyResolverV8::JSBindings* bindings) {
881 return context_->ResolveProxy(query_url, results, bindings);
885 int ProxyResolverV8::Create(const scoped_refptr<net::PacFileData>& script_data,
886 ProxyResolverV8::JSBindings* js_bindings,
887 std::unique_ptr<ProxyResolverV8>* resolver) {
888 DCHECK(script_data.get());
891 if (script_data->utf16().empty())
892 return net::ERR_PAC_SCRIPT_FAILED;
894 // Try parsing the PAC script.
895 std::unique_ptr<Context> context(
896 new Context(g_isolate_factory.Get().GetSharedIsolate()));
897 int rv = context->InitV8(script_data, js_bindings);
899 resolver->reset(new ProxyResolverV8(std::move(context)));
904 size_t ProxyResolverV8::GetTotalHeapSize() {
905 v8::Isolate* isolate =
906 g_isolate_factory.Get().GetSharedIsolateWithoutCreating();
910 v8::Locker locked(isolate);
911 v8::Isolate::Scope isolate_scope(isolate);
912 v8::HeapStatistics heap_statistics;
913 isolate->GetHeapStatistics(&heap_statistics);
914 return heap_statistics.total_heap_size();
918 size_t ProxyResolverV8::GetUsedHeapSize() {
919 v8::Isolate* isolate =
920 g_isolate_factory.Get().GetSharedIsolateWithoutCreating();
924 v8::Locker locked(isolate);
925 v8::Isolate::Scope isolate_scope(isolate);
926 v8::HeapStatistics heap_statistics;
927 isolate->GetHeapStatistics(&heap_statistics);
928 return heap_statistics.used_heap_size();
931 } // namespace proxy_resolver