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
76 namespace net {
77
78 namespace {
79
80 // Pseudo-name for the PAC script.
81 const char kPacResourceName[] = "proxy-pac-script.js";
82 // Pseudo-name for the PAC utility script.
83 const char kPacUtilityResourceName[] = "proxy-pac-utility-script.js";
84
85 // External string wrapper so V8 can access the UTF16 string wrapped by
86 // ProxyResolverScriptData.
87 class V8ExternalStringFromScriptData
88 : public v8::String::ExternalStringResource {
89 public:
V8ExternalStringFromScriptData(const scoped_refptr<ProxyResolverScriptData> & script_data)90 explicit V8ExternalStringFromScriptData(
91 const scoped_refptr<ProxyResolverScriptData>& script_data)
92 : script_data_(script_data) {}
93
data() const94 virtual const uint16_t* data() const {
95 return reinterpret_cast<const uint16*>(script_data_->utf16().data());
96 }
97
length() const98 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.
108 class 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.
V8ExternalASCIILiteral(const char * ascii,size_t length)112 V8ExternalASCIILiteral(const char* ascii, size_t length)
113 : ascii_(ascii), length_(length) {
114 DCHECK(IsStringASCII(ascii));
115 }
116
data() const117 virtual const char* data() const {
118 return ascii_;
119 }
120
length() const121 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.
136 const size_t kMaxStringBytesForCopy = 256;
137
138 // Converts a V8 String to a UTF8 std::string.
V8StringToUTF8(v8::Handle<v8::String> s)139 std::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.
V8StringToUTF16(v8::Handle<v8::String> s)146 string16 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.
ASCIIStringToV8String(const std::string & s)156 v8::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.
ScriptDataToV8String(const scoped_refptr<ProxyResolverScriptData> & s)163 v8::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.
ASCIILiteralToV8String(const char * ascii)174 v8::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.
V8ObjectToUTF16String(v8::Handle<v8::Value> object,string16 * utf16_result)184 bool 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.
GetHostnameArgument(const v8::Arguments & args,std::string * hostname)199 bool 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.
233 struct IPAddress {
IPAddressnet::__anon1f354be90111::IPAddress234 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.
operator <net::__anon1f354be90111::IPAddress241 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.
SortIpAddressList(const std::string & ip_address_list,std::string * sorted_ip_address_list)261 bool 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).
IsInNetEx(const std::string & ip_address,const std::string & ip_prefix)307 bool 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
331 class ProxyResolverV8::Context {
332 public:
Context(ProxyResolverJSBindings * js_bindings)333 explicit Context(ProxyResolverJSBindings* js_bindings)
334 : js_bindings_(js_bindings) {
335 DCHECK(js_bindings != NULL);
336 }
337
~Context()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
ResolveProxy(const GURL & query_url,ProxyInfo * results)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
InitV8(const scoped_refptr<ProxyResolverScriptData> & pac_script)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
SetCurrentRequestContext(ProxyResolverRequestContext * context)477 void SetCurrentRequestContext(ProxyResolverRequestContext* context) {
478 js_bindings_->set_current_request_context(context);
479 }
480
PurgeMemory()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:
GetFindProxyForURL(v8::Local<v8::Value> * function)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.
HandleError(v8::Handle<v8::Message> message)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.
RunScript(v8::Handle<v8::String> script,const char * script_name)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.
AlertCallback(const v8::Arguments & args)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.
MyIpAddressCallback(const v8::Arguments & args)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.
MyIpAddressExCallback(const v8::Arguments & args)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.
DnsResolveCallback(const v8::Arguments & args)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.
DnsResolveExCallback(const v8::Arguments & args)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.
SortIpAddressListCallback(const v8::Arguments & args)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.
IsInNetExCallback(const v8::Arguments & args)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
ProxyResolverV8(ProxyResolverJSBindings * custom_js_bindings)680 ProxyResolverV8::ProxyResolverV8(
681 ProxyResolverJSBindings* custom_js_bindings)
682 : ProxyResolver(true /*expects_pac_bytes*/),
683 js_bindings_(custom_js_bindings) {
684 }
685
~ProxyResolverV8()686 ProxyResolverV8::~ProxyResolverV8() {}
687
GetProxyForURL(const GURL & query_url,ProxyInfo * results,CompletionCallback *,RequestHandle *,const BoundNetLog & net_log)688 int 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
CancelRequest(RequestHandle request)719 void ProxyResolverV8::CancelRequest(RequestHandle request) {
720 // This is a synchronous ProxyResolver; no possibility for async requests.
721 NOTREACHED();
722 }
723
CancelSetPacScript()724 void ProxyResolverV8::CancelSetPacScript() {
725 NOTREACHED();
726 }
727
PurgeMemory()728 void ProxyResolverV8::PurgeMemory() {
729 context_->PurgeMemory();
730 }
731
Shutdown()732 void ProxyResolverV8::Shutdown() {
733 js_bindings_->Shutdown();
734 }
735
SetPacScript(const scoped_refptr<ProxyResolverScriptData> & script_data,CompletionCallback *)736 int 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