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
78namespace net {
79
80namespace {
81
82// Pseudo-name for the PAC script.
83const char kPacResourceName[] = "proxy-pac-script.js";
84// Pseudo-name for the PAC utility script.
85const char kPacUtilityResourceName[] = "proxy-pac-utility-script.js";
86
87// External string wrapper so V8 can access the UTF16 string wrapped by
88// ProxyResolverScriptData.
89class 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.
110class 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.
138const size_t kMaxStringBytesForCopy = 256;
139
140// Converts a V8 String to a UTF8 std::string.
141std::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.
150base::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.
161v8::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.
170v8::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.
184v8::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.
197bool 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.
213bool 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.
247struct 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.
275bool 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).
321bool 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
345class 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
711ProxyResolverV8::ProxyResolverV8()
712    : ProxyResolver(true /*expects_pac_bytes*/),
713      js_bindings_(NULL) {
714}
715
716ProxyResolverV8::~ProxyResolverV8() {}
717
718int 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
736void ProxyResolverV8::CancelRequest(RequestHandle request) {
737  // This is a synchronous ProxyResolver; no possibility for async requests.
738  NOTREACHED();
739}
740
741LoadState ProxyResolverV8::GetLoadState(RequestHandle request) const {
742  NOTREACHED();
743  return LOAD_STATE_IDLE;
744}
745
746void ProxyResolverV8::CancelSetPacScript() {
747  NOTREACHED();
748}
749
750int 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
769void 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
779v8::Isolate* ProxyResolverV8::GetDefaultIsolate() {
780  DCHECK(g_proxy_resolver_isolate_)
781      << "Must call ProxyResolverV8::EnsureIsolateCreated() first";
782  return g_proxy_resolver_isolate_->isolate();
783}
784
785gin::IsolateHolder* ProxyResolverV8::g_proxy_resolver_isolate_ = NULL;
786
787// static
788size_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
800size_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