tor-browser

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

PageThumbProtocolHandler.cpp (9952B)


      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 "PageThumbProtocolHandler.h"
      8 
      9 #include "mozilla/ClearOnShutdown.h"
     10 #include "mozilla/Components.h"
     11 #include "mozilla/ipc/URIParams.h"
     12 #include "mozilla/ipc/URIUtils.h"
     13 #include "mozilla/net/NeckoChild.h"
     14 #include "mozilla/net/NeckoParent.h"
     15 #include "mozilla/RefPtr.h"
     16 #include "mozilla/ResultExtensions.h"
     17 #include "mozilla/Try.h"
     18 
     19 #include "LoadInfo.h"
     20 #include "nsContentUtils.h"
     21 #include "nsServiceManagerUtils.h"
     22 #include "nsIFile.h"
     23 #include "nsIFileChannel.h"
     24 #include "nsIFileStreams.h"
     25 #include "nsIMIMEService.h"
     26 #include "nsIURL.h"
     27 #include "nsIChannel.h"
     28 #include "nsIPageThumbsStorageService.h"
     29 #include "nsIInputStreamPump.h"
     30 #include "nsIStreamListener.h"
     31 #include "nsIInputStream.h"
     32 #include "nsNetUtil.h"
     33 #include "nsURLHelper.h"
     34 #include "prio.h"
     35 #include "SimpleChannel.h"
     36 #include "nsICancelable.h"
     37 
     38 #ifdef MOZ_PLACES
     39 #  include "nsIPlacesPreviewsHelperService.h"
     40 #endif
     41 
     42 #define PAGE_THUMB_HOST "thumbnails"
     43 #define PLACES_PREVIEWS_HOST "places-previews"
     44 #define PAGE_THUMB_SCHEME "moz-page-thumb"
     45 
     46 namespace mozilla {
     47 namespace net {
     48 
     49 LazyLogModule gPageThumbProtocolLog("PageThumbProtocol");
     50 
     51 #undef LOG
     52 #define LOG(level, ...) \
     53  MOZ_LOG(gPageThumbProtocolLog, LogLevel::level, (__VA_ARGS__))
     54 
     55 StaticRefPtr<PageThumbProtocolHandler> PageThumbProtocolHandler::sSingleton;
     56 
     57 NS_IMPL_QUERY_INTERFACE(PageThumbProtocolHandler,
     58                        nsISubstitutingProtocolHandler, nsIProtocolHandler,
     59                        nsISupportsWeakReference)
     60 NS_IMPL_ADDREF_INHERITED(PageThumbProtocolHandler, SubstitutingProtocolHandler)
     61 NS_IMPL_RELEASE_INHERITED(PageThumbProtocolHandler, SubstitutingProtocolHandler)
     62 
     63 already_AddRefed<PageThumbProtocolHandler>
     64 PageThumbProtocolHandler::GetSingleton() {
     65  if (!sSingleton) {
     66    sSingleton = new PageThumbProtocolHandler();
     67    ClearOnShutdown(&sSingleton);
     68  }
     69 
     70  return do_AddRef(sSingleton);
     71 }
     72 
     73 // A moz-page-thumb URI is only loadable by chrome pages in the parent process,
     74 // or privileged content running in the privileged about content process.
     75 PageThumbProtocolHandler::PageThumbProtocolHandler()
     76    : SubstitutingProtocolHandler(PAGE_THUMB_SCHEME) {}
     77 
     78 RefPtr<RemoteStreamPromise> PageThumbProtocolHandler::NewStream(
     79    nsIURI* aChildURI, bool* aTerminateSender) {
     80  MOZ_ASSERT(!IsNeckoChild());
     81  MOZ_ASSERT(NS_IsMainThread());
     82 
     83  if (!aChildURI || !aTerminateSender) {
     84    return RemoteStreamPromise::CreateAndReject(NS_ERROR_INVALID_ARG, __func__);
     85  }
     86 
     87  *aTerminateSender = true;
     88  nsresult rv;
     89 
     90  // We should never receive a URI that isn't for a moz-page-thumb because
     91  // these requests ordinarily come from the child's PageThumbProtocolHandler.
     92  // Ensure this request is for a moz-page-thumb URI. A compromised child
     93  // process could send us any URI.
     94  bool isPageThumbScheme = false;
     95  if (NS_FAILED(aChildURI->SchemeIs(PAGE_THUMB_SCHEME, &isPageThumbScheme)) ||
     96      !isPageThumbScheme) {
     97    return RemoteStreamPromise::CreateAndReject(NS_ERROR_UNKNOWN_PROTOCOL,
     98                                                __func__);
     99  }
    100 
    101  // We should never receive a URI that does not have "thumbnails" as the host.
    102  nsAutoCString host;
    103  if (NS_FAILED(aChildURI->GetAsciiHost(host)) ||
    104      !(host.EqualsLiteral(PAGE_THUMB_HOST) ||
    105        host.EqualsLiteral(PLACES_PREVIEWS_HOST))) {
    106    return RemoteStreamPromise::CreateAndReject(NS_ERROR_UNEXPECTED, __func__);
    107  }
    108 
    109  // For errors after this point, we want to propagate the error to
    110  // the child, but we don't force the child process to be terminated.
    111  *aTerminateSender = false;
    112 
    113  // Make sure the child URI resolves to a file URI. We will then get a file
    114  // channel for the request. The resultant channel should be a file channel
    115  // because we only request remote streams for resource loads where the URI
    116  // resolves to a file.
    117  nsAutoCString resolvedSpec;
    118  rv = ResolveURI(aChildURI, resolvedSpec);
    119  if (NS_FAILED(rv)) {
    120    return RemoteStreamPromise::CreateAndReject(rv, __func__);
    121  }
    122 
    123  return mozilla::net::NeckoParent::CreateRemoteStreamForResolvedURI(
    124      aChildURI, resolvedSpec, ""_ns);
    125 }
    126 
    127 bool PageThumbProtocolHandler::ResolveSpecialCases(const nsACString& aHost,
    128                                                   const nsACString& aPath,
    129                                                   const nsACString& aPathname,
    130                                                   nsACString& aResult) {
    131  // This should match the scheme in PageThumbs.sys.mjs. We will only resolve
    132  // URIs for thumbnails generated by PageThumbs here.
    133  if (!aHost.EqualsLiteral(PAGE_THUMB_HOST) &&
    134      !aHost.EqualsLiteral(PLACES_PREVIEWS_HOST)) {
    135    // moz-page-thumb should always have a "thumbnails" host. We do not intend
    136    // to allow substitution rules to be created for moz-page-thumb.
    137    return false;
    138  }
    139 
    140  // Regardless of the outcome, the scheme will be resolved to file://.
    141  aResult.Assign("file://");
    142 
    143  if (IsNeckoChild()) {
    144    // We will resolve the URI in the parent if load is performed in the child
    145    // because the child does not have access to the profile directory path.
    146    // Technically we could retrieve the path from dom::ContentChild, but I
    147    // would prefer to obtain the path from PageThumbsStorageService (which
    148    // depends on OS.Path). Here, we resolve to the same URI, with the file://
    149    // scheme. This won't ever be accessed directly by the content process,
    150    // and is mainly used to keep the substitution protocol handler mechanism
    151    // happy.
    152    aResult.Append(aHost);
    153    aResult.Append(aPath);
    154  } else {
    155    // Resolve the URI in the parent to the thumbnail file URI since we will
    156    // attempt to open the channel to load the file after this.
    157    nsAutoString thumbnailUrl;
    158    nsresult rv = GetThumbnailPath(aPath, aHost, thumbnailUrl);
    159    if (NS_WARN_IF(NS_FAILED(rv))) {
    160      return false;
    161    }
    162 
    163    aResult.Append(NS_ConvertUTF16toUTF8(thumbnailUrl));
    164  }
    165 
    166  return true;
    167 }
    168 
    169 nsresult PageThumbProtocolHandler::SubstituteChannel(nsIURI* aURI,
    170                                                     nsILoadInfo* aLoadInfo,
    171                                                     nsIChannel** aRetVal) {
    172  // Check if URI resolves to a file URI.
    173  nsAutoCString resolvedSpec;
    174  MOZ_TRY(ResolveURI(aURI, resolvedSpec));
    175 
    176  nsAutoCString scheme;
    177  MOZ_TRY(net_ExtractURLScheme(resolvedSpec, scheme));
    178 
    179  if (!scheme.EqualsLiteral("file")) {
    180    NS_WARNING("moz-page-thumb URIs should only resolve to file URIs.");
    181    return NS_ERROR_NO_INTERFACE;
    182  }
    183 
    184  // Load the URI remotely if accessed from a child.
    185  if (IsNeckoChild()) {
    186    MOZ_TRY(SubstituteRemoteChannel(aURI, aLoadInfo, aRetVal));
    187  }
    188 
    189  return NS_OK;
    190 }
    191 
    192 Result<Ok, nsresult> PageThumbProtocolHandler::SubstituteRemoteChannel(
    193    nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIChannel** aRetVal) {
    194  MOZ_ASSERT(IsNeckoChild());
    195  MOZ_TRY(aURI ? NS_OK : NS_ERROR_INVALID_ARG);
    196  MOZ_TRY(aLoadInfo ? NS_OK : NS_ERROR_INVALID_ARG);
    197 
    198 #ifdef DEBUG
    199  nsAutoCString resolvedSpec;
    200  MOZ_TRY(ResolveURI(aURI, resolvedSpec));
    201 
    202  nsAutoCString scheme;
    203  MOZ_TRY(net_ExtractURLScheme(resolvedSpec, scheme));
    204 
    205  MOZ_ASSERT(scheme.EqualsLiteral("file"));
    206 #endif /* DEBUG */
    207 
    208  RefPtr<RemoteStreamGetter> streamGetter =
    209      new RemoteStreamGetter(aURI, aLoadInfo);
    210 
    211  NewSimpleChannel(aURI, aLoadInfo, streamGetter, aRetVal);
    212  return Ok();
    213 }
    214 
    215 nsresult PageThumbProtocolHandler::GetThumbnailPath(const nsACString& aPath,
    216                                                    const nsACString& aHost,
    217                                                    nsString& aThumbnailPath) {
    218  MOZ_ASSERT(!IsNeckoChild());
    219 
    220  // Ensures that the provided path has a query string. We will start parsing
    221  // from there.
    222  int32_t queryIndex = aPath.FindChar('?');
    223  if (queryIndex <= 0) {
    224    return NS_ERROR_MALFORMED_URI;
    225  }
    226 
    227  // Extract URL from query string.
    228  nsAutoCString url;
    229  bool found =
    230      URLParams::Extract(Substring(aPath, queryIndex + 1), "url"_ns, url);
    231  if (!found || url.IsVoid()) {
    232    return NS_ERROR_NOT_AVAILABLE;
    233  }
    234 
    235  nsresult rv;
    236  if (aHost.EqualsLiteral(PAGE_THUMB_HOST)) {
    237    nsCOMPtr<nsIPageThumbsStorageService> pageThumbsStorage;
    238    pageThumbsStorage = mozilla::components::PageThumbsStorage::Service(&rv);
    239    if (NS_WARN_IF(NS_FAILED(rv))) {
    240      return rv;
    241    }
    242    // Use PageThumbsStorageService to get the local file path of the screenshot
    243    // for the given URL.
    244    rv = pageThumbsStorage->GetFilePathForURL(NS_ConvertUTF8toUTF16(url),
    245                                              aThumbnailPath);
    246 #ifdef MOZ_PLACES
    247  } else if (aHost.EqualsLiteral(PLACES_PREVIEWS_HOST)) {
    248    nsCOMPtr<nsIPlacesPreviewsHelperService> helper;
    249    helper = mozilla::components::PlacesPreviewsHelper::Service(&rv);
    250    if (NS_WARN_IF(NS_FAILED(rv))) {
    251      return rv;
    252    }
    253    rv = helper->GetFilePathForURL(NS_ConvertUTF8toUTF16(url), aThumbnailPath);
    254 #endif
    255  } else {
    256    MOZ_ASSERT_UNREACHABLE("Unknown thumbnail host");
    257    return NS_ERROR_UNEXPECTED;
    258  }
    259  if (NS_WARN_IF(NS_FAILED(rv))) {
    260    return rv;
    261  }
    262  return NS_OK;
    263 }
    264 
    265 // static
    266 void PageThumbProtocolHandler::NewSimpleChannel(
    267    nsIURI* aURI, nsILoadInfo* aLoadinfo, RemoteStreamGetter* aStreamGetter,
    268    nsIChannel** aRetVal) {
    269  nsCOMPtr<nsIChannel> channel = NS_NewSimpleChannel(
    270      aURI, aLoadinfo, aStreamGetter,
    271      [](nsIStreamListener* listener, nsIChannel* simpleChannel,
    272         RemoteStreamGetter* getter) -> RequestOrReason {
    273        return getter->GetAsync(listener, simpleChannel,
    274                                &NeckoChild::SendGetPageThumbStream);
    275      });
    276 
    277  channel.swap(*aRetVal);
    278 }
    279 
    280 #undef LOG
    281 
    282 }  // namespace net
    283 }  // namespace mozilla