Upload upstream chromium 108.0.5359.1
[platform/framework/web/chromium-efl.git] / services / proxy_resolver / proxy_resolver_v8.cc
1 // Copyright 2012 The Chromium Authors
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 "services/proxy_resolver/proxy_resolver_v8.h"
6
7 #include <algorithm>
8 #include <cstdio>
9 #include <memory>
10 #include <utility>
11
12 #include "base/auto_reset.h"
13 #include "base/check_op.h"
14 #include "base/compiler_specific.h"
15 #include "base/debug/leak_annotations.h"
16 #include "base/lazy_instance.h"
17 #include "base/memory/raw_ptr.h"
18 #include "base/notreached.h"
19 #include "base/strings/string_tokenizer.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/utf_string_conversions.h"
22 #include "base/synchronization/lock.h"
23 #include "base/threading/thread_task_runner_handle.h"
24 #include "gin/array_buffer.h"
25 #include "gin/public/isolate_holder.h"
26 #include "gin/v8_initializer.h"
27 #include "net/base/ip_address.h"
28 #include "net/base/net_errors.h"
29 #include "net/proxy_resolution/pac_file_data.h"
30 #include "net/proxy_resolution/proxy_info.h"
31 #include "services/proxy_resolver/pac_js_library.h"
32 #include "url/gurl.h"
33 #include "url/url_canon.h"
34 #include "v8/include/v8.h"
35
36 // Notes on the javascript environment:
37 //
38 // For the majority of the PAC utility functions, we use the same code
39 // as Firefox. See the javascript library that pac_js_library.h pulls in.
40 //
41 // In addition, we implement a subset of Microsoft's extensions to PAC.
42 // - myIpAddressEx()
43 // - dnsResolveEx()
44 // - isResolvableEx()
45 // - isInNetEx()
46 // - sortIpAddressList()
47 //
48 // It is worth noting that the original PAC specification does not describe
49 // the return values on failure. Consequently, there are compatibility
50 // differences between browsers on what to return on failure, which are
51 // illustrated below:
52 //
53 // --------------------+-------------+-------------------+--------------
54 //                     | Firefox3    | InternetExplorer8 |  --> Us <---
55 // --------------------+-------------+-------------------+--------------
56 // myIpAddress()       | "127.0.0.1" |  ???              |  "127.0.0.1"
57 // dnsResolve()        | null        |  false            |  null
58 // myIpAddressEx()     | N/A         |  ""               |  ""
59 // sortIpAddressList() | N/A         |  false            |  false
60 // dnsResolveEx()      | N/A         |  ""               |  ""
61 // isInNetEx()         | N/A         |  false            |  false
62 // --------------------+-------------+-------------------+--------------
63 //
64 // TODO(eroman): The cell above reading ??? means I didn't test it.
65 //
66 // Another difference is in how dnsResolve() and myIpAddress() are
67 // implemented -- whether they should restrict to IPv4 results, or
68 // include both IPv4 and IPv6. The following table illustrates the
69 // differences:
70 //
71 // --------------------+-------------+-------------------+--------------
72 //                     | Firefox3    | InternetExplorer8 |  --> Us <---
73 // --------------------+-------------+-------------------+--------------
74 // myIpAddress()       | IPv4/IPv6   |  IPv4             |  IPv4/IPv6
75 // dnsResolve()        | IPv4/IPv6   |  IPv4             |  IPv4
76 // isResolvable()      | IPv4/IPv6   |  IPv4             |  IPv4
77 // myIpAddressEx()     | N/A         |  IPv4/IPv6        |  IPv4/IPv6
78 // dnsResolveEx()      | N/A         |  IPv4/IPv6        |  IPv4/IPv6
79 // sortIpAddressList() | N/A         |  IPv4/IPv6        |  IPv4/IPv6
80 // isResolvableEx()    | N/A         |  IPv4/IPv6        |  IPv4/IPv6
81 // isInNetEx()         | N/A         |  IPv4/IPv6        |  IPv4/IPv6
82 // -----------------+-------------+-------------------+--------------
83
84 namespace proxy_resolver {
85
86 namespace {
87
88 // Pseudo-name for the PAC script.
89 const char kPacResourceName[] = "proxy-pac-script.js";
90 // Pseudo-name for the PAC utility script.
91 const char kPacUtilityResourceName[] = "proxy-pac-utility-script.js";
92
93 // External string wrapper so V8 can access the UTF16 string wrapped by
94 // net::PacFileData.
95 class V8ExternalStringFromScriptData
96     : public v8::String::ExternalStringResource {
97  public:
98   explicit V8ExternalStringFromScriptData(
99       const scoped_refptr<net::PacFileData>& script_data)
100       : script_data_(script_data) {}
101
102   V8ExternalStringFromScriptData(const V8ExternalStringFromScriptData&) =
103       delete;
104   V8ExternalStringFromScriptData& operator=(
105       const V8ExternalStringFromScriptData&) = delete;
106
107   const uint16_t* data() const override {
108     return reinterpret_cast<const uint16_t*>(script_data_->utf16().data());
109   }
110
111   size_t length() const override { return script_data_->utf16().size(); }
112
113  private:
114   const scoped_refptr<net::PacFileData> script_data_;
115 };
116
117 // External string wrapper so V8 can access a string literal.
118 class V8ExternalASCIILiteral
119     : public v8::String::ExternalOneByteStringResource {
120  public:
121   // |ascii| must be a NULL-terminated C string, and must remain valid
122   // throughout this object's lifetime.
123   V8ExternalASCIILiteral(const char* ascii, size_t length)
124       : ascii_(ascii), length_(length) {
125     DCHECK(base::IsStringASCII(ascii));
126   }
127
128   V8ExternalASCIILiteral(const V8ExternalASCIILiteral&) = delete;
129   V8ExternalASCIILiteral& operator=(const V8ExternalASCIILiteral&) = delete;
130
131   const char* data() const override { return ascii_; }
132
133   size_t length() const override { return length_; }
134
135  private:
136   const char* ascii_;
137   size_t length_;
138 };
139
140 // When creating a v8::String from a C++ string we have two choices: create
141 // a copy, or create a wrapper that shares the same underlying storage.
142 // For small strings it is better to just make a copy, whereas for large
143 // strings there are savings by sharing the storage. This number identifies
144 // the cutoff length for when to start wrapping rather than creating copies.
145 const size_t kMaxStringBytesForCopy = 256;
146
147 // Converts a V8 String to a UTF8 std::string.
148 std::string V8StringToUTF8(v8::Isolate* isolate, v8::Local<v8::String> s) {
149   int len = s->Length();
150   std::string result;
151   if (len > 0)
152     s->WriteUtf8(isolate, base::WriteInto(&result, len + 1));
153   return result;
154 }
155
156 // Converts a V8 String to a UTF16 std::u16string.
157 std::u16string V8StringToUTF16(v8::Isolate* isolate, v8::Local<v8::String> s) {
158   int len = s->Length();
159   std::u16string result;
160   // Note that the reinterpret cast is because on Windows string16 is an alias
161   // to wstring, and hence has character type wchar_t not uint16_t.
162   if (len > 0) {
163     s->Write(isolate,
164              reinterpret_cast<uint16_t*>(base::WriteInto(&result, len + 1)), 0,
165              len);
166   }
167   return result;
168 }
169
170 // Converts an ASCII std::string to a V8 string.
171 v8::Local<v8::String> ASCIIStringToV8String(v8::Isolate* isolate,
172                                             const std::string& s) {
173   DCHECK(base::IsStringASCII(s));
174   return v8::String::NewFromUtf8(isolate, s.data(), v8::NewStringType::kNormal,
175                                  s.size())
176       .ToLocalChecked();
177 }
178
179 // Converts a UTF16 std::u16string (wrapped by a net::PacFileData) to a
180 // V8 string.
181 v8::Local<v8::String> ScriptDataToV8String(
182     v8::Isolate* isolate,
183     const scoped_refptr<net::PacFileData>& s) {
184   if (s->utf16().size() * 2 <= kMaxStringBytesForCopy) {
185     return v8::String::NewFromTwoByte(
186                isolate, reinterpret_cast<const uint16_t*>(s->utf16().data()),
187                v8::NewStringType::kNormal, s->utf16().size())
188         .ToLocalChecked();
189   }
190   return v8::String::NewExternalTwoByte(isolate,
191                                         new V8ExternalStringFromScriptData(s))
192       .ToLocalChecked();
193 }
194
195 // Converts an ASCII string literal to a V8 string.
196 v8::Local<v8::String> ASCIILiteralToV8String(v8::Isolate* isolate,
197                                              const char* ascii) {
198   DCHECK(base::IsStringASCII(ascii));
199   size_t length = strlen(ascii);
200   if (length <= kMaxStringBytesForCopy)
201     return v8::String::NewFromUtf8(isolate, ascii, v8::NewStringType::kNormal,
202                                    length)
203         .ToLocalChecked();
204   return v8::String::NewExternalOneByte(
205              isolate, new V8ExternalASCIILiteral(ascii, length))
206       .ToLocalChecked();
207 }
208
209 // Stringizes a V8 object by calling its toString() method. Returns true
210 // on success. This may fail if the toString() throws an exception.
211 bool V8ObjectToUTF16String(v8::Local<v8::Value> object,
212                            std::u16string* utf16_result,
213                            v8::Isolate* isolate) {
214   if (object.IsEmpty())
215     return false;
216
217   v8::HandleScope scope(isolate);
218   v8::Local<v8::String> str_object;
219   if (!object->ToString(isolate->GetCurrentContext()).ToLocal(&str_object))
220     return false;
221   *utf16_result = V8StringToUTF16(isolate, str_object);
222   return true;
223 }
224
225 // Extracts an hostname argument from |args|. On success returns true
226 // and fills |*hostname| with the result.
227 bool GetHostnameArgument(const v8::FunctionCallbackInfo<v8::Value>& args,
228                          std::string* hostname) {
229   // The first argument should be a string.
230   if (args.Length() == 0 || args[0].IsEmpty() || !args[0]->IsString())
231     return false;
232
233   const std::u16string hostname_utf16 =
234       V8StringToUTF16(args.GetIsolate(), v8::Local<v8::String>::Cast(args[0]));
235
236   // If the hostname is already in ASCII, simply return it as is.
237   if (base::IsStringASCII(hostname_utf16)) {
238     *hostname = base::UTF16ToASCII(hostname_utf16);
239     return true;
240   }
241
242   // Otherwise try to convert it from IDN to punycode.
243   const int kInitialBufferSize = 256;
244   url::RawCanonOutputT<char16_t, kInitialBufferSize> punycode_output;
245   if (!url::IDNToASCII(hostname_utf16.data(), hostname_utf16.length(),
246                        &punycode_output)) {
247     return false;
248   }
249
250   // |punycode_output| should now be ASCII; convert it to a std::string.
251   // (We could use UTF16ToASCII() instead, but that requires an extra string
252   // copy. Since ASCII is a subset of UTF8 the following is equivalent).
253   bool success = base::UTF16ToUTF8(punycode_output.data(),
254                                    punycode_output.length(), hostname);
255   DCHECK(success);
256   DCHECK(base::IsStringASCII(*hostname));
257   return success;
258 }
259
260 // Wrapper around an IP address that stores the original string as well as a
261 // corresponding parsed net::IPAddress.
262
263 // This struct is used as a helper for sorting IP address strings - the IP
264 // literal is parsed just once and used as the sorting key, while also
265 // preserving the original IP literal string.
266 struct IPAddressSortingEntry {
267   IPAddressSortingEntry(const std::string& ip_string,
268                         const net::IPAddress& ip_address)
269       : string_value(ip_string), ip_address(ip_address) {}
270
271   // Used for sorting IP addresses in ascending order in SortIpAddressList().
272   // IPv6 addresses are placed ahead of IPv4 addresses.
273   bool operator<(const IPAddressSortingEntry& rhs) const {
274     const net::IPAddress& ip1 = this->ip_address;
275     const net::IPAddress& ip2 = rhs.ip_address;
276     if (ip1.size() != ip2.size())
277       return ip1.size() > ip2.size();  // IPv6 before IPv4.
278     return ip1 < ip2;                  // Ascending order.
279   }
280
281   std::string string_value;
282   net::IPAddress ip_address;
283 };
284
285 // Handler for "sortIpAddressList(IpAddressList)". |ip_address_list| is a
286 // semi-colon delimited string containing IP addresses.
287 // |sorted_ip_address_list| is the resulting list of sorted semi-colon delimited
288 // IP addresses or an empty string if unable to sort the IP address list.
289 // Returns 'true' if the sorting was successful, and 'false' if the input was an
290 // empty string, a string of separators (";" in this case), or if any of the IP
291 // addresses in the input list failed to parse.
292 bool SortIpAddressList(const std::string& ip_address_list,
293                        std::string* sorted_ip_address_list) {
294   sorted_ip_address_list->clear();
295
296   // Strip all whitespace (mimics IE behavior).
297   std::string cleaned_ip_address_list;
298   base::RemoveChars(ip_address_list, " \t", &cleaned_ip_address_list);
299   if (cleaned_ip_address_list.empty())
300     return false;
301
302   // Split-up IP addresses and store them in a vector.
303   std::vector<IPAddressSortingEntry> ip_vector;
304   net::IPAddress ip_address;
305   base::StringTokenizer str_tok(cleaned_ip_address_list, ";");
306   while (str_tok.GetNext()) {
307     if (!ip_address.AssignFromIPLiteral(str_tok.token()))
308       return false;
309     ip_vector.push_back(IPAddressSortingEntry(str_tok.token(), ip_address));
310   }
311
312   if (ip_vector.empty())  // Can happen if we have something like
313     return false;         // sortIpAddressList(";") or sortIpAddressList("; ;")
314
315   DCHECK(!ip_vector.empty());
316
317   // Sort lists according to ascending numeric value.
318   if (ip_vector.size() > 1)
319     std::stable_sort(ip_vector.begin(), ip_vector.end());
320
321   // Return a semi-colon delimited list of sorted addresses (IPv6 followed by
322   // IPv4).
323   for (size_t i = 0; i < ip_vector.size(); ++i) {
324     if (i > 0)
325       *sorted_ip_address_list += ";";
326     *sorted_ip_address_list += ip_vector[i].string_value;
327   }
328   return true;
329 }
330
331 // Handler for "isInNetEx(ip_address, ip_prefix)". |ip_address| is a string
332 // containing an IPv4/IPv6 address, and |ip_prefix| is a string containg a
333 // slash-delimited IP prefix with the top 'n' bits specified in the bit
334 // field. This returns 'true' if the address is in the same subnet, and
335 // 'false' otherwise. Also returns 'false' if the prefix is in an incorrect
336 // format. If the address types of |ip_address| and |ip_prefix| don't match,
337 // will promote the IPv4 literal to an IPv4 mapped IPv6 literal and
338 // proceed with the comparison.
339 bool IsInNetEx(const std::string& ip_address, const std::string& ip_prefix) {
340   net::IPAddress address;
341   if (!address.AssignFromIPLiteral(ip_address))
342     return false;
343
344   net::IPAddress prefix;
345   size_t prefix_length_in_bits;
346   if (!ParseCIDRBlock(ip_prefix, &prefix, &prefix_length_in_bits))
347     return false;
348
349   return IPAddressMatchesPrefix(address, prefix, prefix_length_in_bits);
350 }
351
352 // Consider only single component domains like 'foo' as plain host names.
353 bool IsPlainHostName(const std::string& hostname_utf8) {
354   if (hostname_utf8.find('.') != std::string::npos)
355     return false;
356
357   // IPv6 literals might not contain any periods, however are not considered
358   // plain host names.
359   net::IPAddress unused;
360   return !unused.AssignFromIPLiteral(hostname_utf8);
361 }
362
363 // All instances of ProxyResolverV8 share the same v8::Isolate. This isolate is
364 // created lazily the first time it is needed and lives until process shutdown.
365 // This creation might happen from any thread, as ProxyResolverV8 is typically
366 // run in a threadpool.
367 //
368 // TODO(eroman): The lazily created isolate is never freed. Instead it should be
369 // disposed once there are no longer any ProxyResolverV8 referencing it.
370 class SharedIsolateFactory {
371  public:
372   SharedIsolateFactory() : has_initialized_v8_(false) {}
373
374   SharedIsolateFactory(const SharedIsolateFactory&) = delete;
375   SharedIsolateFactory& operator=(const SharedIsolateFactory&) = delete;
376
377   // Lazily creates a v8::Isolate, or returns the already created instance.
378   v8::Isolate* GetSharedIsolate() {
379     base::AutoLock lock(lock_);
380
381     if (!holder_) {
382       // Do one-time initialization for V8.
383       if (!has_initialized_v8_) {
384 #if defined(V8_USE_EXTERNAL_STARTUP_DATA)
385 #if defined(USE_V8_CONTEXT_SNAPSHOT)
386         gin::V8Initializer::LoadV8Snapshot(
387             gin::V8SnapshotFileType::kWithAdditionalContext);
388 #else
389         gin::V8Initializer::LoadV8Snapshot();
390 #endif
391 #endif
392
393         // The performance of the proxy resolver is limited by DNS resolution,
394         // and not V8, so tune down V8 to use as little memory as possible.
395         static const char kOptimizeForSize[] = "--optimize_for_size";
396         v8::V8::SetFlagsFromString(kOptimizeForSize, strlen(kOptimizeForSize));
397
398         // Running v8 in jitless mode allows dynamic code to be disabled in the
399         // process.
400         static const char kJitless[] = "--jitless";
401         v8::V8::SetFlagsFromString(kJitless, strlen(kJitless));
402
403         // WebAssembly isn't encountered during resolution, so reduce the
404         // potential attack surface.
405         static const char kNoExposeWasm[] = "--no-expose-wasm";
406         v8::V8::SetFlagsFromString(kNoExposeWasm, strlen(kNoExposeWasm));
407
408         gin::IsolateHolder::Initialize(
409             gin::IsolateHolder::kNonStrictMode,
410             gin::ArrayBufferAllocator::SharedInstance());
411
412         has_initialized_v8_ = true;
413       }
414
415       holder_ = std::make_unique<gin::IsolateHolder>(
416           base::ThreadTaskRunnerHandle::Get(), gin::IsolateHolder::kUseLocker,
417           gin::IsolateHolder::IsolateType::kUtility);
418     }
419
420     return holder_->isolate();
421   }
422
423   v8::Isolate* GetSharedIsolateWithoutCreating() {
424     base::AutoLock lock(lock_);
425     return holder_ ? holder_->isolate() : nullptr;
426   }
427
428  private:
429   base::Lock lock_;
430   std::unique_ptr<gin::IsolateHolder> holder_;
431   bool has_initialized_v8_;
432 };
433
434 base::LazyInstance<SharedIsolateFactory>::Leaky g_isolate_factory =
435     LAZY_INSTANCE_INITIALIZER;
436
437 }  // namespace
438
439 // ProxyResolverV8::Context ---------------------------------------------------
440
441 class ProxyResolverV8::Context {
442  public:
443   explicit Context(v8::Isolate* isolate)
444       : js_bindings_(nullptr), isolate_(isolate) {
445     DCHECK(isolate);
446   }
447
448   ~Context() {
449     v8::Locker locked(isolate_);
450     v8::Isolate::Scope isolate_scope(isolate_);
451
452     v8_this_.Reset();
453     v8_context_.Reset();
454   }
455
456   JSBindings* js_bindings() { return js_bindings_; }
457
458   int ResolveProxy(const GURL& query_url,
459                    net::ProxyInfo* results,
460                    JSBindings* bindings) {
461     DCHECK(bindings);
462     base::AutoReset<JSBindings*> bindings_reset(&js_bindings_, bindings);
463     v8::Locker locked(isolate_);
464     v8::Isolate::Scope isolate_scope(isolate_);
465     v8::Isolate::SafeForTerminationScope safe_for_termination(isolate_);
466     v8::HandleScope scope(isolate_);
467
468     v8::Local<v8::Context> context =
469         v8::Local<v8::Context>::New(isolate_, v8_context_);
470     v8::Context::Scope function_scope(context);
471
472     v8::Local<v8::Value> function;
473     int rv = GetFindProxyForURL(&function);
474     if (rv != net::OK)
475       return rv;
476
477     v8::Local<v8::Value> argv[] = {
478         ASCIIStringToV8String(isolate_, query_url.spec()),
479         ASCIIStringToV8String(isolate_, query_url.HostNoBrackets()),
480     };
481
482     v8::TryCatch try_catch(isolate_);
483     v8::Local<v8::Value> ret;
484     if (!v8::Function::Cast(*function)
485              ->Call(context, context->Global(), std::size(argv), argv)
486              .ToLocal(&ret)) {
487       DCHECK(try_catch.HasCaught());
488       HandleError(try_catch.Message());
489       return net::ERR_PAC_SCRIPT_FAILED;
490     }
491
492     if (!ret->IsString()) {
493       js_bindings()->OnError(-1, u"FindProxyForURL() did not return a string.");
494       return net::ERR_PAC_SCRIPT_FAILED;
495     }
496
497     std::u16string ret_str =
498         V8StringToUTF16(isolate_, v8::Local<v8::String>::Cast(ret));
499
500     if (!base::IsStringASCII(ret_str)) {
501       // TODO(eroman): Rather than failing when a wide string is returned, we
502       //               could extend the parsing to handle IDNA hostnames by
503       //               converting them to ASCII punycode.
504       //               crbug.com/47234
505       std::u16string error_message =
506           u"FindProxyForURL() returned a non-ASCII string (crbug.com/47234): " +
507           ret_str;
508       js_bindings()->OnError(-1, error_message);
509       return net::ERR_PAC_SCRIPT_FAILED;
510     }
511
512     results->UsePacString(base::UTF16ToASCII(ret_str));
513     return net::OK;
514   }
515
516   int InitV8(const scoped_refptr<net::PacFileData>& pac_script,
517              JSBindings* bindings) {
518     base::AutoReset<JSBindings*> bindings_reset(&js_bindings_, bindings);
519     v8::Locker locked(isolate_);
520     v8::Isolate::Scope isolate_scope(isolate_);
521     v8::HandleScope scope(isolate_);
522
523     v8_this_.Reset(isolate_, v8::External::New(isolate_, this));
524     v8::Local<v8::External> v8_this =
525         v8::Local<v8::External>::New(isolate_, v8_this_);
526     v8::Local<v8::ObjectTemplate> global_template =
527         v8::ObjectTemplate::New(isolate_);
528
529     // Attach the javascript bindings.
530     v8::Local<v8::FunctionTemplate> alert_template =
531         v8::FunctionTemplate::New(isolate_, &AlertCallback, v8_this);
532     alert_template->RemovePrototype();
533     global_template->Set(ASCIILiteralToV8String(isolate_, "alert"),
534                          alert_template);
535
536     v8::Local<v8::FunctionTemplate> my_ip_address_template =
537         v8::FunctionTemplate::New(isolate_, &MyIpAddressCallback, v8_this);
538     my_ip_address_template->RemovePrototype();
539     global_template->Set(ASCIILiteralToV8String(isolate_, "myIpAddress"),
540                          my_ip_address_template);
541
542     v8::Local<v8::FunctionTemplate> dns_resolve_template =
543         v8::FunctionTemplate::New(isolate_, &DnsResolveCallback, v8_this);
544     dns_resolve_template->RemovePrototype();
545     global_template->Set(ASCIILiteralToV8String(isolate_, "dnsResolve"),
546                          dns_resolve_template);
547
548     v8::Local<v8::FunctionTemplate> is_plain_host_name_template =
549         v8::FunctionTemplate::New(isolate_, &IsPlainHostNameCallback, v8_this);
550     is_plain_host_name_template->RemovePrototype();
551     global_template->Set(ASCIILiteralToV8String(isolate_, "isPlainHostName"),
552                          is_plain_host_name_template);
553
554     // Microsoft's PAC extensions:
555
556     v8::Local<v8::FunctionTemplate> dns_resolve_ex_template =
557         v8::FunctionTemplate::New(isolate_, &DnsResolveExCallback, v8_this);
558     dns_resolve_ex_template->RemovePrototype();
559     global_template->Set(ASCIILiteralToV8String(isolate_, "dnsResolveEx"),
560                          dns_resolve_ex_template);
561
562     v8::Local<v8::FunctionTemplate> my_ip_address_ex_template =
563         v8::FunctionTemplate::New(isolate_, &MyIpAddressExCallback, v8_this);
564     my_ip_address_ex_template->RemovePrototype();
565     global_template->Set(ASCIILiteralToV8String(isolate_, "myIpAddressEx"),
566                          my_ip_address_ex_template);
567
568     v8::Local<v8::FunctionTemplate> sort_ip_address_list_template =
569         v8::FunctionTemplate::New(isolate_, &SortIpAddressListCallback,
570                                   v8_this);
571     sort_ip_address_list_template->RemovePrototype();
572     global_template->Set(ASCIILiteralToV8String(isolate_, "sortIpAddressList"),
573                          sort_ip_address_list_template);
574
575     v8::Local<v8::FunctionTemplate> is_in_net_ex_template =
576         v8::FunctionTemplate::New(isolate_, &IsInNetExCallback, v8_this);
577     is_in_net_ex_template->RemovePrototype();
578     global_template->Set(ASCIILiteralToV8String(isolate_, "isInNetEx"),
579                          is_in_net_ex_template);
580
581     v8_context_.Reset(isolate_,
582                       v8::Context::New(isolate_, nullptr, global_template));
583
584     v8::Local<v8::Context> context =
585         v8::Local<v8::Context>::New(isolate_, v8_context_);
586     v8::Context::Scope ctx(context);
587
588     // Add the PAC utility functions to the environment.
589     // (This script should never fail, as it is a string literal!)
590     // Note that the two string literals are concatenated.
591     int rv = RunScript(
592         ASCIILiteralToV8String(isolate_, PAC_JS_LIBRARY PAC_JS_LIBRARY_EX),
593         kPacUtilityResourceName);
594     if (rv != net::OK) {
595       NOTREACHED();
596       return rv;
597     }
598
599     // Add the user's PAC code to the environment.
600     rv =
601         RunScript(ScriptDataToV8String(isolate_, pac_script), kPacResourceName);
602     if (rv != net::OK)
603       return rv;
604
605     // At a minimum, the FindProxyForURL() function must be defined for this
606     // to be a legitimiate PAC script.
607     v8::Local<v8::Value> function;
608     return GetFindProxyForURL(&function);
609   }
610
611  private:
612   int GetFindProxyForURL(v8::Local<v8::Value>* function) {
613     v8::Local<v8::Context> context =
614         v8::Local<v8::Context>::New(isolate_, v8_context_);
615
616     v8::TryCatch try_catch(isolate_);
617
618     if (!context->Global()
619              ->Get(context, ASCIILiteralToV8String(isolate_, "FindProxyForURL"))
620              .ToLocal(function)) {
621       DCHECK(try_catch.HasCaught());
622       HandleError(try_catch.Message());
623     }
624
625     // The value should only be empty if an exception was thrown. Code
626     // defensively just in case.
627     DCHECK_EQ(function->IsEmpty(), try_catch.HasCaught());
628     if (function->IsEmpty() || try_catch.HasCaught()) {
629       js_bindings()->OnError(-1,
630                              u"Accessing FindProxyForURL threw an exception.");
631       return net::ERR_PAC_SCRIPT_FAILED;
632     }
633
634     if (!(*function)->IsFunction()) {
635       js_bindings()->OnError(
636           -1, u"FindProxyForURL is undefined or not a function.");
637       return net::ERR_PAC_SCRIPT_FAILED;
638     }
639
640     return net::OK;
641   }
642
643   // Handle an exception thrown by V8.
644   void HandleError(v8::Local<v8::Message> message) {
645     v8::Local<v8::Context> context =
646         v8::Local<v8::Context>::New(isolate_, v8_context_);
647     std::u16string error_message;
648     int line_number = -1;
649
650     if (!message.IsEmpty()) {
651       auto maybe = message->GetLineNumber(context);
652       if (maybe.IsJust())
653         line_number = maybe.FromJust();
654       V8ObjectToUTF16String(message->Get(), &error_message, isolate_);
655     }
656
657     js_bindings()->OnError(line_number, error_message);
658   }
659
660   // Compiles and runs |script| in the current V8 context.
661   // Returns net::OK on success, otherwise an error code.
662   int RunScript(v8::Local<v8::String> script, const char* script_name) {
663     v8::Local<v8::Context> context =
664         v8::Local<v8::Context>::New(isolate_, v8_context_);
665     v8::TryCatch try_catch(isolate_);
666
667     // Compile the script.
668     v8::ScriptOrigin origin = v8::ScriptOrigin(
669         isolate_, ASCIILiteralToV8String(isolate_, script_name));
670     v8::ScriptCompiler::Source script_source(script, origin);
671     v8::Local<v8::Script> code;
672     if (!v8::ScriptCompiler::Compile(
673              context, &script_source, v8::ScriptCompiler::kNoCompileOptions,
674              v8::ScriptCompiler::NoCacheReason::kNoCacheBecausePacScript)
675              .ToLocal(&code)) {
676       DCHECK(try_catch.HasCaught());
677       HandleError(try_catch.Message());
678       return net::ERR_PAC_SCRIPT_FAILED;
679     }
680
681     // Execute.
682     auto result = code->Run(context);
683     if (result.IsEmpty()) {
684       DCHECK(try_catch.HasCaught());
685       HandleError(try_catch.Message());
686       return net::ERR_PAC_SCRIPT_FAILED;
687     }
688
689     return net::OK;
690   }
691
692   // V8 callback for when "alert()" is invoked by the PAC script.
693   static void AlertCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
694     Context* context =
695         static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
696
697     // Like firefox we assume "undefined" if no argument was specified, and
698     // disregard any arguments beyond the first.
699     std::u16string message;
700     if (args.Length() == 0) {
701       message = u"undefined";
702     } else {
703       if (!V8ObjectToUTF16String(args[0], &message, args.GetIsolate()))
704         return;  // toString() threw an exception.
705     }
706
707     context->js_bindings()->Alert(message);
708   }
709
710   // V8 callback for when "myIpAddress()" is invoked by the PAC script.
711   static void MyIpAddressCallback(
712       const v8::FunctionCallbackInfo<v8::Value>& args) {
713     DnsResolveCallbackHelper(args,
714                              net::ProxyResolveDnsOperation::MY_IP_ADDRESS);
715   }
716
717   // V8 callback for when "myIpAddressEx()" is invoked by the PAC script.
718   static void MyIpAddressExCallback(
719       const v8::FunctionCallbackInfo<v8::Value>& args) {
720     DnsResolveCallbackHelper(args,
721                              net::ProxyResolveDnsOperation::MY_IP_ADDRESS_EX);
722   }
723
724   // V8 callback for when "dnsResolve()" is invoked by the PAC script.
725   static void DnsResolveCallback(
726       const v8::FunctionCallbackInfo<v8::Value>& args) {
727     DnsResolveCallbackHelper(args, net::ProxyResolveDnsOperation::DNS_RESOLVE);
728   }
729
730   // V8 callback for when "dnsResolveEx()" is invoked by the PAC script.
731   static void DnsResolveExCallback(
732       const v8::FunctionCallbackInfo<v8::Value>& args) {
733     DnsResolveCallbackHelper(args,
734                              net::ProxyResolveDnsOperation::DNS_RESOLVE_EX);
735   }
736
737   // Shared code for implementing:
738   //   - myIpAddress(), myIpAddressEx(), dnsResolve(), dnsResolveEx().
739   static void DnsResolveCallbackHelper(
740       const v8::FunctionCallbackInfo<v8::Value>& args,
741       net::ProxyResolveDnsOperation op) {
742     Context* context =
743         static_cast<Context*>(v8::External::Cast(*args.Data())->Value());
744
745     std::string hostname;
746
747     // dnsResolve() and dnsResolveEx() need at least 1 argument.
748     if (op == net::ProxyResolveDnsOperation::DNS_RESOLVE ||
749         op == net::ProxyResolveDnsOperation::DNS_RESOLVE_EX) {
750       if (!GetHostnameArgument(args, &hostname)) {
751         if (op == net::ProxyResolveDnsOperation::DNS_RESOLVE)
752           args.GetReturnValue().SetNull();
753         return;
754       }
755     }
756
757     std::string result;
758     bool success;
759     bool terminate = false;
760
761     {
762       v8::Unlocker unlocker(args.GetIsolate());
763       success =
764           context->js_bindings()->ResolveDns(hostname, op, &result, &terminate);
765     }
766
767     if (terminate)
768       args.GetIsolate()->TerminateExecution();
769
770     if (success) {
771       args.GetReturnValue().Set(
772           ASCIIStringToV8String(args.GetIsolate(), result));
773       return;
774     }
775
776     // Each function handles resolution errors differently.
777     switch (op) {
778       case net::ProxyResolveDnsOperation::DNS_RESOLVE:
779         args.GetReturnValue().SetNull();
780         return;
781       case net::ProxyResolveDnsOperation::DNS_RESOLVE_EX:
782         args.GetReturnValue().SetEmptyString();
783         return;
784       case net::ProxyResolveDnsOperation::MY_IP_ADDRESS:
785         args.GetReturnValue().Set(
786             ASCIILiteralToV8String(args.GetIsolate(), "127.0.0.1"));
787         return;
788       case net::ProxyResolveDnsOperation::MY_IP_ADDRESS_EX:
789         args.GetReturnValue().SetEmptyString();
790         return;
791     }
792
793     NOTREACHED();
794   }
795
796   // V8 callback for when "sortIpAddressList()" is invoked by the PAC script.
797   static void SortIpAddressListCallback(
798       const v8::FunctionCallbackInfo<v8::Value>& args) {
799     // We need at least one string argument.
800     if (args.Length() == 0 || args[0].IsEmpty() || !args[0]->IsString()) {
801       args.GetReturnValue().SetNull();
802       return;
803     }
804
805     std::string ip_address_list =
806         V8StringToUTF8(args.GetIsolate(), v8::Local<v8::String>::Cast(args[0]));
807     if (!base::IsStringASCII(ip_address_list)) {
808       args.GetReturnValue().SetNull();
809       return;
810     }
811     std::string sorted_ip_address_list;
812     bool success = SortIpAddressList(ip_address_list, &sorted_ip_address_list);
813     if (!success) {
814       args.GetReturnValue().Set(false);
815       return;
816     }
817     args.GetReturnValue().Set(
818         ASCIIStringToV8String(args.GetIsolate(), sorted_ip_address_list));
819   }
820
821   // V8 callback for when "isInNetEx()" is invoked by the PAC script.
822   static void IsInNetExCallback(
823       const v8::FunctionCallbackInfo<v8::Value>& args) {
824     // We need at least 2 string arguments.
825     if (args.Length() < 2 || args[0].IsEmpty() || !args[0]->IsString() ||
826         args[1].IsEmpty() || !args[1]->IsString()) {
827       args.GetReturnValue().SetNull();
828       return;
829     }
830
831     std::string ip_address =
832         V8StringToUTF8(args.GetIsolate(), v8::Local<v8::String>::Cast(args[0]));
833     if (!base::IsStringASCII(ip_address)) {
834       args.GetReturnValue().Set(false);
835       return;
836     }
837     std::string ip_prefix =
838         V8StringToUTF8(args.GetIsolate(), v8::Local<v8::String>::Cast(args[1]));
839     if (!base::IsStringASCII(ip_prefix)) {
840       args.GetReturnValue().Set(false);
841       return;
842     }
843     args.GetReturnValue().Set(IsInNetEx(ip_address, ip_prefix));
844   }
845
846   // V8 callback for when "isPlainHostName()" is invoked by the PAC script.
847   static void IsPlainHostNameCallback(
848       const v8::FunctionCallbackInfo<v8::Value>& args) {
849     // Need at least 1 string arguments.
850     if (args.Length() < 1 || args[0].IsEmpty() || !args[0]->IsString()) {
851       args.GetIsolate()->ThrowException(
852           v8::Exception::TypeError(ASCIIStringToV8String(
853               args.GetIsolate(), "Requires 1 string parameter")));
854       return;
855     }
856
857     std::string hostname_utf8 =
858         V8StringToUTF8(args.GetIsolate(), v8::Local<v8::String>::Cast(args[0]));
859     args.GetReturnValue().Set(IsPlainHostName(hostname_utf8));
860   }
861
862   mutable base::Lock lock_;
863   ProxyResolverV8::JSBindings* js_bindings_;
864   raw_ptr<v8::Isolate> isolate_;
865   v8::Persistent<v8::External> v8_this_;
866   v8::Persistent<v8::Context> v8_context_;
867 };
868
869 // ProxyResolverV8 ------------------------------------------------------------
870
871 ProxyResolverV8::ProxyResolverV8(std::unique_ptr<Context> context)
872     : context_(std::move(context)) {
873   DCHECK(context_);
874 }
875
876 ProxyResolverV8::~ProxyResolverV8() = default;
877
878 int ProxyResolverV8::GetProxyForURL(const GURL& query_url,
879                                     net::ProxyInfo* results,
880                                     ProxyResolverV8::JSBindings* bindings) {
881   return context_->ResolveProxy(query_url, results, bindings);
882 }
883
884 // static
885 int ProxyResolverV8::Create(const scoped_refptr<net::PacFileData>& script_data,
886                             ProxyResolverV8::JSBindings* js_bindings,
887                             std::unique_ptr<ProxyResolverV8>* resolver) {
888   DCHECK(script_data.get());
889   DCHECK(js_bindings);
890
891   if (script_data->utf16().empty())
892     return net::ERR_PAC_SCRIPT_FAILED;
893
894   // Try parsing the PAC script.
895   std::unique_ptr<Context> context(
896       new Context(g_isolate_factory.Get().GetSharedIsolate()));
897   int rv = context->InitV8(script_data, js_bindings);
898   if (rv == net::OK)
899     resolver->reset(new ProxyResolverV8(std::move(context)));
900   return rv;
901 }
902
903 // static
904 size_t ProxyResolverV8::GetTotalHeapSize() {
905   v8::Isolate* isolate =
906       g_isolate_factory.Get().GetSharedIsolateWithoutCreating();
907   if (!isolate)
908     return 0;
909
910   v8::Locker locked(isolate);
911   v8::Isolate::Scope isolate_scope(isolate);
912   v8::HeapStatistics heap_statistics;
913   isolate->GetHeapStatistics(&heap_statistics);
914   return heap_statistics.total_heap_size();
915 }
916
917 // static
918 size_t ProxyResolverV8::GetUsedHeapSize() {
919   v8::Isolate* isolate =
920       g_isolate_factory.Get().GetSharedIsolateWithoutCreating();
921   if (!isolate)
922     return 0;
923
924   v8::Locker locked(isolate);
925   v8::Isolate::Scope isolate_scope(isolate);
926   v8::HeapStatistics heap_statistics;
927   isolate->GetHeapStatistics(&heap_statistics);
928   return heap_statistics.used_heap_size();
929 }
930
931 }  // namespace proxy_resolver