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/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 
     75 namespace net {
     76 
     77 namespace {
     78 
     79 // Pseudo-name for the PAC script.
     80 const char kPacResourceName[] = "proxy-pac-script.js";
     81 // Pseudo-name for the PAC utility script.
     82 const char kPacUtilityResourceName[] = "proxy-pac-utility-script.js";
     83 
     84 // External string wrapper so V8 can access the UTF16 string wrapped by
     85 // ProxyResolverScriptData.
     86 class 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.
    107 class 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.
    135 const size_t kMaxStringBytesForCopy = 256;
    136 
    137 // Converts a V8 String to a UTF8 std::string.
    138 std::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.
    147 base::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.
    158 v8::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.
    165 v8::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.
    176 v8::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.
    186 bool 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.
    201 bool 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.
    236 struct 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.
    264 bool 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).
    310 bool 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 
    334 class 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 
    692 ProxyResolverV8::ProxyResolverV8()
    693     : ProxyResolver(true /*expects_pac_bytes*/),
    694       js_bindings_(NULL) {
    695 }
    696 
    697 ProxyResolverV8::~ProxyResolverV8() {}
    698 
    699 int 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 
    717 void ProxyResolverV8::CancelRequest(RequestHandle request) {
    718   // This is a synchronous ProxyResolver; no possibility for async requests.
    719   NOTREACHED();
    720 }
    721 
    722 LoadState ProxyResolverV8::GetLoadState(RequestHandle request) const {
    723   NOTREACHED();
    724   return LOAD_STATE_IDLE;
    725 }
    726 
    727 void ProxyResolverV8::CancelSetPacScript() {
    728   NOTREACHED();
    729 }
    730 
    731 void ProxyResolverV8::PurgeMemory() {
    732   context_->PurgeMemory();
    733 }
    734 
    735 int 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
    754 void 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
    765 void 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
    778 v8::Isolate* ProxyResolverV8::GetDefaultIsolate() {
    779   DCHECK(g_default_isolate_)
    780       << "Must call ProxyResolverV8::RememberDefaultIsolate() first";
    781   return g_default_isolate_;
    782 }
    783 
    784 v8::Isolate* ProxyResolverV8::g_default_isolate_ = NULL;
    785 
    786 // static
    787 size_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
    798 size_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