123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931 |
- // Copyright (c) 2012 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "services/proxy_resolver/proxy_resolver_v8.h"
- #include <algorithm>
- #include <cstdio>
- #include <memory>
- #include <utility>
- #include "base/auto_reset.h"
- #include "base/check_op.h"
- #include "base/compiler_specific.h"
- #include "base/debug/leak_annotations.h"
- #include "base/lazy_instance.h"
- #include "base/memory/raw_ptr.h"
- #include "base/notreached.h"
- #include "base/strings/string_tokenizer.h"
- #include "base/strings/string_util.h"
- #include "base/strings/utf_string_conversions.h"
- #include "base/synchronization/lock.h"
- #include "base/threading/thread_task_runner_handle.h"
- #include "gin/array_buffer.h"
- #include "gin/public/isolate_holder.h"
- #include "gin/v8_initializer.h"
- #include "net/base/ip_address.h"
- #include "net/base/net_errors.h"
- #include "net/proxy_resolution/pac_file_data.h"
- #include "net/proxy_resolution/proxy_info.h"
- #include "services/proxy_resolver/pac_js_library.h"
- #include "url/gurl.h"
- #include "url/url_canon.h"
- #include "v8/include/v8.h"
- // Notes on the javascript environment:
- //
- // For the majority of the PAC utility functions, we use the same code
- // as Firefox. See the javascript library that pac_js_library.h pulls in.
- //
- // In addition, we implement a subset of Microsoft's extensions to PAC.
- // - myIpAddressEx()
- // - dnsResolveEx()
- // - isResolvableEx()
- // - isInNetEx()
- // - sortIpAddressList()
- //
- // It is worth noting that the original PAC specification does not describe
- // the return values on failure. Consequently, there are compatibility
- // differences between browsers on what to return on failure, which are
- // illustrated below:
- //
- // --------------------+-------------+-------------------+--------------
- // | Firefox3 | InternetExplorer8 | --> Us <---
- // --------------------+-------------+-------------------+--------------
- // myIpAddress() | "127.0.0.1" | ??? | "127.0.0.1"
- // dnsResolve() | null | false | null
- // myIpAddressEx() | N/A | "" | ""
- // sortIpAddressList() | N/A | false | false
- // dnsResolveEx() | N/A | "" | ""
- // isInNetEx() | N/A | false | false
- // --------------------+-------------+-------------------+--------------
- //
- // TODO(eroman): The cell above reading ??? means I didn't test it.
- //
- // Another difference is in how dnsResolve() and myIpAddress() are
- // implemented -- whether they should restrict to IPv4 results, or
- // include both IPv4 and IPv6. The following table illustrates the
- // differences:
- //
- // --------------------+-------------+-------------------+--------------
- // | Firefox3 | InternetExplorer8 | --> Us <---
- // --------------------+-------------+-------------------+--------------
- // myIpAddress() | IPv4/IPv6 | IPv4 | IPv4/IPv6
- // dnsResolve() | IPv4/IPv6 | IPv4 | IPv4
- // isResolvable() | IPv4/IPv6 | IPv4 | IPv4
- // myIpAddressEx() | N/A | IPv4/IPv6 | IPv4/IPv6
- // dnsResolveEx() | N/A | IPv4/IPv6 | IPv4/IPv6
- // sortIpAddressList() | N/A | IPv4/IPv6 | IPv4/IPv6
- // isResolvableEx() | N/A | IPv4/IPv6 | IPv4/IPv6
- // isInNetEx() | N/A | IPv4/IPv6 | IPv4/IPv6
- // -----------------+-------------+-------------------+--------------
- namespace proxy_resolver {
- namespace {
- // Pseudo-name for the PAC script.
- const char kPacResourceName[] = "proxy-pac-script.js";
- // Pseudo-name for the PAC utility script.
- const char kPacUtilityResourceName[] = "proxy-pac-utility-script.js";
- // External string wrapper so V8 can access the UTF16 string wrapped by
- // net::PacFileData.
- class V8ExternalStringFromScriptData
- : public v8::String::ExternalStringResource {
- public:
- explicit V8ExternalStringFromScriptData(
- const scoped_refptr<net::PacFileData>& script_data)
- : script_data_(script_data) {}
- V8ExternalStringFromScriptData(const V8ExternalStringFromScriptData&) =
- delete;
- V8ExternalStringFromScriptData& operator=(
- const V8ExternalStringFromScriptData&) = delete;
- const uint16_t* data() const override {
- return reinterpret_cast<const uint16_t*>(script_data_->utf16().data());
- }
- size_t length() const override { return script_data_->utf16().size(); }
- private:
- const scoped_refptr<net::PacFileData> script_data_;
- };
- // External string wrapper so V8 can access a string literal.
- class V8ExternalASCIILiteral
- : public v8::String::ExternalOneByteStringResource {
- public:
- // |ascii| must be a NULL-terminated C string, and must remain valid
- // throughout this object's lifetime.
- V8ExternalASCIILiteral(const char* ascii, size_t length)
- : ascii_(ascii), length_(length) {
- DCHECK(base::IsStringASCII(ascii));
- }
- V8ExternalASCIILiteral(const V8ExternalASCIILiteral&) = delete;
- V8ExternalASCIILiteral& operator=(const V8ExternalASCIILiteral&) = delete;
- const char* data() const override { return ascii_; }
- size_t length() const override { return length_; }
- private:
- const char* ascii_;
- size_t length_;
- };
- // When creating a v8::String from a C++ string we have two choices: create
- // a copy, or create a wrapper that shares the same underlying storage.
- // For small strings it is better to just make a copy, whereas for large
- // strings there are savings by sharing the storage. This number identifies
- // the cutoff length for when to start wrapping rather than creating copies.
- const size_t kMaxStringBytesForCopy = 256;
- // Converts a V8 String to a UTF8 std::string.
- std::string V8StringToUTF8(v8::Isolate* isolate, v8::Local<v8::String> s) {
- int len = s->Length();
- std::string result;
- if (len > 0)
- s->WriteUtf8(isolate, base::WriteInto(&result, len + 1));
- return result;
- }
- // Converts a V8 String to a UTF16 std::u16string.
- std::u16string V8StringToUTF16(v8::Isolate* isolate, v8::Local<v8::String> s) {
- int len = s->Length();
- std::u16string result;
- // Note that the reinterpret cast is because on Windows string16 is an alias
- // to wstring, and hence has character type wchar_t not uint16_t.
- if (len > 0) {
- s->Write(isolate,
- reinterpret_cast<uint16_t*>(base::WriteInto(&result, len + 1)), 0,
- len);
- }
- return result;
- }
- // Converts an ASCII std::string to a V8 string.
- v8::Local<v8::String> ASCIIStringToV8String(v8::Isolate* isolate,
- const std::string& s) {
- DCHECK(base::IsStringASCII(s));
- return v8::String::NewFromUtf8(isolate, s.data(), v8::NewStringType::kNormal,
- s.size())
- .ToLocalChecked();
- }
- // Converts a UTF16 std::u16string (wrapped by a net::PacFileData) to a
- // V8 string.
- v8::Local<v8::String> ScriptDataToV8String(
- v8::Isolate* isolate,
- const scoped_refptr<net::PacFileData>& s) {
- if (s->utf16().size() * 2 <= kMaxStringBytesForCopy) {
- return v8::String::NewFromTwoByte(
- isolate, reinterpret_cast<const uint16_t*>(s->utf16().data()),
- v8::NewStringType::kNormal, s->utf16().size())
- .ToLocalChecked();
- }
- return v8::String::NewExternalTwoByte(isolate,
- new V8ExternalStringFromScriptData(s))
- .ToLocalChecked();
- }
- // Converts an ASCII string literal to a V8 string.
- v8::Local<v8::String> ASCIILiteralToV8String(v8::Isolate* isolate,
- const char* ascii) {
- DCHECK(base::IsStringASCII(ascii));
- size_t length = strlen(ascii);
- if (length <= kMaxStringBytesForCopy)
- return v8::String::NewFromUtf8(isolate, ascii, v8::NewStringType::kNormal,
- length)
- .ToLocalChecked();
- return v8::String::NewExternalOneByte(
- isolate, new V8ExternalASCIILiteral(ascii, length))
- .ToLocalChecked();
- }
- // Stringizes a V8 object by calling its toString() method. Returns true
- // on success. This may fail if the toString() throws an exception.
- bool V8ObjectToUTF16String(v8::Local<v8::Value> object,
- std::u16string* utf16_result,
- v8::Isolate* isolate) {
- if (object.IsEmpty())
- return false;
- v8::HandleScope scope(isolate);
- v8::Local<v8::String> str_object;
- if (!object->ToString(isolate->GetCurrentContext()).ToLocal(&str_object))
- return false;
- *utf16_result = V8StringToUTF16(isolate, str_object);
- return true;
- }
- // Extracts an hostname argument from |args|. On success returns true
- // and fills |*hostname| with the result.
- bool GetHostnameArgument(const v8::FunctionCallbackInfo<v8::Value>& args,
- std::string* hostname) {
- // The first argument should be a string.
- if (args.Length() == 0 || args[0].IsEmpty() || !args[0]->IsString())
- return false;
- const std::u16string hostname_utf16 =
- V8StringToUTF16(args.GetIsolate(), v8::Local<v8::String>::Cast(args[0]));
- // If the hostname is already in ASCII, simply return it as is.
- if (base::IsStringASCII(hostname_utf16)) {
- *hostname = base::UTF16ToASCII(hostname_utf16);
- return true;
- }
- // Otherwise try to convert it from IDN to punycode.
- const int kInitialBufferSize = 256;
- url::RawCanonOutputT<char16_t, kInitialBufferSize> punycode_output;
- if (!url::IDNToASCII(hostname_utf16.data(), hostname_utf16.length(),
- &punycode_output)) {
- return false;
- }
- // |punycode_output| should now be ASCII; convert it to a std::string.
- // (We could use UTF16ToASCII() instead, but that requires an extra string
- // copy. Since ASCII is a subset of UTF8 the following is equivalent).
- bool success = base::UTF16ToUTF8(punycode_output.data(),
- punycode_output.length(), hostname);
- DCHECK(success);
- DCHECK(base::IsStringASCII(*hostname));
- return success;
- }
- // Wrapper around an IP address that stores the original string as well as a
- // corresponding parsed net::IPAddress.
- // This struct is used as a helper for sorting IP address strings - the IP
- // literal is parsed just once and used as the sorting key, while also
- // preserving the original IP literal string.
- struct IPAddressSortingEntry {
- IPAddressSortingEntry(const std::string& ip_string,
- const net::IPAddress& ip_address)
- : string_value(ip_string), ip_address(ip_address) {}
- // Used for sorting IP addresses in ascending order in SortIpAddressList().
- // IPv6 addresses are placed ahead of IPv4 addresses.
- bool operator<(const IPAddressSortingEntry& rhs) const {
- const net::IPAddress& ip1 = this->ip_address;
- const net::IPAddress& ip2 = rhs.ip_address;
- if (ip1.size() != ip2.size())
- return ip1.size() > ip2.size(); // IPv6 before IPv4.
- return ip1 < ip2; // Ascending order.
- }
- std::string string_value;
- net::IPAddress ip_address;
- };
- // Handler for "sortIpAddressList(IpAddressList)". |ip_address_list| is a
- // semi-colon delimited string containing IP addresses.
- // |sorted_ip_address_list| is the resulting list of sorted semi-colon delimited
- // IP addresses or an empty string if unable to sort the IP address list.
- // Returns 'true' if the sorting was successful, and 'false' if the input was an
- // empty string, a string of separators (";" in this case), or if any of the IP
- // addresses in the input list failed to parse.
- bool SortIpAddressList(const std::string& ip_address_list,
- std::string* sorted_ip_address_list) {
- sorted_ip_address_list->clear();
- // Strip all whitespace (mimics IE behavior).
- std::string cleaned_ip_address_list;
- base::RemoveChars(ip_address_list, " \t", &cleaned_ip_address_list);
- if (cleaned_ip_address_list.empty())
- return false;
- // Split-up IP addresses and store them in a vector.
- std::vector<IPAddressSortingEntry> ip_vector;
- net::IPAddress ip_address;
- base::StringTokenizer str_tok(cleaned_ip_address_list, ";");
- while (str_tok.GetNext()) {
- if (!ip_address.AssignFromIPLiteral(str_tok.token()))
- return false;
- ip_vector.push_back(IPAddressSortingEntry(str_tok.token(), ip_address));
- }
- if (ip_vector.empty()) // Can happen if we have something like
- return false; // sortIpAddressList(";") or sortIpAddressList("; ;")
- DCHECK(!ip_vector.empty());
- // Sort lists according to ascending numeric value.
- if (ip_vector.size() > 1)
- std::stable_sort(ip_vector.begin(), ip_vector.end());
- // Return a semi-colon delimited list of sorted addresses (IPv6 followed by
- // IPv4).
- for (size_t i = 0; i < ip_vector.size(); ++i) {
- if (i > 0)
- *sorted_ip_address_list += ";";
- *sorted_ip_address_list += ip_vector[i].string_value;
- }
- return true;
- }
- // Handler for "isInNetEx(ip_address, ip_prefix)". |ip_address| is a string
- // containing an IPv4/IPv6 address, and |ip_prefix| is a string containg a
- // slash-delimited IP prefix with the top 'n' bits specified in the bit
- // field. This returns 'true' if the address is in the same subnet, and
- // 'false' otherwise. Also returns 'false' if the prefix is in an incorrect
- // format. If the address types of |ip_address| and |ip_prefix| don't match,
- // will promote the IPv4 literal to an IPv4 mapped IPv6 literal and
- // proceed with the comparison.
- bool IsInNetEx(const std::string& ip_address, const std::string& ip_prefix) {
- net::IPAddress address;
- if (!address.AssignFromIPLiteral(ip_address))
- return false;
- net::IPAddress prefix;
- size_t prefix_length_in_bits;
- if (!ParseCIDRBlock(ip_prefix, &prefix, &prefix_length_in_bits))
- return false;
- return IPAddressMatchesPrefix(address, prefix, prefix_length_in_bits);
- }
- // Consider only single component domains like 'foo' as plain host names.
- bool IsPlainHostName(const std::string& hostname_utf8) {
- if (hostname_utf8.find('.') != std::string::npos)
- return false;
- // IPv6 literals might not contain any periods, however are not considered
- // plain host names.
- net::IPAddress unused;
- return !unused.AssignFromIPLiteral(hostname_utf8);
- }
- // All instances of ProxyResolverV8 share the same v8::Isolate. This isolate is
- // created lazily the first time it is needed and lives until process shutdown.
- // This creation might happen from any thread, as ProxyResolverV8 is typically
- // run in a threadpool.
- //
- // TODO(eroman): The lazily created isolate is never freed. Instead it should be
- // disposed once there are no longer any ProxyResolverV8 referencing it.
- class SharedIsolateFactory {
- public:
- SharedIsolateFactory() : has_initialized_v8_(false) {}
- SharedIsolateFactory(const SharedIsolateFactory&) = delete;
- SharedIsolateFactory& operator=(const SharedIsolateFactory&) = delete;
- // Lazily creates a v8::Isolate, or returns the already created instance.
- v8::Isolate* GetSharedIsolate() {
- base::AutoLock lock(lock_);
- if (!holder_) {
- // Do one-time initialization for V8.
- if (!has_initialized_v8_) {
- #if defined(V8_USE_EXTERNAL_STARTUP_DATA)
- #if defined(USE_V8_CONTEXT_SNAPSHOT)
- gin::V8Initializer::LoadV8Snapshot(
- gin::V8SnapshotFileType::kWithAdditionalContext);
- #else
- gin::V8Initializer::LoadV8Snapshot();
- #endif
- #endif
- // The performance of the proxy resolver is limited by DNS resolution,
- // and not V8, so tune down V8 to use as little memory as possible.
- static const char kOptimizeForSize[] = "--optimize_for_size";
- v8::V8::SetFlagsFromString(kOptimizeForSize, strlen(kOptimizeForSize));
- // Running v8 in jitless mode allows dynamic code to be disabled in the
- // process.
- static const char kJitless[] = "--jitless";
- v8::V8::SetFlagsFromString(kJitless, strlen(kJitless));
- // WebAssembly isn't encountered during resolution, so reduce the
- // potential attack surface.
- static const char kNoExposeWasm[] = "--no-expose-wasm";
- v8::V8::SetFlagsFromString(kNoExposeWasm, strlen(kNoExposeWasm));
- gin::IsolateHolder::Initialize(
- gin::IsolateHolder::kNonStrictMode,
- gin::ArrayBufferAllocator::SharedInstance());
- has_initialized_v8_ = true;
- }
- holder_ = std::make_unique<gin::IsolateHolder>(
- base::ThreadTaskRunnerHandle::Get(), gin::IsolateHolder::kUseLocker,
- gin::IsolateHolder::IsolateType::kUtility);
- }
- return holder_->isolate();
- }
- v8::Isolate* GetSharedIsolateWithoutCreating() {
- base::AutoLock lock(lock_);
- return holder_ ? holder_->isolate() : nullptr;
- }
- private:
- base::Lock lock_;
- std::unique_ptr<gin::IsolateHolder> holder_;
- bool has_initialized_v8_;
- };
- base::LazyInstance<SharedIsolateFactory>::Leaky g_isolate_factory =
- LAZY_INSTANCE_INITIALIZER;
- } // namespace
- // ProxyResolverV8::Context ---------------------------------------------------
- class ProxyResolverV8::Context {
- public:
- explicit Context(v8::Isolate* isolate)
- : js_bindings_(nullptr), isolate_(isolate) {
- DCHECK(isolate);
- }
- ~Context() {
- v8::Locker locked(isolate_);
- v8::Isolate::Scope isolate_scope(isolate_);
- v8_this_.Reset();
- v8_context_.Reset();
- }
- JSBindings* js_bindings() { return js_bindings_; }
- int ResolveProxy(const GURL& query_url,
- net::ProxyInfo* results,
- JSBindings* bindings) {
- DCHECK(bindings);
- base::AutoReset<JSBindings*> bindings_reset(&js_bindings_, bindings);
- v8::Locker locked(isolate_);
- v8::Isolate::Scope isolate_scope(isolate_);
- v8::Isolate::SafeForTerminationScope safe_for_termination(isolate_);
- v8::HandleScope scope(isolate_);
- v8::Local<v8::Context> context =
- v8::Local<v8::Context>::New(isolate_, v8_context_);
- v8::Context::Scope function_scope(context);
- v8::Local<v8::Value> function;
- int rv = GetFindProxyForURL(&function);
- if (rv != net::OK)
- return rv;
- v8::Local<v8::Value> argv[] = {
- ASCIIStringToV8String(isolate_, query_url.spec()),
- ASCIIStringToV8String(isolate_, query_url.HostNoBrackets()),
- };
- v8::TryCatch try_catch(isolate_);
- v8::Local<v8::Value> ret;
- if (!v8::Function::Cast(*function)
- ->Call(context, context->Global(), std::size(argv), argv)
- .ToLocal(&ret)) {
- DCHECK(try_catch.HasCaught());
- HandleError(try_catch.Message());
- return net::ERR_PAC_SCRIPT_FAILED;
- }
- if (!ret->IsString()) {
- js_bindings()->OnError(-1, u"FindProxyForURL() did not return a string.");
- return net::ERR_PAC_SCRIPT_FAILED;
- }
- std::u16string ret_str =
- V8StringToUTF16(isolate_, v8::Local<v8::String>::Cast(ret));
- if (!base::IsStringASCII(ret_str)) {
- // TODO(eroman): Rather than failing when a wide string is returned, we
- // could extend the parsing to handle IDNA hostnames by
- // converting them to ASCII punycode.
- // crbug.com/47234
- std::u16string error_message =
- u"FindProxyForURL() returned a non-ASCII string (crbug.com/47234): " +
- ret_str;
- js_bindings()->OnError(-1, error_message);
- return net::ERR_PAC_SCRIPT_FAILED;
- }
- results->UsePacString(base::UTF16ToASCII(ret_str));
- return net::OK;
- }
- int InitV8(const scoped_refptr<net::PacFileData>& pac_script,
- JSBindings* bindings) {
- base::AutoReset<JSBindings*> bindings_reset(&js_bindings_, bindings);
- v8::Locker locked(isolate_);
- v8::Isolate::Scope isolate_scope(isolate_);
- v8::HandleScope scope(isolate_);
- v8_this_.Reset(isolate_, v8::External::New(isolate_, this));
- v8::Local<v8::External> v8_this =
- v8::Local<v8::External>::New(isolate_, v8_this_);
- v8::Local<v8::ObjectTemplate> global_template =
- v8::ObjectTemplate::New(isolate_);
- // Attach the javascript bindings.
- v8::Local<v8::FunctionTemplate> alert_template =
- v8::FunctionTemplate::New(isolate_, &AlertCallback, v8_this);
- alert_template->RemovePrototype();
- global_template->Set(ASCIILiteralToV8String(isolate_, "alert"),
- alert_template);
- v8::Local<v8::FunctionTemplate> my_ip_address_template =
- v8::FunctionTemplate::New(isolate_, &MyIpAddressCallback, v8_this);
- my_ip_address_template->RemovePrototype();
- global_template->Set(ASCIILiteralToV8String(isolate_, "myIpAddress"),
- my_ip_address_template);
- v8::Local<v8::FunctionTemplate> dns_resolve_template =
- v8::FunctionTemplate::New(isolate_, &DnsResolveCallback, v8_this);
- dns_resolve_template->RemovePrototype();
- global_template->Set(ASCIILiteralToV8String(isolate_, "dnsResolve"),
- dns_resolve_template);
- v8::Local<v8::FunctionTemplate> is_plain_host_name_template =
- v8::FunctionTemplate::New(isolate_, &IsPlainHostNameCallback, v8_this);
- is_plain_host_name_template->RemovePrototype();
- global_template->Set(ASCIILiteralToV8String(isolate_, "isPlainHostName"),
- is_plain_host_name_template);
- // Microsoft's PAC extensions:
- v8::Local<v8::FunctionTemplate> dns_resolve_ex_template =
- v8::FunctionTemplate::New(isolate_, &DnsResolveExCallback, v8_this);
- dns_resolve_ex_template->RemovePrototype();
- global_template->Set(ASCIILiteralToV8String(isolate_, "dnsResolveEx"),
- dns_resolve_ex_template);
- v8::Local<v8::FunctionTemplate> my_ip_address_ex_template =
- v8::FunctionTemplate::New(isolate_, &MyIpAddressExCallback, v8_this);
- my_ip_address_ex_template->RemovePrototype();
- global_template->Set(ASCIILiteralToV8String(isolate_, "myIpAddressEx"),
- my_ip_address_ex_template);
- v8::Local<v8::FunctionTemplate> sort_ip_address_list_template =
- v8::FunctionTemplate::New(isolate_, &SortIpAddressListCallback,
- v8_this);
- sort_ip_address_list_template->RemovePrototype();
- global_template->Set(ASCIILiteralToV8String(isolate_, "sortIpAddressList"),
- sort_ip_address_list_template);
- v8::Local<v8::FunctionTemplate> is_in_net_ex_template =
- v8::FunctionTemplate::New(isolate_, &IsInNetExCallback, v8_this);
- is_in_net_ex_template->RemovePrototype();
- global_template->Set(ASCIILiteralToV8String(isolate_, "isInNetEx"),
- is_in_net_ex_template);
- v8_context_.Reset(isolate_,
- v8::Context::New(isolate_, nullptr, global_template));
- v8::Local<v8::Context> context =
- v8::Local<v8::Context>::New(isolate_, v8_context_);
- v8::Context::Scope ctx(context);
- // Add the PAC utility functions to the environment.
- // (This script should never fail, as it is a string literal!)
- // Note that the two string literals are concatenated.
- int rv = RunScript(
- ASCIILiteralToV8String(isolate_, PAC_JS_LIBRARY PAC_JS_LIBRARY_EX),
- kPacUtilityResourceName);
- if (rv != net::OK) {
- NOTREACHED();
- return rv;
- }
- // Add the user's PAC code to the environment.
- rv =
- RunScript(ScriptDataToV8String(isolate_, pac_script), kPacResourceName);
- if (rv != net::OK)
- return rv;
- // At a minimum, the FindProxyForURL() function must be defined for this
- // to be a legitimiate PAC script.
- v8::Local<v8::Value> function;
- return GetFindProxyForURL(&function);
- }
- private:
- int GetFindProxyForURL(v8::Local<v8::Value>* function) {
- v8::Local<v8::Context> context =
- v8::Local<v8::Context>::New(isolate_, v8_context_);
- v8::TryCatch try_catch(isolate_);
- if (!context->Global()
- ->Get(context, ASCIILiteralToV8String(isolate_, "FindProxyForURL"))
- .ToLocal(function)) {
- DCHECK(try_catch.HasCaught());
- HandleError(try_catch.Message());
- }
- // The value should only be empty if an exception was thrown. Code
- // defensively just in case.
- DCHECK_EQ(function->IsEmpty(), try_catch.HasCaught());
- if (function->IsEmpty() || try_catch.HasCaught()) {
- js_bindings()->OnError(-1,
- u"Accessing FindProxyForURL threw an exception.");
- return net::ERR_PAC_SCRIPT_FAILED;
- }
- if (!(*function)->IsFunction()) {
- js_bindings()->OnError(
- -1, u"FindProxyForURL is undefined or not a function.");
- return net::ERR_PAC_SCRIPT_FAILED;
- }
- return net::OK;
- }
- // Handle an exception thrown by V8.
- void HandleError(v8::Local<v8::Message> message) {
- v8::Local<v8::Context> context =
- v8::Local<v8::Context>::New(isolate_, v8_context_);
- std::u16string error_message;
- int line_number = -1;
- if (!message.IsEmpty()) {
- auto maybe = message->GetLineNumber(context);
- if (maybe.IsJust())
- line_number = maybe.FromJust();
- V8ObjectToUTF16String(message->Get(), &error_message, isolate_);
- }
- js_bindings()->OnError(line_number, error_message);
- }
- // Compiles and runs |script| in the current V8 context.
- // Returns net::OK on success, otherwise an error code.
- int RunScript(v8::Local<v8::String> script, const char* script_name) {
- v8::Local<v8::Context> context =
- v8::Local<v8::Context>::New(isolate_, v8_context_);
- v8::TryCatch try_catch(isolate_);
- // Compile the script.
- v8::ScriptOrigin origin = v8::ScriptOrigin(
- isolate_, ASCIILiteralToV8String(isolate_, script_name));
- v8::ScriptCompiler::Source script_source(script, origin);
- v8::Local<v8::Script> code;
- if (!v8::ScriptCompiler::Compile(
- context, &script_source, v8::ScriptCompiler::kNoCompileOptions,
- v8::ScriptCompiler::NoCacheReason::kNoCacheBecausePacScript)
- .ToLocal(&code)) {
- DCHECK(try_catch.HasCaught());
- HandleError(try_catch.Message());
- return net::ERR_PAC_SCRIPT_FAILED;
- }
- // Execute.
- auto result = code->Run(context);
- if (result.IsEmpty()) {
- DCHECK(try_catch.HasCaught());
- HandleError(try_catch.Message());
- return net::ERR_PAC_SCRIPT_FAILED;
- }
- return net::OK;
- }
- // V8 callback for when "alert()" is invoked by the PAC script.
- static void AlertCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
- Context* context =
- static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
- // Like firefox we assume "undefined" if no argument was specified, and
- // disregard any arguments beyond the first.
- std::u16string message;
- if (args.Length() == 0) {
- message = u"undefined";
- } else {
- if (!V8ObjectToUTF16String(args[0], &message, args.GetIsolate()))
- return; // toString() threw an exception.
- }
- context->js_bindings()->Alert(message);
- }
- // V8 callback for when "myIpAddress()" is invoked by the PAC script.
- static void MyIpAddressCallback(
- const v8::FunctionCallbackInfo<v8::Value>& args) {
- DnsResolveCallbackHelper(args,
- net::ProxyResolveDnsOperation::MY_IP_ADDRESS);
- }
- // V8 callback for when "myIpAddressEx()" is invoked by the PAC script.
- static void MyIpAddressExCallback(
- const v8::FunctionCallbackInfo<v8::Value>& args) {
- DnsResolveCallbackHelper(args,
- net::ProxyResolveDnsOperation::MY_IP_ADDRESS_EX);
- }
- // V8 callback for when "dnsResolve()" is invoked by the PAC script.
- static void DnsResolveCallback(
- const v8::FunctionCallbackInfo<v8::Value>& args) {
- DnsResolveCallbackHelper(args, net::ProxyResolveDnsOperation::DNS_RESOLVE);
- }
- // V8 callback for when "dnsResolveEx()" is invoked by the PAC script.
- static void DnsResolveExCallback(
- const v8::FunctionCallbackInfo<v8::Value>& args) {
- DnsResolveCallbackHelper(args,
- net::ProxyResolveDnsOperation::DNS_RESOLVE_EX);
- }
- // Shared code for implementing:
- // - myIpAddress(), myIpAddressEx(), dnsResolve(), dnsResolveEx().
- static void DnsResolveCallbackHelper(
- const v8::FunctionCallbackInfo<v8::Value>& args,
- net::ProxyResolveDnsOperation op) {
- Context* context =
- static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
- std::string hostname;
- // dnsResolve() and dnsResolveEx() need at least 1 argument.
- if (op == net::ProxyResolveDnsOperation::DNS_RESOLVE ||
- op == net::ProxyResolveDnsOperation::DNS_RESOLVE_EX) {
- if (!GetHostnameArgument(args, &hostname)) {
- if (op == net::ProxyResolveDnsOperation::DNS_RESOLVE)
- args.GetReturnValue().SetNull();
- return;
- }
- }
- std::string result;
- bool success;
- bool terminate = false;
- {
- v8::Unlocker unlocker(args.GetIsolate());
- success =
- context->js_bindings()->ResolveDns(hostname, op, &result, &terminate);
- }
- if (terminate)
- args.GetIsolate()->TerminateExecution();
- if (success) {
- args.GetReturnValue().Set(
- ASCIIStringToV8String(args.GetIsolate(), result));
- return;
- }
- // Each function handles resolution errors differently.
- switch (op) {
- case net::ProxyResolveDnsOperation::DNS_RESOLVE:
- args.GetReturnValue().SetNull();
- return;
- case net::ProxyResolveDnsOperation::DNS_RESOLVE_EX:
- args.GetReturnValue().SetEmptyString();
- return;
- case net::ProxyResolveDnsOperation::MY_IP_ADDRESS:
- args.GetReturnValue().Set(
- ASCIILiteralToV8String(args.GetIsolate(), "127.0.0.1"));
- return;
- case net::ProxyResolveDnsOperation::MY_IP_ADDRESS_EX:
- args.GetReturnValue().SetEmptyString();
- return;
- }
- NOTREACHED();
- }
- // V8 callback for when "sortIpAddressList()" is invoked by the PAC script.
- static void SortIpAddressListCallback(
- const v8::FunctionCallbackInfo<v8::Value>& args) {
- // We need at least one string argument.
- if (args.Length() == 0 || args[0].IsEmpty() || !args[0]->IsString()) {
- args.GetReturnValue().SetNull();
- return;
- }
- std::string ip_address_list =
- V8StringToUTF8(args.GetIsolate(), v8::Local<v8::String>::Cast(args[0]));
- if (!base::IsStringASCII(ip_address_list)) {
- args.GetReturnValue().SetNull();
- return;
- }
- std::string sorted_ip_address_list;
- bool success = SortIpAddressList(ip_address_list, &sorted_ip_address_list);
- if (!success) {
- args.GetReturnValue().Set(false);
- return;
- }
- args.GetReturnValue().Set(
- ASCIIStringToV8String(args.GetIsolate(), sorted_ip_address_list));
- }
- // V8 callback for when "isInNetEx()" is invoked by the PAC script.
- static void IsInNetExCallback(
- const v8::FunctionCallbackInfo<v8::Value>& args) {
- // We need at least 2 string arguments.
- if (args.Length() < 2 || args[0].IsEmpty() || !args[0]->IsString() ||
- args[1].IsEmpty() || !args[1]->IsString()) {
- args.GetReturnValue().SetNull();
- return;
- }
- std::string ip_address =
- V8StringToUTF8(args.GetIsolate(), v8::Local<v8::String>::Cast(args[0]));
- if (!base::IsStringASCII(ip_address)) {
- args.GetReturnValue().Set(false);
- return;
- }
- std::string ip_prefix =
- V8StringToUTF8(args.GetIsolate(), v8::Local<v8::String>::Cast(args[1]));
- if (!base::IsStringASCII(ip_prefix)) {
- args.GetReturnValue().Set(false);
- return;
- }
- args.GetReturnValue().Set(IsInNetEx(ip_address, ip_prefix));
- }
- // V8 callback for when "isPlainHostName()" is invoked by the PAC script.
- static void IsPlainHostNameCallback(
- const v8::FunctionCallbackInfo<v8::Value>& args) {
- // Need at least 1 string arguments.
- if (args.Length() < 1 || args[0].IsEmpty() || !args[0]->IsString()) {
- args.GetIsolate()->ThrowException(
- v8::Exception::TypeError(ASCIIStringToV8String(
- args.GetIsolate(), "Requires 1 string parameter")));
- return;
- }
- std::string hostname_utf8 =
- V8StringToUTF8(args.GetIsolate(), v8::Local<v8::String>::Cast(args[0]));
- args.GetReturnValue().Set(IsPlainHostName(hostname_utf8));
- }
- mutable base::Lock lock_;
- ProxyResolverV8::JSBindings* js_bindings_;
- raw_ptr<v8::Isolate> isolate_;
- v8::Persistent<v8::External> v8_this_;
- v8::Persistent<v8::Context> v8_context_;
- };
- // ProxyResolverV8 ------------------------------------------------------------
- ProxyResolverV8::ProxyResolverV8(std::unique_ptr<Context> context)
- : context_(std::move(context)) {
- DCHECK(context_);
- }
- ProxyResolverV8::~ProxyResolverV8() = default;
- int ProxyResolverV8::GetProxyForURL(const GURL& query_url,
- net::ProxyInfo* results,
- ProxyResolverV8::JSBindings* bindings) {
- return context_->ResolveProxy(query_url, results, bindings);
- }
- // static
- int ProxyResolverV8::Create(const scoped_refptr<net::PacFileData>& script_data,
- ProxyResolverV8::JSBindings* js_bindings,
- std::unique_ptr<ProxyResolverV8>* resolver) {
- DCHECK(script_data.get());
- DCHECK(js_bindings);
- if (script_data->utf16().empty())
- return net::ERR_PAC_SCRIPT_FAILED;
- // Try parsing the PAC script.
- std::unique_ptr<Context> context(
- new Context(g_isolate_factory.Get().GetSharedIsolate()));
- int rv = context->InitV8(script_data, js_bindings);
- if (rv == net::OK)
- resolver->reset(new ProxyResolverV8(std::move(context)));
- return rv;
- }
- // static
- size_t ProxyResolverV8::GetTotalHeapSize() {
- v8::Isolate* isolate =
- g_isolate_factory.Get().GetSharedIsolateWithoutCreating();
- if (!isolate)
- return 0;
- v8::Locker locked(isolate);
- v8::Isolate::Scope isolate_scope(isolate);
- v8::HeapStatistics heap_statistics;
- isolate->GetHeapStatistics(&heap_statistics);
- return heap_statistics.total_heap_size();
- }
- // static
- size_t ProxyResolverV8::GetUsedHeapSize() {
- v8::Isolate* isolate =
- g_isolate_factory.Get().GetSharedIsolateWithoutCreating();
- if (!isolate)
- return 0;
- v8::Locker locked(isolate);
- v8::Isolate::Scope isolate_scope(isolate);
- v8::HeapStatistics heap_statistics;
- isolate->GetHeapStatistics(&heap_statistics);
- return heap_statistics.used_heap_size();
- }
- } // namespace proxy_resolver
|