Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / net / proxy / proxy_resolver_v8.cc
blob73f0428a2cfbdd1349602bc876d93a8593003004
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.
5 #include "net/proxy/proxy_resolver_v8.h"
7 #include <algorithm>
8 #include <cstdio>
10 #include "base/auto_reset.h"
11 #include "base/basictypes.h"
12 #include "base/compiler_specific.h"
13 #include "base/debug/leak_annotations.h"
14 #include "base/lazy_instance.h"
15 #include "base/logging.h"
16 #include "base/strings/string_tokenizer.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/synchronization/lock.h"
20 #include "gin/array_buffer.h"
21 #include "gin/public/isolate_holder.h"
22 #include "gin/v8_initializer.h"
23 #include "net/base/ip_address_number.h"
24 #include "net/base/net_errors.h"
25 #include "net/proxy/proxy_info.h"
26 #include "net/proxy/proxy_resolver_script.h"
27 #include "net/proxy/proxy_resolver_script_data.h"
28 #include "url/gurl.h"
29 #include "url/url_canon.h"
30 #include "v8/include/v8.h"
32 // Notes on the javascript environment:
34 // For the majority of the PAC utility functions, we use the same code
35 // as Firefox. See the javascript library that proxy_resolver_scipt.h
36 // pulls in.
38 // In addition, we implement a subset of Microsoft's extensions to PAC.
39 // - myIpAddressEx()
40 // - dnsResolveEx()
41 // - isResolvableEx()
42 // - isInNetEx()
43 // - sortIpAddressList()
45 // It is worth noting that the original PAC specification does not describe
46 // the return values on failure. Consequently, there are compatibility
47 // differences between browsers on what to return on failure, which are
48 // illustrated below:
50 // --------------------+-------------+-------------------+--------------
51 // | Firefox3 | InternetExplorer8 | --> Us <---
52 // --------------------+-------------+-------------------+--------------
53 // myIpAddress() | "127.0.0.1" | ??? | "127.0.0.1"
54 // dnsResolve() | null | false | null
55 // myIpAddressEx() | N/A | "" | ""
56 // sortIpAddressList() | N/A | false | false
57 // dnsResolveEx() | N/A | "" | ""
58 // isInNetEx() | N/A | false | false
59 // --------------------+-------------+-------------------+--------------
61 // TODO(eroman): The cell above reading ??? means I didn't test it.
63 // Another difference is in how dnsResolve() and myIpAddress() are
64 // implemented -- whether they should restrict to IPv4 results, or
65 // include both IPv4 and IPv6. The following table illustrates the
66 // differences:
68 // --------------------+-------------+-------------------+--------------
69 // | Firefox3 | InternetExplorer8 | --> Us <---
70 // --------------------+-------------+-------------------+--------------
71 // myIpAddress() | IPv4/IPv6 | IPv4 | IPv4
72 // dnsResolve() | IPv4/IPv6 | IPv4 | IPv4
73 // isResolvable() | IPv4/IPv6 | IPv4 | IPv4
74 // myIpAddressEx() | N/A | IPv4/IPv6 | IPv4/IPv6
75 // dnsResolveEx() | N/A | IPv4/IPv6 | IPv4/IPv6
76 // sortIpAddressList() | N/A | IPv4/IPv6 | IPv4/IPv6
77 // isResolvableEx() | N/A | IPv4/IPv6 | IPv4/IPv6
78 // isInNetEx() | N/A | IPv4/IPv6 | IPv4/IPv6
79 // -----------------+-------------+-------------------+--------------
81 namespace net {
83 namespace {
85 // Pseudo-name for the PAC script.
86 const char kPacResourceName[] = "proxy-pac-script.js";
87 // Pseudo-name for the PAC utility script.
88 const char kPacUtilityResourceName[] = "proxy-pac-utility-script.js";
90 // External string wrapper so V8 can access the UTF16 string wrapped by
91 // ProxyResolverScriptData.
92 class V8ExternalStringFromScriptData
93 : public v8::String::ExternalStringResource {
94 public:
95 explicit V8ExternalStringFromScriptData(
96 const scoped_refptr<ProxyResolverScriptData>& script_data)
97 : script_data_(script_data) {}
99 const uint16_t* data() const override {
100 return reinterpret_cast<const uint16*>(script_data_->utf16().data());
103 size_t length() const override { return script_data_->utf16().size(); }
105 private:
106 const scoped_refptr<ProxyResolverScriptData> script_data_;
107 DISALLOW_COPY_AND_ASSIGN(V8ExternalStringFromScriptData);
110 // External string wrapper so V8 can access a string literal.
111 class V8ExternalASCIILiteral
112 : public v8::String::ExternalOneByteStringResource {
113 public:
114 // |ascii| must be a NULL-terminated C string, and must remain valid
115 // throughout this object's lifetime.
116 V8ExternalASCIILiteral(const char* ascii, size_t length)
117 : ascii_(ascii), length_(length) {
118 DCHECK(base::IsStringASCII(ascii));
121 const char* data() const override { return ascii_; }
123 size_t length() const override { return length_; }
125 private:
126 const char* ascii_;
127 size_t length_;
128 DISALLOW_COPY_AND_ASSIGN(V8ExternalASCIILiteral);
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;
138 // Converts a V8 String to a UTF8 std::string.
139 std::string V8StringToUTF8(v8::Local<v8::String> s) {
140 int len = s->Length();
141 std::string result;
142 if (len > 0)
143 s->WriteUtf8(base::WriteInto(&result, len + 1));
144 return result;
147 // Converts a V8 String to a UTF16 base::string16.
148 base::string16 V8StringToUTF16(v8::Local<v8::String> s) {
149 int len = s->Length();
150 base::string16 result;
151 // Note that the reinterpret cast is because on Windows string16 is an alias
152 // to wstring, and hence has character type wchar_t not uint16_t.
153 if (len > 0) {
154 s->Write(reinterpret_cast<uint16_t*>(base::WriteInto(&result, len + 1)), 0,
155 len);
157 return result;
160 // Converts an ASCII std::string to a V8 string.
161 v8::Local<v8::String> ASCIIStringToV8String(v8::Isolate* isolate,
162 const std::string& s) {
163 DCHECK(base::IsStringASCII(s));
164 return v8::String::NewFromUtf8(isolate, s.data(), v8::NewStringType::kNormal,
165 s.size()).ToLocalChecked();
168 // Converts a UTF16 base::string16 (warpped by a ProxyResolverScriptData) to a
169 // V8 string.
170 v8::Local<v8::String> ScriptDataToV8String(
171 v8::Isolate* isolate, const scoped_refptr<ProxyResolverScriptData>& s) {
172 if (s->utf16().size() * 2 <= kMaxStringBytesForCopy) {
173 return v8::String::NewFromTwoByte(
174 isolate, reinterpret_cast<const uint16_t*>(s->utf16().data()),
175 v8::NewStringType::kNormal, s->utf16().size()).ToLocalChecked();
177 return v8::String::NewExternalTwoByte(
178 isolate, new V8ExternalStringFromScriptData(s)).ToLocalChecked();
181 // Converts an ASCII string literal to a V8 string.
182 v8::Local<v8::String> ASCIILiteralToV8String(v8::Isolate* isolate,
183 const char* ascii) {
184 DCHECK(base::IsStringASCII(ascii));
185 size_t length = strlen(ascii);
186 if (length <= kMaxStringBytesForCopy)
187 return v8::String::NewFromUtf8(isolate, ascii, v8::NewStringType::kNormal,
188 length).ToLocalChecked();
189 return v8::String::NewExternalOneByte(
190 isolate, new V8ExternalASCIILiteral(ascii, length))
191 .ToLocalChecked();
194 // Stringizes a V8 object by calling its toString() method. Returns true
195 // on success. This may fail if the toString() throws an exception.
196 bool V8ObjectToUTF16String(v8::Local<v8::Value> object,
197 base::string16* utf16_result,
198 v8::Isolate* isolate) {
199 if (object.IsEmpty())
200 return false;
202 v8::HandleScope scope(isolate);
203 v8::Local<v8::String> str_object;
204 if (!object->ToString(isolate->GetCurrentContext()).ToLocal(&str_object))
205 return false;
206 *utf16_result = V8StringToUTF16(str_object);
207 return true;
210 // Extracts an hostname argument from |args|. On success returns true
211 // and fills |*hostname| with the result.
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;
218 const base::string16 hostname_utf16 =
219 V8StringToUTF16(v8::Local<v8::String>::Cast(args[0]));
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;
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;
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;
246 // Wrapper for passing around IP address strings and IPAddressNumber objects.
247 struct IPAddress {
248 IPAddress(const std::string& ip_string, const IPAddressNumber& ip_number)
249 : string_value(ip_string),
250 ip_address_number(ip_number) {
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.
264 std::string string_value;
265 IPAddressNumber ip_address_number;
268 // Handler for "sortIpAddressList(IpAddressList)". |ip_address_list| is a
269 // semi-colon delimited string containing IP addresses.
270 // |sorted_ip_address_list| is the resulting list of sorted semi-colon delimited
271 // IP addresses or an empty string if unable to sort the IP address list.
272 // Returns 'true' if the sorting was successful, and 'false' if the input was an
273 // empty string, a string of separators (";" in this case), or if any of the IP
274 // addresses in the input list failed to parse.
275 bool SortIpAddressList(const std::string& ip_address_list,
276 std::string* sorted_ip_address_list) {
277 sorted_ip_address_list->clear();
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;
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));
295 if (ip_vector.empty()) // Can happen if we have something like
296 return false; // sortIpAddressList(";") or sortIpAddressList("; ;")
298 DCHECK(!ip_vector.empty());
300 // Sort lists according to ascending numeric value.
301 if (ip_vector.size() > 1)
302 std::stable_sort(ip_vector.begin(), ip_vector.end());
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;
311 return true;
314 // Handler for "isInNetEx(ip_address, ip_prefix)". |ip_address| is a string
315 // containing an IPv4/IPv6 address, and |ip_prefix| is a string containg a
316 // slash-delimited IP prefix with the top 'n' bits specified in the bit
317 // field. This returns 'true' if the address is in the same subnet, and
318 // 'false' otherwise. Also returns 'false' if the prefix is in an incorrect
319 // format, or if an address and prefix of different types are used (e.g. IPv6
320 // address and IPv4 prefix).
321 bool IsInNetEx(const std::string& ip_address, const std::string& ip_prefix) {
322 IPAddressNumber address;
323 if (!ParseIPLiteralToNumber(ip_address, &address))
324 return false;
326 IPAddressNumber prefix;
327 size_t prefix_length_in_bits;
328 if (!ParseCIDRBlock(ip_prefix, &prefix, &prefix_length_in_bits))
329 return false;
331 // Both |address| and |prefix| must be of the same type (IPv4 or IPv6).
332 if (address.size() != prefix.size())
333 return false;
335 DCHECK((address.size() == 4 && prefix.size() == 4) ||
336 (address.size() == 16 && prefix.size() == 16));
338 return IPNumberMatchesPrefix(address, prefix, prefix_length_in_bits);
341 // Consider only single component domains like 'foo' as plain host names.
342 bool IsPlainHostName(const std::string& hostname_utf8) {
343 if (hostname_utf8.find('.') != std::string::npos)
344 return false;
346 // IPv6 literals might not contain any periods, however are not considered
347 // plain host names.
348 IPAddressNumber unused;
349 return !ParseIPLiteralToNumber(hostname_utf8, &unused);
352 // All instances of ProxyResolverV8 share the same v8::Isolate. This isolate is
353 // created lazily the first time it is needed and lives until process shutdown.
354 // This creation might happen from any thread, as ProxyResolverV8 is typically
355 // run in a threadpool.
357 // TODO(eroman): The lazily created isolate is never freed. Instead it should be
358 // disposed once there are no longer any ProxyResolverV8 referencing it.
359 class SharedIsolateFactory {
360 public:
361 SharedIsolateFactory() : has_initialized_v8_(false) {}
363 // Lazily creates a v8::Isolate, or returns the already created instance.
364 v8::Isolate* GetSharedIsolate() {
365 base::AutoLock l(lock_);
367 if (!holder_) {
368 // Do one-time initialization for V8.
369 if (!has_initialized_v8_) {
370 #ifdef V8_USE_EXTERNAL_STARTUP_DATA
371 gin::V8Initializer::LoadV8Snapshot();
372 gin::V8Initializer::LoadV8Natives();
373 #endif
375 gin::IsolateHolder::Initialize(
376 gin::IsolateHolder::kNonStrictMode,
377 gin::ArrayBufferAllocator::SharedInstance());
379 has_initialized_v8_ = true;
382 holder_.reset(new gin::IsolateHolder(gin::IsolateHolder::kUseLocker));
385 return holder_->isolate();
388 v8::Isolate* GetSharedIsolateWithoutCreating() {
389 base::AutoLock l(lock_);
390 return holder_ ? holder_->isolate() : NULL;
393 private:
394 base::Lock lock_;
395 scoped_ptr<gin::IsolateHolder> holder_;
396 bool has_initialized_v8_;
398 DISALLOW_COPY_AND_ASSIGN(SharedIsolateFactory);
401 base::LazyInstance<SharedIsolateFactory>::Leaky g_isolate_factory =
402 LAZY_INSTANCE_INITIALIZER;
404 } // namespace
406 // ProxyResolverV8::Context ---------------------------------------------------
408 class ProxyResolverV8::Context {
409 public:
410 explicit Context(v8::Isolate* isolate)
411 : js_bindings_(nullptr), isolate_(isolate) {
412 DCHECK(isolate);
415 ~Context() {
416 v8::Locker locked(isolate_);
417 v8::Isolate::Scope isolate_scope(isolate_);
419 v8_this_.Reset();
420 v8_context_.Reset();
423 JSBindings* js_bindings() { return js_bindings_; }
425 int ResolveProxy(const GURL& query_url,
426 ProxyInfo* results,
427 JSBindings* bindings) {
428 DCHECK(bindings);
429 base::AutoReset<JSBindings*> bindings_reset(&js_bindings_, bindings);
430 v8::Locker locked(isolate_);
431 v8::Isolate::Scope isolate_scope(isolate_);
432 v8::HandleScope scope(isolate_);
434 v8::Local<v8::Context> context =
435 v8::Local<v8::Context>::New(isolate_, v8_context_);
436 v8::Context::Scope function_scope(context);
438 v8::Local<v8::Value> function;
439 int rv = GetFindProxyForURL(&function);
440 if (rv != OK)
441 return rv;
443 v8::Local<v8::Value> argv[] = {
444 ASCIIStringToV8String(isolate_, query_url.spec()),
445 ASCIIStringToV8String(isolate_, query_url.HostNoBrackets()),
448 v8::TryCatch try_catch(isolate_);
449 v8::Local<v8::Value> ret;
450 if (!v8::Function::Cast(*function)
451 ->Call(context, context->Global(), arraysize(argv), argv)
452 .ToLocal(&ret)) {
453 DCHECK(try_catch.HasCaught());
454 HandleError(try_catch.Message());
455 return ERR_PAC_SCRIPT_FAILED;
458 if (!ret->IsString()) {
459 js_bindings()->OnError(
460 -1, base::ASCIIToUTF16("FindProxyForURL() did not return a string."));
461 return ERR_PAC_SCRIPT_FAILED;
464 base::string16 ret_str = V8StringToUTF16(v8::Local<v8::String>::Cast(ret));
466 if (!base::IsStringASCII(ret_str)) {
467 // TODO(eroman): Rather than failing when a wide string is returned, we
468 // could extend the parsing to handle IDNA hostnames by
469 // converting them to ASCII punycode.
470 // crbug.com/47234
471 base::string16 error_message =
472 base::ASCIIToUTF16("FindProxyForURL() returned a non-ASCII string "
473 "(crbug.com/47234): ") + ret_str;
474 js_bindings()->OnError(-1, error_message);
475 return ERR_PAC_SCRIPT_FAILED;
478 results->UsePacString(base::UTF16ToASCII(ret_str));
479 return OK;
482 int InitV8(const scoped_refptr<ProxyResolverScriptData>& pac_script,
483 JSBindings* bindings) {
484 base::AutoReset<JSBindings*> bindings_reset(&js_bindings_, bindings);
485 v8::Locker locked(isolate_);
486 v8::Isolate::Scope isolate_scope(isolate_);
487 v8::HandleScope scope(isolate_);
489 v8_this_.Reset(isolate_, v8::External::New(isolate_, this));
490 v8::Local<v8::External> v8_this =
491 v8::Local<v8::External>::New(isolate_, v8_this_);
492 v8::Local<v8::ObjectTemplate> global_template =
493 v8::ObjectTemplate::New(isolate_);
495 // Attach the javascript bindings.
496 v8::Local<v8::FunctionTemplate> alert_template =
497 v8::FunctionTemplate::New(isolate_, &AlertCallback, v8_this);
498 global_template->Set(ASCIILiteralToV8String(isolate_, "alert"),
499 alert_template);
501 v8::Local<v8::FunctionTemplate> my_ip_address_template =
502 v8::FunctionTemplate::New(isolate_, &MyIpAddressCallback, v8_this);
503 global_template->Set(ASCIILiteralToV8String(isolate_, "myIpAddress"),
504 my_ip_address_template);
506 v8::Local<v8::FunctionTemplate> dns_resolve_template =
507 v8::FunctionTemplate::New(isolate_, &DnsResolveCallback, v8_this);
508 global_template->Set(ASCIILiteralToV8String(isolate_, "dnsResolve"),
509 dns_resolve_template);
511 v8::Local<v8::FunctionTemplate> is_plain_host_name_template =
512 v8::FunctionTemplate::New(isolate_, &IsPlainHostNameCallback, v8_this);
513 global_template->Set(ASCIILiteralToV8String(isolate_, "isPlainHostName"),
514 is_plain_host_name_template);
516 // Microsoft's PAC extensions:
518 v8::Local<v8::FunctionTemplate> dns_resolve_ex_template =
519 v8::FunctionTemplate::New(isolate_, &DnsResolveExCallback, v8_this);
520 global_template->Set(ASCIILiteralToV8String(isolate_, "dnsResolveEx"),
521 dns_resolve_ex_template);
523 v8::Local<v8::FunctionTemplate> my_ip_address_ex_template =
524 v8::FunctionTemplate::New(isolate_, &MyIpAddressExCallback, v8_this);
525 global_template->Set(ASCIILiteralToV8String(isolate_, "myIpAddressEx"),
526 my_ip_address_ex_template);
528 v8::Local<v8::FunctionTemplate> sort_ip_address_list_template =
529 v8::FunctionTemplate::New(isolate_,
530 &SortIpAddressListCallback,
531 v8_this);
532 global_template->Set(ASCIILiteralToV8String(isolate_, "sortIpAddressList"),
533 sort_ip_address_list_template);
535 v8::Local<v8::FunctionTemplate> is_in_net_ex_template =
536 v8::FunctionTemplate::New(isolate_, &IsInNetExCallback, v8_this);
537 global_template->Set(ASCIILiteralToV8String(isolate_, "isInNetEx"),
538 is_in_net_ex_template);
540 v8_context_.Reset(
541 isolate_, v8::Context::New(isolate_, NULL, global_template));
543 v8::Local<v8::Context> context =
544 v8::Local<v8::Context>::New(isolate_, v8_context_);
545 v8::Context::Scope ctx(context);
547 // Add the PAC utility functions to the environment.
548 // (This script should never fail, as it is a string literal!)
549 // Note that the two string literals are concatenated.
550 int rv = RunScript(
551 ASCIILiteralToV8String(
552 isolate_,
553 PROXY_RESOLVER_SCRIPT
554 PROXY_RESOLVER_SCRIPT_EX),
555 kPacUtilityResourceName);
556 if (rv != OK) {
557 NOTREACHED();
558 return rv;
561 // Add the user's PAC code to the environment.
562 rv =
563 RunScript(ScriptDataToV8String(isolate_, pac_script), kPacResourceName);
564 if (rv != OK)
565 return rv;
567 // At a minimum, the FindProxyForURL() function must be defined for this
568 // to be a legitimiate PAC script.
569 v8::Local<v8::Value> function;
570 return GetFindProxyForURL(&function);
573 private:
574 int GetFindProxyForURL(v8::Local<v8::Value>* function) {
575 v8::Local<v8::Context> context =
576 v8::Local<v8::Context>::New(isolate_, v8_context_);
578 v8::TryCatch try_catch(isolate_);
580 if (!context->Global()
581 ->Get(context, ASCIILiteralToV8String(isolate_, "FindProxyForURL"))
582 .ToLocal(function)) {
583 DCHECK(try_catch.HasCaught());
584 HandleError(try_catch.Message());
587 // The value should only be empty if an exception was thrown. Code
588 // defensively just in case.
589 DCHECK_EQ(function->IsEmpty(), try_catch.HasCaught());
590 if (function->IsEmpty() || try_catch.HasCaught()) {
591 js_bindings()->OnError(
593 base::ASCIIToUTF16("Accessing FindProxyForURL threw an exception."));
594 return ERR_PAC_SCRIPT_FAILED;
597 if (!(*function)->IsFunction()) {
598 js_bindings()->OnError(
599 -1, base::ASCIIToUTF16(
600 "FindProxyForURL is undefined or not a function."));
601 return ERR_PAC_SCRIPT_FAILED;
604 return OK;
607 // Handle an exception thrown by V8.
608 void HandleError(v8::Local<v8::Message> message) {
609 v8::Local<v8::Context> context =
610 v8::Local<v8::Context>::New(isolate_, v8_context_);
611 base::string16 error_message;
612 int line_number = -1;
614 if (!message.IsEmpty()) {
615 auto maybe = message->GetLineNumber(context);
616 if (maybe.IsJust())
617 line_number = maybe.FromJust();
618 V8ObjectToUTF16String(message->Get(), &error_message, isolate_);
621 js_bindings()->OnError(line_number, error_message);
624 // Compiles and runs |script| in the current V8 context.
625 // Returns OK on success, otherwise an error code.
626 int RunScript(v8::Local<v8::String> script, const char* script_name) {
627 v8::Local<v8::Context> context =
628 v8::Local<v8::Context>::New(isolate_, v8_context_);
629 v8::TryCatch try_catch(isolate_);
631 // Compile the script.
632 v8::ScriptOrigin origin =
633 v8::ScriptOrigin(ASCIILiteralToV8String(isolate_, script_name));
634 v8::Local<v8::Script> code;
635 if (!v8::Script::Compile(context, script, &origin).ToLocal(&code)) {
636 DCHECK(try_catch.HasCaught());
637 HandleError(try_catch.Message());
638 return ERR_PAC_SCRIPT_FAILED;
641 // Execute.
642 auto result = code->Run(context);
643 if (result.IsEmpty()) {
644 DCHECK(try_catch.HasCaught());
645 HandleError(try_catch.Message());
646 return ERR_PAC_SCRIPT_FAILED;
649 return OK;
652 // V8 callback for when "alert()" is invoked by the PAC script.
653 static void AlertCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
654 Context* context =
655 static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
657 // Like firefox we assume "undefined" if no argument was specified, and
658 // disregard any arguments beyond the first.
659 base::string16 message;
660 if (args.Length() == 0) {
661 message = base::ASCIIToUTF16("undefined");
662 } else {
663 if (!V8ObjectToUTF16String(args[0], &message, args.GetIsolate()))
664 return; // toString() threw an exception.
667 context->js_bindings()->Alert(message);
670 // V8 callback for when "myIpAddress()" is invoked by the PAC script.
671 static void MyIpAddressCallback(
672 const v8::FunctionCallbackInfo<v8::Value>& args) {
673 DnsResolveCallbackHelper(args, JSBindings::MY_IP_ADDRESS);
676 // V8 callback for when "myIpAddressEx()" is invoked by the PAC script.
677 static void MyIpAddressExCallback(
678 const v8::FunctionCallbackInfo<v8::Value>& args) {
679 DnsResolveCallbackHelper(args, JSBindings::MY_IP_ADDRESS_EX);
682 // V8 callback for when "dnsResolve()" is invoked by the PAC script.
683 static void DnsResolveCallback(
684 const v8::FunctionCallbackInfo<v8::Value>& args) {
685 DnsResolveCallbackHelper(args, JSBindings::DNS_RESOLVE);
688 // V8 callback for when "dnsResolveEx()" is invoked by the PAC script.
689 static void DnsResolveExCallback(
690 const v8::FunctionCallbackInfo<v8::Value>& args) {
691 DnsResolveCallbackHelper(args, JSBindings::DNS_RESOLVE_EX);
694 // Shared code for implementing:
695 // - myIpAddress(), myIpAddressEx(), dnsResolve(), dnsResolveEx().
696 static void DnsResolveCallbackHelper(
697 const v8::FunctionCallbackInfo<v8::Value>& args,
698 JSBindings::ResolveDnsOperation op) {
699 Context* context =
700 static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
702 std::string hostname;
704 // dnsResolve() and dnsResolveEx() need at least 1 argument.
705 if (op == JSBindings::DNS_RESOLVE || op == JSBindings::DNS_RESOLVE_EX) {
706 if (!GetHostnameArgument(args, &hostname)) {
707 if (op == JSBindings::DNS_RESOLVE)
708 args.GetReturnValue().SetNull();
709 return;
713 std::string result;
714 bool success;
715 bool terminate = false;
718 v8::Unlocker unlocker(args.GetIsolate());
719 success = context->js_bindings()->ResolveDns(
720 hostname, op, &result, &terminate);
723 if (terminate)
724 args.GetIsolate()->TerminateExecution();
726 if (success) {
727 args.GetReturnValue().Set(
728 ASCIIStringToV8String(args.GetIsolate(), result));
729 return;
732 // Each function handles resolution errors differently.
733 switch (op) {
734 case JSBindings::DNS_RESOLVE:
735 args.GetReturnValue().SetNull();
736 return;
737 case JSBindings::DNS_RESOLVE_EX:
738 args.GetReturnValue().SetEmptyString();
739 return;
740 case JSBindings::MY_IP_ADDRESS:
741 args.GetReturnValue().Set(
742 ASCIILiteralToV8String(args.GetIsolate(), "127.0.0.1"));
743 return;
744 case JSBindings::MY_IP_ADDRESS_EX:
745 args.GetReturnValue().SetEmptyString();
746 return;
749 NOTREACHED();
752 // V8 callback for when "sortIpAddressList()" is invoked by the PAC script.
753 static void SortIpAddressListCallback(
754 const v8::FunctionCallbackInfo<v8::Value>& args) {
755 // We need at least one string argument.
756 if (args.Length() == 0 || args[0].IsEmpty() || !args[0]->IsString()) {
757 args.GetReturnValue().SetNull();
758 return;
761 std::string ip_address_list =
762 V8StringToUTF8(v8::Local<v8::String>::Cast(args[0]));
763 if (!base::IsStringASCII(ip_address_list)) {
764 args.GetReturnValue().SetNull();
765 return;
767 std::string sorted_ip_address_list;
768 bool success = SortIpAddressList(ip_address_list, &sorted_ip_address_list);
769 if (!success) {
770 args.GetReturnValue().Set(false);
771 return;
773 args.GetReturnValue().Set(
774 ASCIIStringToV8String(args.GetIsolate(), sorted_ip_address_list));
777 // V8 callback for when "isInNetEx()" is invoked by the PAC script.
778 static void IsInNetExCallback(
779 const v8::FunctionCallbackInfo<v8::Value>& args) {
780 // We need at least 2 string arguments.
781 if (args.Length() < 2 || args[0].IsEmpty() || !args[0]->IsString() ||
782 args[1].IsEmpty() || !args[1]->IsString()) {
783 args.GetReturnValue().SetNull();
784 return;
787 std::string ip_address =
788 V8StringToUTF8(v8::Local<v8::String>::Cast(args[0]));
789 if (!base::IsStringASCII(ip_address)) {
790 args.GetReturnValue().Set(false);
791 return;
793 std::string ip_prefix =
794 V8StringToUTF8(v8::Local<v8::String>::Cast(args[1]));
795 if (!base::IsStringASCII(ip_prefix)) {
796 args.GetReturnValue().Set(false);
797 return;
799 args.GetReturnValue().Set(IsInNetEx(ip_address, ip_prefix));
802 // V8 callback for when "isPlainHostName()" is invoked by the PAC script.
803 static void IsPlainHostNameCallback(
804 const v8::FunctionCallbackInfo<v8::Value>& args) {
805 // Need at least 1 string arguments.
806 if (args.Length() < 1 || args[0].IsEmpty() || !args[0]->IsString()) {
807 args.GetIsolate()->ThrowException(
808 v8::Exception::TypeError(ASCIIStringToV8String(
809 args.GetIsolate(), "Requires 1 string parameter")));
810 return;
813 std::string hostname_utf8 =
814 V8StringToUTF8(v8::Local<v8::String>::Cast(args[0]));
815 args.GetReturnValue().Set(IsPlainHostName(hostname_utf8));
818 mutable base::Lock lock_;
819 ProxyResolverV8::JSBindings* js_bindings_;
820 v8::Isolate* isolate_;
821 v8::Persistent<v8::External> v8_this_;
822 v8::Persistent<v8::Context> v8_context_;
825 // ProxyResolverV8 ------------------------------------------------------------
827 ProxyResolverV8::ProxyResolverV8(scoped_ptr<Context> context)
828 : context_(context.Pass()) {
829 DCHECK(context_);
832 ProxyResolverV8::~ProxyResolverV8() {}
834 int ProxyResolverV8::GetProxyForURL(const GURL& query_url,
835 ProxyInfo* results,
836 ProxyResolverV8::JSBindings* bindings) {
837 return context_->ResolveProxy(query_url, results, bindings);
840 // static
841 int ProxyResolverV8::Create(
842 const scoped_refptr<ProxyResolverScriptData>& script_data,
843 ProxyResolverV8::JSBindings* js_bindings,
844 scoped_ptr<ProxyResolverV8>* resolver) {
845 DCHECK(script_data.get());
846 DCHECK(js_bindings);
848 if (script_data->utf16().empty())
849 return ERR_PAC_SCRIPT_FAILED;
851 // Try parsing the PAC script.
852 scoped_ptr<Context> context(
853 new Context(g_isolate_factory.Get().GetSharedIsolate()));
854 int rv = context->InitV8(script_data, js_bindings);
855 if (rv == OK)
856 resolver->reset(new ProxyResolverV8(context.Pass()));
857 return rv;
860 // static
861 size_t ProxyResolverV8::GetTotalHeapSize() {
862 v8::Isolate* isolate =
863 g_isolate_factory.Get().GetSharedIsolateWithoutCreating();
864 if (!isolate)
865 return 0;
867 v8::Locker locked(isolate);
868 v8::Isolate::Scope isolate_scope(isolate);
869 v8::HeapStatistics heap_statistics;
870 isolate->GetHeapStatistics(&heap_statistics);
871 return heap_statistics.total_heap_size();
874 // static
875 size_t ProxyResolverV8::GetUsedHeapSize() {
876 v8::Isolate* isolate =
877 g_isolate_factory.Get().GetSharedIsolateWithoutCreating();
878 if (!isolate)
879 return 0;
881 v8::Locker locked(isolate);
882 v8::Isolate::Scope isolate_scope(isolate);
883 v8::HeapStatistics heap_statistics;
884 isolate->GetHeapStatistics(&heap_statistics);
885 return heap_statistics.used_heap_size();
888 } // namespace net