summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorericroman@google.com <ericroman@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-02-23 19:10:45 +0000
committerericroman@google.com <ericroman@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-02-23 19:10:45 +0000
commit943c808476fc4ccea2175a4f76bc05346c53c209 (patch)
treeae0c1fe78fa44eeb238770a6051fc639885572f8
parent45bdf86341670d95573acbc699f2d2998a25cf1d (diff)
downloadchromium_src-943c808476fc4ccea2175a4f76bc05346c53c209.zip
chromium_src-943c808476fc4ccea2175a4f76bc05346c53c209.tar.gz
chromium_src-943c808476fc4ccea2175a4f76bc05346c53c209.tar.bz2
Add ProxyResolverV8 class.darin@chromium.org is the original author of 'proxy_resolver_v8.cc' and 'proxy_resolver_script.h'.
Review URL: http://codereview.chromium.org/21391 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@10199 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--net/base/net_error_list.h5
-rw-r--r--net/data/proxy_resolver_v8_unittest/direct.js4
-rw-r--r--net/data/proxy_resolver_v8_unittest/missing_close_brace.js6
-rw-r--r--net/data/proxy_resolver_v8_unittest/no_entrypoint.js2
-rw-r--r--net/data/proxy_resolver_v8_unittest/pac_library_unittest.js130
-rw-r--r--net/data/proxy_resolver_v8_unittest/passthrough.js45
-rw-r--r--net/data/proxy_resolver_v8_unittest/return_empty_string.js4
-rw-r--r--net/data/proxy_resolver_v8_unittest/return_function.js4
-rw-r--r--net/data/proxy_resolver_v8_unittest/return_integer.js4
-rw-r--r--net/data/proxy_resolver_v8_unittest/return_null.js4
-rw-r--r--net/data/proxy_resolver_v8_unittest/return_object.js4
-rw-r--r--net/data/proxy_resolver_v8_unittest/return_undefined.js4
-rw-r--r--net/data/proxy_resolver_v8_unittest/return_unicode.js4
-rw-r--r--net/data/proxy_resolver_v8_unittest/side_effects.js10
-rw-r--r--net/data/proxy_resolver_v8_unittest/unhandled_exception.js5
-rw-r--r--net/proxy/proxy_resolver_script.h267
-rw-r--r--net/proxy/proxy_resolver_v8.cc131
-rw-r--r--net/proxy/proxy_resolver_v8.h56
-rw-r--r--net/proxy/proxy_resolver_v8_unittest.cc232
19 files changed, 920 insertions, 1 deletions
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h
index c472f54..c2722bd 100644
--- a/net/base/net_error_list.h
+++ b/net/base/net_error_list.h
@@ -227,9 +227,12 @@ NET_ERROR(RESPONSE_HEADERS_TOO_BIG, -325)
// The PAC requested by HTTP did not have a valid status code (non-200).
NET_ERROR(PAC_STATUS_NOT_OK, -326)
+// The evaluation of the PAC script failed.
+NET_ERROR(PAC_SCRIPT_FAILED, -327)
+
// The response was 401 (Unauthorized), yet the request was a CONNECT request
// to a proxy.
-NET_ERROR(UNEXPECTED_SERVER_AUTH, -327)
+NET_ERROR(UNEXPECTED_SERVER_AUTH, -328)
// The cache does not have the requested entry.
NET_ERROR(CACHE_MISS, -400)
diff --git a/net/data/proxy_resolver_v8_unittest/direct.js b/net/data/proxy_resolver_v8_unittest/direct.js
new file mode 100644
index 0000000..43a04da
--- /dev/null
+++ b/net/data/proxy_resolver_v8_unittest/direct.js
@@ -0,0 +1,4 @@
+function FindProxyForURL(url, host) {
+ return "DIRECT";
+}
+
diff --git a/net/data/proxy_resolver_v8_unittest/missing_close_brace.js b/net/data/proxy_resolver_v8_unittest/missing_close_brace.js
new file mode 100644
index 0000000..8018f8f
--- /dev/null
+++ b/net/data/proxy_resolver_v8_unittest/missing_close_brace.js
@@ -0,0 +1,6 @@
+// This PAC script is invalid, because there is a missing close brace
+// on the function FindProxyForURL().
+
+function FindProxyForURL(url, host) {
+ return "DIRECT";
+
diff --git a/net/data/proxy_resolver_v8_unittest/no_entrypoint.js b/net/data/proxy_resolver_v8_unittest/no_entrypoint.js
new file mode 100644
index 0000000..8993059
--- /dev/null
+++ b/net/data/proxy_resolver_v8_unittest/no_entrypoint.js
@@ -0,0 +1,2 @@
+var x = "This is an invalid PAC script because it lacks a " +
+ "FindProxyForURL() function";
diff --git a/net/data/proxy_resolver_v8_unittest/pac_library_unittest.js b/net/data/proxy_resolver_v8_unittest/pac_library_unittest.js
new file mode 100644
index 0000000..d460b99
--- /dev/null
+++ b/net/data/proxy_resolver_v8_unittest/pac_library_unittest.js
@@ -0,0 +1,130 @@
+// This should output "PROXY success:80" if all the tests pass.
+// Otherwise it will output "PROXY failure:<num-failures>".
+//
+// This aims to unit-test the PAC library functions, which are
+// exposed in the PAC's execution environment. (Namely, dnsDomainLevels,
+// timeRange, etc.)
+
+function FindProxyForURL(url, host) {
+ var numTestsFailed = 0;
+
+ // Run all the tests
+ for (var test in Tests) {
+ var t = new TestContext(test);
+
+ // Run the test.
+ Tests[test](t);
+
+ if (t.failed()) {
+ numTestsFailed++;
+ }
+ }
+
+ if (numTestsFailed == 0) {
+ return "PROXY success:80";
+ }
+ return "PROXY failure:" + numTestsFailed;
+}
+
+// --------------------------
+// Tests
+// --------------------------
+
+var Tests = {};
+
+Tests.testDnsDomainIs = function(t) {
+ t.expectTrue(dnsDomainIs("google.com", ".com"));
+ t.expectTrue(dnsDomainIs("google.co.uk", ".co.uk"));
+ t.expectFalse(dnsDomainIs("google.com", ".co.uk"));
+};
+
+Tests.testDnsDomainLevels = function(t) {
+ t.expectEquals(0, dnsDomainLevels("www"));
+ t.expectEquals(2, dnsDomainLevels("www.google.com"));
+ t.expectEquals(3, dnsDomainLevels("192.168.1.1"));
+};
+
+Tests.testIsInNet = function(t) {
+ // TODO(eroman):
+
+ // t.expectTrue(
+ // isInNet("192.89.132.25", "192.89.132.25", "255.255.255.255"));
+ // t.expectFalse(
+ // isInNet("193.89.132.25", "192.89.132.25", "255.255.255.255"));
+ //
+ // t.expectTrue(isInNet("192.89.132.25", "192.89.0.0", "255.255.0.0"));
+ // t.expectFalse(isInNet("193.89.132.25", "192.89.0.0", "255.255.0.0"));
+};
+
+Tests.testIsPlainHostName = function(t) {
+ t.expectTrue(isPlainHostName("google"));
+ t.expectFalse(isPlainHostName("google.com"));
+};
+
+Tests.testLocalHostOrDomainIs = function(t) {
+ t.expectTrue(localHostOrDomainIs("www.google.com", "www.google.com"));
+ t.expectTrue(localHostOrDomainIs("www", "www.google.com"));
+ t.expectFalse(localHostOrDomainIs("maps.google.com", "www.google.com"));
+};
+
+Tests.testShExpMatch = function(t) {
+ // TODO(eroman):
+
+ //t.expectTrue(shExpMatch("http://maps.google.com/blah/foo/moreblah.jpg",
+ // ".*/foo/.*jpg"));
+
+ //t.expectFalse(shExpMatch("http://maps.google.com/blah/foo/moreblah.jpg",
+ // ".*/foo/.*.html"));
+};
+
+Tests.testWeekdayRange = function(t) {
+ // TODO(eroman)
+};
+
+Tests.testDateRange = function(t) {
+ // TODO(eroman)
+};
+
+Tests.testTimeRange = function(t) {
+ // TODO(eroman)
+};
+
+// --------------------------
+// Helpers
+// --------------------------
+
+// |name| is the name of the test being executed, it will be used when logging
+// errors.
+function TestContext(name) {
+ this.numFailures_ = 0;
+ this.name_ = name;
+};
+
+TestContext.prototype.failed = function() {
+ return this.numFailures_ != 0;
+};
+
+TestContext.prototype.expectEquals = function(expectation, actual) {
+ if (!(expectation === actual)) {
+ this.numFailures_++;
+ this.log("FAIL: expected: " + expectation + ", actual: " + actual);
+ }
+};
+
+TestContext.prototype.expectTrue = function(x) {
+ this.expectEquals(true, x);
+};
+
+TestContext.prototype.expectFalse = function(x) {
+ this.expectEquals(false, x);
+};
+
+TestContext.prototype.log = function(x) {
+ // Prefix with the test name that generated the log.
+ try {
+ alert(this.name_ + ": " + x);
+ } catch(e) {
+ // In case alert() is not defined.
+ }
+};
+
diff --git a/net/data/proxy_resolver_v8_unittest/passthrough.js b/net/data/proxy_resolver_v8_unittest/passthrough.js
new file mode 100644
index 0000000..832ac66
--- /dev/null
+++ b/net/data/proxy_resolver_v8_unittest/passthrough.js
@@ -0,0 +1,45 @@
+// Return a single-proxy result, which encodes ALL the arguments that were
+// passed to FindProxyForURL().
+
+function FindProxyForURL(url, host) {
+ if (arguments.length != 2) {
+ throw "Wrong number of arguments passed to FindProxyForURL!";
+ return "FAIL";
+ }
+
+ return "PROXY " + makePseudoHost(url + "." + host);
+}
+
+// Form a string that kind-of resembles a host. We will replace any
+// non-alphanumeric character with a dot, then fix up the oddly placed dots.
+function makePseudoHost(str) {
+ var result = "";
+
+ for (var i = 0; i < str.length; ++i) {
+ var c = str.charAt(i);
+ if (!isValidPseudoHostChar(c)) {
+ c = '.'; // Replace unsupported characters with a dot.
+ }
+
+ // Take care not to place multiple adjacent dots,
+ // a dot at the beginning, or a dot at the end.
+ if (c == '.' &&
+ (result.length == 0 ||
+ i == str.length - 1 ||
+ result.charAt(result.length - 1) == '.')) {
+ continue;
+ }
+ result += c;
+ }
+ return result;
+}
+
+function isValidPseudoHostChar(c) {
+ if (c >= '0' && c <= '9')
+ return true;
+ if (c >= 'a' && c <= 'z')
+ return true;
+ if (c >= 'A' && c <= 'Z')
+ return true;
+ return false;
+}
diff --git a/net/data/proxy_resolver_v8_unittest/return_empty_string.js b/net/data/proxy_resolver_v8_unittest/return_empty_string.js
new file mode 100644
index 0000000..3342196
--- /dev/null
+++ b/net/data/proxy_resolver_v8_unittest/return_empty_string.js
@@ -0,0 +1,4 @@
+function FindProxyForURL(url, host) {
+ return "";
+}
+
diff --git a/net/data/proxy_resolver_v8_unittest/return_function.js b/net/data/proxy_resolver_v8_unittest/return_function.js
new file mode 100644
index 0000000..9005553
--- /dev/null
+++ b/net/data/proxy_resolver_v8_unittest/return_function.js
@@ -0,0 +1,4 @@
+function FindProxyForURL(url, host) {
+ return FindProxyForURL;
+}
+
diff --git a/net/data/proxy_resolver_v8_unittest/return_integer.js b/net/data/proxy_resolver_v8_unittest/return_integer.js
new file mode 100644
index 0000000..d86b299
--- /dev/null
+++ b/net/data/proxy_resolver_v8_unittest/return_integer.js
@@ -0,0 +1,4 @@
+function FindProxyForURL(url, host) {
+ return 0;
+}
+
diff --git a/net/data/proxy_resolver_v8_unittest/return_null.js b/net/data/proxy_resolver_v8_unittest/return_null.js
new file mode 100644
index 0000000..6cf90c5
--- /dev/null
+++ b/net/data/proxy_resolver_v8_unittest/return_null.js
@@ -0,0 +1,4 @@
+function FindProxyForURL(url, host) {
+ return null;
+}
+
diff --git a/net/data/proxy_resolver_v8_unittest/return_object.js b/net/data/proxy_resolver_v8_unittest/return_object.js
new file mode 100644
index 0000000..3824f8a
--- /dev/null
+++ b/net/data/proxy_resolver_v8_unittest/return_object.js
@@ -0,0 +1,4 @@
+function FindProxyForURL(url, host) {
+ return {result: "PROXY foo"};
+}
+
diff --git a/net/data/proxy_resolver_v8_unittest/return_undefined.js b/net/data/proxy_resolver_v8_unittest/return_undefined.js
new file mode 100644
index 0000000..0f0aa98
--- /dev/null
+++ b/net/data/proxy_resolver_v8_unittest/return_undefined.js
@@ -0,0 +1,4 @@
+function FindProxyForURL(url, host) {
+ return undefined;
+}
+
diff --git a/net/data/proxy_resolver_v8_unittest/return_unicode.js b/net/data/proxy_resolver_v8_unittest/return_unicode.js
new file mode 100644
index 0000000..5ecdd1c
--- /dev/null
+++ b/net/data/proxy_resolver_v8_unittest/return_unicode.js
@@ -0,0 +1,4 @@
+// U+200B is the codepoint for zero-width-space.
+function FindProxyForURL(url, host) {
+ return "PROXY foo.com\u200B";
+}
diff --git a/net/data/proxy_resolver_v8_unittest/side_effects.js b/net/data/proxy_resolver_v8_unittest/side_effects.js
new file mode 100644
index 0000000..39b3b2d
--- /dev/null
+++ b/net/data/proxy_resolver_v8_unittest/side_effects.js
@@ -0,0 +1,10 @@
+if (!gCounter) {
+ // We write it this way so if the script gets loaded twice,
+ // gCounter remains dirty.
+ var gCounter = 0;
+}
+
+function FindProxyForURL(url, host) {
+ return "PROXY sideffect_" + gCounter++;
+}
+
diff --git a/net/data/proxy_resolver_v8_unittest/unhandled_exception.js b/net/data/proxy_resolver_v8_unittest/unhandled_exception.js
new file mode 100644
index 0000000..9cc2856
--- /dev/null
+++ b/net/data/proxy_resolver_v8_unittest/unhandled_exception.js
@@ -0,0 +1,5 @@
+function FindProxyForURL(url, host) {
+ // This will throw a runtime exception.
+ return "PROXY x" + undefined_variable;
+}
+
diff --git a/net/proxy/proxy_resolver_script.h b/net/proxy/proxy_resolver_script.h
new file mode 100644
index 0000000..ddcb66b
--- /dev/null
+++ b/net/proxy/proxy_resolver_script.h
@@ -0,0 +1,267 @@
+/* ***** 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' |
+// 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})$/(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" \
+
+#endif // NET_PROXY_PROXY_RESOLVER_SCRIPT_H_
diff --git a/net/proxy/proxy_resolver_v8.cc b/net/proxy/proxy_resolver_v8.cc
new file mode 100644
index 0000000..f26a6f6
--- /dev/null
+++ b/net/proxy/proxy_resolver_v8.cc
@@ -0,0 +1,131 @@
+// Copyright (c) 2009 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 "net/base/net_errors.h"
+#include "net/proxy/proxy_resolver_script.h"
+#include "v8/include/v8.h"
+
+// TODO(eroman):
+// - javascript binding for "alert()"
+// - javascript binding for "myIpAddress()"
+// - javascript binding for "dnsResolve()"
+// - log errors for PAC authors (like if "FindProxyForURL" is missing, or
+// the javascript was malformed, etc..
+
+namespace net {
+
+namespace {
+
+std::string V8StringToStdString(v8::Handle<v8::String> s) {
+ int len = s->Utf8Length();
+ std::string result;
+ s->WriteUtf8(WriteInto(&result, len + 1), len);
+ return result;
+}
+
+v8::Local<v8::String> StdStringToV8String(const std::string& s) {
+ return v8::String::New(s.data(), s.size());
+}
+
+} // namespace
+
+// ProxyResolverV8::Context ---------------------------------------------------
+
+class ProxyResolverV8::Context {
+ public:
+ explicit Context(const std::string& pac_data) {
+ InitV8(pac_data);
+ }
+
+ ~Context() {
+ v8::Locker locked;
+ v8::HandleScope scope;
+
+ v8_context_.Dispose();
+ }
+
+ int ResolveProxy(const GURL& query_url, ProxyInfo* results) {
+ v8::Locker locked;
+ v8::HandleScope scope;
+
+ v8::Context::Scope function_scope(v8_context_);
+
+ v8::Local<v8::Value> function =
+ v8_context_->Global()->Get(v8::String::New("FindProxyForURL"));
+ if (!function->IsFunction())
+ return ERR_PAC_SCRIPT_FAILED;
+
+ v8::Handle<v8::Value> argv[] = {
+ StdStringToV8String(query_url.spec()),
+ StdStringToV8String(query_url.host()),
+ };
+
+ v8::Local<v8::Value> ret = v8::Function::Cast(*function)->Call(
+ v8_context_->Global(), arraysize(argv), argv);
+
+ // The handle is empty if an exception was thrown from "FindProxyForURL".
+ if (ret.IsEmpty())
+ return ERR_PAC_SCRIPT_FAILED;
+
+ if (!ret->IsString())
+ return ERR_PAC_SCRIPT_FAILED;
+
+ std::string ret_str = V8StringToStdString(ret->ToString());
+
+ results->UsePacString(ret_str);
+
+ return OK;
+ }
+
+ private:
+ void InitV8(const std::string& pac_data) {
+ v8::Locker locked;
+ v8::HandleScope scope;
+
+ v8::Local<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New();
+
+ v8_context_ = v8::Context::New(NULL, global_template);
+ v8::Context::Scope ctx(v8_context_);
+
+ std::string text_raw = pac_data + PROXY_RESOLVER_SCRIPT;
+
+ v8::Local<v8::String> text = StdStringToV8String(text_raw);
+ v8::ScriptOrigin origin = v8::ScriptOrigin(v8::String::New(""));
+ v8::Local<v8::Script> code = v8::Script::Compile(text, &origin);
+ if (!code.IsEmpty())
+ code->Run();
+ }
+
+ v8::Persistent<v8::Context> v8_context_;
+};
+
+// ProxyResolverV8 ------------------------------------------------------------
+
+// the |false| argument to ProxyResolver means the ProxyService will handle
+// downloading of the PAC script, and notify changes through SetPacScript().
+ProxyResolverV8::ProxyResolverV8() : ProxyResolver(false /*does_fetch*/) {}
+
+ProxyResolverV8::~ProxyResolverV8() {}
+
+int ProxyResolverV8::GetProxyForURL(const GURL& query_url,
+ const GURL& /*pac_url*/,
+ ProxyInfo* results) {
+ // If the V8 instance has not been initialized (either because SetPacScript()
+ // wasn't called yet, or because it was called with empty string).
+ if (!context_.get())
+ return ERR_FAILED;
+
+ // Otherwise call into V8.
+ return context_->ResolveProxy(query_url, results);
+}
+
+void ProxyResolverV8::SetPacScript(const std::string& data) {
+ context_.reset();
+ if (!data.empty())
+ context_.reset(new Context(data));
+}
+
+} // namespace net
diff --git a/net/proxy/proxy_resolver_v8.h b/net/proxy/proxy_resolver_v8.h
new file mode 100644
index 0000000..312b557
--- /dev/null
+++ b/net/proxy/proxy_resolver_v8.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2009 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_
+
+#include <string>
+
+#include "base/scoped_ptr.h"
+#include "net/proxy/proxy_service.h"
+
+namespace net {
+
+// 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 ProxyResolver {
+ public:
+ ProxyResolverV8();
+ ~ProxyResolverV8();
+
+ // ProxyResolver implementation:
+ virtual int GetProxyForURL(const GURL& query_url,
+ const GURL& /*pac_url*/,
+ ProxyInfo* results);
+ virtual void SetPacScript(const std::string& bytes);
+
+ 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;
+ scoped_ptr<Context> context_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProxyResolverV8);
+};
+
+} // namespace net
+
+#endif // NET_PROXY_PROXY_RESOLVER_V8_H_
diff --git a/net/proxy/proxy_resolver_v8_unittest.cc b/net/proxy/proxy_resolver_v8_unittest.cc
new file mode 100644
index 0000000..b576aa9
--- /dev/null
+++ b/net/proxy/proxy_resolver_v8_unittest.cc
@@ -0,0 +1,232 @@
+// Copyright (c) 2009 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 "base/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/proxy/proxy_resolver_v8.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Initialize |resolver| with the PAC script data at |filename|.
+void InitWithScriptFromDisk(net::ProxyResolver* resolver,
+ const char* filename) {
+ FilePath path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ path = path.AppendASCII("net");
+ path = path.AppendASCII("data");
+ path = path.AppendASCII("proxy_resolver_v8_unittest");
+ path = path.AppendASCII(filename);
+
+ // Try to read the file from disk.
+ std::string file_contents;
+ bool ok = file_util::ReadFileToString(path, &file_contents);
+
+ // If we can't load the file from disk, something is misconfigured.
+ LOG_IF(ERROR, !ok) << "Failed to read file: " << path.value();
+ ASSERT_TRUE(ok);
+
+ // Load the PAC script into the ProxyResolver.
+ resolver->SetPacScript(file_contents);
+}
+
+// Doesn't really matter what these values are for many of the tests.
+const GURL kQueryUrl("http://www.google.com");
+const GURL kPacUrl;
+
+}
+
+TEST(ProxyResolverV8Test, Direct) {
+ net::ProxyResolverV8 resolver;
+ InitWithScriptFromDisk(&resolver, "direct.js");
+
+ net::ProxyInfo proxy_info;
+ int result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
+
+ EXPECT_EQ(net::OK, result);
+ EXPECT_TRUE(proxy_info.is_direct());
+}
+
+TEST(ProxyResolverV8Test, ReturnEmptyString) {
+ net::ProxyResolverV8 resolver;
+ InitWithScriptFromDisk(&resolver, "return_empty_string.js");
+
+ net::ProxyInfo proxy_info;
+ int result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
+
+ EXPECT_EQ(net::OK, result);
+ EXPECT_TRUE(proxy_info.is_direct());
+}
+
+TEST(ProxyResolverV8Test, Basic) {
+ net::ProxyResolverV8 resolver;
+ InitWithScriptFromDisk(&resolver, "passthrough.js");
+
+ // The "FindProxyForURL" of this PAC script simply concatenates all of the
+ // arguments into a pseudo-host. The purpose of this test is to verify that
+ // the correct arguments are being passed to FindProxyForURL().
+ {
+ net::ProxyInfo proxy_info;
+ int result = resolver.GetProxyForURL(GURL("http://query.com/path"),
+ kPacUrl, &proxy_info);
+ EXPECT_EQ(net::OK, result);
+ EXPECT_EQ("http.query.com.path.query.com:80",
+ proxy_info.proxy_server().ToURI());
+ }
+ {
+ net::ProxyInfo proxy_info;
+ int result = resolver.GetProxyForURL(GURL("ftp://query.com:90/path"),
+ kPacUrl, &proxy_info);
+ EXPECT_EQ(net::OK, result);
+ // Note that FindProxyForURL(url, host) does not expect |host| to contain
+ // the port number.
+ EXPECT_EQ("ftp.query.com.90.path.query.com:80",
+ proxy_info.proxy_server().ToURI());
+ }
+}
+
+TEST(ProxyResolverV8Test, BadReturnType) {
+ // These are the filenames of PAC scripts which each return a non-string
+ // types for FindProxyForURL(). They should all fail with
+ // ERR_PAC_SCRIPT_FAILED.
+ static const char* const filenames[] = {
+ "return_undefined.js",
+ "return_integer.js",
+ "return_function.js",
+ "return_object.js",
+ // TODO(eroman): Should 'null' be considered equivalent to "DIRECT" ?
+ "return_null.js"
+ };
+
+ for (size_t i = 0; i < arraysize(filenames); ++i) {
+ net::ProxyResolverV8 resolver;
+ InitWithScriptFromDisk(&resolver, filenames[i]);
+
+ net::ProxyInfo proxy_info;
+ int result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
+
+ EXPECT_EQ(net::ERR_PAC_SCRIPT_FAILED, result);
+ }
+}
+
+// Try using a PAC script which defines no "FindProxyForURL" function.
+TEST(ProxyResolverV8Test, NoEntryPoint) {
+ net::ProxyResolverV8 resolver;
+ InitWithScriptFromDisk(&resolver, "no_entrypoint.js");
+
+ net::ProxyInfo proxy_info;
+ int result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
+
+ EXPECT_EQ(net::ERR_PAC_SCRIPT_FAILED, result);
+}
+
+// Try loading a malformed PAC script.
+TEST(ProxyResolverV8Test, ParseError) {
+ net::ProxyResolverV8 resolver;
+ InitWithScriptFromDisk(&resolver, "missing_close_brace.js");
+
+ net::ProxyInfo proxy_info;
+ int result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
+
+ EXPECT_EQ(net::ERR_PAC_SCRIPT_FAILED, result);
+}
+
+// Run a PAC script several times, which has side-effects.
+TEST(ProxyResolverV8Test, SideEffects) {
+ net::ProxyResolverV8 resolver;
+ InitWithScriptFromDisk(&resolver, "side_effects.js");
+
+ // The PAC script increments a counter each time we invoke it.
+ for (int i = 0; i < 3; ++i) {
+ net::ProxyInfo proxy_info;
+ int result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
+ EXPECT_EQ(net::OK, result);
+ EXPECT_EQ(StringPrintf("sideffect_%d:80", i),
+ proxy_info.proxy_server().ToURI());
+ }
+
+ // Reload the script -- the javascript environment should be reset, hence
+ // the counter starts over.
+ InitWithScriptFromDisk(&resolver, "side_effects.js");
+
+ for (int i = 0; i < 3; ++i) {
+ net::ProxyInfo proxy_info;
+ int result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
+ EXPECT_EQ(net::OK, result);
+ EXPECT_EQ(StringPrintf("sideffect_%d:80", i),
+ proxy_info.proxy_server().ToURI());
+ }
+}
+
+// Execute a PAC script which throws an exception in FindProxyForURL.
+TEST(ProxyResolverV8Test, UnhandledException) {
+ net::ProxyResolverV8 resolver;
+ InitWithScriptFromDisk(&resolver, "unhandled_exception.js");
+
+ net::ProxyInfo proxy_info;
+ int result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
+
+ EXPECT_EQ(net::ERR_PAC_SCRIPT_FAILED, result);
+}
+
+// TODO(eroman): This test is disabed right now, since the parsing of
+// host/port doesn't check for non-ascii characters.
+TEST(ProxyResolverV8Test, DISABLED_ReturnUnicode) {
+ net::ProxyResolverV8 resolver;
+ InitWithScriptFromDisk(&resolver, "return_unicode.js");
+
+ net::ProxyInfo proxy_info;
+ int result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
+
+ // The result from this resolve was unparseable, because it
+ // wasn't ascii.
+ EXPECT_EQ(net::ERR_PAC_SCRIPT_FAILED, result);
+}
+
+// Test the PAC library functions that we expose in the JS environmnet.
+TEST(ProxyResolverV8Test, JavascriptLibrary) {
+ net::ProxyResolverV8 resolver;
+ InitWithScriptFromDisk(&resolver, "pac_library_unittest.js");
+
+ net::ProxyInfo proxy_info;
+ int result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
+
+ // If the javascript side of this unit-test fails, it will throw a javascript
+ // exception. Otherwise it will return "PROXY success:80".
+ EXPECT_EQ(net::OK, result);
+ EXPECT_EQ("success:80", proxy_info.proxy_server().ToURI());
+}
+
+// Try resolving when SetPacScript() has not been called.
+TEST(ProxyResolverV8Test, NoSetPacScript) {
+ net::ProxyResolverV8 resolver;
+
+ net::ProxyInfo proxy_info;
+
+ // Resolve should fail, as we are not yet initialized with a script.
+ int result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
+ EXPECT_EQ(net::ERR_FAILED, result);
+
+ // Initialize it.
+ InitWithScriptFromDisk(&resolver, "direct.js");
+
+ // Resolve should now succeed.
+ result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
+ EXPECT_EQ(net::OK, result);
+
+ // Clear it, by initializing with an empty string.
+ resolver.SetPacScript(std::string());
+
+ // Resolve should fail again now.
+ result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
+ EXPECT_EQ(net::ERR_FAILED, result);
+
+ // Load a good script once more.
+ InitWithScriptFromDisk(&resolver, "direct.js");
+ result = resolver.GetProxyForURL(kQueryUrl, kPacUrl, &proxy_info);
+ EXPECT_EQ(net::OK, result);
+} \ No newline at end of file