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