diff options
author | Jason Monk <jmonk@google.com> | 2013-07-22 13:20:39 -0400 |
---|---|---|
committer | Jason Monk <jmonk@google.com> | 2013-07-22 16:07:05 -0400 |
commit | fc93418c483ce474a1f4888b50f92574a1b81be3 (patch) | |
tree | 18cc12b9518e70f024acf924b409101b7bc891f5 | |
parent | 2e12de22c3b9d316711d63e5911f3b6a6cf5bec4 (diff) | |
download | chromium-libpac-fc93418c483ce474a1f4888b50f92574a1b81be3.tar.gz |
Shared library that provides a PAC file parser.
This adds a shared library that provides a javascript proxy resolver extracted
from the chromium project. The resolver is designed to parse proxy auto-config
(PAC) files that implement a single javascript function
(FindProxyForURL(url, host)).
Change-Id: I241fe44555cb7a9f187fe98d265aa6dc8f1bec20
-rw-r--r-- | Android.mk | 27 | ||||
-rw-r--r-- | LICENSE | 27 | ||||
-rw-r--r-- | MODULE_LICENSE_BSD | 0 | ||||
-rw-r--r-- | NOTICE | 27 | ||||
-rw-r--r-- | README | 7 | ||||
-rw-r--r-- | src/net_util.cc | 126 | ||||
-rw-r--r-- | src/net_util.h | 68 | ||||
-rw-r--r-- | src/proxy_resolver_js_bindings.cc | 116 | ||||
-rw-r--r-- | src/proxy_resolver_js_bindings.h | 69 | ||||
-rw-r--r-- | src/proxy_resolver_script.h | 276 | ||||
-rw-r--r-- | src/proxy_resolver_v8.cc | 726 | ||||
-rw-r--r-- | src/proxy_resolver_v8.h | 68 |
12 files changed, 1537 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..2b280b9 --- /dev/null +++ b/Android.mk @@ -0,0 +1,27 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_CPP_EXTENSION := .cc + +# Set up the target identity +LOCAL_MODULE := libpac +LOCAL_MODULE_CLASS := SHARED_LIBRARIES + +LOCAL_SRC_FILES := \ + src/proxy_resolver_v8.cc \ + src/proxy_resolver_js_bindings.cc \ + src/net_util.cc + +LOCAL_CFLAGS += \ + -Wno-endif-labels \ + -Wno-import \ + -Wno-format \ + +LOCAL_C_INCLUDES += $(LOCAL_PATH)/src external/v8 + +LOCAL_STATIC_LIBRARIES := libv8 +LOCAL_SHARED_LIBRARIES := libstlport liblog + +include external/stlport/libstlport.mk + +include $(BUILD_SHARED_LIBRARY) @@ -0,0 +1,27 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/MODULE_LICENSE_BSD b/MODULE_LICENSE_BSD new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/MODULE_LICENSE_BSD @@ -0,0 +1,27 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. @@ -0,0 +1,7 @@ +This library is based on the proxy_resolver_v8 and other classes taken from +chromium project. + +Modifications have been made to remove dependencies on chromium utility +functions and classes. To make this library accessible on Android, the string +utilities have been modified to use stl and the network functions have been +modified to use UNIX functions. diff --git a/src/net_util.cc b/src/net_util.cc new file mode 100644 index 0000000..32a8193 --- /dev/null +++ b/src/net_util.cc @@ -0,0 +1,126 @@ +// Copyright (c) 2011 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 <algorithm> +#include <iterator> +#include <map> + +#include <fcntl.h> +#include <netdb.h> +#include <net/if.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <string.h> + +#include "net_util.h" + +namespace net { + +#ifndef INET6_ADDRSTRLEN /* for non IPv6 machines */ +#define INET6_ADDRSTRLEN 46 +#endif + +bool ParseIPLiteralToNumber(const std::string& ip_literal, + IPAddressNumber* ip_number) { + char buf[sizeof(struct in6_addr)]; + int size = sizeof(struct in_addr); + int mode = AF_INET; + if (ip_literal.find(':') != std::string::npos) { + mode = AF_INET6; + size = sizeof(struct in6_addr); + } + inet_pton(mode, ip_literal.c_str(), buf); + for (int i = 0; i < size; i++) { + (*ip_number)[i] = buf[i]; + } + return true; +} + +IPAddressNumber ConvertIPv4NumberToIPv6Number( + const IPAddressNumber& ipv4_number) { + // IPv4-mapped addresses are formed by: + // <80 bits of zeros> + <16 bits of ones> + <32-bit IPv4 address>. + IPAddressNumber ipv6_number; + ipv6_number.reserve(16); + ipv6_number.insert(ipv6_number.end(), 10, 0); + ipv6_number.push_back(0xFF); + ipv6_number.push_back(0xFF); + ipv6_number.insert(ipv6_number.end(), ipv4_number.begin(), ipv4_number.end()); + return ipv6_number; +} + +bool ParseCIDRBlock(const std::string& cidr_literal, + IPAddressNumber* ip_number, + size_t* prefix_length_in_bits) { + // We expect CIDR notation to match one of these two templates: + // <IPv4-literal> "/" <number of bits> + // <IPv6-literal> "/" <number of bits> + + std::vector<std::string> parts; + unsigned int split = cidr_literal.find('/'); + if (split == std::string::npos) + return false; + parts.push_back(cidr_literal.substr(0, split)); + parts.push_back(cidr_literal.substr(split + 1)); + if (parts[1].find('/') != std::string::npos) + return false; + + // Parse the IP address. + if (!ParseIPLiteralToNumber(parts[0], ip_number)) + return false; + + // Parse the prefix length. + int number_of_bits = atoi(parts[1].c_str()); + + // Make sure the prefix length is in a valid range. + if (number_of_bits < 0 || + number_of_bits > static_cast<int>(ip_number->size() * 8)) + return false; + + *prefix_length_in_bits = static_cast<size_t>(number_of_bits); + return true; +} + +bool IPNumberMatchesPrefix(const IPAddressNumber& ip_number, + const IPAddressNumber& ip_prefix, + size_t prefix_length_in_bits) { + // Both the input IP address and the prefix IP address should be + // either IPv4 or IPv6. + + // In case we have an IPv6 / IPv4 mismatch, convert the IPv4 addresses to + // IPv6 addresses in order to do the comparison. + if (ip_number.size() != ip_prefix.size()) { + if (ip_number.size() == 4) { + return IPNumberMatchesPrefix(ConvertIPv4NumberToIPv6Number(ip_number), + ip_prefix, prefix_length_in_bits); + } + return IPNumberMatchesPrefix(ip_number, + ConvertIPv4NumberToIPv6Number(ip_prefix), + 96 + prefix_length_in_bits); + } + + // Otherwise we are comparing two IPv4 addresses, or two IPv6 addresses. + // Compare all the bytes that fall entirely within the prefix. + int num_entire_bytes_in_prefix = prefix_length_in_bits / 8; + for (int i = 0; i < num_entire_bytes_in_prefix; ++i) { + if (ip_number[i] != ip_prefix[i]) + return false; + } + + // In case the prefix was not a multiple of 8, there will be 1 byte + // which is only partially masked. + int remaining_bits = prefix_length_in_bits % 8; + if (remaining_bits != 0) { + unsigned char mask = 0xFF << (8 - remaining_bits); + int i = num_entire_bytes_in_prefix; + if ((ip_number[i] & mask) != (ip_prefix[i] & mask)) + return false; + } + + return true; +} + +} // namespace net diff --git a/src/net_util.h b/src/net_util.h new file mode 100644 index 0000000..e50008f --- /dev/null +++ b/src/net_util.h @@ -0,0 +1,68 @@ +// Copyright (c) 2011 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. + +#ifndef NET_BASE_NET_UTIL_H_ +#define NET_BASE_NET_UTIL_H_ +#pragma once + +#if defined(OS_WIN) +#include <windows.h> +#include <ws2tcpip.h> +#elif defined(OS_POSIX) +#include <sys/socket.h> +#endif + +#include <list> +#include <string> +#include <set> +#include <vector> + +#include <cstdint> + +namespace net { + +// IPAddressNumber is used to represent an IP address's numeric value as an +// array of bytes, from most significant to least significant. This is the +// network byte ordering. +// +// IPv4 addresses will have length 4, whereas IPv6 address will have length 16. +typedef std::vector<unsigned char> IPAddressNumber; +typedef std::vector<IPAddressNumber> IPAddressList; + +// Parses an IP address literal (either IPv4 or IPv6) to its numeric value. +// Returns true on success and fills |ip_number| with the numeric value. +bool ParseIPLiteralToNumber(const std::string& ip_literal, + IPAddressNumber* ip_number); + +// Parses an IP block specifier from CIDR notation to an +// (IP address, prefix length) pair. Returns true on success and fills +// |*ip_number| with the numeric value of the IP address and sets +// |*prefix_length_in_bits| with the length of the prefix. +// +// CIDR notation literals can use either IPv4 or IPv6 literals. Some examples: +// +// 10.10.3.1/20 +// a:b:c::/46 +// ::1/128 +bool ParseCIDRBlock(const std::string& cidr_literal, + IPAddressNumber* ip_number, + size_t* prefix_length_in_bits); + +// Compares an IP address to see if it falls within the specified IP block. +// Returns true if it does, false otherwise. +// +// The IP block is given by (|ip_prefix|, |prefix_length_in_bits|) -- any +// IP address whose |prefix_length_in_bits| most significant bits match +// |ip_prefix| will be matched. +// +// In cases when an IPv4 address is being compared to an IPv6 address prefix +// and vice versa, the IPv4 addresses will be converted to IPv4-mapped +// (IPv6) addresses. +bool IPNumberMatchesPrefix(const IPAddressNumber& ip_number, + const IPAddressNumber& ip_prefix, + size_t prefix_length_in_bits); + +} // namespace net + +#endif // NET_BASE_NET_UTIL_H_ diff --git a/src/proxy_resolver_js_bindings.cc b/src/proxy_resolver_js_bindings.cc new file mode 100644 index 0000000..143179c --- /dev/null +++ b/src/proxy_resolver_js_bindings.cc @@ -0,0 +1,116 @@ +// Copyright (c) 2010 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 "proxy_resolver_js_bindings.h" + +#include <netdb.h> +#include <unistd.h> +#include <cstddef> +#include <memory> +#include <string> + +#include "net_util.h" + +namespace net { + +// ProxyResolverJSBindings implementation. +class DefaultJSBindings : public ProxyResolverJSBindings { + public: + DefaultJSBindings() { + } + + // Handler for "alert(message)". + virtual void Alert(const std::wstring& message) { + // TODO: Fix error handling + } + + // Handler for "myIpAddress()". + // TODO: Perhaps enumerate the interfaces directly, using + // getifaddrs(). + virtual bool MyIpAddress(std::string* first_ip_address) { + return MyIpAddressImpl(first_ip_address); + } + + // Handler for "myIpAddressEx()". + virtual bool MyIpAddressEx(std::string* ip_address_list) { + return MyIpAddressExImpl(ip_address_list); + } + + // Handler for "dnsResolve(host)". + virtual bool DnsResolve(const std::string& host, + std::string* first_ip_address) { + return DnsResolveImpl(host, first_ip_address); + } + + // Handler for "dnsResolveEx(host)". + virtual bool DnsResolveEx(const std::string& host, + std::string* ip_address_list) { + return DnsResolveExImpl(host, ip_address_list); + } + + // Handler for when an error is encountered. |line_number| may be -1. + virtual void OnError(int line_number, const std::wstring& message) { + } + + private: + bool MyIpAddressImpl(std::string* first_ip_address) { + std::string my_hostname = GetHostName(); + if (my_hostname.empty()) + return false; + return DnsResolveImpl(my_hostname, first_ip_address); + } + + bool MyIpAddressExImpl(std::string* ip_address_list) { + std::string my_hostname = GetHostName(); + if (my_hostname.empty()) + return false; + return DnsResolveExImpl(my_hostname, ip_address_list); + } + + bool DnsResolveImpl(const std::string& host, + std::string* first_ip_address) { + struct hostent* he = gethostbyname(host.c_str()); + + if (he == NULL) { + return false; + } + *first_ip_address = std::string(he->h_addr); + return true; + } + + bool DnsResolveExImpl(const std::string& host, + std::string* ip_address_list) { + struct hostent* he = gethostbyname(host.c_str()); + + if (he == NULL) { + return false; + } + std::string address_list_str; + for (char** addr = &he->h_addr; *addr != NULL; ++addr) { + if (!address_list_str.empty()) + address_list_str += ";"; + const std::string address_string = std::string(*addr); + if (address_string.empty()) + return false; + address_list_str += address_string; + } + *ip_address_list = std::string(he->h_addr); + return true; + } + + std::string GetHostName() { + char buffer[256]; + if (gethostname(buffer, 256) != 0) { + buffer[0] = '\0'; + } + return std::string(buffer); + } +}; + +// static +ProxyResolverJSBindings* ProxyResolverJSBindings::CreateDefault() { + return new DefaultJSBindings(); +} + +} // namespace net diff --git a/src/proxy_resolver_js_bindings.h b/src/proxy_resolver_js_bindings.h new file mode 100644 index 0000000..fd8fad3 --- /dev/null +++ b/src/proxy_resolver_js_bindings.h @@ -0,0 +1,69 @@ +// Copyright (c) 2010 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. + +#ifndef NET_PROXY_PROXY_RESOLVER_JS_BINDINGS_H_ +#define NET_PROXY_PROXY_RESOLVER_JS_BINDINGS_H_ +#pragma once + +#include <string> + + +namespace net { + +class HostResolver; +class NetLog; + +// Interface for the javascript bindings. +class ProxyResolverJSBindings { + public: + ProxyResolverJSBindings() {}// : current_request_context_(NULL) {} + + virtual ~ProxyResolverJSBindings() {} + + // Handler for "alert(message)" + virtual void Alert(const std::wstring& message) = 0; + + // Handler for "myIpAddress()". Returns true on success and fills + // |*first_ip_address| with the result. + virtual bool MyIpAddress(std::string* first_ip_address) = 0; + + // Handler for "myIpAddressEx()". Returns true on success and fills + // |*ip_address_list| with the result. + // + // This is a Microsoft extension to PAC for IPv6, see: + // http://blogs.msdn.com/b/wndp/archive/2006/07/13/ipv6-pac-extensions-v0-9.aspx + + virtual bool MyIpAddressEx(std::string* ip_address_list) = 0; + + // Handler for "dnsResolve(host)". Returns true on success and fills + // |*first_ip_address| with the result. + virtual bool DnsResolve(const std::string& host, + std::string* first_ip_address) = 0; + + // Handler for "dnsResolveEx(host)". Returns true on success and fills + // |*ip_address_list| with the result. + // + // This is a Microsoft extension to PAC for IPv6, see: + // http://blogs.msdn.com/b/wndp/archive/2006/07/13/ipv6-pac-extensions-v0-9.aspx + virtual bool DnsResolveEx(const std::string& host, + std::string* ip_address_list) = 0; + + // Handler for when an error is encountered. |line_number| may be -1 + // if a line number is not applicable to this error. + virtual void OnError(int line_number, const std::wstring& error) = 0; + + // Creates a default javascript bindings implementation that will: + // - Send script error messages to both VLOG(1) and the NetLog. + // - Send script alert()s to both VLOG(1) and the NetLog. + // - Use the provided host resolver to service dnsResolve(). + // + // Note that |host_resolver| will be used in sync mode mode. + static ProxyResolverJSBindings* CreateDefault(); + + private: +}; + +} // namespace net + +#endif // NET_PROXY_PROXY_RESOLVER_JS_BINDINGS_H_ diff --git a/src/proxy_resolver_script.h b/src/proxy_resolver_script.h new file mode 100644 index 0000000..283eff9 --- /dev/null +++ b/src/proxy_resolver_script.h @@ -0,0 +1,276 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Akhil Arora <akhil.arora@sun.com> + * Tomi Leppikangas <Tomi.Leppikangas@oulu.fi> + * Darin Fisher <darin@meer.net> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef NET_PROXY_PROXY_RESOLVER_SCRIPT_H_ +#define NET_PROXY_PROXY_RESOLVER_SCRIPT_H_ + +// The following code was formatted from: +// 'mozilla/netwerk/base/src/nsProxyAutoConfig.js' (1.55) +// +// Using the command: +// $ cat nsProxyAutoConfig.js | +// awk '/var pacUtils/,/EOF/' | +// sed -e 's/^\s*$/""/g' | +// sed -e 's/"\s*[+]\s*$/"/g' | +// sed -e 's/"$/" \\/g' | +// sed -e 's/\/(ipaddr);/\/.exec(ipaddr);/g' | +// grep -v '^var pacUtils =' +#define PROXY_RESOLVER_SCRIPT \ + "function dnsDomainIs(host, domain) {\n" \ + " return (host.length >= domain.length &&\n" \ + " host.substring(host.length - domain.length) == domain);\n" \ + "}\n" \ + "" \ + "function dnsDomainLevels(host) {\n" \ + " return host.split('.').length-1;\n" \ + "}\n" \ + "" \ + "function convert_addr(ipchars) {\n" \ + " var bytes = ipchars.split('.');\n" \ + " var result = ((bytes[0] & 0xff) << 24) |\n" \ + " ((bytes[1] & 0xff) << 16) |\n" \ + " ((bytes[2] & 0xff) << 8) |\n" \ + " (bytes[3] & 0xff);\n" \ + " return result;\n" \ + "}\n" \ + "" \ + "function isInNet(ipaddr, pattern, maskstr) {\n" \ + " var test = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/.exec(ipaddr);\n" \ + " if (test == null) {\n" \ + " ipaddr = dnsResolve(ipaddr);\n" \ + " if (ipaddr == null)\n" \ + " return false;\n" \ + " } else if (test[1] > 255 || test[2] > 255 || \n" \ + " test[3] > 255 || test[4] > 255) {\n" \ + " return false; // not an IP address\n" \ + " }\n" \ + " var host = convert_addr(ipaddr);\n" \ + " var pat = convert_addr(pattern);\n" \ + " var mask = convert_addr(maskstr);\n" \ + " return ((host & mask) == (pat & mask));\n" \ + " \n" \ + "}\n" \ + "" \ + "function isPlainHostName(host) {\n" \ + " return (host.search('\\\\.') == -1);\n" \ + "}\n" \ + "" \ + "function isResolvable(host) {\n" \ + " var ip = dnsResolve(host);\n" \ + " return (ip != null);\n" \ + "}\n" \ + "" \ + "function localHostOrDomainIs(host, hostdom) {\n" \ + " return (host == hostdom) ||\n" \ + " (hostdom.lastIndexOf(host + '.', 0) == 0);\n" \ + "}\n" \ + "" \ + "function shExpMatch(url, pattern) {\n" \ + " pattern = pattern.replace(/\\./g, '\\\\.');\n" \ + " pattern = pattern.replace(/\\*/g, '.*');\n" \ + " pattern = pattern.replace(/\\?/g, '.');\n" \ + " var newRe = new RegExp('^'+pattern+'$');\n" \ + " return newRe.test(url);\n" \ + "}\n" \ + "" \ + "var wdays = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6};\n" \ + "" \ + "var months = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11};\n" \ + "" \ + "function weekdayRange() {\n" \ + " function getDay(weekday) {\n" \ + " if (weekday in wdays) {\n" \ + " return wdays[weekday];\n" \ + " }\n" \ + " return -1;\n" \ + " }\n" \ + " var date = new Date();\n" \ + " var argc = arguments.length;\n" \ + " var wday;\n" \ + " if (argc < 1)\n" \ + " return false;\n" \ + " if (arguments[argc - 1] == 'GMT') {\n" \ + " argc--;\n" \ + " wday = date.getUTCDay();\n" \ + " } else {\n" \ + " wday = date.getDay();\n" \ + " }\n" \ + " var wd1 = getDay(arguments[0]);\n" \ + " var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1;\n" \ + " return (wd1 == -1 || wd2 == -1) ? false\n" \ + " : (wd1 <= wday && wday <= wd2);\n" \ + "}\n" \ + "" \ + "function dateRange() {\n" \ + " function getMonth(name) {\n" \ + " if (name in months) {\n" \ + " return months[name];\n" \ + " }\n" \ + " return -1;\n" \ + " }\n" \ + " var date = new Date();\n" \ + " var argc = arguments.length;\n" \ + " if (argc < 1) {\n" \ + " return false;\n" \ + " }\n" \ + " var isGMT = (arguments[argc - 1] == 'GMT');\n" \ + "\n" \ + " if (isGMT) {\n" \ + " argc--;\n" \ + " }\n" \ + " // function will work even without explict handling of this case\n" \ + " if (argc == 1) {\n" \ + " var tmp = parseInt(arguments[0]);\n" \ + " if (isNaN(tmp)) {\n" \ + " return ((isGMT ? date.getUTCMonth() : date.getMonth()) ==\n" \ + "getMonth(arguments[0]));\n" \ + " } else if (tmp < 32) {\n" \ + " return ((isGMT ? date.getUTCDate() : date.getDate()) == tmp);\n" \ + " } else { \n" \ + " return ((isGMT ? date.getUTCFullYear() : date.getFullYear()) ==\n" \ + "tmp);\n" \ + " }\n" \ + " }\n" \ + " var year = date.getFullYear();\n" \ + " var date1, date2;\n" \ + " date1 = new Date(year, 0, 1, 0, 0, 0);\n" \ + " date2 = new Date(year, 11, 31, 23, 59, 59);\n" \ + " var adjustMonth = false;\n" \ + " for (var i = 0; i < (argc >> 1); i++) {\n" \ + " var tmp = parseInt(arguments[i]);\n" \ + " if (isNaN(tmp)) {\n" \ + " var mon = getMonth(arguments[i]);\n" \ + " date1.setMonth(mon);\n" \ + " } else if (tmp < 32) {\n" \ + " adjustMonth = (argc <= 2);\n" \ + " date1.setDate(tmp);\n" \ + " } else {\n" \ + " date1.setFullYear(tmp);\n" \ + " }\n" \ + " }\n" \ + " for (var i = (argc >> 1); i < argc; i++) {\n" \ + " var tmp = parseInt(arguments[i]);\n" \ + " if (isNaN(tmp)) {\n" \ + " var mon = getMonth(arguments[i]);\n" \ + " date2.setMonth(mon);\n" \ + " } else if (tmp < 32) {\n" \ + " date2.setDate(tmp);\n" \ + " } else {\n" \ + " date2.setFullYear(tmp);\n" \ + " }\n" \ + " }\n" \ + " if (adjustMonth) {\n" \ + " date1.setMonth(date.getMonth());\n" \ + " date2.setMonth(date.getMonth());\n" \ + " }\n" \ + " if (isGMT) {\n" \ + " var tmp = date;\n" \ + " tmp.setFullYear(date.getUTCFullYear());\n" \ + " tmp.setMonth(date.getUTCMonth());\n" \ + " tmp.setDate(date.getUTCDate());\n" \ + " tmp.setHours(date.getUTCHours());\n" \ + " tmp.setMinutes(date.getUTCMinutes());\n" \ + " tmp.setSeconds(date.getUTCSeconds());\n" \ + " date = tmp;\n" \ + " }\n" \ + " return ((date1 <= date) && (date <= date2));\n" \ + "}\n" \ + "" \ + "function timeRange() {\n" \ + " var argc = arguments.length;\n" \ + " var date = new Date();\n" \ + " var isGMT= false;\n" \ + "\n" \ + " if (argc < 1) {\n" \ + " return false;\n" \ + " }\n" \ + " if (arguments[argc - 1] == 'GMT') {\n" \ + " isGMT = true;\n" \ + " argc--;\n" \ + " }\n" \ + "\n" \ + " var hour = isGMT ? date.getUTCHours() : date.getHours();\n" \ + " var date1, date2;\n" \ + " date1 = new Date();\n" \ + " date2 = new Date();\n" \ + "\n" \ + " if (argc == 1) {\n" \ + " return (hour == arguments[0]);\n" \ + " } else if (argc == 2) {\n" \ + " return ((arguments[0] <= hour) && (hour <= arguments[1]));\n" \ + " } else {\n" \ + " switch (argc) {\n" \ + " case 6:\n" \ + " date1.setSeconds(arguments[2]);\n" \ + " date2.setSeconds(arguments[5]);\n" \ + " case 4:\n" \ + " var middle = argc >> 1;\n" \ + " date1.setHours(arguments[0]);\n" \ + " date1.setMinutes(arguments[1]);\n" \ + " date2.setHours(arguments[middle]);\n" \ + " date2.setMinutes(arguments[middle + 1]);\n" \ + " if (middle == 2) {\n" \ + " date2.setSeconds(59);\n" \ + " }\n" \ + " break;\n" \ + " default:\n" \ + " throw 'timeRange: bad number of arguments'\n" \ + " }\n" \ + " }\n" \ + "\n" \ + " if (isGMT) {\n" \ + " date.setFullYear(date.getUTCFullYear());\n" \ + " date.setMonth(date.getUTCMonth());\n" \ + " date.setDate(date.getUTCDate());\n" \ + " date.setHours(date.getUTCHours());\n" \ + " date.setMinutes(date.getUTCMinutes());\n" \ + " date.setSeconds(date.getUTCSeconds());\n" \ + " }\n" \ + " return ((date1 <= date) && (date <= date2));\n" \ + "}\n" + +// This is a Microsoft extension to PAC for IPv6, see: +// http://blogs.msdn.com/b/wndp/archive/2006/07/13/ipv6-pac-extensions-v0-9.aspx +#define PROXY_RESOLVER_SCRIPT_EX \ + "function isResolvableEx(host) {\n" \ + " var ipList = dnsResolveEx(host);\n" \ + " return (ipList != '');\n" \ + "}\n" + +#endif // NET_PROXY_PROXY_RESOLVER_SCRIPT_H_ diff --git a/src/proxy_resolver_v8.cc b/src/proxy_resolver_v8.cc new file mode 100644 index 0000000..b6ac654 --- /dev/null +++ b/src/proxy_resolver_v8.cc @@ -0,0 +1,726 @@ +// Copyright (c) 2010 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 <algorithm> +#include <cstdio> +#include <string> + +#include "proxy_resolver_v8.h" + +#include "proxy_resolver_script.h" +#include "net_util.h" +#include <include/v8.h> +#include <algorithm> +#include <vector> + +#include <iostream> + +#include <string.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: 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 +// -----------------+-------------+-------------------+-------------- + +static bool DoIsStringASCII(const std::wstring& str) { + for (size_t i = 0; i < str.length(); i++) { + unsigned char c = str[i]; + if (c > 0x7F) + return false; + } + return true; +} + +bool IsStringASCII(const std::wstring& str) { + return DoIsStringASCII(str); +} + +std::string UTF16ToASCII(const std::wstring& utf16) { + return std::string(utf16.begin(), utf16.end()); +} + +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 std::wstring& script_data) + : script_data_(script_data) {} + + virtual const uint16_t* data() const { + return reinterpret_cast<const uint16_t*>(script_data_.data()); + } + + virtual size_t length() const { + return script_data_.size(); + } + + private: + const std::wstring& script_data_; +// DISALLOW_COPY_AND_ASSIGN(V8ExternalStringFromScriptData); +}; + +// External string wrapper so V8 can access a string literal. +class V8ExternalASCIILiteral : public v8::String::ExternalAsciiStringResource { + 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) { + + } + + virtual const char* data() const { + return ascii_; + } + + virtual size_t length() const { + 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; + +template <class string_type> +inline typename string_type::value_type* WriteInto(string_type* str, + size_t length_with_null) { + str->reserve(length_with_null); + str->resize(length_with_null - 1); + return &((*str)[0]); +} + +// Converts a V8 String to a UTF8 std::string. +std::string V8StringToUTF8(v8::Handle<v8::String> s) { + std::string result; + s->WriteUtf8(WriteInto(&result, s->Length() + 1)); + return result; +} + +// Converts a V8 String to a UTF16 string. +std::wstring V8StringToUTF16(v8::Handle<v8::String> s) { + int len = s->Length(); + std::wstring result; + // Note that the reinterpret cast is because on Windows string is an alias + // to wstring, and hence has character type wchar_t not uint16_t. + s->Write(reinterpret_cast<uint16_t*>(WriteInto(&result, len + 1)), 0, len); + return result; +} + +// Converts an ASCII std::string to a V8 string. +v8::Local<v8::String> ASCIIStringToV8String(const std::string& s) { + return v8::String::New(s.data(), s.size()); +} + +// Converts an ASCII string literal to a V8 string. +v8::Local<v8::String> ASCIILiteralToV8String(const char* ascii) { +// DCHECK(IsStringASCII(ascii)); + size_t length = strlen(ascii); + if (length <= kMaxStringBytesForCopy) + return v8::String::New(ascii, length); + return v8::String::NewExternal(new V8ExternalASCIILiteral(ascii, length)); +} + +// 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::Handle<v8::Value> object, + std::wstring* utf16_result) { + if (object.IsEmpty()) + return false; + + v8::HandleScope scope; + v8::Local<v8::String> str_object = object->ToString(); + if (str_object.IsEmpty()) + 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::Arguments& 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::wstring hostname_utf16 = V8StringToUTF16(args[0]->ToString()); + + // If the hostname is already in ASCII, simply return it as is. + if (IsStringASCII(hostname_utf16)) { + *hostname = UTF16ToASCII(hostname_utf16); + return true; + } + return false; +} + +// Wrapper for passing around IP address strings and IPAddressNumber objects. +struct IPAddress { + IPAddress(const std::string& ip_string, const IPAddressNumber& ip_number) + : string_value(ip_string), + ip_address_number(ip_number) { + } + + // Used for sorting IP addresses in ascending order in SortIpAddressList(). + // IP6 addresses are placed ahead of IPv4 addresses. + bool operator<(const IPAddress& rhs) const { + const IPAddressNumber& ip1 = this->ip_address_number; + const IPAddressNumber& ip2 = rhs.ip_address_number; + if (ip1.size() != ip2.size()) + return ip1.size() > ip2.size(); // IPv6 before IPv4. + return memcmp(&ip1[0], &ip2[0], ip1.size()) < 0; // Ascending order. + } + + std::string string_value; + IPAddressNumber ip_address_number; +}; + +template<typename STR> +bool RemoveCharsT(const STR& input, + const typename STR::value_type remove_chars[], + STR* output) { + bool removed = false; + size_t found; + + *output = input; + + found = output->find_first_of(remove_chars); + while (found != STR::npos) { + removed = true; + output->replace(found, 1, STR()); + found = output->find_first_of(remove_chars, found); + } + + return removed; +} + +bool RemoveChars(const std::wstring& input, + const wchar_t remove_chars[], + std::wstring* output) { + return RemoveCharsT(input, remove_chars, output); +} + +bool RemoveChars(const std::string& input, + const char remove_chars[], + std::string* output) { + return RemoveCharsT(input, remove_chars, output); +} + +// 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; + 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<IPAddress> ip_vector; + IPAddressNumber ip_num; + char *tok_list = strtok((char *)cleaned_ip_address_list.c_str(), ";"); + while (tok_list != NULL) { + if (!ParseIPLiteralToNumber(tok_list, &ip_num)) + return false; + ip_vector.push_back(IPAddress(tok_list, ip_num)); + tok_list = strtok(tok_list, ";"); + } + + if (ip_vector.empty()) // Can happen if we have something like + return false; // sortIpAddressList(";") or sortIpAddressList("; ;") + + // 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) { + IPAddressNumber address; + if (!ParseIPLiteralToNumber(ip_address, &address)) + return false; + + IPAddressNumber 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; + + return IPNumberMatchesPrefix(address, prefix, prefix_length_in_bits); +} + +} // namespace + +// ProxyResolverV8::Context --------------------------------------------------- + +class ProxyResolverV8::Context { + public: + explicit Context(ProxyResolverJSBindings* js_bindings) + : js_bindings_(js_bindings) { + } + + ~Context() { + v8::Locker locked; + + v8_this_.Dispose(); + v8_context_.Dispose(); + + // Run the V8 garbage collector. We do this to be sure the + // ExternalStringResource objects we allocated get properly disposed. + // Otherwise when running the unit-tests they may get leaked. + // See crbug.com/48145. + PurgeMemory(); + } + + int ResolveProxy(const std::string url, const std::string host, std::string* results) { + v8::Locker locked; + v8::HandleScope scope; + + v8::Context::Scope function_scope(v8_context_); + + v8::Local<v8::Value> function; + if (!GetFindProxyForURL(&function)) { + *results = "FindProxyForURL() is undefined"; + return ERR_PAC_SCRIPT_FAILED; + } + + v8::Handle<v8::Value> argv[] = { + ASCIIStringToV8String(url), + ASCIIStringToV8String(host) }; + + v8::TryCatch try_catch; + v8::Local<v8::Value> ret = v8::Function::Cast(*function)->Call( + v8_context_->Global(), 2, argv); + + if (try_catch.HasCaught()) { + *results = V8StringToUTF8(try_catch.Message()->Get()); + return ERR_PAC_SCRIPT_FAILED; + } + + if (!ret->IsString()) { + *results = "FindProxyForURL() did not return a string."; + return ERR_PAC_SCRIPT_FAILED; + } + + std::wstring ret_str = V8StringToUTF16(ret->ToString()); + + if (!IsStringASCII(ret_str)) { + // TODO: 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 + *results = "FindProxyForURL() returned a non-ASCII string"; + return ERR_PAC_SCRIPT_FAILED; + } + + *results = V8StringToUTF8(ret->ToString()); + return OK; + } + + int InitV8(const std::string& pac_script) { + v8::Locker locked; + v8::HandleScope scope; + + v8_this_ = v8::Persistent<v8::External>::New(v8::External::New(this)); + v8::Local<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New(); + + // Attach the javascript bindings. + v8::Local<v8::FunctionTemplate> alert_template = + v8::FunctionTemplate::New(&AlertCallback, v8_this_); + global_template->Set(ASCIILiteralToV8String("alert"), alert_template); + + v8::Local<v8::FunctionTemplate> my_ip_address_template = + v8::FunctionTemplate::New(&MyIpAddressCallback, v8_this_); + global_template->Set(ASCIILiteralToV8String("myIpAddress"), + my_ip_address_template); + + v8::Local<v8::FunctionTemplate> dns_resolve_template = + v8::FunctionTemplate::New(&DnsResolveCallback, v8_this_); + global_template->Set(ASCIILiteralToV8String("dnsResolve"), + dns_resolve_template); + + // Microsoft's PAC extensions: + + v8::Local<v8::FunctionTemplate> dns_resolve_ex_template = + v8::FunctionTemplate::New(&DnsResolveExCallback, v8_this_); + global_template->Set(ASCIILiteralToV8String("dnsResolveEx"), + dns_resolve_ex_template); + + v8::Local<v8::FunctionTemplate> my_ip_address_ex_template = + v8::FunctionTemplate::New(&MyIpAddressExCallback, v8_this_); + global_template->Set(ASCIILiteralToV8String("myIpAddressEx"), + my_ip_address_ex_template); + + v8::Local<v8::FunctionTemplate> sort_ip_address_list_template = + v8::FunctionTemplate::New(&SortIpAddressListCallback, v8_this_); + global_template->Set(ASCIILiteralToV8String("sortIpAddressList"), + sort_ip_address_list_template); + + v8::Local<v8::FunctionTemplate> is_in_net_ex_template = + v8::FunctionTemplate::New(&IsInNetExCallback, v8_this_); + global_template->Set(ASCIILiteralToV8String("isInNetEx"), + is_in_net_ex_template); + + v8_context_ = v8::Context::New(NULL, global_template); + + v8::Context::Scope ctx(v8_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( + PROXY_RESOLVER_SCRIPT + PROXY_RESOLVER_SCRIPT_EX), + kPacUtilityResourceName); + if (rv != OK) { + return rv; + } + + // Add the user's PAC code to the environment. + rv = RunScript(ASCIIStringToV8String(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; + if (!GetFindProxyForURL(&function)) + return ERR_PAC_SCRIPT_FAILED; + + return OK; + } + + void PurgeMemory() { + v8::Locker locked; + // Repeatedly call the V8 idle notification until it returns true ("nothing + // more to free"). Note that it makes more sense to do this than to + // implement a new "delete everything" pass because object references make + // it difficult to free everything possible in just one pass. + while (!v8::V8::IdleNotification()) + ; + } + + private: + bool GetFindProxyForURL(v8::Local<v8::Value>* function) { + *function = v8_context_->Global()->Get( + ASCIILiteralToV8String("FindProxyForURL")); + return (*function)->IsFunction(); + } + + // Handle an exception thrown by V8. + void HandleError(v8::Handle<v8::Message> message) { + if (message.IsEmpty()) + return; + } + + // Compiles and runs |script| in the current V8 context. + // Returns OK on success, otherwise an error code. + int RunScript(v8::Handle<v8::String> script, const char* script_name) { + v8::TryCatch try_catch; + + // Compile the script. + v8::ScriptOrigin origin = + v8::ScriptOrigin(ASCIILiteralToV8String(script_name)); + v8::Local<v8::Script> code = v8::Script::Compile(script, &origin); + + // Execute. + if (!code.IsEmpty()) + code->Run(); + + // Check for errors. + if (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 v8::Handle<v8::Value> AlertCallback(const v8::Arguments& 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::wstring message; + if (args.Length() == 0) { + std::string undef = "undefined"; + std::wstring wundef(undef.begin(), undef.end()); + message = wundef; + } else { + if (!V8ObjectToUTF16String(args[0], &message)) + return v8::Undefined(); // toString() threw an exception. + } + + context->js_bindings_->Alert(message); + return v8::Undefined(); + } + + // V8 callback for when "myIpAddress()" is invoked by the PAC script. + static v8::Handle<v8::Value> MyIpAddressCallback(const v8::Arguments& args) { + Context* context = + static_cast<Context*>(v8::External::Cast(*args.Data())->Value()); + + std::string result; + bool success; + + { + v8::Unlocker unlocker; + + // We shouldn't be called with any arguments, but will not complain if + // we are. + success = context->js_bindings_->MyIpAddress(&result); + } + + if (!success) + return ASCIILiteralToV8String("127.0.0.1"); + return ASCIIStringToV8String(result); + } + + // V8 callback for when "myIpAddressEx()" is invoked by the PAC script. + static v8::Handle<v8::Value> MyIpAddressExCallback( + const v8::Arguments& args) { + Context* context = + static_cast<Context*>(v8::External::Cast(*args.Data())->Value()); + + std::string ip_address_list; + bool success; + + { + v8::Unlocker unlocker; + + // We shouldn't be called with any arguments, but will not complain if + // we are. + success = context->js_bindings_->MyIpAddressEx(&ip_address_list); + } + + if (!success) + ip_address_list = std::string(); + return ASCIIStringToV8String(ip_address_list); + } + + // V8 callback for when "dnsResolve()" is invoked by the PAC script. + static v8::Handle<v8::Value> DnsResolveCallback(const v8::Arguments& args) { + Context* context = + static_cast<Context*>(v8::External::Cast(*args.Data())->Value()); + + // We need at least one string argument. + std::string hostname; + if (!GetHostnameArgument(args, &hostname)) + return v8::Null(); + + std::string ip_address; + bool success; + + { + v8::Unlocker unlocker; + success = context->js_bindings_->DnsResolve(hostname, &ip_address); + } + + return success ? ASCIIStringToV8String(ip_address) : v8::Null(); + } + + // V8 callback for when "dnsResolveEx()" is invoked by the PAC script. + static v8::Handle<v8::Value> DnsResolveExCallback(const v8::Arguments& args) { + Context* context = + static_cast<Context*>(v8::External::Cast(*args.Data())->Value()); + + // We need at least one string argument. + std::string hostname; + if (!GetHostnameArgument(args, &hostname)) + return v8::Undefined(); + + std::string ip_address_list; + bool success; + + { + v8::Unlocker unlocker; + success = context->js_bindings_->DnsResolveEx(hostname, &ip_address_list); + } + + if (!success) + ip_address_list = std::string(); + + return ASCIIStringToV8String(ip_address_list); + } + + // V8 callback for when "sortIpAddressList()" is invoked by the PAC script. + static v8::Handle<v8::Value> SortIpAddressListCallback( + const v8::Arguments& args) { + // We need at least one string argument. + if (args.Length() == 0 || args[0].IsEmpty() || !args[0]->IsString()) + return v8::Null(); + + std::string ip_address_list = V8StringToUTF8(args[0]->ToString()); + std::string sorted_ip_address_list; + bool success = SortIpAddressList(ip_address_list, &sorted_ip_address_list); + if (!success) + return v8::False(); + return ASCIIStringToV8String(sorted_ip_address_list); + } + + // V8 callback for when "isInNetEx()" is invoked by the PAC script. + static v8::Handle<v8::Value> IsInNetExCallback(const v8::Arguments& args) { + // We need at least 2 string arguments. + if (args.Length() < 2 || args[0].IsEmpty() || !args[0]->IsString() || + args[1].IsEmpty() || !args[1]->IsString()) + return v8::Null(); + + std::string ip_address = V8StringToUTF8(args[0]->ToString()); + std::string ip_prefix = V8StringToUTF8(args[1]->ToString()); + return IsInNetEx(ip_address, ip_prefix) ? v8::True() : v8::False(); + } + + ProxyResolverJSBindings* js_bindings_; + v8::Persistent<v8::External> v8_this_; + v8::Persistent<v8::Context> v8_context_; +}; + +// ProxyResolverV8 ------------------------------------------------------------ + +ProxyResolverV8::ProxyResolverV8( + ProxyResolverJSBindings* custom_js_bindings) + : context_(NULL), js_bindings_(custom_js_bindings) { +} + +ProxyResolverV8::~ProxyResolverV8() { + +} + +int ProxyResolverV8::GetProxyForURL(const std::string spec, const std::string host, + std::string* results) { + // If the V8 instance has not been initialized (either because + // SetPacScript() wasn't called yet, or because it failed. + if (context_ == NULL) + return ERR_FAILED; + + // Otherwise call into V8. + int rv = context_->ResolveProxy(spec, host, results); + + return rv; +} + +void ProxyResolverV8::CancelRequest(RequestHandle request) { +} + +void ProxyResolverV8::CancelSetPacScript() { +} + +void ProxyResolverV8::PurgeMemory() { + context_->PurgeMemory(); +} + +void ProxyResolverV8::Shutdown() { +} + +int ProxyResolverV8::SetPacScript(std::string& script_data) { + if (context_ != NULL) { + delete context_; + } + if (script_data.empty()) + return ERR_PAC_SCRIPT_FAILED; + + // Try parsing the PAC script. + context_ = new Context(js_bindings_); + int rv; + if ((rv = context_->InitV8(script_data)) != OK) { + context_ = NULL; + } + if (rv != OK) + context_ = NULL; + return rv; +} + +} // namespace net diff --git a/src/proxy_resolver_v8.h b/src/proxy_resolver_v8.h new file mode 100644 index 0000000..22112bf --- /dev/null +++ b/src/proxy_resolver_v8.h @@ -0,0 +1,68 @@ +// Copyright (c) 2011 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. + +#ifndef NET_PROXY_PROXY_RESOLVER_V8_H_ +#define NET_PROXY_PROXY_RESOLVER_V8_H_ +#pragma once +#include "proxy_resolver_js_bindings.h" + +namespace net { + +typedef void* RequestHandle; +typedef void* CompletionCallback; + +#define OK 0 +#define ERR_PAC_SCRIPT_FAILED -1 +#define ERR_FAILED -2 + +// Implementation of ProxyResolver that uses V8 to evaluate PAC scripts. +// +// ---------------------------------------------------------------------------- +// !!! Important note on threading model: +// ---------------------------------------------------------------------------- +// There can be only one instance of V8 running at a time. To enforce this +// constraint, ProxyResolverV8 holds a v8::Locker during execution. Therefore +// it is OK to run multiple instances of ProxyResolverV8 on different threads, +// since only one will be running inside V8 at a time. +// +// It is important that *ALL* instances of V8 in the process be using +// v8::Locker. If not there can be race conditions beween the non-locked V8 +// instances and the locked V8 instances used by ProxyResolverV8 (assuming they +// run on different threads). +// +// This is the case with the V8 instance used by chromium's renderer -- it runs +// on a different thread from ProxyResolver (renderer thread vs PAC thread), +// and does not use locking since it expects to be alone. +class ProxyResolverV8 { + public: + // Constructs a ProxyResolverV8 with custom bindings. ProxyResolverV8 takes + // ownership of |custom_js_bindings| and deletes it when ProxyResolverV8 + // is destroyed. + explicit ProxyResolverV8(ProxyResolverJSBindings* custom_js_bindings); + + virtual ~ProxyResolverV8(); + + ProxyResolverJSBindings* js_bindings() { return js_bindings_; } + + virtual int GetProxyForURL(const std::string spec, const std::string host, + std::string* results); + virtual void CancelRequest(RequestHandle request); + virtual void CancelSetPacScript(); + virtual void PurgeMemory(); + virtual void Shutdown(); + virtual int SetPacScript(std::string& script_data); + + private: + // Context holds the Javascript state for the most recently loaded PAC + // script. It corresponds with the data from the last call to + // SetPacScript(). + class Context; + Context* context_; + + ProxyResolverJSBindings* js_bindings_; +}; + +} // namespace net + +#endif // NET_PROXY_PROXY_RESOLVER_V8_H_ |