Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/clients/manager/ClientChannelHelper.cpp
Line
Count
Source (jump to first uncovered line)
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 "ClientChannelHelper.h"
8
9
#include "ClientManager.h"
10
#include "ClientSource.h"
11
#include "MainThreadUtils.h"
12
#include "mozilla/dom/ServiceWorkerDescriptor.h"
13
#include "mozilla/ipc/BackgroundUtils.h"
14
#include "nsContentUtils.h"
15
#include "nsIAsyncVerifyRedirectCallback.h"
16
#include "nsIChannel.h"
17
#include "nsIChannelEventSink.h"
18
#include "nsIDocShell.h"
19
#include "nsIHttpChannelInternal.h"
20
#include "nsIInterfaceRequestor.h"
21
#include "nsIInterfaceRequestorUtils.h"
22
23
namespace mozilla {
24
namespace dom {
25
26
using mozilla::ipc::PrincipalInfoToPrincipal;
27
28
namespace {
29
30
class ClientChannelHelper final : public nsIInterfaceRequestor
31
                                , public nsIChannelEventSink
32
{
33
  nsCOMPtr<nsIInterfaceRequestor> mOuter;
34
  nsCOMPtr<nsISerialEventTarget> mEventTarget;
35
36
0
  ~ClientChannelHelper() = default;
37
38
  NS_IMETHOD
39
  GetInterface(const nsIID& aIID, void** aResultOut) override
40
0
  {
41
0
    if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
42
0
      *aResultOut = static_cast<nsIChannelEventSink*>(this);
43
0
      NS_ADDREF_THIS();
44
0
      return NS_OK;
45
0
    }
46
0
47
0
    if (mOuter) {
48
0
      return mOuter->GetInterface(aIID, aResultOut);
49
0
    }
50
0
51
0
    return NS_ERROR_NO_INTERFACE;
52
0
  }
53
54
  NS_IMETHOD
55
  AsyncOnChannelRedirect(nsIChannel* aOldChannel,
56
                         nsIChannel* aNewChannel,
57
                         uint32_t aFlags,
58
                         nsIAsyncVerifyRedirectCallback *aCallback) override
59
0
  {
60
0
    MOZ_ASSERT(NS_IsMainThread());
61
0
62
0
    nsCOMPtr<nsILoadInfo> oldLoadInfo;
63
0
    nsresult rv = aOldChannel->GetLoadInfo(getter_AddRefs(oldLoadInfo));
64
0
    NS_ENSURE_SUCCESS(rv, rv);
65
0
66
0
    nsCOMPtr<nsILoadInfo> newLoadInfo;
67
0
    rv = aNewChannel->GetLoadInfo(getter_AddRefs(newLoadInfo));
68
0
    NS_ENSURE_SUCCESS(rv, rv);
69
0
70
0
    rv = nsContentUtils::CheckSameOrigin(aOldChannel, aNewChannel);
71
0
    if (NS_WARN_IF(NS_FAILED(rv) && rv != NS_ERROR_DOM_BAD_URI)) {
72
0
      return rv;
73
0
    }
74
0
75
0
    UniquePtr<ClientSource> reservedClient = oldLoadInfo->TakeReservedClientSource();
76
0
77
0
    // If its a same-origin redirect we just move our reserved client to the
78
0
    // new channel.
79
0
    if (NS_SUCCEEDED(rv)) {
80
0
      if (reservedClient) {
81
0
        newLoadInfo->GiveReservedClientSource(std::move(reservedClient));
82
0
      }
83
0
84
0
      // It seems sometimes necko passes two channels with the same LoadInfo.
85
0
      // We only need to move the reserved/initial ClientInfo over if we
86
0
      // actually have a different LoadInfo.
87
0
      else if (oldLoadInfo != newLoadInfo) {
88
0
        const Maybe<ClientInfo>& reservedClientInfo =
89
0
          oldLoadInfo->GetReservedClientInfo();
90
0
91
0
        const Maybe<ClientInfo>& initialClientInfo =
92
0
          oldLoadInfo->GetInitialClientInfo();
93
0
94
0
        MOZ_DIAGNOSTIC_ASSERT(reservedClientInfo.isNothing() ||
95
0
                              initialClientInfo.isNothing());
96
0
97
0
        if (reservedClientInfo.isSome()) {
98
0
          newLoadInfo->SetReservedClientInfo(reservedClientInfo.ref());
99
0
        }
100
0
101
0
        if (initialClientInfo.isSome()) {
102
0
          newLoadInfo->SetInitialClientInfo(initialClientInfo.ref());
103
0
        }
104
0
      }
105
0
    }
106
0
107
0
    // If it's a cross-origin redirect then we discard the old reserved client
108
0
    // and create a new one.
109
0
    else {
110
0
      // If CheckSameOrigin() worked, then the security manager must exist.
111
0
      nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
112
0
      MOZ_DIAGNOSTIC_ASSERT(ssm);
113
0
114
0
      nsCOMPtr<nsIPrincipal> principal;
115
0
      rv = ssm->GetChannelResultPrincipal(aNewChannel, getter_AddRefs(principal));
116
0
      NS_ENSURE_SUCCESS(rv, rv);
117
0
118
0
      // Create the new ClientSource.  This should only happen for window
119
0
      // Clients since support cross-origin redirects are blocked by the
120
0
      // same-origin security policy.
121
0
      reservedClient.reset();
122
0
      reservedClient = ClientManager::CreateSource(ClientType::Window,
123
0
                                                   mEventTarget, principal);
124
0
      MOZ_DIAGNOSTIC_ASSERT(reservedClient);
125
0
126
0
      newLoadInfo->GiveReservedClientSource(std::move(reservedClient));
127
0
    }
128
0
129
0
    uint32_t redirectMode = nsIHttpChannelInternal::REDIRECT_MODE_MANUAL;
130
0
    nsCOMPtr<nsIHttpChannelInternal> http = do_QueryInterface(aOldChannel);
131
0
    if (http) {
132
0
      MOZ_ALWAYS_SUCCEEDS(http->GetRedirectMode(&redirectMode));
133
0
    }
134
0
135
0
    // Normally we keep the controller across channel redirects, but we must
136
0
    // clear it when a document load redirects.  Only do this for real
137
0
    // redirects, however.
138
0
    //
139
0
    // This is effectively described in step 4.2 of:
140
0
    //
141
0
    //  https://fetch.spec.whatwg.org/#http-fetch
142
0
    //
143
0
    // The spec sets the service-workers mode to none when the request is
144
0
    // configured to *not* follow redirects.  This prevents any further
145
0
    // service workers from intercepting.  The first service worker that
146
0
    // had a shot at the FetchEvent remains the controller in this case.
147
0
    if (!(aFlags & nsIChannelEventSink::REDIRECT_INTERNAL) &&
148
0
        redirectMode != nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW) {
149
0
      newLoadInfo->ClearController();
150
0
    }
151
0
152
0
    nsCOMPtr<nsIChannelEventSink> outerSink = do_GetInterface(mOuter);
153
0
    if (outerSink) {
154
0
      return outerSink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags,
155
0
                                               aCallback);
156
0
    }
157
0
158
0
    aCallback->OnRedirectVerifyCallback(NS_OK);
159
0
    return NS_OK;
160
0
  }
161
162
public:
163
  ClientChannelHelper(nsIInterfaceRequestor* aOuter,
164
                      nsISerialEventTarget* aEventTarget)
165
    : mOuter(aOuter)
166
    , mEventTarget(aEventTarget)
167
0
  {
168
0
  }
169
170
  NS_DECL_ISUPPORTS
171
};
172
173
NS_IMPL_ISUPPORTS(ClientChannelHelper, nsIInterfaceRequestor,
174
                                       nsIChannelEventSink);
175
176
} // anonymous namespace
177
178
nsresult
179
AddClientChannelHelper(nsIChannel* aChannel,
180
                       Maybe<ClientInfo>&& aReservedClientInfo,
181
                       Maybe<ClientInfo>&& aInitialClientInfo,
182
                       nsISerialEventTarget* aEventTarget)
183
0
{
184
0
  MOZ_ASSERT(NS_IsMainThread());
185
0
186
0
  Maybe<ClientInfo> initialClientInfo(std::move(aInitialClientInfo));
187
0
  Maybe<ClientInfo> reservedClientInfo(std::move(aReservedClientInfo));
188
0
  MOZ_DIAGNOSTIC_ASSERT(reservedClientInfo.isNothing() ||
189
0
                        initialClientInfo.isNothing());
190
0
191
0
  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
192
0
  NS_ENSURE_TRUE(loadInfo, NS_ERROR_FAILURE);
193
0
194
0
  nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
195
0
  NS_ENSURE_TRUE(ssm, NS_ERROR_FAILURE);
196
0
197
0
  nsCOMPtr<nsIPrincipal> channelPrincipal;
198
0
  nsresult rv = ssm->GetChannelResultPrincipal(aChannel, getter_AddRefs(channelPrincipal));
199
0
  NS_ENSURE_SUCCESS(rv, rv);
200
0
201
0
  // Only allow the initial ClientInfo to be set if the current channel
202
0
  // principal matches.
203
0
  if (initialClientInfo.isSome()) {
204
0
    nsCOMPtr<nsIPrincipal> initialPrincipal =
205
0
      PrincipalInfoToPrincipal(initialClientInfo.ref().PrincipalInfo(), nullptr);
206
0
207
0
    bool equals = false;
208
0
    rv = initialPrincipal ? initialPrincipal->Equals(channelPrincipal, &equals)
209
0
                          : NS_ERROR_FAILURE;
210
0
    if (NS_FAILED(rv) || !equals) {
211
0
      initialClientInfo.reset();
212
0
    }
213
0
  }
214
0
215
0
  // Only allow the reserved ClientInfo to be set if the current channel
216
0
  // principal matches.
217
0
  if (reservedClientInfo.isSome()) {
218
0
    nsCOMPtr<nsIPrincipal> reservedPrincipal =
219
0
      PrincipalInfoToPrincipal(reservedClientInfo.ref().PrincipalInfo(), nullptr);
220
0
221
0
    bool equals = false;
222
0
    rv = reservedPrincipal ? reservedPrincipal->Equals(channelPrincipal, &equals)
223
0
                           : NS_ERROR_FAILURE;
224
0
    if (NS_FAILED(rv) || !equals) {
225
0
      reservedClientInfo.reset();
226
0
    }
227
0
  }
228
0
229
0
  nsCOMPtr<nsIInterfaceRequestor> outerCallbacks;
230
0
  rv = aChannel->GetNotificationCallbacks(getter_AddRefs(outerCallbacks));
231
0
  NS_ENSURE_SUCCESS(rv, rv);
232
0
233
0
  UniquePtr<ClientSource> reservedClient;
234
0
  if (initialClientInfo.isNothing() && reservedClientInfo.isNothing()) {
235
0
    // Wait to reserve the client until we are reasonably sure this method
236
0
    // will succeed.  We should only follow this path for window clients.
237
0
    // Workers should always provide a reserved ClientInfo since their
238
0
    // ClientSource object is owned by a different thread.
239
0
    reservedClient = ClientManager::CreateSource(ClientType::Window,
240
0
                                                 aEventTarget,
241
0
                                                 channelPrincipal);
242
0
    MOZ_DIAGNOSTIC_ASSERT(reservedClient);
243
0
  }
244
0
245
0
  RefPtr<ClientChannelHelper> helper =
246
0
    new ClientChannelHelper(outerCallbacks, aEventTarget);
247
0
248
0
  // Only set the callbacks helper if we are able to reserve the client
249
0
  // successfully.
250
0
  rv = aChannel->SetNotificationCallbacks(helper);
251
0
  NS_ENSURE_SUCCESS(rv, rv);
252
0
253
0
  // Finally preserve the various client values on the nsILoadInfo once the
254
0
  // redirect helper has been added to the channel.
255
0
  if (reservedClient) {
256
0
    loadInfo->GiveReservedClientSource(std::move(reservedClient));
257
0
  }
258
0
259
0
  if (initialClientInfo.isSome()) {
260
0
    loadInfo->SetInitialClientInfo(initialClientInfo.ref());
261
0
  }
262
0
263
0
  if (reservedClientInfo.isSome()) {
264
0
    loadInfo->SetReservedClientInfo(reservedClientInfo.ref());
265
0
  }
266
0
267
0
  return NS_OK;
268
0
}
269
270
} // namespace dom
271
} // namespace mozilla