proxy_resolver_v8.cc 34 KB

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