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