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