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