mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-12-01 01:36:09 +03:00
915 lines
32 KiB
C++
915 lines
32 KiB
C++
// 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 "net/proxy/proxy_resolver_v8.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstdio>
|
|
#include <utility>
|
|
|
|
#include "base/auto_reset.h"
|
|
#include "base/compiler_specific.h"
|
|
#include "base/debug/leak_annotations.h"
|
|
#include "base/lazy_instance.h"
|
|
#include "base/logging.h"
|
|
#include "base/macros.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/proxy_info.h"
|
|
#include "net/proxy/proxy_resolver_script.h"
|
|
#include "net/proxy/proxy_resolver_script_data.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 proxy_resolver_scipt.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
|
|
// 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 net {
|
|
|
|
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
|
|
// ProxyResolverScriptData.
|
|
class V8ExternalStringFromScriptData
|
|
: public v8::String::ExternalStringResource {
|
|
public:
|
|
explicit V8ExternalStringFromScriptData(
|
|
const scoped_refptr<ProxyResolverScriptData>& script_data)
|
|
: script_data_(script_data) {}
|
|
|
|
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<ProxyResolverScriptData> script_data_;
|
|
DISALLOW_COPY_AND_ASSIGN(V8ExternalStringFromScriptData);
|
|
};
|
|
|
|
// 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));
|
|
}
|
|
|
|
const char* data() const override { return ascii_; }
|
|
|
|
size_t length() const override { return length_; }
|
|
|
|
private:
|
|
const char* ascii_;
|
|
size_t length_;
|
|
DISALLOW_COPY_AND_ASSIGN(V8ExternalASCIILiteral);
|
|
};
|
|
|
|
// 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::Local<v8::String> s) {
|
|
int len = s->Length();
|
|
std::string result;
|
|
if (len > 0)
|
|
s->WriteUtf8(base::WriteInto(&result, len + 1));
|
|
return result;
|
|
}
|
|
|
|
// Converts a V8 String to a UTF16 base::string16.
|
|
base::string16 V8StringToUTF16(v8::Local<v8::String> s) {
|
|
int len = s->Length();
|
|
base::string16 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(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 base::string16 (warpped by a ProxyResolverScriptData) to a
|
|
// V8 string.
|
|
v8::Local<v8::String> ScriptDataToV8String(
|
|
v8::Isolate* isolate, const scoped_refptr<ProxyResolverScriptData>& 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,
|
|
base::string16* 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(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 base::string16 hostname_utf16 =
|
|
V8StringToUTF16(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<base::char16, 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 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 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 IPAddress& ip1 = this->ip_address;
|
|
const 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;
|
|
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;
|
|
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, or if an address and prefix of different types are used (e.g. IPv6
|
|
// address and IPv4 prefix).
|
|
bool IsInNetEx(const std::string& ip_address, const std::string& ip_prefix) {
|
|
IPAddress address;
|
|
if (!address.AssignFromIPLiteral(ip_address))
|
|
return false;
|
|
|
|
IPAddress prefix;
|
|
size_t prefix_length_in_bits;
|
|
if (!ParseCIDRBlock(ip_prefix, &prefix, &prefix_length_in_bits))
|
|
return false;
|
|
|
|
// Both |address| and |prefix| must be of the same type (IPv4 or IPv6).
|
|
if (address.size() != prefix.size())
|
|
return false;
|
|
|
|
DCHECK((address.IsIPv4() && prefix.IsIPv4()) ||
|
|
(address.IsIPv6() && prefix.IsIPv6()));
|
|
|
|
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.
|
|
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) {}
|
|
|
|
// 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_) {
|
|
#ifdef V8_USE_EXTERNAL_STARTUP_DATA
|
|
gin::V8Initializer::LoadV8Snapshot();
|
|
gin::V8Initializer::LoadV8Natives();
|
|
#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));
|
|
static const char kNoOpt[] = "--noopt";
|
|
v8::V8::SetFlagsFromString(kNoOpt, strlen(kNoOpt));
|
|
|
|
gin::IsolateHolder::Initialize(
|
|
gin::IsolateHolder::kNonStrictMode,
|
|
gin::IsolateHolder::kStableV8Extras,
|
|
gin::ArrayBufferAllocator::SharedInstance());
|
|
|
|
has_initialized_v8_ = true;
|
|
}
|
|
|
|
holder_.reset(new gin::IsolateHolder(base::ThreadTaskRunnerHandle::Get(),
|
|
gin::IsolateHolder::kUseLocker));
|
|
}
|
|
|
|
return holder_->isolate();
|
|
}
|
|
|
|
v8::Isolate* GetSharedIsolateWithoutCreating() {
|
|
base::AutoLock lock(lock_);
|
|
return holder_ ? holder_->isolate() : NULL;
|
|
}
|
|
|
|
private:
|
|
base::Lock lock_;
|
|
std::unique_ptr<gin::IsolateHolder> holder_;
|
|
bool has_initialized_v8_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(SharedIsolateFactory);
|
|
};
|
|
|
|
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,
|
|
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::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 != 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(), arraysize(argv), argv)
|
|
.ToLocal(&ret)) {
|
|
DCHECK(try_catch.HasCaught());
|
|
HandleError(try_catch.Message());
|
|
return ERR_PAC_SCRIPT_FAILED;
|
|
}
|
|
|
|
if (!ret->IsString()) {
|
|
js_bindings()->OnError(
|
|
-1, base::ASCIIToUTF16("FindProxyForURL() did not return a string."));
|
|
return ERR_PAC_SCRIPT_FAILED;
|
|
}
|
|
|
|
base::string16 ret_str = V8StringToUTF16(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
|
|
base::string16 error_message =
|
|
base::ASCIIToUTF16("FindProxyForURL() returned a non-ASCII string "
|
|
"(crbug.com/47234): ") + ret_str;
|
|
js_bindings()->OnError(-1, error_message);
|
|
return ERR_PAC_SCRIPT_FAILED;
|
|
}
|
|
|
|
results->UsePacString(base::UTF16ToASCII(ret_str));
|
|
return OK;
|
|
}
|
|
|
|
int InitV8(const scoped_refptr<ProxyResolverScriptData>& 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_, NULL, 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_,
|
|
PROXY_RESOLVER_SCRIPT
|
|
PROXY_RESOLVER_SCRIPT_EX),
|
|
kPacUtilityResourceName);
|
|
if (rv != OK) {
|
|
NOTREACHED();
|
|
return rv;
|
|
}
|
|
|
|
// Add the user's PAC code to the environment.
|
|
rv =
|
|
RunScript(ScriptDataToV8String(isolate_, pac_script), kPacResourceName);
|
|
if (rv != 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,
|
|
base::ASCIIToUTF16("Accessing FindProxyForURL threw an exception."));
|
|
return ERR_PAC_SCRIPT_FAILED;
|
|
}
|
|
|
|
if (!(*function)->IsFunction()) {
|
|
js_bindings()->OnError(
|
|
-1, base::ASCIIToUTF16(
|
|
"FindProxyForURL is undefined or not a function."));
|
|
return ERR_PAC_SCRIPT_FAILED;
|
|
}
|
|
|
|
return 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_);
|
|
base::string16 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 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(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 ERR_PAC_SCRIPT_FAILED;
|
|
}
|
|
|
|
// Execute.
|
|
auto result = code->Run(context);
|
|
if (result.IsEmpty()) {
|
|
DCHECK(try_catch.HasCaught());
|
|
HandleError(try_catch.Message());
|
|
return ERR_PAC_SCRIPT_FAILED;
|
|
}
|
|
|
|
return 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.
|
|
base::string16 message;
|
|
if (args.Length() == 0) {
|
|
message = base::ASCIIToUTF16("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, JSBindings::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, JSBindings::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, JSBindings::DNS_RESOLVE);
|
|
}
|
|
|
|
// V8 callback for when "dnsResolveEx()" is invoked by the PAC script.
|
|
static void DnsResolveExCallback(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
DnsResolveCallbackHelper(args, JSBindings::DNS_RESOLVE_EX);
|
|
}
|
|
|
|
// Shared code for implementing:
|
|
// - myIpAddress(), myIpAddressEx(), dnsResolve(), dnsResolveEx().
|
|
static void DnsResolveCallbackHelper(
|
|
const v8::FunctionCallbackInfo<v8::Value>& args,
|
|
JSBindings::ResolveDnsOperation 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 == JSBindings::DNS_RESOLVE || op == JSBindings::DNS_RESOLVE_EX) {
|
|
if (!GetHostnameArgument(args, &hostname)) {
|
|
if (op == JSBindings::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 JSBindings::DNS_RESOLVE:
|
|
args.GetReturnValue().SetNull();
|
|
return;
|
|
case JSBindings::DNS_RESOLVE_EX:
|
|
args.GetReturnValue().SetEmptyString();
|
|
return;
|
|
case JSBindings::MY_IP_ADDRESS:
|
|
args.GetReturnValue().Set(
|
|
ASCIILiteralToV8String(args.GetIsolate(), "127.0.0.1"));
|
|
return;
|
|
case JSBindings::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(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(v8::Local<v8::String>::Cast(args[0]));
|
|
if (!base::IsStringASCII(ip_address)) {
|
|
args.GetReturnValue().Set(false);
|
|
return;
|
|
}
|
|
std::string ip_prefix =
|
|
V8StringToUTF8(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(v8::Local<v8::String>::Cast(args[0]));
|
|
args.GetReturnValue().Set(IsPlainHostName(hostname_utf8));
|
|
}
|
|
|
|
mutable base::Lock lock_;
|
|
ProxyResolverV8::JSBindings* js_bindings_;
|
|
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() {}
|
|
|
|
int ProxyResolverV8::GetProxyForURL(const GURL& query_url,
|
|
ProxyInfo* results,
|
|
ProxyResolverV8::JSBindings* bindings) {
|
|
return context_->ResolveProxy(query_url, results, bindings);
|
|
}
|
|
|
|
// static
|
|
int ProxyResolverV8::Create(
|
|
const scoped_refptr<ProxyResolverScriptData>& script_data,
|
|
ProxyResolverV8::JSBindings* js_bindings,
|
|
std::unique_ptr<ProxyResolverV8>* resolver) {
|
|
DCHECK(script_data.get());
|
|
DCHECK(js_bindings);
|
|
|
|
if (script_data->utf16().empty())
|
|
return 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 == 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 net
|