tor-browser

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

ClientNavigateOpChild.cpp (12353B)


      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
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "ClientNavigateOpChild.h"
      8 
      9 #include "ClientSource.h"
     10 #include "ClientSourceChild.h"
     11 #include "ClientState.h"
     12 #include "ReferrerInfo.h"
     13 #include "mozilla/dom/Document.h"
     14 #include "mozilla/dom/PolicyContainer.h"
     15 #include "nsDocShellLoadState.h"
     16 #include "nsIDocShell.h"
     17 #include "nsIWebNavigation.h"
     18 #include "nsIWebProgress.h"
     19 #include "nsIWebProgressListener.h"
     20 #include "nsNetUtil.h"
     21 #include "nsPIDOMWindow.h"
     22 #include "nsURLHelper.h"
     23 
     24 namespace mozilla::dom {
     25 
     26 namespace {
     27 
     28 class NavigateLoadListener final : public nsIWebProgressListener,
     29                                   public nsSupportsWeakReference {
     30  RefPtr<ClientOpPromise::Private> mPromise;
     31  RefPtr<nsPIDOMWindowOuter> mOuterWindow;
     32  nsCOMPtr<nsIURI> mBaseURL;
     33 
     34  ~NavigateLoadListener() = default;
     35 
     36 public:
     37  NavigateLoadListener(ClientOpPromise::Private* aPromise,
     38                       nsPIDOMWindowOuter* aOuterWindow, nsIURI* aBaseURL)
     39      : mPromise(aPromise), mOuterWindow(aOuterWindow), mBaseURL(aBaseURL) {
     40    MOZ_DIAGNOSTIC_ASSERT(mPromise);
     41    MOZ_DIAGNOSTIC_ASSERT(mOuterWindow);
     42    MOZ_DIAGNOSTIC_ASSERT(mBaseURL);
     43  }
     44 
     45  NS_IMETHOD
     46  OnStateChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
     47                uint32_t aStateFlags, nsresult aResult) override {
     48    if (!(aStateFlags & STATE_IS_DOCUMENT) ||
     49        !(aStateFlags & (STATE_STOP | STATE_TRANSFERRING))) {
     50      return NS_OK;
     51    }
     52 
     53    aWebProgress->RemoveProgressListener(this);
     54 
     55    nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
     56    if (!channel) {
     57      // This is not going to happen; how could it?
     58      CopyableErrorResult result;
     59      result.ThrowInvalidStateError("Bad request");
     60      mPromise->Reject(result, __func__);
     61      return NS_OK;
     62    }
     63 
     64    nsCOMPtr<nsIURI> channelURL;
     65    nsresult rv = NS_GetFinalChannelURI(channel, getter_AddRefs(channelURL));
     66    if (NS_FAILED(rv)) {
     67      CopyableErrorResult result;
     68      // XXXbz We can't actually get here; NS_GetFinalChannelURI never fails in
     69      // practice!
     70      result.Throw(rv);
     71      mPromise->Reject(result, __func__);
     72      return NS_OK;
     73    }
     74 
     75    nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
     76    MOZ_DIAGNOSTIC_ASSERT(ssm);
     77 
     78    // If the resulting window is not same origin, then resolve immediately
     79    // without returning any information about the new Client.  This is
     80    // step 6.10 in the Client.navigate(url) spec.
     81    // todo: if you intend to update CheckSameOriginURI to log the error to the
     82    // console you also need to update the 'aFromPrivateWindow' argument.
     83    rv = ssm->CheckSameOriginURI(mBaseURL, channelURL, false, false);
     84    if (NS_FAILED(rv)) {
     85      mPromise->Resolve(CopyableErrorResult(), __func__);
     86      return NS_OK;
     87    }
     88 
     89    nsPIDOMWindowInner* innerWindow = mOuterWindow->GetCurrentInnerWindow();
     90    MOZ_DIAGNOSTIC_ASSERT(innerWindow);
     91 
     92    Maybe<ClientInfo> clientInfo = innerWindow->GetClientInfo();
     93    MOZ_DIAGNOSTIC_ASSERT(clientInfo.isSome());
     94 
     95    Maybe<ClientState> clientState = innerWindow->GetClientState();
     96    MOZ_DIAGNOSTIC_ASSERT(clientState.isSome());
     97 
     98    // Otherwise, if the new window is same-origin we want to return a
     99    // ClientInfoAndState object so we can provide a Client snapshot
    100    // to the caller.  This is step 6.11 and 6.12 in the Client.navigate(url)
    101    // spec.
    102    mPromise->Resolve(
    103        ClientInfoAndState(clientInfo.ref().ToIPC(), clientState.ref().ToIPC()),
    104        __func__);
    105 
    106    return NS_OK;
    107  }
    108 
    109  NS_IMETHOD
    110  OnProgressChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
    111                   int32_t aCurSelfProgress, int32_t aMaxSelfProgress,
    112                   int32_t aCurTotalProgress,
    113                   int32_t aMaxTotalProgress) override {
    114    MOZ_CRASH("Unexpected notification.");
    115    return NS_OK;
    116  }
    117 
    118  NS_IMETHOD
    119  OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
    120                   nsIURI* aLocation, uint32_t aFlags) override {
    121    MOZ_CRASH("Unexpected notification.");
    122    return NS_OK;
    123  }
    124 
    125  NS_IMETHOD
    126  OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
    127                 nsresult aStatus, const char16_t* aMessage) override {
    128    MOZ_CRASH("Unexpected notification.");
    129    return NS_OK;
    130  }
    131 
    132  NS_IMETHOD
    133  OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
    134                   uint32_t aState) override {
    135    MOZ_CRASH("Unexpected notification.");
    136    return NS_OK;
    137  }
    138 
    139  NS_IMETHOD
    140  OnContentBlockingEvent(nsIWebProgress* aWebProgress, nsIRequest* aRequest,
    141                         uint32_t aEvent) override {
    142    MOZ_CRASH("Unexpected notification.");
    143    return NS_OK;
    144  }
    145 
    146  NS_DECL_ISUPPORTS
    147 };
    148 
    149 NS_IMPL_ISUPPORTS(NavigateLoadListener, nsIWebProgressListener,
    150                  nsISupportsWeakReference);
    151 
    152 }  // anonymous namespace
    153 
    154 RefPtr<ClientOpPromise> ClientNavigateOpChild::DoNavigate(
    155    const ClientNavigateOpConstructorArgs& aArgs) {
    156  nsCOMPtr<nsPIDOMWindowInner> window;
    157 
    158  // Navigating the target client window will result in the original
    159  // ClientSource being destroyed.  To avoid potential UAF mistakes
    160  // we use a small scope to access the ClientSource object.  Once
    161  // we have a strong reference to the window object we should not
    162  // access the ClientSource again.
    163  {
    164    ClientSourceChild* targetActor =
    165        static_cast<ClientSourceChild*>(aArgs.target().AsChild().get());
    166    MOZ_DIAGNOSTIC_ASSERT(targetActor);
    167 
    168    ClientSource* target = targetActor->GetSource();
    169    if (!target) {
    170      CopyableErrorResult rv;
    171      rv.ThrowInvalidStateError("Unknown Client");
    172      return ClientOpPromise::CreateAndReject(rv, __func__);
    173    }
    174 
    175    window = target->GetInnerWindow();
    176    if (!window) {
    177      CopyableErrorResult rv;
    178      rv.ThrowInvalidStateError("Client load for a destroyed Window");
    179      return ClientOpPromise::CreateAndReject(rv, __func__);
    180    }
    181  }
    182 
    183  MOZ_ASSERT(NS_IsMainThread());
    184 
    185  mSerialEventTarget = GetMainThreadSerialEventTarget();
    186 
    187  // In theory we could do the URL work before paying the IPC overhead
    188  // cost, but in practice its easier to do it here.  The ClientHandle
    189  // may be off-main-thread while this method is guaranteed to always
    190  // be main thread.
    191  nsCOMPtr<nsIURI> baseURL;
    192  nsresult rv = NS_NewURI(getter_AddRefs(baseURL), aArgs.baseURL());
    193  if (NS_FAILED(rv)) {
    194    // This is rather unexpected: This is the worker URL we passed from the
    195    // parent, so we expect this to parse fine!
    196    CopyableErrorResult result;
    197    result.ThrowInvalidStateError("Invalid worker URL");
    198    return ClientOpPromise::CreateAndReject(result, __func__);
    199  }
    200 
    201  // There is an edge case for view-source url here. According to the wpt test
    202  // windowclient-navigate.https.html, a view-source URL with a relative inner
    203  // URL should be treated as an invalid URL. However, we will still resolve it
    204  // into a valid view-source URL since the baseURL is involved while creating
    205  // the URI. So, an invalid view-source URL will be treated as a valid URL
    206  // in this case. To address this, we should not take the baseURL into account
    207  // for the view-source URL.
    208  bool shouldUseBaseURL = true;
    209  nsAutoCString scheme;
    210  if (NS_SUCCEEDED(net_ExtractURLScheme(aArgs.url(), scheme)) &&
    211      scheme.LowerCaseEqualsLiteral("view-source")) {
    212    shouldUseBaseURL = false;
    213  }
    214 
    215  nsCOMPtr<nsIURI> url;
    216  rv = NS_NewURI(getter_AddRefs(url), aArgs.url(), nullptr,
    217                 shouldUseBaseURL ? baseURL.get() : nullptr);
    218  if (NS_FAILED(rv)) {
    219    // Per https://w3c.github.io/ServiceWorker/#dom-windowclient-navigate step
    220    // 2, if the URL fails to parse, we reject with a TypeError.
    221    nsPrintfCString err("Invalid URL \"%s\"", aArgs.url().get());
    222    CopyableErrorResult result;
    223    result.ThrowTypeError(err);
    224    return ClientOpPromise::CreateAndReject(result, __func__);
    225  }
    226 
    227  if (NS_IsAboutBlankAllowQueryAndFragment(url)) {
    228    CopyableErrorResult result;
    229    result.ThrowTypeError("Navigation to \"about:blank\" is not allowed");
    230    return ClientOpPromise::CreateAndReject(result, __func__);
    231  }
    232 
    233  RefPtr<Document> doc = window->GetExtantDoc();
    234  if (!doc || !doc->IsActive()) {
    235    CopyableErrorResult result;
    236    result.ThrowInvalidStateError("Document is not active.");
    237    return ClientOpPromise::CreateAndReject(result, __func__);
    238  }
    239 
    240  nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
    241 
    242  nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
    243  nsCOMPtr<nsIWebProgress> webProgress = do_GetInterface(docShell);
    244  if (!docShell || !webProgress) {
    245    CopyableErrorResult result;
    246    result.ThrowInvalidStateError(
    247        "Document's browsing context has been discarded");
    248    return ClientOpPromise::CreateAndReject(result, __func__);
    249  }
    250 
    251  RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(url);
    252  loadState->SetTriggeringPrincipal(principal);
    253  loadState->SetTriggeringSandboxFlags(doc->GetSandboxFlags());
    254  loadState->SetPolicyContainer(doc->GetPolicyContainer());
    255 
    256  auto referrerInfo = MakeRefPtr<ReferrerInfo>(*doc);
    257  loadState->SetReferrerInfo(referrerInfo);
    258  loadState->SetLoadType(LOAD_STOP_CONTENT);
    259  loadState->SetSourceBrowsingContext(docShell->GetBrowsingContext());
    260  loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE);
    261  loadState->SetFirstParty(true);
    262  loadState->SetHasValidUserGestureActivation(
    263      doc->HasValidTransientUserGestureActivation());
    264  rv = docShell->LoadURI(loadState, false);
    265  if (NS_FAILED(rv)) {
    266    /// There are tests that try sending file:/// and mixed-content URLs
    267    /// in here and expect them to reject with a TypeError.  This does not match
    268    /// the spec, but does match the current behavior of both us and Chrome.
    269    /// https://github.com/w3c/ServiceWorker/issues/1500 tracks sorting that
    270    /// out.
    271    /// We now run security checks asynchronously, so these tests now
    272    /// just fail to load rather than hitting this failure path. I've
    273    /// marked them as failing for now until they get fixed to match the
    274    /// spec.
    275    nsPrintfCString err("Invalid URL \"%s\"", aArgs.url().get());
    276    CopyableErrorResult result;
    277    result.ThrowTypeError(err);
    278    return ClientOpPromise::CreateAndReject(result, __func__);
    279  }
    280 
    281  RefPtr<ClientOpPromise::Private> promise =
    282      new ClientOpPromise::Private(__func__);
    283 
    284  nsCOMPtr<nsIWebProgressListener> listener =
    285      new NavigateLoadListener(promise, window->GetOuterWindow(), baseURL);
    286 
    287  rv = webProgress->AddProgressListener(listener,
    288                                        nsIWebProgress::NOTIFY_STATE_DOCUMENT);
    289  if (NS_FAILED(rv)) {
    290    CopyableErrorResult result;
    291    // XXXbz Can we throw something better here?
    292    result.Throw(rv);
    293    promise->Reject(result, __func__);
    294    return promise;
    295  }
    296 
    297  return promise->Then(
    298      mSerialEventTarget, __func__,
    299      [listener](const ClientOpPromise::ResolveOrRejectValue& aValue) {
    300        return ClientOpPromise::CreateAndResolveOrReject(aValue, __func__);
    301      });
    302 }
    303 
    304 void ClientNavigateOpChild::ActorDestroy(ActorDestroyReason aReason) {
    305  mPromiseRequestHolder.DisconnectIfExists();
    306 }
    307 
    308 void ClientNavigateOpChild::Init(const ClientNavigateOpConstructorArgs& aArgs) {
    309  RefPtr<ClientOpPromise> promise = DoNavigate(aArgs);
    310 
    311  // Normally we get the event target from the window in DoNavigate().  If a
    312  // failure occurred, though, we may need to fall back to the current thread
    313  // target.
    314  if (!mSerialEventTarget) {
    315    mSerialEventTarget = GetCurrentSerialEventTarget();
    316  }
    317 
    318  // Capturing `this` is safe here since we clear the mPromiseRequestHolder in
    319  // ActorDestroy.
    320  promise
    321      ->Then(
    322          mSerialEventTarget, __func__,
    323          [this](const ClientOpResult& aResult) {
    324            mPromiseRequestHolder.Complete();
    325            PClientNavigateOpChild::Send__delete__(this, aResult);
    326          },
    327          [this](const CopyableErrorResult& aResult) {
    328            mPromiseRequestHolder.Complete();
    329            PClientNavigateOpChild::Send__delete__(this, aResult);
    330          })
    331      ->Track(mPromiseRequestHolder);
    332 }
    333 
    334 }  // namespace mozilla::dom