tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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