1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "net/proxy/proxy_resolver_v8.h"
6
7#include <algorithm>
8#include <cstdio>
9
10#include "base/basictypes.h"
11#include "base/compiler_specific.h"
12#include "base/logging.h"
13#include "base/strings/string_tokenizer.h"
14#include "base/strings/string_util.h"
15#include "base/strings/utf_string_conversions.h"
16#include "base/synchronization/lock.h"
17#include "net/base/net_errors.h"
18#include "net/base/net_log.h"
19#include "net/base/net_util.h"
20#include "net/proxy/proxy_info.h"
21#include "net/proxy/proxy_resolver_script.h"
22#include "url/gurl.h"
23#include "url/url_canon.h"
24#include "v8/include/v8.h"
25
26// Notes on the javascript environment:
27//
28// For the majority of the PAC utility functions, we use the same code
29// as Firefox. See the javascript library that proxy_resolver_scipt.h
30// pulls in.
31//
32// In addition, we implement a subset of Microsoft's extensions to PAC.
33// - myIpAddressEx()
34// - dnsResolveEx()
35// - isResolvableEx()
36// - isInNetEx()
37// - sortIpAddressList()
38//
39// It is worth noting that the original PAC specification does not describe
40// the return values on failure. Consequently, there are compatibility
41// differences between browsers on what to return on failure, which are
42// illustrated below:
43//
44// --------------------+-------------+-------------------+--------------
45//                     | Firefox3    | InternetExplorer8 |  --> Us <---
46// --------------------+-------------+-------------------+--------------
47// myIpAddress()       | "127.0.0.1" |  ???              |  "127.0.0.1"
48// dnsResolve()        | null        |  false            |  null
49// myIpAddressEx()     | N/A         |  ""               |  ""
50// sortIpAddressList() | N/A         |  false            |  false
51// dnsResolveEx()      | N/A         |  ""               |  ""
52// isInNetEx()         | N/A         |  false            |  false
53// --------------------+-------------+-------------------+--------------
54//
55// TODO(eroman): The cell above reading ??? means I didn't test it.
56//
57// Another difference is in how dnsResolve() and myIpAddress() are
58// implemented -- whether they should restrict to IPv4 results, or
59// include both IPv4 and IPv6. The following table illustrates the
60// differences:
61//
62// --------------------+-------------+-------------------+--------------
63//                     | Firefox3    | InternetExplorer8 |  --> Us <---
64// --------------------+-------------+-------------------+--------------
65// myIpAddress()       | IPv4/IPv6   |  IPv4             |  IPv4
66// dnsResolve()        | IPv4/IPv6   |  IPv4             |  IPv4
67// isResolvable()      | IPv4/IPv6   |  IPv4             |  IPv4
68// myIpAddressEx()     | N/A         |  IPv4/IPv6        |  IPv4/IPv6
69// dnsResolveEx()      | N/A         |  IPv4/IPv6        |  IPv4/IPv6
70// sortIpAddressList() | N/A         |  IPv4/IPv6        |  IPv4/IPv6
71// isResolvableEx()    | N/A         |  IPv4/IPv6        |  IPv4/IPv6
72// isInNetEx()         | N/A         |  IPv4/IPv6        |  IPv4/IPv6
73// -----------------+-------------+-------------------+--------------
74
75namespace net {
76
77namespace {
78
79// Pseudo-name for the PAC script.
80const char kPacResourceName[] = "proxy-pac-script.js";
81// Pseudo-name for the PAC utility script.
82const char kPacUtilityResourceName[] = "proxy-pac-utility-script.js";
83
84// External string wrapper so V8 can access the UTF16 string wrapped by
85// ProxyResolverScriptData.
86class V8ExternalStringFromScriptData
87    : public v8::String::ExternalStringResource {
88 public:
89  explicit V8ExternalStringFromScriptData(
90      const scoped_refptr<ProxyResolverScriptData>& script_data)
91      : script_data_(script_data) {}
92
93  virtual const uint16_t* data() const OVERRIDE {
94    return reinterpret_cast<const uint16*>(script_data_->utf16().data());
95  }
96
97  virtual size_t length() const OVERRIDE {
98    return script_data_->utf16().size();
99  }
100
101 private:
102  const scoped_refptr<ProxyResolverScriptData> script_data_;
103  DISALLOW_COPY_AND_ASSIGN(V8ExternalStringFromScriptData);
104};
105
106// External string wrapper so V8 can access a string literal.
107class V8ExternalASCIILiteral : public v8::String::ExternalAsciiStringResource {
108 public:
109  // |ascii| must be a NULL-terminated C string, and must remain valid
110  // throughout this object's lifetime.
111  V8ExternalASCIILiteral(const char* ascii, size_t length)
112      : ascii_(ascii), length_(length) {
113    DCHECK(IsStringASCII(ascii));
114  }
115
116  virtual const char* data() const OVERRIDE {
117    return ascii_;
118  }
119
120  virtual size_t length() const OVERRIDE {
121    return length_;
122  }
123
124 private:
125  const char* ascii_;
126  size_t length_;
127  DISALLOW_COPY_AND_ASSIGN(V8ExternalASCIILiteral);
128};
129
130// When creating a v8::String from a C++ string we have two choices: create
131// a copy, or create a wrapper that shares the same underlying storage.
132// For small strings it is better to just make a copy, whereas for large
133// strings there are savings by sharing the storage. This number identifies
134// the cutoff length for when to start wrapping rather than creating copies.
135const size_t kMaxStringBytesForCopy = 256;
136
137// Converts a V8 String to a UTF8 std::string.
138std::string V8StringToUTF8(v8::Handle<v8::String> s) {
139  int len = s->Length();
140  std::string result;
141  if (len > 0)
142    s->WriteUtf8(WriteInto(&result, len + 1));
143  return result;
144}
145
146// Converts a V8 String to a UTF16 base::string16.
147base::string16 V8StringToUTF16(v8::Handle<v8::String> s) {
148  int len = s->Length();
149  base::string16 result;
150  // Note that the reinterpret cast is because on Windows string16 is an alias
151  // to wstring, and hence has character type wchar_t not uint16_t.
152  if (len > 0)
153    s->Write(reinterpret_cast<uint16_t*>(WriteInto(&result, len + 1)), 0, len);
154  return result;
155}
156
157// Converts an ASCII std::string to a V8 string.
158v8::Local<v8::String> ASCIIStringToV8String(const std::string& s) {
159  DCHECK(IsStringASCII(s));
160  return v8::String::New(s.data(), s.size());
161}
162
163// Converts a UTF16 base::string16 (warpped by a ProxyResolverScriptData) to a
164// V8 string.
165v8::Local<v8::String> ScriptDataToV8String(
166    const scoped_refptr<ProxyResolverScriptData>& s) {
167  if (s->utf16().size() * 2 <= kMaxStringBytesForCopy) {
168    return v8::String::New(
169        reinterpret_cast<const uint16_t*>(s->utf16().data()),
170        s->utf16().size());
171  }
172  return v8::String::NewExternal(new V8ExternalStringFromScriptData(s));
173}
174
175// Converts an ASCII string literal to a V8 string.
176v8::Local<v8::String> ASCIILiteralToV8String(const char* ascii) {
177  DCHECK(IsStringASCII(ascii));
178  size_t length = strlen(ascii);
179  if (length <= kMaxStringBytesForCopy)
180    return v8::String::New(ascii, length);
181  return v8::String::NewExternal(new V8ExternalASCIILiteral(ascii, length));
182}
183
184// Stringizes a V8 object by calling its toString() method. Returns true
185// on success. This may fail if the toString() throws an exception.
186bool V8ObjectToUTF16String(v8::Handle<v8::Value> object,
187                           base::string16* utf16_result) {
188  if (object.IsEmpty())
189    return false;
190
191  v8::HandleScope scope;
192  v8::Local<v8::String> str_object = object->ToString();
193  if (str_object.IsEmpty())
194    return false;
195  *utf16_result = V8StringToUTF16(str_object);
196  return true;
197}
198
199// Extracts an hostname argument from |args|. On success returns true
200// and fills |*hostname| with the result.
201bool GetHostnameArgument(const v8::FunctionCallbackInfo<v8::Value>& args,
202                         std::string* hostname) {
203  // The first argument should be a string.
204  if (args.Length() == 0 || args[0].IsEmpty() || !args[0]->IsString())
205    return false;
206
207  const base::string16 hostname_utf16 = V8StringToUTF16(args[0]->ToString());
208
209  // If the hostname is already in ASCII, simply return it as is.
210  if (IsStringASCII(hostname_utf16)) {
211    *hostname = UTF16ToASCII(hostname_utf16);
212    return true;
213  }
214
215  // Otherwise try to convert it from IDN to punycode.
216  const int kInitialBufferSize = 256;
217  url_canon::RawCanonOutputT<char16, kInitialBufferSize> punycode_output;
218  if (!url_canon::IDNToASCII(hostname_utf16.data(),
219                             hostname_utf16.length(),
220                             &punycode_output)) {
221    return false;
222  }
223
224  // |punycode_output| should now be ASCII; convert it to a std::string.
225  // (We could use UTF16ToASCII() instead, but that requires an extra string
226  // copy. Since ASCII is a subset of UTF8 the following is equivalent).
227  bool success = UTF16ToUTF8(punycode_output.data(),
228                             punycode_output.length(),
229                             hostname);
230  DCHECK(success);
231  DCHECK(IsStringASCII(*hostname));
232  return success;
233}
234
235// Wrapper for passing around IP address strings and IPAddressNumber objects.
236struct IPAddress {
237  IPAddress(const std::string& ip_string, const IPAddressNumber& ip_number)
238      : string_value(ip_string),
239        ip_address_number(ip_number) {
240  }
241
242  // Used for sorting IP addresses in ascending order in SortIpAddressList().
243  // IP6 addresses are placed ahead of IPv4 addresses.
244  bool operator<(const IPAddress& rhs) const {
245    const IPAddressNumber& ip1 = this->ip_address_number;
246    const IPAddressNumber& ip2 = rhs.ip_address_number;
247    if (ip1.size() != ip2.size())
248      return ip1.size() > ip2.size();  // IPv6 before IPv4.
249    DCHECK(ip1.size() == ip2.size());
250    return memcmp(&ip1[0], &ip2[0], ip1.size()) < 0;  // Ascending order.
251  }
252
253  std::string string_value;
254  IPAddressNumber ip_address_number;
255};
256
257// Handler for "sortIpAddressList(IpAddressList)". |ip_address_list| is a
258// semi-colon delimited string containing IP addresses.
259// |sorted_ip_address_list| is the resulting list of sorted semi-colon delimited
260// IP addresses or an empty string if unable to sort the IP address list.
261// Returns 'true' if the sorting was successful, and 'false' if the input was an
262// empty string, a string of separators (";" in this case), or if any of the IP
263// addresses in the input list failed to parse.
264bool SortIpAddressList(const std::string& ip_address_list,
265                       std::string* sorted_ip_address_list) {
266  sorted_ip_address_list->clear();
267
268  // Strip all whitespace (mimics IE behavior).
269  std::string cleaned_ip_address_list;
270  RemoveChars(ip_address_list, " \t", &cleaned_ip_address_list);
271  if (cleaned_ip_address_list.empty())
272    return false;
273
274  // Split-up IP addresses and store them in a vector.
275  std::vector<IPAddress> ip_vector;
276  IPAddressNumber ip_num;
277  base::StringTokenizer str_tok(cleaned_ip_address_list, ";");
278  while (str_tok.GetNext()) {
279    if (!ParseIPLiteralToNumber(str_tok.token(), &ip_num))
280      return false;
281    ip_vector.push_back(IPAddress(str_tok.token(), ip_num));
282  }
283
284  if (ip_vector.empty())  // Can happen if we have something like
285    return false;         // sortIpAddressList(";") or sortIpAddressList("; ;")
286
287  DCHECK(!ip_vector.empty());
288
289  // Sort lists according to ascending numeric value.
290  if (ip_vector.size() > 1)
291    std::stable_sort(ip_vector.begin(), ip_vector.end());
292
293  // Return a semi-colon delimited list of sorted addresses (IPv6 followed by
294  // IPv4).
295  for (size_t i = 0; i < ip_vector.size(); ++i) {
296    if (i > 0)
297      *sorted_ip_address_list += ";";
298    *sorted_ip_address_list += ip_vector[i].string_value;
299  }
300  return true;
301}
302
303// Handler for "isInNetEx(ip_address, ip_prefix)". |ip_address| is a string
304// containing an IPv4/IPv6 address, and |ip_prefix| is a string containg a
305// slash-delimited IP prefix with the top 'n' bits specified in the bit
306// field. This returns 'true' if the address is in the same subnet, and
307// 'false' otherwise. Also returns 'false' if the prefix is in an incorrect
308// format, or if an address and prefix of different types are used (e.g. IPv6
309// address and IPv4 prefix).
310bool IsInNetEx(const std::string& ip_address, const std::string& ip_prefix) {
311  IPAddressNumber address;
312  if (!ParseIPLiteralToNumber(ip_address, &address))
313    return false;
314
315  IPAddressNumber prefix;
316  size_t prefix_length_in_bits;
317  if (!ParseCIDRBlock(ip_prefix, &prefix, &prefix_length_in_bits))
318    return false;
319
320  // Both |address| and |prefix| must be of the same type (IPv4 or IPv6).
321  if (address.size() != prefix.size())
322    return false;
323
324  DCHECK((address.size() == 4 && prefix.size() == 4) ||
325         (address.size() == 16 && prefix.size() == 16));
326
327  return IPNumberMatchesPrefix(address, prefix, prefix_length_in_bits);
328}
329
330}  // namespace
331
332// ProxyResolverV8::Context ---------------------------------------------------
333
334class ProxyResolverV8::Context {
335 public:
336  Context(ProxyResolverV8* parent, v8::Isolate* isolate)
337      : parent_(parent),
338        isolate_(isolate) {
339    DCHECK(isolate);
340  }
341
342  ~Context() {
343    v8::Locker locked(isolate_);
344
345    v8_this_.Dispose(isolate_);
346    v8_context_.Dispose(isolate_);
347  }
348
349  JSBindings* js_bindings() {
350    return parent_->js_bindings_;
351  }
352
353  int ResolveProxy(const GURL& query_url, ProxyInfo* results) {
354    v8::Locker locked(isolate_);
355    v8::HandleScope scope(isolate_);
356
357    v8::Local<v8::Context> context =
358        v8::Local<v8::Context>::New(isolate_, v8_context_);
359    v8::Context::Scope function_scope(context);
360
361    v8::Local<v8::Value> function;
362    if (!GetFindProxyForURL(&function)) {
363      js_bindings()->OnError(
364          -1, ASCIIToUTF16("FindProxyForURL() is undefined."));
365      return ERR_PAC_SCRIPT_FAILED;
366    }
367
368    v8::Handle<v8::Value> argv[] = {
369      ASCIIStringToV8String(query_url.spec()),
370      ASCIIStringToV8String(query_url.HostNoBrackets()),
371    };
372
373    v8::TryCatch try_catch;
374    v8::Local<v8::Value> ret = v8::Function::Cast(*function)->Call(
375        context->Global(), arraysize(argv), argv);
376
377    if (try_catch.HasCaught()) {
378      HandleError(try_catch.Message());
379      return ERR_PAC_SCRIPT_FAILED;
380    }
381
382    if (!ret->IsString()) {
383      js_bindings()->OnError(
384          -1, ASCIIToUTF16("FindProxyForURL() did not return a string."));
385      return ERR_PAC_SCRIPT_FAILED;
386    }
387
388    base::string16 ret_str = V8StringToUTF16(ret->ToString());
389
390    if (!IsStringASCII(ret_str)) {
391      // TODO(eroman): Rather than failing when a wide string is returned, we
392      //               could extend the parsing to handle IDNA hostnames by
393      //               converting them to ASCII punycode.
394      //               crbug.com/47234
395      base::string16 error_message =
396          ASCIIToUTF16("FindProxyForURL() returned a non-ASCII string "
397                       "(crbug.com/47234): ") + ret_str;
398      js_bindings()->OnError(-1, error_message);
399      return ERR_PAC_SCRIPT_FAILED;
400    }
401
402    results->UsePacString(UTF16ToASCII(ret_str));
403    return OK;
404  }
405
406  int InitV8(const scoped_refptr<ProxyResolverScriptData>& pac_script) {
407    v8::Locker locked(isolate_);
408    v8::HandleScope scope(isolate_);
409
410    v8_this_.Reset(isolate_, v8::External::New(this));
411    v8::Local<v8::External> v8_this =
412        v8::Local<v8::External>::New(isolate_, v8_this_);
413    v8::Local<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New();
414
415    // Attach the javascript bindings.
416    v8::Local<v8::FunctionTemplate> alert_template =
417        v8::FunctionTemplate::New(&AlertCallback, v8_this);
418    global_template->Set(ASCIILiteralToV8String("alert"), alert_template);
419
420    v8::Local<v8::FunctionTemplate> my_ip_address_template =
421        v8::FunctionTemplate::New(&MyIpAddressCallback, v8_this);
422    global_template->Set(ASCIILiteralToV8String("myIpAddress"),
423        my_ip_address_template);
424
425    v8::Local<v8::FunctionTemplate> dns_resolve_template =
426        v8::FunctionTemplate::New(&DnsResolveCallback, v8_this);
427    global_template->Set(ASCIILiteralToV8String("dnsResolve"),
428        dns_resolve_template);
429
430    // Microsoft's PAC extensions:
431
432    v8::Local<v8::FunctionTemplate> dns_resolve_ex_template =
433        v8::FunctionTemplate::New(&DnsResolveExCallback, v8_this);
434    global_template->Set(ASCIILiteralToV8String("dnsResolveEx"),
435                         dns_resolve_ex_template);
436
437    v8::Local<v8::FunctionTemplate> my_ip_address_ex_template =
438        v8::FunctionTemplate::New(&MyIpAddressExCallback, v8_this);
439    global_template->Set(ASCIILiteralToV8String("myIpAddressEx"),
440                         my_ip_address_ex_template);
441
442    v8::Local<v8::FunctionTemplate> sort_ip_address_list_template =
443        v8::FunctionTemplate::New(&SortIpAddressListCallback, v8_this);
444    global_template->Set(ASCIILiteralToV8String("sortIpAddressList"),
445                         sort_ip_address_list_template);
446
447    v8::Local<v8::FunctionTemplate> is_in_net_ex_template =
448        v8::FunctionTemplate::New(&IsInNetExCallback, v8_this);
449    global_template->Set(ASCIILiteralToV8String("isInNetEx"),
450                         is_in_net_ex_template);
451
452    v8_context_.Reset(
453        isolate_, v8::Context::New(isolate_, NULL, global_template));
454
455    v8::Local<v8::Context> context =
456        v8::Local<v8::Context>::New(isolate_, v8_context_);
457    v8::Context::Scope ctx(context);
458
459    // Add the PAC utility functions to the environment.
460    // (This script should never fail, as it is a string literal!)
461    // Note that the two string literals are concatenated.
462    int rv = RunScript(
463        ASCIILiteralToV8String(
464            PROXY_RESOLVER_SCRIPT
465            PROXY_RESOLVER_SCRIPT_EX),
466        kPacUtilityResourceName);
467    if (rv != OK) {
468      NOTREACHED();
469      return rv;
470    }
471
472    // Add the user's PAC code to the environment.
473    rv = RunScript(ScriptDataToV8String(pac_script), kPacResourceName);
474    if (rv != OK)
475      return rv;
476
477    // At a minimum, the FindProxyForURL() function must be defined for this
478    // to be a legitimiate PAC script.
479    v8::Local<v8::Value> function;
480    if (!GetFindProxyForURL(&function)) {
481      js_bindings()->OnError(
482          -1, ASCIIToUTF16("FindProxyForURL() is undefined."));
483      return ERR_PAC_SCRIPT_FAILED;
484    }
485
486    return OK;
487  }
488
489  void PurgeMemory() {
490    v8::Locker locked(isolate_);
491    v8::V8::LowMemoryNotification();
492  }
493
494 private:
495  bool GetFindProxyForURL(v8::Local<v8::Value>* function) {
496    v8::Local<v8::Context> context =
497        v8::Local<v8::Context>::New(v8::Isolate::GetCurrent(), v8_context_);
498    *function =
499        context->Global()->Get(ASCIILiteralToV8String("FindProxyForURL"));
500    return (*function)->IsFunction();
501  }
502
503  // Handle an exception thrown by V8.
504  void HandleError(v8::Handle<v8::Message> message) {
505    base::string16 error_message;
506    int line_number = -1;
507
508    if (!message.IsEmpty()) {
509      line_number = message->GetLineNumber();
510      V8ObjectToUTF16String(message->Get(), &error_message);
511    }
512
513    js_bindings()->OnError(line_number, error_message);
514  }
515
516  // Compiles and runs |script| in the current V8 context.
517  // Returns OK on success, otherwise an error code.
518  int RunScript(v8::Handle<v8::String> script, const char* script_name) {
519    v8::TryCatch try_catch;
520
521    // Compile the script.
522    v8::ScriptOrigin origin =
523        v8::ScriptOrigin(ASCIILiteralToV8String(script_name));
524    v8::Local<v8::Script> code = v8::Script::Compile(script, &origin);
525
526    // Execute.
527    if (!code.IsEmpty())
528      code->Run();
529
530    // Check for errors.
531    if (try_catch.HasCaught()) {
532      HandleError(try_catch.Message());
533      return ERR_PAC_SCRIPT_FAILED;
534    }
535
536    return OK;
537  }
538
539  // V8 callback for when "alert()" is invoked by the PAC script.
540  static void AlertCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
541    Context* context =
542        static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
543
544    // Like firefox we assume "undefined" if no argument was specified, and
545    // disregard any arguments beyond the first.
546    base::string16 message;
547    if (args.Length() == 0) {
548      message = ASCIIToUTF16("undefined");
549    } else {
550      if (!V8ObjectToUTF16String(args[0], &message))
551        return;  // toString() threw an exception.
552    }
553
554    context->js_bindings()->Alert(message);
555  }
556
557  // V8 callback for when "myIpAddress()" is invoked by the PAC script.
558  static void MyIpAddressCallback(
559      const v8::FunctionCallbackInfo<v8::Value>& args) {
560    DnsResolveCallbackHelper(args, JSBindings::MY_IP_ADDRESS);
561  }
562
563  // V8 callback for when "myIpAddressEx()" is invoked by the PAC script.
564  static void MyIpAddressExCallback(
565      const v8::FunctionCallbackInfo<v8::Value>& args) {
566    DnsResolveCallbackHelper(args, JSBindings::MY_IP_ADDRESS_EX);
567  }
568
569  // V8 callback for when "dnsResolve()" is invoked by the PAC script.
570  static void DnsResolveCallback(
571      const v8::FunctionCallbackInfo<v8::Value>& args) {
572    DnsResolveCallbackHelper(args, JSBindings::DNS_RESOLVE);
573  }
574
575  // V8 callback for when "dnsResolveEx()" is invoked by the PAC script.
576  static void DnsResolveExCallback(
577      const v8::FunctionCallbackInfo<v8::Value>& args) {
578    DnsResolveCallbackHelper(args, JSBindings::DNS_RESOLVE_EX);
579  }
580
581  // Shared code for implementing:
582  //   - myIpAddress(), myIpAddressEx(), dnsResolve(), dnsResolveEx().
583  static void DnsResolveCallbackHelper(
584      const v8::FunctionCallbackInfo<v8::Value>& args,
585      JSBindings::ResolveDnsOperation op) {
586    Context* context =
587        static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
588
589    std::string hostname;
590
591    // dnsResolve() and dnsResolveEx() need at least 1 argument.
592    if (op == JSBindings::DNS_RESOLVE || op == JSBindings::DNS_RESOLVE_EX) {
593      if (!GetHostnameArgument(args, &hostname)) {
594        if (op == JSBindings::DNS_RESOLVE)
595          args.GetReturnValue().SetNull();
596        return;
597      }
598    }
599
600    std::string result;
601    bool success;
602    bool terminate = false;
603
604    {
605      v8::Unlocker unlocker(args.GetIsolate());
606      success = context->js_bindings()->ResolveDns(
607          hostname, op, &result, &terminate);
608    }
609
610    if (terminate)
611      v8::V8::TerminateExecution(args.GetIsolate());
612
613    if (success) {
614      args.GetReturnValue().Set(ASCIIStringToV8String(result));
615      return;
616    }
617
618    // Each function handles resolution errors differently.
619    switch (op) {
620      case JSBindings::DNS_RESOLVE:
621        args.GetReturnValue().SetNull();
622        return;
623      case JSBindings::DNS_RESOLVE_EX:
624        args.GetReturnValue().SetEmptyString();
625        return;
626      case JSBindings::MY_IP_ADDRESS:
627        args.GetReturnValue().Set(ASCIILiteralToV8String("127.0.0.1"));
628        return;
629      case JSBindings::MY_IP_ADDRESS_EX:
630        args.GetReturnValue().SetEmptyString();
631        return;
632    }
633
634    NOTREACHED();
635  }
636
637  // V8 callback for when "sortIpAddressList()" is invoked by the PAC script.
638  static void SortIpAddressListCallback(
639      const v8::FunctionCallbackInfo<v8::Value>& args) {
640    // We need at least one string argument.
641    if (args.Length() == 0 || args[0].IsEmpty() || !args[0]->IsString()) {
642      args.GetReturnValue().SetNull();
643      return;
644    }
645
646    std::string ip_address_list = V8StringToUTF8(args[0]->ToString());
647    if (!IsStringASCII(ip_address_list)) {
648      args.GetReturnValue().SetNull();
649      return;
650    }
651    std::string sorted_ip_address_list;
652    bool success = SortIpAddressList(ip_address_list, &sorted_ip_address_list);
653    if (!success) {
654      args.GetReturnValue().Set(false);
655      return;
656    }
657    args.GetReturnValue().Set(ASCIIStringToV8String(sorted_ip_address_list));
658  }
659
660  // V8 callback for when "isInNetEx()" is invoked by the PAC script.
661  static void IsInNetExCallback(
662      const v8::FunctionCallbackInfo<v8::Value>& args) {
663    // We need at least 2 string arguments.
664    if (args.Length() < 2 || args[0].IsEmpty() || !args[0]->IsString() ||
665        args[1].IsEmpty() || !args[1]->IsString()) {
666      args.GetReturnValue().SetNull();
667      return;
668    }
669
670    std::string ip_address = V8StringToUTF8(args[0]->ToString());
671    if (!IsStringASCII(ip_address)) {
672      args.GetReturnValue().Set(false);
673      return;
674    }
675    std::string ip_prefix = V8StringToUTF8(args[1]->ToString());
676    if (!IsStringASCII(ip_prefix)) {
677      args.GetReturnValue().Set(false);
678      return;
679    }
680    args.GetReturnValue().Set(IsInNetEx(ip_address, ip_prefix));
681  }
682
683  mutable base::Lock lock_;
684  ProxyResolverV8* parent_;
685  v8::Isolate* isolate_;
686  v8::Persistent<v8::External> v8_this_;
687  v8::Persistent<v8::Context> v8_context_;
688};
689
690// ProxyResolverV8 ------------------------------------------------------------
691
692ProxyResolverV8::ProxyResolverV8()
693    : ProxyResolver(true /*expects_pac_bytes*/),
694      js_bindings_(NULL) {
695}
696
697ProxyResolverV8::~ProxyResolverV8() {}
698
699int ProxyResolverV8::GetProxyForURL(
700    const GURL& query_url, ProxyInfo* results,
701    const CompletionCallback& /*callback*/,
702    RequestHandle* /*request*/,
703    const BoundNetLog& net_log) {
704  DCHECK(js_bindings_);
705
706  // If the V8 instance has not been initialized (either because
707  // SetPacScript() wasn't called yet, or because it failed.
708  if (!context_)
709    return ERR_FAILED;
710
711  // Otherwise call into V8.
712  int rv = context_->ResolveProxy(query_url, results);
713
714  return rv;
715}
716
717void ProxyResolverV8::CancelRequest(RequestHandle request) {
718  // This is a synchronous ProxyResolver; no possibility for async requests.
719  NOTREACHED();
720}
721
722LoadState ProxyResolverV8::GetLoadState(RequestHandle request) const {
723  NOTREACHED();
724  return LOAD_STATE_IDLE;
725}
726
727void ProxyResolverV8::CancelSetPacScript() {
728  NOTREACHED();
729}
730
731void ProxyResolverV8::PurgeMemory() {
732  context_->PurgeMemory();
733}
734
735int ProxyResolverV8::SetPacScript(
736    const scoped_refptr<ProxyResolverScriptData>& script_data,
737    const CompletionCallback& /*callback*/) {
738  DCHECK(script_data.get());
739  DCHECK(js_bindings_);
740
741  context_.reset();
742  if (script_data->utf16().empty())
743    return ERR_PAC_SCRIPT_FAILED;
744
745  // Try parsing the PAC script.
746  scoped_ptr<Context> context(new Context(this, GetDefaultIsolate()));
747  int rv = context->InitV8(script_data);
748  if (rv == OK)
749    context_.reset(context.release());
750  return rv;
751}
752
753// static
754void ProxyResolverV8::RememberDefaultIsolate() {
755  v8::Isolate* isolate = v8::Isolate::GetCurrent();
756  DCHECK(isolate)
757      << "ProxyResolverV8::RememberDefaultIsolate called on wrong thread";
758  DCHECK(g_default_isolate_ == NULL || g_default_isolate_ == isolate)
759      << "Default Isolate can not be changed";
760  g_default_isolate_ = isolate;
761}
762
763#if defined(OS_WIN)
764// static
765void ProxyResolverV8::CreateIsolate() {
766  v8::Isolate* isolate = v8::Isolate::New();
767  DCHECK(isolate);
768  DCHECK(g_default_isolate_ == NULL) << "Default Isolate can not be set twice";
769
770  isolate->Enter();
771  v8::V8::Initialize();
772
773  g_default_isolate_ = isolate;
774}
775#endif  // defined(OS_WIN)
776
777// static
778v8::Isolate* ProxyResolverV8::GetDefaultIsolate() {
779  DCHECK(g_default_isolate_)
780      << "Must call ProxyResolverV8::RememberDefaultIsolate() first";
781  return g_default_isolate_;
782}
783
784v8::Isolate* ProxyResolverV8::g_default_isolate_ = NULL;
785
786// static
787size_t ProxyResolverV8::GetTotalHeapSize() {
788  if (!g_default_isolate_)
789    return 0;
790
791  v8::Locker locked(g_default_isolate_);
792  v8::HeapStatistics heap_statistics;
793  g_default_isolate_->GetHeapStatistics(&heap_statistics);
794  return heap_statistics.total_heap_size();
795}
796
797// static
798size_t ProxyResolverV8::GetUsedHeapSize() {
799  if (!g_default_isolate_)
800    return 0;
801
802  v8::Locker locked(g_default_isolate_);
803  v8::HeapStatistics heap_statistics;
804  g_default_isolate_->GetHeapStatistics(&heap_statistics);
805  return heap_statistics.used_heap_size();
806}
807
808}  // namespace net
809