ProxyAutoConfig.cpp (30068B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "ProxyAutoConfig.h" 8 #include "nsICancelable.h" 9 #include "nsIDNSListener.h" 10 #include "nsIDNSRecord.h" 11 #include "nsIDNSService.h" 12 #include "nsINamed.h" 13 #include "nsThreadUtils.h" 14 #include "nsIConsoleService.h" 15 #include "nsIURLParser.h" 16 #include "nsJSUtils.h" 17 #include "jsfriendapi.h" 18 #include "js/CallAndConstruct.h" // JS_CallFunctionName 19 #include "js/CompilationAndEvaluation.h" // JS::Compile 20 #include "js/ContextOptions.h" 21 #include "js/Initialization.h" 22 #include "js/PropertyAndElement.h" // JS_DefineFunctions, JS_GetProperty 23 #include "js/PropertySpec.h" 24 #include "js/SourceText.h" // JS::Source{Ownership,Text} 25 #include "js/Utility.h" 26 #include "js/Warnings.h" // JS::SetWarningReporter 27 #include "prnetdb.h" 28 #include "nsITimer.h" 29 #include "mozilla/Atomics.h" 30 #include "mozilla/SpinEventLoopUntil.h" 31 #include "mozilla/ipc/Endpoint.h" 32 #include "mozilla/net/DNS.h" 33 #include "mozilla/net/SocketProcessChild.h" 34 #include "mozilla/net/SocketProcessParent.h" 35 #include "mozilla/net/ProxyAutoConfigChild.h" 36 #include "mozilla/net/ProxyAutoConfigParent.h" 37 #include "mozilla/Utf8.h" // mozilla::Utf8Unit 38 #include "nsServiceManagerUtils.h" 39 #include "nsNetCID.h" 40 41 #if defined(XP_MACOSX) 42 # include "nsMacUtilsImpl.h" 43 #endif 44 45 #include "XPCSelfHostedShmem.h" 46 47 namespace mozilla { 48 namespace net { 49 50 // These are some global helper symbols the PAC format requires that we provide 51 // that are initialized as part of the global javascript context used for PAC 52 // evaluations. Additionally dnsResolve(host) and myIpAddress() are supplied in 53 // the same context but are implemented as c++ helpers. alert(msg) is similarly 54 // defined. 55 // 56 // Per ProxyAutoConfig::Init, this data must be ASCII. 57 58 static const char sAsciiPacUtils[] = 59 #include "ascii_pac_utils.inc" 60 ; 61 62 // sRunning is defined for the helper functions only while the 63 // Javascript engine is running and the PAC object cannot be deleted 64 // or reset. 65 static Atomic<uint32_t, Relaxed>& RunningIndex() { 66 static Atomic<uint32_t, Relaxed> sRunningIndex(0xdeadbeef); 67 return sRunningIndex; 68 } 69 static ProxyAutoConfig* GetRunning() { 70 MOZ_ASSERT(RunningIndex() != 0xdeadbeef); 71 return static_cast<ProxyAutoConfig*>(PR_GetThreadPrivate(RunningIndex())); 72 } 73 74 static void SetRunning(ProxyAutoConfig* arg) { 75 MOZ_ASSERT(RunningIndex() != 0xdeadbeef); 76 MOZ_DIAGNOSTIC_ASSERT_IF(!arg, GetRunning() != nullptr); 77 MOZ_DIAGNOSTIC_ASSERT_IF(arg, GetRunning() == nullptr); 78 PR_SetThreadPrivate(RunningIndex(), arg); 79 } 80 81 // The PACResolver is used for dnsResolve() 82 class PACResolver final : public nsIDNSListener, 83 public nsITimerCallback, 84 public nsINamed { 85 public: 86 NS_DECL_THREADSAFE_ISUPPORTS 87 88 explicit PACResolver(nsIEventTarget* aTarget) 89 : mStatus(NS_ERROR_FAILURE), 90 mMainThreadEventTarget(aTarget), 91 mMutex("PACResolver::Mutex") {} 92 93 // nsIDNSListener 94 NS_IMETHOD OnLookupComplete(nsICancelable* request, nsIDNSRecord* record, 95 nsresult status) override { 96 nsCOMPtr<nsITimer> timer; 97 { 98 MutexAutoLock lock(mMutex); 99 timer.swap(mTimer); 100 mRequest = nullptr; 101 } 102 103 if (timer) { 104 timer->Cancel(); 105 } 106 107 mStatus = status; 108 mResponse = record; 109 return NS_OK; 110 } 111 112 // nsITimerCallback 113 NS_IMETHOD Notify(nsITimer* timer) override { 114 nsCOMPtr<nsICancelable> request; 115 { 116 MutexAutoLock lock(mMutex); 117 request.swap(mRequest); 118 mTimer = nullptr; 119 } 120 if (request) { 121 request->Cancel(NS_ERROR_NET_TIMEOUT); 122 } 123 return NS_OK; 124 } 125 126 // nsINamed 127 NS_IMETHOD GetName(nsACString& aName) override { 128 aName.AssignLiteral("PACResolver"); 129 return NS_OK; 130 } 131 132 nsresult mStatus; 133 nsCOMPtr<nsICancelable> mRequest; 134 nsCOMPtr<nsIDNSRecord> mResponse; 135 nsCOMPtr<nsITimer> mTimer; 136 nsCOMPtr<nsIEventTarget> mMainThreadEventTarget; 137 Mutex mMutex MOZ_UNANNOTATED; 138 139 private: 140 ~PACResolver() = default; 141 }; 142 NS_IMPL_ISUPPORTS(PACResolver, nsIDNSListener, nsITimerCallback, nsINamed) 143 144 static void PACLogToConsole(nsString& aMessage) { 145 if (XRE_IsSocketProcess()) { 146 auto task = [message(aMessage)]() { 147 SocketProcessChild* child = SocketProcessChild::GetSingleton(); 148 if (child) { 149 (void)child->SendOnConsoleMessage(message); 150 } 151 }; 152 if (NS_IsMainThread()) { 153 task(); 154 } else { 155 NS_DispatchToMainThread(NS_NewRunnableFunction("PACLogToConsole", task)); 156 } 157 return; 158 } 159 160 nsCOMPtr<nsIConsoleService> consoleService = 161 do_GetService(NS_CONSOLESERVICE_CONTRACTID); 162 if (!consoleService) return; 163 164 consoleService->LogStringMessage(aMessage.get()); 165 } 166 167 // Javascript errors and warnings are logged to the main error console 168 static void PACLogErrorOrWarning(const nsAString& aKind, 169 JSErrorReport* aReport) { 170 nsString formattedMessage(u"PAC Execution "_ns); 171 formattedMessage += aKind; 172 formattedMessage += u": "_ns; 173 if (aReport->message()) { 174 formattedMessage.Append(NS_ConvertUTF8toUTF16(aReport->message().c_str())); 175 } 176 formattedMessage += u" ["_ns; 177 formattedMessage.Append(aReport->linebuf(), aReport->linebufLength()); 178 formattedMessage += u"]"_ns; 179 PACLogToConsole(formattedMessage); 180 } 181 182 static void PACWarningReporter(JSContext* aCx, JSErrorReport* aReport) { 183 MOZ_ASSERT(aReport); 184 MOZ_ASSERT(aReport->isWarning()); 185 186 PACLogErrorOrWarning(u"Warning"_ns, aReport); 187 } 188 189 class MOZ_STACK_CLASS AutoPACErrorReporter { 190 JSContext* mCx; 191 192 public: 193 explicit AutoPACErrorReporter(JSContext* aCx) : mCx(aCx) {} 194 ~AutoPACErrorReporter() { 195 if (!JS_IsExceptionPending(mCx)) { 196 return; 197 } 198 JS::ExceptionStack exnStack(mCx); 199 if (!JS::StealPendingExceptionStack(mCx, &exnStack)) { 200 return; 201 } 202 203 JS::ErrorReportBuilder report(mCx); 204 if (!report.init(mCx, exnStack, JS::ErrorReportBuilder::WithSideEffects)) { 205 JS_ClearPendingException(mCx); 206 return; 207 } 208 209 PACLogErrorOrWarning(u"Error"_ns, report.report()); 210 } 211 }; 212 213 // timeout of 0 means the normal necko timeout strategy, otherwise the dns 214 // request will be canceled after aTimeout milliseconds 215 static bool PACResolve(const nsACString& aHostName, NetAddr* aNetAddr, 216 unsigned int aTimeout) { 217 if (!GetRunning()) { 218 NS_WARNING("PACResolve without a running ProxyAutoConfig object"); 219 return false; 220 } 221 222 return GetRunning()->ResolveAddress(aHostName, aNetAddr, aTimeout); 223 } 224 225 ProxyAutoConfig::ProxyAutoConfig() 226 227 { 228 MOZ_COUNT_CTOR(ProxyAutoConfig); 229 } 230 231 bool ProxyAutoConfig::ResolveAddress(const nsACString& aHostName, 232 NetAddr* aNetAddr, unsigned int aTimeout) { 233 nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID); 234 if (!dns) return false; 235 236 RefPtr<PACResolver> helper = new PACResolver(mMainThreadEventTarget); 237 OriginAttributes attrs; 238 239 // When the PAC script attempts to resolve a domain, we must make sure we 240 // don't use TRR, otherwise the TRR channel might also attempt to resolve 241 // a name and we'll have a deadlock. 242 nsIDNSService::DNSFlags flags = 243 nsIDNSService::RESOLVE_PRIORITY_MEDIUM | 244 nsIDNSService::GetFlagsFromTRRMode(nsIRequest::TRR_DISABLED_MODE); 245 246 if (NS_FAILED(dns->AsyncResolveNative( 247 aHostName, nsIDNSService::RESOLVE_TYPE_DEFAULT, flags, nullptr, 248 helper, GetCurrentSerialEventTarget(), attrs, 249 getter_AddRefs(helper->mRequest)))) { 250 return false; 251 } 252 253 if (aTimeout && helper->mRequest) { 254 if (!mTimer) mTimer = NS_NewTimer(); 255 if (mTimer) { 256 mTimer->SetTarget(mMainThreadEventTarget); 257 mTimer->InitWithCallback(helper, aTimeout, nsITimer::TYPE_ONE_SHOT); 258 helper->mTimer = mTimer; 259 } 260 } 261 262 // Spin the event loop of the pac thread until lookup is complete. 263 // nsPACman is responsible for keeping a queue and only allowing 264 // one PAC execution at a time even when it is called re-entrantly. 265 SpinEventLoopUntil("ProxyAutoConfig::ResolveAddress"_ns, [&, helper, this]() { 266 if (!helper->mRequest) { 267 return true; 268 } 269 if (this->mShutdown) { 270 NS_WARNING("mShutdown set with PAC request not cancelled"); 271 MOZ_ASSERT(NS_FAILED(helper->mStatus)); 272 return true; 273 } 274 return false; 275 }); 276 277 if (NS_FAILED(helper->mStatus)) { 278 return false; 279 } 280 281 nsCOMPtr<nsIDNSAddrRecord> rec = do_QueryInterface(helper->mResponse); 282 return !(!rec || NS_FAILED(rec->GetNextAddr(0, aNetAddr))); 283 } 284 285 static bool PACResolveToString(const nsACString& aHostName, 286 nsCString& aDottedDecimal, 287 unsigned int aTimeout) { 288 NetAddr netAddr; 289 if (!PACResolve(aHostName, &netAddr, aTimeout)) return false; 290 291 char dottedDecimal[128]; 292 if (!netAddr.ToStringBuffer(dottedDecimal, sizeof(dottedDecimal))) { 293 return false; 294 } 295 296 aDottedDecimal.Assign(dottedDecimal); 297 return true; 298 } 299 300 // dnsResolve(host) javascript implementation 301 static bool PACDnsResolve(JSContext* cx, unsigned int argc, JS::Value* vp) { 302 JS::CallArgs args = CallArgsFromVp(argc, vp); 303 304 if (NS_IsMainThread()) { 305 NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?"); 306 return false; 307 } 308 309 if (!args.requireAtLeast(cx, "dnsResolve", 1)) return false; 310 311 // Previously we didn't check the type of the argument, so just converted it 312 // to string. A badly written PAC file oculd pass null or undefined here 313 // which could lead to odd results if there are any hosts called "null" 314 // on the network. See bug 1724345 comment 6. 315 if (!args[0].isString()) { 316 args.rval().setNull(); 317 return true; 318 } 319 320 JS::Rooted<JSString*> arg1(cx); 321 arg1 = args[0].toString(); 322 323 nsAutoJSString hostName; 324 nsAutoCString dottedDecimal; 325 326 if (!hostName.init(cx, arg1)) return false; 327 if (PACResolveToString(NS_ConvertUTF16toUTF8(hostName), dottedDecimal, 0)) { 328 JSString* dottedDecimalString = JS_NewStringCopyZ(cx, dottedDecimal.get()); 329 if (!dottedDecimalString) { 330 return false; 331 } 332 333 args.rval().setString(dottedDecimalString); 334 } else { 335 args.rval().setNull(); 336 } 337 338 return true; 339 } 340 341 // myIpAddress() javascript implementation 342 static bool PACMyIpAddress(JSContext* cx, unsigned int argc, JS::Value* vp) { 343 JS::CallArgs args = JS::CallArgsFromVp(argc, vp); 344 345 if (NS_IsMainThread()) { 346 NS_WARNING("DNS Resolution From PAC on Main Thread. How did that happen?"); 347 return false; 348 } 349 350 if (!GetRunning()) { 351 NS_WARNING("PAC myIPAddress without a running ProxyAutoConfig object"); 352 return false; 353 } 354 355 return GetRunning()->MyIPAddress(args); 356 } 357 358 // proxyAlert(msg) javascript implementation 359 static bool PACProxyAlert(JSContext* cx, unsigned int argc, JS::Value* vp) { 360 JS::CallArgs args = CallArgsFromVp(argc, vp); 361 362 if (!args.requireAtLeast(cx, "alert", 1)) return false; 363 364 JS::Rooted<JSString*> arg1(cx, JS::ToString(cx, args[0])); 365 if (!arg1) return false; 366 367 nsAutoJSString message; 368 if (!message.init(cx, arg1)) return false; 369 370 nsAutoString alertMessage; 371 alertMessage.AssignLiteral(u"PAC-alert: "); 372 alertMessage.Append(message); 373 PACLogToConsole(alertMessage); 374 375 args.rval().setUndefined(); /* return undefined */ 376 return true; 377 } 378 379 static const JSFunctionSpec PACGlobalFunctions[] = { 380 JS_FN("dnsResolve", PACDnsResolve, 1, 0), 381 382 // a global "var pacUseMultihomedDNS = true;" will change behavior 383 // of myIpAddress to actively use DNS 384 JS_FN("myIpAddress", PACMyIpAddress, 0, 0), 385 JS_FN("alert", PACProxyAlert, 1, 0), JS_FS_END}; 386 387 // JSContextWrapper is a c++ object that manages the context for the JS engine 388 // used on the PAC thread. It is initialized and destroyed on the PAC thread. 389 class JSContextWrapper { 390 public: 391 static JSContextWrapper* Create(uint32_t aExtraHeapSize) { 392 JSContext* cx = JS_NewContext(JS::DefaultHeapMaxBytes + aExtraHeapSize); 393 if (NS_WARN_IF(!cx)) return nullptr; 394 395 // PAC scripts are user-provided scripts that run in the parent process. 396 // Disable Ion because we don't require it and it reduces attack surface. 397 // Disable security checks because we cannot enforce restrictions on these 398 // scripts. 399 JS::ContextOptionsRef(cx) 400 .setDisableIon() 401 .setDisableEvalSecurityChecks() 402 .setDisableFilenameSecurityChecks(); 403 404 JSContextWrapper* entry = new JSContextWrapper(cx); 405 if (NS_FAILED(entry->Init())) { 406 delete entry; 407 return nullptr; 408 } 409 410 return entry; 411 } 412 413 JSContext* Context() const { return mContext; } 414 415 JSObject* Global() const { return mGlobal; } 416 417 ~JSContextWrapper() { 418 mGlobal = nullptr; 419 420 MOZ_COUNT_DTOR(JSContextWrapper); 421 422 if (mContext) { 423 JS_DestroyContext(mContext); 424 } 425 } 426 427 void SetOK() { mOK = true; } 428 429 bool IsOK() { return mOK; } 430 431 private: 432 JSContext* mContext; 433 JS::PersistentRooted<JSObject*> mGlobal; 434 bool mOK; 435 436 static const JSClass sGlobalClass; 437 438 explicit JSContextWrapper(JSContext* cx) 439 : mContext(cx), mGlobal(cx, nullptr), mOK(false) { 440 MOZ_COUNT_CTOR(JSContextWrapper); 441 } 442 443 nsresult Init() { 444 /* 445 * Not setting this will cause JS_CHECK_RECURSION to report false 446 * positives 447 */ 448 JS_SetNativeStackQuota(mContext, 128 * sizeof(size_t) * 1024); 449 450 JS::SetWarningReporter(mContext, PACWarningReporter); 451 452 // When available, set the self-hosted shared memory to be read, so that 453 // we can decode the self-hosted content instead of parsing it. 454 { 455 auto& shm = xpc::SelfHostedShmem::GetSingleton(); 456 JS::SelfHostedCache selfHostedContent = shm.Content(); 457 458 if (!JS::InitSelfHostedCode(mContext, selfHostedContent)) { 459 return NS_ERROR_OUT_OF_MEMORY; 460 } 461 } 462 463 JS::RealmOptions options; 464 options.creationOptions().setNewCompartmentInSystemZone(); 465 options.behaviors() 466 .setClampAndJitterTime(false) 467 .setReduceTimerPrecisionCallerType( 468 RTPCallerTypeToToken(RTPCallerType::Normal)); 469 mGlobal = JS_NewGlobalObject(mContext, &sGlobalClass, nullptr, 470 JS::DontFireOnNewGlobalHook, options); 471 if (!mGlobal) { 472 JS_ClearPendingException(mContext); 473 return NS_ERROR_OUT_OF_MEMORY; 474 } 475 JS::Rooted<JSObject*> global(mContext, mGlobal); 476 477 JSAutoRealm ar(mContext, global); 478 AutoPACErrorReporter aper(mContext); 479 if (!JS_DefineFunctions(mContext, global, PACGlobalFunctions)) { 480 return NS_ERROR_FAILURE; 481 } 482 483 JS_FireOnNewGlobalObject(mContext, global); 484 485 return NS_OK; 486 } 487 }; 488 489 const JSClass JSContextWrapper::sGlobalClass = {"PACResolutionThreadGlobal", 490 JSCLASS_GLOBAL_FLAGS, 491 &JS::DefaultGlobalClassOps}; 492 493 void ProxyAutoConfig::SetThreadLocalIndex(uint32_t index) { 494 RunningIndex() = index; 495 } 496 497 nsresult ProxyAutoConfig::ConfigurePAC(const nsACString& aPACURI, 498 const nsACString& aPACScriptData, 499 bool aIncludePath, 500 uint32_t aExtraHeapSize, 501 nsISerialEventTarget* aEventTarget) { 502 mShutdown = false; // Shutdown needs to be called prior to destruction 503 504 mPACURI = aPACURI; 505 506 // The full PAC script data is the concatenation of 1) the various functions 507 // exposed to PAC scripts in |sAsciiPacUtils| and 2) the user-provided PAC 508 // script data. Historically this was single-byte Latin-1 text (usually just 509 // ASCII, but bug 296163 has a real-world Latin-1 example). We now support 510 // UTF-8 if the full data validates as UTF-8, before falling back to Latin-1. 511 // (Technically this is a breaking change: intentional Latin-1 scripts that 512 // happen to be valid UTF-8 may have different behavior. We assume such cases 513 // are vanishingly rare.) 514 // 515 // Supporting both UTF-8 and Latin-1 requires that the functions exposed to 516 // PAC scripts be both UTF-8- and Latin-1-compatible: that is, they must be 517 // ASCII. 518 mConcatenatedPACData = sAsciiPacUtils; 519 if (!mConcatenatedPACData.Append(aPACScriptData, mozilla::fallible)) { 520 return NS_ERROR_OUT_OF_MEMORY; 521 } 522 523 mIncludePath = aIncludePath; 524 mExtraHeapSize = aExtraHeapSize; 525 mMainThreadEventTarget = aEventTarget; 526 527 if (!GetRunning()) return SetupJS(); 528 529 mJSNeedsSetup = true; 530 return NS_OK; 531 } 532 533 nsresult ProxyAutoConfig::SetupJS() { 534 mJSNeedsSetup = false; 535 MOZ_DIAGNOSTIC_ASSERT(!GetRunning(), "JIT is running"); 536 if (GetRunning()) { 537 return NS_ERROR_ALREADY_INITIALIZED; 538 } 539 540 #if defined(XP_MACOSX) 541 nsMacUtilsImpl::EnableTCSMIfAvailable(); 542 #endif 543 544 delete mJSContext; 545 mJSContext = nullptr; 546 547 if (mConcatenatedPACData.IsEmpty()) return NS_ERROR_FAILURE; 548 549 NS_GetCurrentThread()->SetCanInvokeJS(true); 550 551 mJSContext = JSContextWrapper::Create(mExtraHeapSize); 552 if (!mJSContext) return NS_ERROR_FAILURE; 553 554 JSContext* cx = mJSContext->Context(); 555 JSAutoRealm ar(cx, mJSContext->Global()); 556 AutoPACErrorReporter aper(cx); 557 558 // check if this is a data: uri so that we don't spam the js console with 559 // huge meaningless strings. this is not on the main thread, so it can't 560 // use nsIURI scheme methods 561 bool isDataURI = 562 nsDependentCSubstring(mPACURI, 0, 5).LowerCaseEqualsASCII("data:", 5); 563 564 SetRunning(this); 565 566 JS::Rooted<JSObject*> global(cx, mJSContext->Global()); 567 568 auto CompilePACScript = [this](JSContext* cx) -> JSScript* { 569 JS::CompileOptions options(cx); 570 options.setSkipFilenameValidation(true); 571 options.setFileAndLine(this->mPACURI.get(), 1); 572 573 // Per ProxyAutoConfig::Init, compile as UTF-8 if the full data is UTF-8, 574 // and otherwise inflate Latin-1 to UTF-16 and compile that. 575 const char* scriptData = this->mConcatenatedPACData.get(); 576 size_t scriptLength = this->mConcatenatedPACData.Length(); 577 if (mozilla::IsUtf8(mozilla::Span(scriptData, scriptLength))) { 578 JS::SourceText<Utf8Unit> srcBuf; 579 if (!srcBuf.init(cx, scriptData, scriptLength, 580 JS::SourceOwnership::Borrowed)) { 581 return nullptr; 582 } 583 584 return JS::Compile(cx, options, srcBuf); 585 } 586 587 // nsReadableUtils.h says that "ASCII" is a misnomer "for legacy reasons", 588 // and this handles not just ASCII but Latin-1 too. 589 NS_ConvertASCIItoUTF16 inflated(this->mConcatenatedPACData); 590 591 JS::SourceText<char16_t> source; 592 if (!source.init(cx, inflated.get(), inflated.Length(), 593 JS::SourceOwnership::Borrowed)) { 594 return nullptr; 595 } 596 597 return JS::Compile(cx, options, source); 598 }; 599 600 JS::Rooted<JSScript*> script(cx, CompilePACScript(cx)); 601 if (!script || !JS_ExecuteScript(cx, script)) { 602 nsString alertMessage(u"PAC file failed to install from "_ns); 603 if (isDataURI) { 604 alertMessage += u"data: URI"_ns; 605 } else { 606 alertMessage += NS_ConvertUTF8toUTF16(mPACURI); 607 } 608 PACLogToConsole(alertMessage); 609 SetRunning(nullptr); 610 return NS_ERROR_FAILURE; 611 } 612 SetRunning(nullptr); 613 614 mJSContext->SetOK(); 615 nsString alertMessage(u"PAC file installed from "_ns); 616 if (isDataURI) { 617 alertMessage += u"data: URI"_ns; 618 } else { 619 alertMessage += NS_ConvertUTF8toUTF16(mPACURI); 620 } 621 PACLogToConsole(alertMessage); 622 623 // we don't need these now 624 mConcatenatedPACData.Truncate(); 625 mPACURI.Truncate(); 626 627 return NS_OK; 628 } 629 630 void ProxyAutoConfig::GetProxyForURIWithCallback( 631 const nsACString& aTestURI, const nsACString& aTestHost, 632 std::function<void(nsresult aStatus, const nsACString& aResult)>&& 633 aCallback) { 634 nsAutoCString result; 635 nsresult status = GetProxyForURI(aTestURI, aTestHost, result); 636 aCallback(status, result); 637 } 638 639 nsresult ProxyAutoConfig::GetProxyForURI(const nsACString& aTestURI, 640 const nsACString& aTestHost, 641 nsACString& result) { 642 if (mJSNeedsSetup) SetupJS(); 643 644 if (!mJSContext || !mJSContext->IsOK()) return NS_ERROR_NOT_AVAILABLE; 645 646 JSContext* cx = mJSContext->Context(); 647 JSAutoRealm ar(cx, mJSContext->Global()); 648 AutoPACErrorReporter aper(cx); 649 650 // the sRunning flag keeps a new PAC file from being installed 651 // while the event loop is spinning on a DNS function. Don't early return. 652 SetRunning(this); 653 mRunningHost = aTestHost; 654 655 nsresult rv = NS_ERROR_FAILURE; 656 nsCString clensedURI(aTestURI); 657 658 if (!mIncludePath) { 659 nsCOMPtr<nsIURLParser> urlParser = 660 do_GetService(NS_STDURLPARSER_CONTRACTID); 661 int32_t pathLen = 0; 662 if (urlParser) { 663 uint32_t schemePos; 664 int32_t schemeLen; 665 uint32_t authorityPos; 666 int32_t authorityLen; 667 uint32_t pathPos; 668 rv = urlParser->ParseURL(aTestURI.BeginReading(), aTestURI.Length(), 669 &schemePos, &schemeLen, &authorityPos, 670 &authorityLen, &pathPos, &pathLen); 671 } 672 if (NS_SUCCEEDED(rv)) { 673 if (pathLen) { 674 // cut off the path but leave the initial slash 675 pathLen--; 676 } 677 aTestURI.Left(clensedURI, aTestURI.Length() - pathLen); 678 } 679 } 680 681 JS::Rooted<JSString*> uriString( 682 cx, 683 JS_NewStringCopyN(cx, clensedURI.BeginReading(), clensedURI.Length())); 684 JS::Rooted<JSString*> hostString( 685 cx, JS_NewStringCopyN(cx, aTestHost.BeginReading(), aTestHost.Length())); 686 687 if (uriString && hostString) { 688 JS::RootedValueArray<2> args(cx); 689 args[0].setString(uriString); 690 args[1].setString(hostString); 691 692 JS::Rooted<JS::Value> rval(cx); 693 JS::Rooted<JSObject*> global(cx, mJSContext->Global()); 694 bool ok = JS_CallFunctionName(cx, global, "FindProxyForURL", args, &rval); 695 696 if (ok && rval.isString()) { 697 nsAutoJSString pacString; 698 if (pacString.init(cx, rval.toString())) { 699 CopyUTF16toUTF8(pacString, result); 700 rv = NS_OK; 701 } 702 } 703 } 704 705 mRunningHost.Truncate(); 706 SetRunning(nullptr); 707 return rv; 708 } 709 710 void ProxyAutoConfig::GC() { 711 if (!mJSContext || !mJSContext->IsOK()) return; 712 713 JSAutoRealm ar(mJSContext->Context(), mJSContext->Global()); 714 JS_MaybeGC(mJSContext->Context()); 715 } 716 717 ProxyAutoConfig::~ProxyAutoConfig() { 718 MOZ_COUNT_DTOR(ProxyAutoConfig); 719 MOZ_ASSERT(mShutdown, "Shutdown must be called before dtor."); 720 NS_ASSERTION(!mJSContext, 721 "~ProxyAutoConfig leaking JS context that " 722 "should have been deleted on pac thread"); 723 } 724 725 void ProxyAutoConfig::Shutdown() { 726 MOZ_ASSERT(!NS_IsMainThread(), "wrong thread for shutdown"); 727 728 if (NS_WARN_IF(GetRunning()) || mShutdown) { 729 return; 730 } 731 732 mShutdown = true; 733 delete mJSContext; 734 mJSContext = nullptr; 735 } 736 737 bool ProxyAutoConfig::SrcAddress(const NetAddr* remoteAddress, 738 nsCString& localAddress) { 739 PRFileDesc* fd; 740 fd = PR_OpenUDPSocket(remoteAddress->raw.family); 741 if (!fd) return false; 742 743 PRNetAddr prRemoteAddress; 744 NetAddrToPRNetAddr(remoteAddress, &prRemoteAddress); 745 if (PR_Connect(fd, &prRemoteAddress, 0) != PR_SUCCESS) { 746 PR_Close(fd); 747 return false; 748 } 749 750 PRNetAddr localName; 751 if (PR_GetSockName(fd, &localName) != PR_SUCCESS) { 752 PR_Close(fd); 753 return false; 754 } 755 756 PR_Close(fd); 757 758 char dottedDecimal[128]; 759 if (PR_NetAddrToString(&localName, dottedDecimal, sizeof(dottedDecimal)) != 760 PR_SUCCESS) { 761 return false; 762 } 763 764 localAddress.Assign(dottedDecimal); 765 766 return true; 767 } 768 769 // hostName is run through a dns lookup and then a udp socket is connected 770 // to the result. If that all works, the local IP address of the socket is 771 // returned to the javascript caller and |*aResult| is set to true. Otherwise 772 // |*aResult| is set to false. 773 bool ProxyAutoConfig::MyIPAddressTryHost(const nsACString& hostName, 774 unsigned int timeout, 775 const JS::CallArgs& aArgs, 776 bool* aResult) { 777 *aResult = false; 778 779 NetAddr remoteAddress; 780 nsAutoCString localDottedDecimal; 781 JSContext* cx = mJSContext->Context(); 782 783 if (PACResolve(hostName, &remoteAddress, timeout) && 784 SrcAddress(&remoteAddress, localDottedDecimal)) { 785 JSString* dottedDecimalString = 786 JS_NewStringCopyZ(cx, localDottedDecimal.get()); 787 if (!dottedDecimalString) { 788 return false; 789 } 790 791 *aResult = true; 792 aArgs.rval().setString(dottedDecimalString); 793 } 794 return true; 795 } 796 797 bool ProxyAutoConfig::MyIPAddress(const JS::CallArgs& aArgs) { 798 nsAutoCString remoteDottedDecimal; 799 nsAutoCString localDottedDecimal; 800 JSContext* cx = mJSContext->Context(); 801 JS::Rooted<JS::Value> v(cx); 802 JS::Rooted<JSObject*> global(cx, mJSContext->Global()); 803 804 bool useMultihomedDNS = 805 JS_GetProperty(cx, global, "pacUseMultihomedDNS", &v) && 806 !v.isUndefined() && ToBoolean(v); 807 808 // first, lookup the local address of a socket connected 809 // to the host of uri being resolved by the pac file. This is 810 // v6 safe.. but is the last step like that 811 bool rvalAssigned = false; 812 if (useMultihomedDNS) { 813 if (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) || 814 rvalAssigned) { 815 return rvalAssigned; 816 } 817 } else { 818 // we can still do the fancy multi homing thing if the host is a literal 819 if (HostIsIPLiteral(mRunningHost) && 820 (!MyIPAddressTryHost(mRunningHost, kTimeout, aArgs, &rvalAssigned) || 821 rvalAssigned)) { 822 return rvalAssigned; 823 } 824 } 825 826 // next, look for a route to a public internet address that doesn't need DNS. 827 // This is the google anycast dns address, but it doesn't matter if it 828 // remains operable (as we don't contact it) as long as the address stays 829 // in commonly routed IP address space. 830 remoteDottedDecimal.AssignLiteral("8.8.8.8"); 831 if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) || 832 rvalAssigned) { 833 return rvalAssigned; 834 } 835 836 // finally, use the old algorithm based on the local hostname 837 nsAutoCString hostName; 838 nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID); 839 // without multihomedDNS use such a short timeout that we are basically 840 // just looking at the cache for raw dotted decimals 841 uint32_t timeout = useMultihomedDNS ? kTimeout : 1; 842 if (dns && NS_SUCCEEDED(dns->GetMyHostName(hostName)) && 843 PACResolveToString(hostName, localDottedDecimal, timeout)) { 844 JSString* dottedDecimalString = 845 JS_NewStringCopyZ(cx, localDottedDecimal.get()); 846 if (!dottedDecimalString) { 847 return false; 848 } 849 850 aArgs.rval().setString(dottedDecimalString); 851 return true; 852 } 853 854 // next try a couple RFC 1918 variants.. maybe there is a 855 // local route 856 remoteDottedDecimal.AssignLiteral("192.168.0.1"); 857 if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) || 858 rvalAssigned) { 859 return rvalAssigned; 860 } 861 862 // more RFC 1918 863 remoteDottedDecimal.AssignLiteral("10.0.0.1"); 864 if (!MyIPAddressTryHost(remoteDottedDecimal, 0, aArgs, &rvalAssigned) || 865 rvalAssigned) { 866 return rvalAssigned; 867 } 868 869 // who knows? let's fallback to localhost 870 localDottedDecimal.AssignLiteral("127.0.0.1"); 871 JSString* dottedDecimalString = 872 JS_NewStringCopyZ(cx, localDottedDecimal.get()); 873 if (!dottedDecimalString) { 874 return false; 875 } 876 877 aArgs.rval().setString(dottedDecimalString); 878 return true; 879 } 880 881 RemoteProxyAutoConfig::RemoteProxyAutoConfig() = default; 882 883 RemoteProxyAutoConfig::~RemoteProxyAutoConfig() = default; 884 885 nsresult RemoteProxyAutoConfig::Init(nsIThread* aPACThread) { 886 MOZ_ASSERT(NS_IsMainThread()); 887 888 RefPtr<SocketProcessParent> socketProcessParent = 889 SocketProcessParent::GetSingleton(); 890 if (!socketProcessParent) { 891 return NS_ERROR_NOT_AVAILABLE; 892 } 893 894 ipc::Endpoint<PProxyAutoConfigParent> parent; 895 ipc::Endpoint<PProxyAutoConfigChild> child; 896 nsresult rv = PProxyAutoConfig::CreateEndpoints( 897 ipc::EndpointProcInfo::Current(), 898 socketProcessParent->OtherEndpointProcInfo(), &parent, &child); 899 if (NS_FAILED(rv)) { 900 return rv; 901 } 902 903 (void)socketProcessParent->SendInitProxyAutoConfigChild(std::move(child)); 904 mProxyAutoConfigParent = new ProxyAutoConfigParent(); 905 return aPACThread->Dispatch( 906 NS_NewRunnableFunction("ProxyAutoConfigParent::ProxyAutoConfigParent", 907 [proxyAutoConfigParent(mProxyAutoConfigParent), 908 endpoint{std::move(parent)}]() mutable { 909 proxyAutoConfigParent->Init(std::move(endpoint)); 910 })); 911 } 912 913 nsresult RemoteProxyAutoConfig::ConfigurePAC(const nsACString& aPACURI, 914 const nsACString& aPACScriptData, 915 bool aIncludePath, 916 uint32_t aExtraHeapSize, 917 nsISerialEventTarget*) { 918 (void)mProxyAutoConfigParent->SendConfigurePAC(aPACURI, aPACScriptData, 919 aIncludePath, aExtraHeapSize); 920 return NS_OK; 921 } 922 923 void RemoteProxyAutoConfig::Shutdown() { mProxyAutoConfigParent->Close(); } 924 925 void RemoteProxyAutoConfig::GC() { 926 // Do nothing. GC would be performed when there is not pending query in socket 927 // process. 928 } 929 930 void RemoteProxyAutoConfig::GetProxyForURIWithCallback( 931 const nsACString& aTestURI, const nsACString& aTestHost, 932 std::function<void(nsresult aStatus, const nsACString& aResult)>&& 933 aCallback) { 934 if (!mProxyAutoConfigParent->CanSend()) { 935 return; 936 } 937 938 mProxyAutoConfigParent->SendGetProxyForURI( 939 aTestURI, aTestHost, 940 [aCallback](std::tuple<nsresult, nsCString>&& aResult) { 941 auto [status, result] = aResult; 942 aCallback(status, result); 943 }, 944 [aCallback](mozilla::ipc::ResponseRejectReason&& aReason) { 945 aCallback(NS_ERROR_FAILURE, ""_ns); 946 }); 947 } 948 949 } // namespace net 950 } // namespace mozilla