ExtensionProtocolHandler.cpp (32988B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 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 file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "ExtensionProtocolHandler.h" 8 9 #include "mozilla/BinarySearch.h" 10 #include "mozilla/Components.h" 11 #include "mozilla/ClearOnShutdown.h" 12 #include "mozilla/dom/Promise.h" 13 #include "mozilla/dom/Promise-inl.h" 14 #include "mozilla/ExtensionPolicyService.h" 15 #include "mozilla/FileUtils.h" 16 #include "mozilla/ipc/FileDescriptor.h" 17 #include "mozilla/ipc/IPCStreamUtils.h" 18 #include "mozilla/ipc/URIUtils.h" 19 #include "mozilla/net/NeckoChild.h" 20 #include "mozilla/Omnijar.h" 21 #include "mozilla/RefPtr.h" 22 #include "mozilla/ResultExtensions.h" 23 #include "mozilla/Try.h" 24 25 #include "FileDescriptorFile.h" 26 #include "LoadInfo.h" 27 #include "nsContentUtils.h" 28 #include "nsServiceManagerUtils.h" 29 #include "nsDirectoryServiceDefs.h" 30 #include "nsICancelable.h" 31 #include "nsIFile.h" 32 #include "nsIFileChannel.h" 33 #include "nsIFileStreams.h" 34 #include "nsIFileURL.h" 35 #include "nsIJARChannel.h" 36 #include "nsIMIMEService.h" 37 #include "nsIURL.h" 38 #include "nsIChannel.h" 39 #include "nsIInputStreamPump.h" 40 #include "nsIJARURI.h" 41 #include "nsIStreamListener.h" 42 #include "nsIInputStream.h" 43 #include "nsIStreamConverterService.h" 44 #include "nsNetUtil.h" 45 #include "nsReadableUtils.h" 46 #include "nsURLHelper.h" 47 #include "prio.h" 48 #include "SimpleChannel.h" 49 50 #if defined(XP_WIN) 51 # include "nsILocalFileWin.h" 52 # include "WinUtils.h" 53 #endif 54 55 #if defined(XP_MACOSX) 56 # include "nsMacUtilsImpl.h" 57 #endif 58 59 #define EXTENSION_SCHEME "moz-extension" 60 using mozilla::dom::Promise; 61 using mozilla::ipc::FileDescriptor; 62 63 // A list of file extensions containing purely static data, which can be loaded 64 // from an extension before the extension is fully ready. The main purpose of 65 // this is to allow image resources from theme XPIs to load quickly during 66 // browser startup. 67 // 68 // The layout of this array is chosen in order to prevent the need for runtime 69 // relocation, which an array of char* pointers would require. It also has the 70 // benefit of being more compact when the difference in length between the 71 // longest and average string is less than 8 bytes. The length of the 72 // char[] array must match the size of the longest entry in the list. 73 // 74 // This list must be kept sorted. 75 static const char sStaticFileExtensions[][5] = { 76 // clang-format off 77 "bmp", 78 "gif", 79 "ico", 80 "jpeg", 81 "jpg", 82 "png", 83 "svg", 84 // clang-format on 85 }; 86 87 namespace mozilla { 88 89 namespace net { 90 91 using extensions::URLInfo; 92 93 LazyLogModule gExtProtocolLog("ExtProtocol"); 94 #undef LOG 95 #define LOG(...) MOZ_LOG(gExtProtocolLog, LogLevel::Debug, (__VA_ARGS__)) 96 97 StaticRefPtr<ExtensionProtocolHandler> ExtensionProtocolHandler::sSingleton; 98 99 /** 100 * Helper class used with SimpleChannel to asynchronously obtain an input 101 * stream or file descriptor from the parent for a remote moz-extension load 102 * from the child. 103 */ 104 class ExtensionStreamGetter final : public nsICancelable { 105 NS_DECL_ISUPPORTS 106 NS_DECL_NSICANCELABLE 107 108 public: 109 // To use when getting a remote input stream for a resource 110 // in an unpacked extension. 111 ExtensionStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo) 112 : mURI(aURI), mLoadInfo(aLoadInfo), mIsJarChannel(false) { 113 MOZ_ASSERT(aURI); 114 MOZ_ASSERT(aLoadInfo); 115 116 SetupEventTarget(); 117 } 118 119 // To use when getting an FD for a packed extension JAR file 120 // in order to load a resource. 121 ExtensionStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo, 122 already_AddRefed<nsIJARChannel>&& aJarChannel, 123 nsIFile* aJarFile) 124 : mURI(aURI), 125 mLoadInfo(aLoadInfo), 126 mJarChannel(std::move(aJarChannel)), 127 mJarFile(aJarFile), 128 mIsJarChannel(true) { 129 MOZ_ASSERT(aURI); 130 MOZ_ASSERT(aLoadInfo); 131 MOZ_ASSERT(mJarChannel); 132 MOZ_ASSERT(aJarFile); 133 134 SetupEventTarget(); 135 } 136 137 void SetupEventTarget() { 138 mMainThreadEventTarget = GetMainThreadSerialEventTarget(); 139 } 140 141 // Get an input stream or file descriptor from the parent asynchronously. 142 RequestOrReason GetAsync(nsIStreamListener* aListener, nsIChannel* aChannel); 143 144 // Handle an input stream being returned from the parent 145 void OnStream(already_AddRefed<nsIInputStream> aStream); 146 147 // Handle file descriptor being returned from the parent 148 void OnFD(const FileDescriptor& aFD); 149 150 static void CancelRequest(nsIStreamListener* aListener, nsIChannel* aChannel, 151 nsresult aResult); 152 153 private: 154 ~ExtensionStreamGetter() = default; 155 156 nsCOMPtr<nsIURI> mURI; 157 nsCOMPtr<nsILoadInfo> mLoadInfo; 158 nsCOMPtr<nsIJARChannel> mJarChannel; 159 nsCOMPtr<nsIInputStreamPump> mPump; 160 nsCOMPtr<nsIFile> mJarFile; 161 nsCOMPtr<nsIStreamListener> mListener; 162 nsCOMPtr<nsIChannel> mChannel; 163 nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget; 164 bool mIsJarChannel; 165 bool mCanceled{false}; 166 nsresult mStatus{NS_OK}; 167 }; 168 169 NS_IMPL_ISUPPORTS(ExtensionStreamGetter, nsICancelable) 170 171 class ExtensionJARFileOpener final : public nsISupports { 172 public: 173 ExtensionJARFileOpener(nsIFile* aFile, 174 NeckoParent::GetExtensionFDResolver& aResolve) 175 : mFile(aFile), mResolve(aResolve) { 176 MOZ_ASSERT(aFile); 177 MOZ_ASSERT(aResolve); 178 } 179 180 NS_IMETHOD OpenFile() { 181 MOZ_ASSERT(!NS_IsMainThread()); 182 AutoFDClose prFileDesc; 183 184 #if defined(XP_WIN) 185 nsresult rv; 186 nsCOMPtr<nsILocalFileWin> winFile = do_QueryInterface(mFile, &rv); 187 MOZ_ASSERT(winFile); 188 if (NS_SUCCEEDED(rv)) { 189 rv = winFile->OpenNSPRFileDescShareDelete(PR_RDONLY, 0, 190 getter_Transfers(prFileDesc)); 191 } 192 #else 193 nsresult rv = 194 mFile->OpenNSPRFileDesc(PR_RDONLY, 0, getter_Transfers(prFileDesc)); 195 #endif /* XP_WIN */ 196 197 if (NS_SUCCEEDED(rv)) { 198 mFD = FileDescriptor(FileDescriptor::PlatformHandleType( 199 PR_FileDesc2NativeHandle(prFileDesc.get()))); 200 } 201 202 nsCOMPtr<nsIRunnable> event = 203 mozilla::NewRunnableMethod("ExtensionJarFileFDResolver", this, 204 &ExtensionJARFileOpener::SendBackFD); 205 206 rv = NS_DispatchToMainThread(event, nsIEventTarget::DISPATCH_NORMAL); 207 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread"); 208 return NS_OK; 209 } 210 211 NS_IMETHOD SendBackFD() { 212 MOZ_ASSERT(NS_IsMainThread()); 213 mResolve(mFD); 214 mResolve = nullptr; 215 return NS_OK; 216 } 217 218 NS_DECL_THREADSAFE_ISUPPORTS 219 220 private: 221 virtual ~ExtensionJARFileOpener() = default; 222 223 nsCOMPtr<nsIFile> mFile; 224 NeckoParent::GetExtensionFDResolver mResolve; 225 FileDescriptor mFD; 226 }; 227 228 NS_IMPL_ISUPPORTS(ExtensionJARFileOpener, nsISupports) 229 230 // The amount of time, in milliseconds, that the file opener thread will remain 231 // allocated after it is used. This value chosen because to match other uses 232 // of LazyIdleThread. 233 #define DEFAULT_THREAD_TIMEOUT_MS 30000 234 235 // Request an FD or input stream from the parent. 236 RequestOrReason ExtensionStreamGetter::GetAsync(nsIStreamListener* aListener, 237 nsIChannel* aChannel) { 238 MOZ_ASSERT(IsNeckoChild()); 239 MOZ_ASSERT(mMainThreadEventTarget); 240 241 mListener = aListener; 242 mChannel = aChannel; 243 244 nsCOMPtr<nsICancelable> cancelableRequest(this); 245 246 RefPtr<ExtensionStreamGetter> self = this; 247 if (mIsJarChannel) { 248 // Request an FD for this moz-extension URI 249 gNeckoChild->SendGetExtensionFD(mURI)->Then( 250 mMainThreadEventTarget, __func__, 251 [self](const FileDescriptor& fd) { self->OnFD(fd); }, 252 [self](const mozilla::ipc::ResponseRejectReason) { 253 self->OnFD(FileDescriptor()); 254 }); 255 return RequestOrCancelable(WrapNotNull(cancelableRequest)); 256 } 257 258 // Request an input stream for this moz-extension URI 259 gNeckoChild->SendGetExtensionStream(mURI)->Then( 260 mMainThreadEventTarget, __func__, 261 [self](const RefPtr<nsIInputStream>& stream) { 262 self->OnStream(do_AddRef(stream)); 263 }, 264 [self](const mozilla::ipc::ResponseRejectReason) { 265 self->OnStream(nullptr); 266 }); 267 return RequestOrCancelable(WrapNotNull(cancelableRequest)); 268 } 269 270 // Called to cancel the ongoing async request. 271 NS_IMETHODIMP 272 ExtensionStreamGetter::Cancel(nsresult aStatus) { 273 if (mCanceled) { 274 return NS_OK; 275 } 276 277 mCanceled = true; 278 mStatus = aStatus; 279 280 if (mPump) { 281 mPump->CancelWithReason(aStatus, "ExtensionStreamGetter::Cancel"_ns); 282 mPump = nullptr; 283 } 284 285 if (mIsJarChannel && mJarChannel) { 286 mJarChannel->CancelWithReason(aStatus, "ExtensionStreamGetter::Cancel"_ns); 287 } 288 289 return NS_OK; 290 } 291 292 // static 293 void ExtensionStreamGetter::CancelRequest(nsIStreamListener* aListener, 294 nsIChannel* aChannel, 295 nsresult aResult) { 296 MOZ_ASSERT(aListener); 297 MOZ_ASSERT(aChannel); 298 299 aListener->OnStartRequest(aChannel); 300 aListener->OnStopRequest(aChannel, aResult); 301 aChannel->CancelWithReason(NS_BINDING_ABORTED, 302 "ExtensionStreamGetter::CancelRequest"_ns); 303 } 304 305 // Handle an input stream sent from the parent. 306 void ExtensionStreamGetter::OnStream(already_AddRefed<nsIInputStream> aStream) { 307 MOZ_ASSERT(IsNeckoChild()); 308 MOZ_ASSERT(mChannel); 309 MOZ_ASSERT(mListener); 310 MOZ_ASSERT(mMainThreadEventTarget); 311 312 nsCOMPtr<nsIInputStream> stream = std::move(aStream); 313 nsCOMPtr<nsIChannel> channel = std::move(mChannel); 314 315 // We must keep an owning reference to the listener 316 // until we pass it on to AsyncRead. 317 nsCOMPtr<nsIStreamListener> listener = std::move(mListener); 318 319 if (mCanceled) { 320 // The channel that has created this stream getter has been canceled. 321 CancelRequest(listener, channel, mStatus); 322 return; 323 } 324 325 if (!stream) { 326 // The parent didn't send us back a stream. 327 CancelRequest(listener, channel, NS_ERROR_FILE_ACCESS_DENIED); 328 return; 329 } 330 331 nsCOMPtr<nsIInputStreamPump> pump; 332 nsresult rv = NS_NewInputStreamPump(getter_AddRefs(pump), stream.forget(), 0, 333 0, false, mMainThreadEventTarget); 334 if (NS_FAILED(rv)) { 335 CancelRequest(listener, channel, rv); 336 return; 337 } 338 339 rv = pump->AsyncRead(listener); 340 if (NS_FAILED(rv)) { 341 CancelRequest(listener, channel, rv); 342 return; 343 } 344 345 mPump = pump; 346 } 347 348 // Handle an FD sent from the parent. 349 void ExtensionStreamGetter::OnFD(const FileDescriptor& aFD) { 350 MOZ_ASSERT(IsNeckoChild()); 351 MOZ_ASSERT(mChannel); 352 MOZ_ASSERT(mListener); 353 354 nsCOMPtr<nsIChannel> channel = std::move(mChannel); 355 356 // We must keep an owning reference to the listener 357 // until we pass it on to AsyncOpen. 358 nsCOMPtr<nsIStreamListener> listener = std::move(mListener); 359 360 if (mCanceled) { 361 // The channel that has created this stream getter has been canceled. 362 CancelRequest(listener, channel, mStatus); 363 return; 364 } 365 366 if (!aFD.IsValid()) { 367 // The parent didn't send us back a valid file descriptor. 368 CancelRequest(listener, channel, NS_ERROR_FILE_ACCESS_DENIED); 369 return; 370 } 371 372 RefPtr<FileDescriptorFile> fdFile = new FileDescriptorFile(aFD, mJarFile); 373 mJarChannel->SetJarFile(fdFile); 374 nsresult rv = mJarChannel->AsyncOpen(listener); 375 if (NS_FAILED(rv)) { 376 CancelRequest(listener, channel, rv); 377 } 378 } 379 380 NS_IMPL_QUERY_INTERFACE(ExtensionProtocolHandler, 381 nsISubstitutingProtocolHandler, nsIProtocolHandler, 382 nsISupportsWeakReference) 383 NS_IMPL_ADDREF_INHERITED(ExtensionProtocolHandler, SubstitutingProtocolHandler) 384 NS_IMPL_RELEASE_INHERITED(ExtensionProtocolHandler, SubstitutingProtocolHandler) 385 386 already_AddRefed<ExtensionProtocolHandler> 387 ExtensionProtocolHandler::GetSingleton() { 388 if (!sSingleton) { 389 sSingleton = new ExtensionProtocolHandler(); 390 ClearOnShutdown(&sSingleton); 391 } 392 return do_AddRef(sSingleton); 393 } 394 395 ExtensionProtocolHandler::ExtensionProtocolHandler() 396 : SubstitutingProtocolHandler(EXTENSION_SCHEME) { 397 // Note, extensions.webextensions.protocol.remote=false is for 398 // debugging purposes only. With process-level sandboxing, child 399 // processes (specifically content and extension processes), will 400 // not be able to load most moz-extension URI's when the pref is 401 // set to false. 402 mUseRemoteFileChannels = 403 IsNeckoChild() && 404 Preferences::GetBool("extensions.webextensions.protocol.remote"); 405 } 406 407 static inline ExtensionPolicyService& EPS() { 408 return ExtensionPolicyService::GetSingleton(); 409 } 410 411 bool ExtensionProtocolHandler::ResolveSpecialCases(const nsACString& aHost, 412 const nsACString& aPath, 413 const nsACString& aPathname, 414 nsACString& aResult) { 415 MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(), 416 "The ExtensionPolicyService is not thread safe"); 417 // Create special moz-extension://foo/_generated_background_page.html page 418 // for all registered extensions. We can't just do this as a substitution 419 // because substitutions can only match on host. 420 if (!SubstitutingProtocolHandler::HasSubstitution(aHost)) { 421 return false; 422 } 423 424 if (aPathname.EqualsLiteral("/_generated_background_page.html")) { 425 (void)EPS().GetGeneratedBackgroundPageUrl(aHost, aResult); 426 return !aResult.IsEmpty(); 427 } 428 429 return false; 430 } 431 432 // For file or JAR URI's, substitute in a remote channel. 433 Result<Ok, nsresult> ExtensionProtocolHandler::SubstituteRemoteChannel( 434 nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIChannel** aRetVal) { 435 MOZ_ASSERT(IsNeckoChild()); 436 MOZ_TRY(aURI ? NS_OK : NS_ERROR_INVALID_ARG); 437 MOZ_TRY(aLoadInfo ? NS_OK : NS_ERROR_INVALID_ARG); 438 439 nsAutoCString unResolvedSpec; 440 MOZ_TRY(aURI->GetSpec(unResolvedSpec)); 441 442 nsAutoCString resolvedSpec; 443 MOZ_TRY(ResolveURI(aURI, resolvedSpec)); 444 445 // Use the target URI scheme to determine if this is a packed or unpacked 446 // extension URI. For unpacked extensions, we'll request an input stream 447 // from the parent. For a packed extension, we'll request a file descriptor 448 // for the JAR file. 449 nsAutoCString scheme; 450 MOZ_TRY(net_ExtractURLScheme(resolvedSpec, scheme)); 451 452 if (scheme.EqualsLiteral("file")) { 453 // Unpacked extension 454 SubstituteRemoteFileChannel(aURI, aLoadInfo, resolvedSpec, aRetVal); 455 return Ok(); 456 } 457 458 if (scheme.EqualsLiteral("jar")) { 459 // Packed extension 460 return SubstituteRemoteJarChannel(aURI, aLoadInfo, resolvedSpec, aRetVal); 461 } 462 463 // Only unpacked resource files and JAR files are remoted. 464 // No other moz-extension loads should be reading from the filesystem. 465 return Ok(); 466 } 467 468 void OpenWhenReady( 469 Promise* aPromise, nsIStreamListener* aListener, nsIChannel* aChannel, 470 const std::function<nsresult(nsIStreamListener*, nsIChannel*)>& aCallback) { 471 nsCOMPtr<nsIStreamListener> listener(aListener); 472 nsCOMPtr<nsIChannel> channel(aChannel); 473 474 (void)aPromise->ThenWithCycleCollectedArgs( 475 [channel, aCallback]( 476 JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv, 477 nsIStreamListener* aListener) -> already_AddRefed<Promise> { 478 nsresult rv = aCallback(aListener, channel); 479 if (NS_FAILED(rv)) { 480 ExtensionStreamGetter::CancelRequest(aListener, channel, rv); 481 } 482 return nullptr; 483 }, 484 listener); 485 } 486 487 nsresult ExtensionProtocolHandler::SubstituteChannel(nsIURI* aURI, 488 nsILoadInfo* aLoadInfo, 489 nsIChannel** result) { 490 if (mUseRemoteFileChannels) { 491 MOZ_TRY(SubstituteRemoteChannel(aURI, aLoadInfo, result)); 492 } 493 494 auto* policy = EPS().GetByURL(aURI); 495 NS_ENSURE_TRUE(policy, NS_ERROR_UNEXPECTED); 496 497 RefPtr<dom::Promise> readyPromise(policy->ReadyPromise()); 498 499 nsresult rv; 500 nsCOMPtr<nsIURL> url = do_QueryInterface(aURI, &rv); 501 MOZ_TRY(rv); 502 503 nsAutoCString ext; 504 MOZ_TRY(url->GetFileExtension(ext)); 505 ToLowerCase(ext); 506 507 nsCOMPtr<nsIChannel> channel; 508 if (ext.EqualsLiteral("css")) { 509 // Filter CSS files to replace locale message tokens with localized strings. 510 static const auto convert = [](nsIStreamListener* listener, 511 nsIChannel* channel, 512 nsIChannel* origChannel) -> nsresult { 513 nsresult rv; 514 nsCOMPtr<nsIStreamConverterService> convService; 515 convService = mozilla::components::StreamConverter::Service(&rv); 516 MOZ_TRY(rv); 517 518 nsCOMPtr<nsIURI> uri; 519 MOZ_TRY(channel->GetURI(getter_AddRefs(uri))); 520 521 const char* kFromType = "application/vnd.mozilla.webext.unlocalized"; 522 const char* kToType = "text/css"; 523 524 nsCOMPtr<nsIStreamListener> converter; 525 MOZ_TRY(convService->AsyncConvertData(kFromType, kToType, listener, uri, 526 getter_AddRefs(converter))); 527 528 return origChannel->AsyncOpen(converter); 529 }; 530 531 channel = NS_NewSimpleChannel( 532 aURI, aLoadInfo, *result, 533 [readyPromise](nsIStreamListener* listener, nsIChannel* channel, 534 nsIChannel* origChannel) -> RequestOrReason { 535 if (readyPromise) { 536 nsCOMPtr<nsIChannel> chan(channel); 537 OpenWhenReady( 538 readyPromise, listener, origChannel, 539 [chan](nsIStreamListener* aListener, nsIChannel* aChannel) { 540 return convert(aListener, chan, aChannel); 541 }); 542 } else { 543 MOZ_TRY(convert(listener, channel, origChannel)); 544 } 545 nsCOMPtr<nsIRequest> request(origChannel); 546 return RequestOrCancelable(WrapNotNull(request)); 547 }); 548 } else if (readyPromise) { 549 size_t matchIdx; 550 if (BinarySearchIf( 551 sStaticFileExtensions, 0, std::size(sStaticFileExtensions), 552 [&ext](const char* aOther) { 553 return Compare(ext, nsDependentCString(aOther)); 554 }, 555 &matchIdx)) { 556 // This is a static resource that shouldn't depend on the extension being 557 // ready. Don't bother waiting for it. 558 return NS_OK; 559 } 560 561 channel = NS_NewSimpleChannel( 562 aURI, aLoadInfo, *result, 563 [readyPromise](nsIStreamListener* listener, nsIChannel* channel, 564 nsIChannel* origChannel) -> RequestOrReason { 565 OpenWhenReady(readyPromise, listener, origChannel, 566 [](nsIStreamListener* aListener, nsIChannel* aChannel) { 567 return aChannel->AsyncOpen(aListener); 568 }); 569 570 nsCOMPtr<nsIRequest> request(origChannel); 571 return RequestOrCancelable(WrapNotNull(request)); 572 }); 573 } else { 574 return NS_OK; 575 } 576 577 NS_ENSURE_TRUE(channel, NS_ERROR_OUT_OF_MEMORY); 578 if (aLoadInfo) { 579 nsCOMPtr<nsILoadInfo> loadInfo = 580 static_cast<LoadInfo*>(aLoadInfo)->CloneForNewRequest(); 581 (*result)->SetLoadInfo(loadInfo); 582 } 583 584 channel.swap(*result); 585 return NS_OK; 586 } 587 588 Result<bool, nsresult> ExtensionProtocolHandler::AllowExternalResource( 589 nsIFile* aExtensionDir, nsIFile* aRequestedFile) { 590 MOZ_ASSERT(!IsNeckoChild()); 591 592 #if defined(XP_WIN) 593 // On Windows, non-package builds don't use symlinks so we never need to 594 // allow a resource from outside of the extension dir. 595 return false; 596 #else 597 if (mozilla::IsPackagedBuild()) { 598 return false; 599 } 600 601 // On Mac and Linux unpackaged dev builds, system extensions use 602 // symlinks to point to resources in the repo dir which we have to 603 // allow loading. Before we allow an unpacked extension to load a 604 // resource outside of the extension dir, we make sure the extension 605 // dir is within the app directory. 606 bool result = MOZ_TRY(AppDirContains(aExtensionDir)); 607 if (!result) { 608 return false; 609 } 610 611 # if defined(XP_MACOSX) 612 // Additionally, on Mac dev builds, we make sure that the requested 613 // resource is within the repo dir. We don't perform this check on Linux 614 // because we don't have a reliable path to the repo dir on Linux. 615 return DevRepoContains(aRequestedFile); 616 # else /* XP_MACOSX */ 617 return true; 618 # endif 619 #endif /* defined(XP_WIN) */ 620 } 621 622 #if defined(XP_MACOSX) 623 // The |aRequestedFile| argument must already be Normalize()'d 624 Result<bool, nsresult> ExtensionProtocolHandler::DevRepoContains( 625 nsIFile* aRequestedFile) { 626 MOZ_ASSERT(!mozilla::IsPackagedBuild()); 627 MOZ_ASSERT(!IsNeckoChild()); 628 629 // On the first invocation, set mDevRepo 630 if (!mAlreadyCheckedDevRepo) { 631 mAlreadyCheckedDevRepo = true; 632 MOZ_TRY(nsMacUtilsImpl::GetRepoDir(getter_AddRefs(mDevRepo))); 633 if (MOZ_LOG_TEST(gExtProtocolLog, LogLevel::Debug)) { 634 nsAutoCString repoPath; 635 (void)mDevRepo->GetNativePath(repoPath); 636 LOG("Repo path: %s", repoPath.get()); 637 } 638 } 639 640 bool result = false; 641 if (mDevRepo) { 642 MOZ_TRY(mDevRepo->Contains(aRequestedFile, &result)); 643 } 644 return result; 645 } 646 #endif /* XP_MACOSX */ 647 648 #if !defined(XP_WIN) 649 Result<bool, nsresult> ExtensionProtocolHandler::AppDirContains( 650 nsIFile* aExtensionDir) { 651 MOZ_ASSERT(!mozilla::IsPackagedBuild()); 652 MOZ_ASSERT(!IsNeckoChild()); 653 654 // On the first invocation, set mAppDir 655 if (!mAlreadyCheckedAppDir) { 656 mAlreadyCheckedAppDir = true; 657 MOZ_TRY(NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(mAppDir))); 658 if (MOZ_LOG_TEST(gExtProtocolLog, LogLevel::Debug)) { 659 nsAutoCString appDirPath; 660 (void)mAppDir->GetNativePath(appDirPath); 661 LOG("AppDir path: %s", appDirPath.get()); 662 } 663 } 664 665 bool result = false; 666 if (mAppDir) { 667 MOZ_TRY(mAppDir->Contains(aExtensionDir, &result)); 668 } 669 return result; 670 } 671 #endif /* !defined(XP_WIN) */ 672 673 static void LogExternalResourceError(nsIFile* aExtensionDir, 674 nsIFile* aRequestedFile) { 675 MOZ_ASSERT(aExtensionDir); 676 MOZ_ASSERT(aRequestedFile); 677 678 LOG("Rejecting external unpacked extension resource [%s] from " 679 "extension directory [%s]", 680 aRequestedFile->HumanReadablePath().get(), 681 aExtensionDir->HumanReadablePath().get()); 682 } 683 684 Result<nsCOMPtr<nsIInputStream>, nsresult> ExtensionProtocolHandler::NewStream( 685 nsIURI* aChildURI, bool* aTerminateSender) { 686 MOZ_ASSERT(!IsNeckoChild()); 687 MOZ_TRY(aChildURI ? NS_OK : NS_ERROR_INVALID_ARG); 688 MOZ_TRY(aTerminateSender ? NS_OK : NS_ERROR_INVALID_ARG); 689 690 *aTerminateSender = true; 691 nsresult rv; 692 693 // We should never receive a URI that isn't for a moz-extension because 694 // these requests ordinarily come from the child's ExtensionProtocolHandler. 695 // Ensure this request is for a moz-extension URI. A rogue child process 696 // could send us any URI. 697 if (!aChildURI->SchemeIs(EXTENSION_SCHEME)) { 698 return Err(NS_ERROR_UNKNOWN_PROTOCOL); 699 } 700 701 // For errors after this point, we want to propagate the error to 702 // the child, but we don't force the child to be terminated because 703 // the error is likely to be due to a bug in the extension. 704 *aTerminateSender = false; 705 706 /* 707 * Make sure there is a substitution installed for the host found 708 * in the child's request URI and make sure the host resolves to 709 * a directory. 710 */ 711 712 nsAutoCString host; 713 MOZ_TRY(aChildURI->GetAsciiHost(host)); 714 715 // Lookup the directory this host string resolves to 716 nsCOMPtr<nsIURI> baseURI; 717 MOZ_TRY(GetSubstitution(host, getter_AddRefs(baseURI))); 718 719 // The result should be a file URL for the extension base dir 720 nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(baseURI, &rv); 721 MOZ_TRY(rv); 722 723 nsCOMPtr<nsIFile> extensionDir; 724 MOZ_TRY(fileURL->GetFile(getter_AddRefs(extensionDir))); 725 726 bool isDirectory = false; 727 MOZ_TRY(extensionDir->IsDirectory(&isDirectory)); 728 if (!isDirectory) { 729 // The host should map to a directory for unpacked extensions 730 return Err(NS_ERROR_FILE_NOT_DIRECTORY); 731 } 732 733 // Make sure the child URI resolves to a file URI then get a file 734 // channel for the request. The resultant channel should be a 735 // file channel because we only request remote streams for unpacked 736 // extension resource loads where the URI resolves to a file. 737 nsAutoCString resolvedSpec; 738 MOZ_TRY(ResolveURI(aChildURI, resolvedSpec)); 739 740 nsAutoCString resolvedScheme; 741 MOZ_TRY(net_ExtractURLScheme(resolvedSpec, resolvedScheme)); 742 if (!resolvedScheme.EqualsLiteral("file")) { 743 return Err(NS_ERROR_UNEXPECTED); 744 } 745 746 nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv); 747 MOZ_TRY(rv); 748 749 nsCOMPtr<nsIURI> resolvedURI; 750 MOZ_TRY(ioService->NewURI(resolvedSpec, nullptr, nullptr, 751 getter_AddRefs(resolvedURI))); 752 753 // We use the system principal to get a file channel for the request, 754 // but only after we've checked (above) that the child URI is of 755 // moz-extension scheme and that the URI host maps to a directory. 756 nsCOMPtr<nsIChannel> channel; 757 MOZ_TRY(NS_NewChannel(getter_AddRefs(channel), resolvedURI, 758 nsContentUtils::GetSystemPrincipal(), 759 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, 760 nsIContentPolicy::TYPE_OTHER)); 761 762 nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(channel, &rv); 763 MOZ_TRY(rv); 764 765 nsCOMPtr<nsIFile> requestedFile; 766 MOZ_TRY(fileChannel->GetFile(getter_AddRefs(requestedFile))); 767 768 /* 769 * Make sure the file we resolved to is within the extension directory. 770 */ 771 772 // Normalize paths for sane comparisons. nsIFile::Contains depends on 773 // it for reliable subpath checks. 774 MOZ_TRY(extensionDir->Normalize()); 775 MOZ_TRY(requestedFile->Normalize()); 776 #if defined(XP_WIN) 777 if (!widget::WinUtils::ResolveJunctionPointsAndSymLinks(extensionDir) || 778 !widget::WinUtils::ResolveJunctionPointsAndSymLinks(requestedFile)) { 779 return Err(NS_ERROR_FILE_ACCESS_DENIED); 780 } 781 #endif 782 783 bool isResourceFromExtensionDir = false; 784 MOZ_TRY(extensionDir->Contains(requestedFile, &isResourceFromExtensionDir)); 785 if (!isResourceFromExtensionDir) { 786 bool isAllowed = 787 MOZ_TRY(AllowExternalResource(extensionDir, requestedFile)); 788 if (!isAllowed) { 789 LogExternalResourceError(extensionDir, requestedFile); 790 return Err(NS_ERROR_FILE_ACCESS_DENIED); 791 } 792 } 793 794 nsCOMPtr<nsIInputStream> inputStream = MOZ_TRY(NS_NewLocalFileInputStream( 795 requestedFile, PR_RDONLY, -1, nsIFileInputStream::DEFER_OPEN)); 796 797 return inputStream; 798 } 799 800 Result<Ok, nsresult> ExtensionProtocolHandler::NewFD( 801 nsIURI* aChildURI, bool* aTerminateSender, 802 NeckoParent::GetExtensionFDResolver& aResolve) { 803 MOZ_ASSERT(!IsNeckoChild()); 804 MOZ_TRY(aChildURI ? NS_OK : NS_ERROR_INVALID_ARG); 805 MOZ_TRY(aTerminateSender ? NS_OK : NS_ERROR_INVALID_ARG); 806 807 *aTerminateSender = true; 808 nsresult rv; 809 810 // Ensure this is a moz-extension URI 811 if (!aChildURI->SchemeIs(EXTENSION_SCHEME)) { 812 return Err(NS_ERROR_UNKNOWN_PROTOCOL); 813 } 814 815 // For errors after this point, we want to propagate the error to 816 // the child, but we don't force the child to be terminated. 817 *aTerminateSender = false; 818 819 nsAutoCString host; 820 MOZ_TRY(aChildURI->GetAsciiHost(host)); 821 822 // We expect the host string to map to a JAR file because the URI 823 // should refer to a web accessible resource for an enabled extension. 824 nsCOMPtr<nsIURI> subURI; 825 MOZ_TRY(GetSubstitution(host, getter_AddRefs(subURI))); 826 827 nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(subURI, &rv); 828 MOZ_TRY(rv); 829 830 nsCOMPtr<nsIURI> innerFileURI; 831 MOZ_TRY(jarURI->GetJARFile(getter_AddRefs(innerFileURI))); 832 833 nsCOMPtr<nsIFileURL> innerFileURL = do_QueryInterface(innerFileURI, &rv); 834 MOZ_TRY(rv); 835 836 nsCOMPtr<nsIFile> jarFile; 837 MOZ_TRY(innerFileURL->GetFile(getter_AddRefs(jarFile))); 838 839 if (!mFileOpenerThread) { 840 mFileOpenerThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, 841 "ExtensionProtocolHandler"); 842 } 843 844 RefPtr<ExtensionJARFileOpener> fileOpener = 845 new ExtensionJARFileOpener(jarFile, aResolve); 846 847 nsCOMPtr<nsIRunnable> event = mozilla::NewRunnableMethod( 848 "ExtensionJarFileOpener", fileOpener, &ExtensionJARFileOpener::OpenFile); 849 850 MOZ_TRY(mFileOpenerThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL)); 851 852 return Ok(); 853 } 854 855 // Set the channel's content type using the provided URI's type 856 857 // static 858 void ExtensionProtocolHandler::SetContentType(nsIURI* aURI, 859 nsIChannel* aChannel) { 860 nsresult rv; 861 nsCOMPtr<nsIMIMEService> mime = do_GetService("@mozilla.org/mime;1", &rv); 862 if (NS_SUCCEEDED(rv)) { 863 nsAutoCString contentType; 864 rv = mime->GetTypeFromURI(aURI, contentType); 865 if (NS_SUCCEEDED(rv)) { 866 (void)aChannel->SetContentType(contentType); 867 } 868 } 869 } 870 871 // Gets a SimpleChannel that wraps the provided ExtensionStreamGetter 872 873 // static 874 void ExtensionProtocolHandler::NewSimpleChannel( 875 nsIURI* aURI, nsILoadInfo* aLoadinfo, ExtensionStreamGetter* aStreamGetter, 876 nsIChannel** aRetVal) { 877 nsCOMPtr<nsIChannel> channel = NS_NewSimpleChannel( 878 aURI, aLoadinfo, aStreamGetter, 879 [](nsIStreamListener* listener, nsIChannel* simpleChannel, 880 ExtensionStreamGetter* getter) -> RequestOrReason { 881 return getter->GetAsync(listener, simpleChannel); 882 }); 883 884 SetContentType(aURI, channel); 885 channel.swap(*aRetVal); 886 } 887 888 // Gets a SimpleChannel that wraps the provided channel 889 890 // static 891 void ExtensionProtocolHandler::NewSimpleChannel(nsIURI* aURI, 892 nsILoadInfo* aLoadinfo, 893 nsIChannel* aChannel, 894 nsIChannel** aRetVal) { 895 nsCOMPtr<nsIChannel> channel = NS_NewSimpleChannel( 896 aURI, aLoadinfo, aChannel, 897 [](nsIStreamListener* listener, nsIChannel* simpleChannel, 898 nsIChannel* origChannel) -> RequestOrReason { 899 nsresult rv = origChannel->AsyncOpen(listener); 900 if (NS_FAILED(rv)) { 901 simpleChannel->Cancel(NS_BINDING_ABORTED); 902 return Err(rv); 903 } 904 nsCOMPtr<nsIRequest> request(origChannel); 905 return RequestOrCancelable(WrapNotNull(request)); 906 }); 907 908 SetContentType(aURI, channel); 909 channel.swap(*aRetVal); 910 } 911 912 void ExtensionProtocolHandler::SubstituteRemoteFileChannel( 913 nsIURI* aURI, nsILoadInfo* aLoadinfo, nsACString& aResolvedFileSpec, 914 nsIChannel** aRetVal) { 915 MOZ_ASSERT(IsNeckoChild()); 916 917 RefPtr<ExtensionStreamGetter> streamGetter = 918 new ExtensionStreamGetter(aURI, aLoadinfo); 919 920 NewSimpleChannel(aURI, aLoadinfo, streamGetter, aRetVal); 921 } 922 923 static Result<Ok, nsresult> LogCacheCheck(const nsIJARChannel* aJarChannel, 924 nsIJARURI* aJarURI, bool aIsCached) { 925 nsresult rv; 926 927 nsCOMPtr<nsIURI> innerFileURI; 928 MOZ_TRY(aJarURI->GetJARFile(getter_AddRefs(innerFileURI))); 929 930 nsCOMPtr<nsIFileURL> innerFileURL = do_QueryInterface(innerFileURI, &rv); 931 MOZ_TRY(rv); 932 933 nsCOMPtr<nsIFile> jarFile; 934 MOZ_TRY(innerFileURL->GetFile(getter_AddRefs(jarFile))); 935 936 nsAutoCString uriSpec, jarSpec; 937 (void)aJarURI->GetSpec(uriSpec); 938 (void)innerFileURI->GetSpec(jarSpec); 939 LOG("[JARChannel %p] Cache %s: %s (%s)", aJarChannel, 940 aIsCached ? "hit" : "miss", uriSpec.get(), jarSpec.get()); 941 942 return Ok(); 943 } 944 945 Result<Ok, nsresult> ExtensionProtocolHandler::SubstituteRemoteJarChannel( 946 nsIURI* aURI, nsILoadInfo* aLoadinfo, nsACString& aResolvedSpec, 947 nsIChannel** aRetVal) { 948 MOZ_ASSERT(IsNeckoChild()); 949 nsresult rv; 950 951 // Build a JAR URI for this jar:file:// URI and use it to extract the 952 // inner file URI. 953 nsCOMPtr<nsIURI> uri; 954 MOZ_TRY(NS_NewURI(getter_AddRefs(uri), aResolvedSpec)); 955 956 nsCOMPtr<nsIJARURI> jarURI = do_QueryInterface(uri, &rv); 957 MOZ_TRY(rv); 958 959 nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(*aRetVal, &rv); 960 MOZ_TRY(rv); 961 962 bool isCached = false; 963 MOZ_TRY(jarChannel->EnsureCached(&isCached)); 964 if (MOZ_LOG_TEST(gExtProtocolLog, LogLevel::Debug)) { 965 (void)LogCacheCheck(jarChannel, jarURI, isCached); 966 } 967 968 if (isCached) { 969 // Using a SimpleChannel with an ExtensionStreamGetter here (like the 970 // non-cached JAR case) isn't needed to load the extension resource 971 // because we don't need to ask the parent for an FD for the JAR, but 972 // wrapping the JARChannel in a SimpleChannel allows HTTP forwarding to 973 // moz-extension URI's to work because HTTP forwarding requires the 974 // target channel implement nsIChildChannel. 975 NewSimpleChannel(aURI, aLoadinfo, jarChannel.get(), aRetVal); 976 return Ok(); 977 } 978 979 nsCOMPtr<nsIURI> innerFileURI; 980 MOZ_TRY(jarURI->GetJARFile(getter_AddRefs(innerFileURI))); 981 982 nsCOMPtr<nsIFileURL> innerFileURL = do_QueryInterface(innerFileURI, &rv); 983 MOZ_TRY(rv); 984 985 nsCOMPtr<nsIFile> jarFile; 986 MOZ_TRY(innerFileURL->GetFile(getter_AddRefs(jarFile))); 987 988 RefPtr<ExtensionStreamGetter> streamGetter = 989 new ExtensionStreamGetter(aURI, aLoadinfo, jarChannel.forget(), jarFile); 990 991 NewSimpleChannel(aURI, aLoadinfo, streamGetter, aRetVal); 992 return Ok(); 993 } 994 995 } // namespace net 996 } // namespace mozilla