ClientChannelHelper.cpp (16245B)
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/StaticPrefs_privacy.h" 13 #include "mozilla/StoragePrincipalHelper.h" 14 #include "mozilla/dom/ClientsBinding.h" 15 #include "mozilla/dom/ServiceWorkerDescriptor.h" 16 #include "mozilla/ipc/BackgroundUtils.h" 17 #include "nsContentUtils.h" 18 #include "nsIAsyncVerifyRedirectCallback.h" 19 #include "nsIChannel.h" 20 #include "nsIChannelEventSink.h" 21 #include "nsIHttpChannelInternal.h" 22 #include "nsIInterfaceRequestor.h" 23 #include "nsIInterfaceRequestorUtils.h" 24 25 namespace mozilla::dom { 26 27 using mozilla::ipc::PrincipalInfoToPrincipal; 28 29 namespace { 30 31 // In the default mode, ClientChannelHelper runs in the content process and 32 // handles all redirects. When we use DocumentChannel, redirects aren't exposed 33 // to the content process, so we run an instance of this in both processes, one 34 // to handle redirects in the parent and one to handle the final channel 35 // replacement (DocumentChannelChild 'redirects' to the final channel) in the 36 // child. 37 38 class ClientChannelHelper : public nsIInterfaceRequestor, 39 public nsIChannelEventSink { 40 protected: 41 nsCOMPtr<nsIInterfaceRequestor> mOuter; 42 nsCOMPtr<nsISerialEventTarget> mEventTarget; 43 44 virtual ~ClientChannelHelper() = default; 45 46 NS_IMETHOD 47 GetInterface(const nsIID& aIID, void** aResultOut) override { 48 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { 49 *aResultOut = static_cast<nsIChannelEventSink*>(this); 50 NS_ADDREF_THIS(); 51 return NS_OK; 52 } 53 54 if (mOuter) { 55 return mOuter->GetInterface(aIID, aResultOut); 56 } 57 58 return NS_ERROR_NO_INTERFACE; 59 } 60 61 virtual void CreateClient(nsILoadInfo* aLoadInfo, nsIPrincipal* aPrincipal) { 62 CreateClientForPrincipal(aLoadInfo, aPrincipal, mEventTarget); 63 } 64 65 NS_IMETHOD 66 AsyncOnChannelRedirect(nsIChannel* aOldChannel, nsIChannel* aNewChannel, 67 uint32_t aFlags, 68 nsIAsyncVerifyRedirectCallback* aCallback) override { 69 MOZ_ASSERT(NS_IsMainThread()); 70 71 nsresult rv = nsContentUtils::CheckSameOrigin(aOldChannel, aNewChannel); 72 if (NS_WARN_IF(NS_FAILED(rv) && rv != NS_ERROR_DOM_BAD_URI)) { 73 return rv; 74 } 75 76 nsCOMPtr<nsILoadInfo> oldLoadInfo = aOldChannel->LoadInfo(); 77 nsCOMPtr<nsILoadInfo> newLoadInfo = aNewChannel->LoadInfo(); 78 79 UniquePtr<ClientSource> reservedClient = 80 oldLoadInfo->TakeReservedClientSource(); 81 82 // If its a same-origin redirect we just move our reserved client to the 83 // new channel. 84 if (NS_SUCCEEDED(rv)) { 85 if (reservedClient) { 86 newLoadInfo->GiveReservedClientSource(std::move(reservedClient)); 87 } 88 89 // It seems sometimes necko passes two channels with the same LoadInfo. 90 // We only need to move the reserved/initial ClientInfo over if we 91 // actually have a different LoadInfo. 92 else if (oldLoadInfo != newLoadInfo) { 93 const Maybe<ClientInfo>& reservedClientInfo = 94 oldLoadInfo->GetReservedClientInfo(); 95 96 const Maybe<ClientInfo>& initialClientInfo = 97 oldLoadInfo->GetInitialClientInfo(); 98 99 MOZ_DIAGNOSTIC_ASSERT(reservedClientInfo.isNothing() || 100 initialClientInfo.isNothing()); 101 102 if (reservedClientInfo.isSome()) { 103 // Create a new client for the case the controller is cleared for the 104 // new loadInfo. ServiceWorkerManager::DispatchFetchEvent() called 105 // ServiceWorkerManager::StartControllingClient() making the old 106 // client to be controlled eventually. However, the controller setting 107 // propagation to the child process could happen later than 108 // nsGlobalWindowInner::EnsureClientSource(), such that 109 // nsGlobalWindowInner will be controlled as unexpected. 110 if (oldLoadInfo->GetController().isSome() && 111 newLoadInfo->GetController().isNothing()) { 112 nsCOMPtr<nsIPrincipal> foreignPartitionedPrincipal; 113 rv = StoragePrincipalHelper::GetPrincipal( 114 aNewChannel, 115 StaticPrefs::privacy_partition_serviceWorkers() 116 ? StoragePrincipalHelper::eForeignPartitionedPrincipal 117 : StoragePrincipalHelper::eRegularPrincipal, 118 getter_AddRefs(foreignPartitionedPrincipal)); 119 NS_ENSURE_SUCCESS(rv, rv); 120 reservedClient.reset(); 121 CreateClient(newLoadInfo, foreignPartitionedPrincipal); 122 } else { 123 newLoadInfo->SetReservedClientInfo(reservedClientInfo.ref()); 124 } 125 } 126 127 if (initialClientInfo.isSome()) { 128 newLoadInfo->SetInitialClientInfo(initialClientInfo.ref()); 129 } 130 } 131 } 132 133 // If it's a cross-origin redirect then we discard the old reserved client 134 // and create a new one. 135 else { 136 nsCOMPtr<nsIPrincipal> foreignPartitionedPrincipal; 137 rv = StoragePrincipalHelper::GetPrincipal( 138 aNewChannel, 139 StaticPrefs::privacy_partition_serviceWorkers() 140 ? StoragePrincipalHelper::eForeignPartitionedPrincipal 141 : StoragePrincipalHelper::eRegularPrincipal, 142 getter_AddRefs(foreignPartitionedPrincipal)); 143 NS_ENSURE_SUCCESS(rv, rv); 144 145 reservedClient.reset(); 146 CreateClient(newLoadInfo, foreignPartitionedPrincipal); 147 } 148 149 uint32_t redirectMode = nsIHttpChannelInternal::REDIRECT_MODE_MANUAL; 150 nsCOMPtr<nsIHttpChannelInternal> http = do_QueryInterface(aOldChannel); 151 if (http) { 152 MOZ_ALWAYS_SUCCEEDS(http->GetRedirectMode(&redirectMode)); 153 } 154 155 // Normally we keep the controller across channel redirects, but we must 156 // clear it when a document load redirects. Only do this for real 157 // redirects, however. 158 // 159 // This is effectively described in step 4.2 of: 160 // 161 // https://fetch.spec.whatwg.org/#http-fetch 162 // 163 // The spec sets the service-workers mode to none when the request is 164 // configured to *not* follow redirects. This prevents any further 165 // service workers from intercepting. The first service worker that 166 // had a shot at the FetchEvent remains the controller in this case. 167 if (!(aFlags & nsIChannelEventSink::REDIRECT_INTERNAL) && 168 redirectMode != nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW) { 169 newLoadInfo->ClearController(); 170 } 171 172 nsCOMPtr<nsIChannelEventSink> outerSink = do_GetInterface(mOuter); 173 if (outerSink) { 174 return outerSink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, 175 aCallback); 176 } 177 178 aCallback->OnRedirectVerifyCallback(NS_OK); 179 return NS_OK; 180 } 181 182 public: 183 ClientChannelHelper(nsIInterfaceRequestor* aOuter, 184 nsISerialEventTarget* aEventTarget) 185 : mOuter(aOuter), mEventTarget(aEventTarget) {} 186 187 NS_DECL_ISUPPORTS 188 189 virtual void CreateClientForPrincipal(nsILoadInfo* aLoadInfo, 190 nsIPrincipal* aPrincipal, 191 nsISerialEventTarget* aEventTarget) { 192 // Create the new ClientSource. This should only happen for window 193 // Clients since support cross-origin redirects are blocked by the 194 // same-origin security policy. 195 UniquePtr<ClientSource> reservedClient = ClientManager::CreateSource( 196 ClientType::Window, aEventTarget, aPrincipal); 197 MOZ_DIAGNOSTIC_ASSERT(reservedClient); 198 199 aLoadInfo->GiveReservedClientSource(std::move(reservedClient)); 200 } 201 }; 202 203 NS_IMPL_ISUPPORTS(ClientChannelHelper, nsIInterfaceRequestor, 204 nsIChannelEventSink); 205 206 class ClientChannelHelperParent final : public ClientChannelHelper { 207 ~ClientChannelHelperParent() { 208 // This requires that if a load completes, the associated ClientSource is 209 // created and registers itself before this ClientChannelHelperParent is 210 // destroyed. Otherwise, we may incorrectly "forget" a future ClientSource 211 // which will actually be created. 212 SetFutureSourceInfo(Nothing()); 213 } 214 215 void CreateClient(nsILoadInfo* aLoadInfo, nsIPrincipal* aPrincipal) override { 216 CreateClientForPrincipal(aLoadInfo, aPrincipal, mEventTarget); 217 } 218 219 void SetFutureSourceInfo(Maybe<ClientInfo>&& aClientInfo) { 220 if (mRecentFutureSourceInfo) { 221 // No-op if the corresponding ClientSource has alrady been created, but 222 // it's not known if that's the case here. 223 ClientManager::ForgetFutureSource(*mRecentFutureSourceInfo); 224 } 225 226 if (aClientInfo) { 227 (void)NS_WARN_IF(!ClientManager::ExpectFutureSource(*aClientInfo)); 228 } 229 230 mRecentFutureSourceInfo = std::move(aClientInfo); 231 } 232 233 // Keep track of the most recent ClientInfo created which isn't backed by a 234 // ClientSource, which is used to notify ClientManagerService that the 235 // ClientSource won't ever actually be constructed. 236 Maybe<ClientInfo> mRecentFutureSourceInfo; 237 238 public: 239 void CreateClientForPrincipal(nsILoadInfo* aLoadInfo, 240 nsIPrincipal* aPrincipal, 241 nsISerialEventTarget* aEventTarget) override { 242 // If we're managing redirects in the parent, then we don't want 243 // to create a new ClientSource (since those need to live with 244 // the global), so just allocate a new ClientInfo/id and we can 245 // create a ClientSource when the final channel propagates back 246 // to the child. 247 Maybe<ClientInfo> reservedInfo = 248 ClientManager::CreateInfo(ClientType::Window, aPrincipal); 249 if (reservedInfo) { 250 aLoadInfo->SetReservedClientInfo(*reservedInfo); 251 SetFutureSourceInfo(std::move(reservedInfo)); 252 } 253 } 254 ClientChannelHelperParent(nsIInterfaceRequestor* aOuter, 255 nsISerialEventTarget* aEventTarget) 256 : ClientChannelHelper(aOuter, nullptr) {} 257 }; 258 259 class ClientChannelHelperChild final : public ClientChannelHelper { 260 ~ClientChannelHelperChild() = default; 261 262 NS_IMETHOD 263 AsyncOnChannelRedirect(nsIChannel* aOldChannel, nsIChannel* aNewChannel, 264 uint32_t aFlags, 265 nsIAsyncVerifyRedirectCallback* aCallback) override { 266 MOZ_ASSERT(NS_IsMainThread()); 267 268 // All ClientInfo allocation should have been handled in the parent process 269 // by ClientChannelHelperParent, so the only remaining thing to do is to 270 // allocate a ClientSource around the ClientInfo on the channel. 271 CreateReservedSourceIfNeeded(aNewChannel, mEventTarget); 272 273 nsCOMPtr<nsIChannelEventSink> outerSink = do_GetInterface(mOuter); 274 if (outerSink) { 275 return outerSink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, 276 aCallback); 277 } 278 279 aCallback->OnRedirectVerifyCallback(NS_OK); 280 return NS_OK; 281 } 282 283 public: 284 ClientChannelHelperChild(nsIInterfaceRequestor* aOuter, 285 nsISerialEventTarget* aEventTarget) 286 : ClientChannelHelper(aOuter, aEventTarget) {} 287 }; 288 289 } // anonymous namespace 290 291 template <typename T> 292 nsresult AddClientChannelHelperInternal(nsIChannel* aChannel, 293 Maybe<ClientInfo>&& aReservedClientInfo, 294 Maybe<ClientInfo>&& aInitialClientInfo, 295 nsISerialEventTarget* aEventTarget) { 296 MOZ_ASSERT(NS_IsMainThread()); 297 298 Maybe<ClientInfo> initialClientInfo(std::move(aInitialClientInfo)); 299 Maybe<ClientInfo> reservedClientInfo(std::move(aReservedClientInfo)); 300 MOZ_DIAGNOSTIC_ASSERT(reservedClientInfo.isNothing() || 301 initialClientInfo.isNothing()); 302 303 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 304 305 nsCOMPtr<nsIPrincipal> channelForeignPartitionedPrincipal; 306 nsresult rv = StoragePrincipalHelper::GetPrincipal( 307 aChannel, 308 StaticPrefs::privacy_partition_serviceWorkers() 309 ? StoragePrincipalHelper::eForeignPartitionedPrincipal 310 : StoragePrincipalHelper::eRegularPrincipal, 311 getter_AddRefs(channelForeignPartitionedPrincipal)); 312 NS_ENSURE_SUCCESS(rv, rv); 313 314 // Only allow the initial ClientInfo to be set if the current channel 315 // principal matches. 316 if (initialClientInfo.isSome()) { 317 auto initialPrincipalOrErr = 318 PrincipalInfoToPrincipal(initialClientInfo.ref().PrincipalInfo()); 319 320 bool equals = false; 321 rv = initialPrincipalOrErr.isErr() 322 ? initialPrincipalOrErr.unwrapErr() 323 : initialPrincipalOrErr.unwrap()->Equals( 324 channelForeignPartitionedPrincipal, &equals); 325 if (NS_FAILED(rv) || !equals) { 326 initialClientInfo.reset(); 327 } 328 } 329 330 // Only allow the reserved ClientInfo to be set if the current channel 331 // principal matches. 332 if (reservedClientInfo.isSome()) { 333 auto reservedPrincipalOrErr = 334 PrincipalInfoToPrincipal(reservedClientInfo.ref().PrincipalInfo()); 335 336 bool equals = false; 337 rv = reservedPrincipalOrErr.isErr() 338 ? reservedPrincipalOrErr.unwrapErr() 339 : reservedPrincipalOrErr.unwrap()->Equals( 340 channelForeignPartitionedPrincipal, &equals); 341 if (NS_FAILED(rv) || !equals) { 342 reservedClientInfo.reset(); 343 } 344 } 345 346 nsCOMPtr<nsIInterfaceRequestor> outerCallbacks; 347 rv = aChannel->GetNotificationCallbacks(getter_AddRefs(outerCallbacks)); 348 NS_ENSURE_SUCCESS(rv, rv); 349 350 RefPtr<ClientChannelHelper> helper = new T(outerCallbacks, aEventTarget); 351 352 if (initialClientInfo.isNothing() && reservedClientInfo.isNothing()) { 353 helper->CreateClientForPrincipal( 354 loadInfo, channelForeignPartitionedPrincipal, aEventTarget); 355 } 356 357 // Only set the callbacks helper if we are able to reserve the client 358 // successfully. 359 rv = aChannel->SetNotificationCallbacks(helper); 360 NS_ENSURE_SUCCESS(rv, rv); 361 362 if (initialClientInfo.isSome()) { 363 loadInfo->SetInitialClientInfo(initialClientInfo.ref()); 364 } 365 366 if (reservedClientInfo.isSome()) { 367 loadInfo->SetReservedClientInfo(reservedClientInfo.ref()); 368 } 369 370 return NS_OK; 371 } 372 373 nsresult AddClientChannelHelper(nsIChannel* aChannel, 374 Maybe<ClientInfo>&& aReservedClientInfo, 375 Maybe<ClientInfo>&& aInitialClientInfo, 376 nsISerialEventTarget* aEventTarget) { 377 return AddClientChannelHelperInternal<ClientChannelHelper>( 378 aChannel, std::move(aReservedClientInfo), std::move(aInitialClientInfo), 379 aEventTarget); 380 } 381 382 nsresult AddClientChannelHelperInParent( 383 nsIChannel* aChannel, Maybe<ClientInfo>&& aInitialClientInfo) { 384 Maybe<ClientInfo> emptyReservedInfo; 385 return AddClientChannelHelperInternal<ClientChannelHelperParent>( 386 aChannel, std::move(emptyReservedInfo), std::move(aInitialClientInfo), 387 nullptr); 388 } 389 390 nsresult AddClientChannelHelperInChild(nsIChannel* aChannel, 391 nsISerialEventTarget* aEventTarget) { 392 MOZ_ASSERT(NS_IsMainThread()); 393 394 nsCOMPtr<nsIInterfaceRequestor> outerCallbacks; 395 nsresult rv = 396 aChannel->GetNotificationCallbacks(getter_AddRefs(outerCallbacks)); 397 NS_ENSURE_SUCCESS(rv, rv); 398 399 RefPtr<ClientChannelHelper> helper = 400 new ClientChannelHelperChild(outerCallbacks, aEventTarget); 401 402 // Only set the callbacks helper if we are able to reserve the client 403 // successfully. 404 rv = aChannel->SetNotificationCallbacks(helper); 405 NS_ENSURE_SUCCESS(rv, rv); 406 407 return NS_OK; 408 } 409 410 void CreateReservedSourceIfNeeded(nsIChannel* aChannel, 411 nsISerialEventTarget* aEventTarget) { 412 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 413 const Maybe<ClientInfo>& reservedClientInfo = 414 loadInfo->GetReservedClientInfo(); 415 416 if (reservedClientInfo) { 417 UniquePtr<ClientSource> reservedClient = 418 ClientManager::CreateSourceFromInfo(*reservedClientInfo, aEventTarget); 419 loadInfo->GiveReservedClientSource(std::move(reservedClient)); 420 } 421 } 422 423 } // namespace mozilla::dom