CacheOpParent.cpp (10869B)
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 "mozilla/dom/cache/CacheOpParent.h" 8 9 #include "mozilla/ErrorResult.h" 10 #include "mozilla/StaticPrefs_browser.h" 11 #include "mozilla/dom/cache/AutoUtils.h" 12 #include "mozilla/dom/cache/ManagerId.h" 13 #include "mozilla/dom/cache/ReadStream.h" 14 #include "mozilla/dom/cache/SavedTypes.h" 15 #include "mozilla/ipc/IPCStreamUtils.h" 16 #include "mozilla/ipc/InputStreamUtils.h" 17 18 namespace mozilla::dom::cache { 19 20 using mozilla::ipc::PBackgroundParent; 21 22 CacheOpParent::CacheOpParent(const WeakRefParentType& aIpcManager, 23 const CacheOpArgs& aOpArgs, CacheId aCacheId, 24 Namespace aNamespace) 25 : mIpcManager(aIpcManager), 26 mCacheId(aCacheId), 27 mNamespace(aNamespace), 28 mOpArgs(aOpArgs) { 29 MOZ_DIAGNOSTIC_ASSERT(mIpcManager.isSome()); 30 } 31 32 CacheOpParent::~CacheOpParent() { NS_ASSERT_OWNINGTHREAD(CacheOpParent); } 33 34 void CacheOpParent::Execute(const SafeRefPtr<ManagerId>& aManagerId) { 35 NS_ASSERT_OWNINGTHREAD(CacheOpParent); 36 MOZ_DIAGNOSTIC_ASSERT(!mManager); 37 MOZ_DIAGNOSTIC_ASSERT(!mVerifier); 38 39 auto managerOrErr = cache::Manager::AcquireCreateIfNonExistent(aManagerId); 40 if (NS_WARN_IF(managerOrErr.isErr())) { 41 (void)Send__delete__(this, CopyableErrorResult(managerOrErr.unwrapErr()), 42 void_t()); 43 return; 44 } 45 46 Execute(managerOrErr.unwrap()); 47 } 48 49 void CacheOpParent::Execute(SafeRefPtr<cache::Manager> aManager) { 50 NS_ASSERT_OWNINGTHREAD(CacheOpParent); 51 MOZ_DIAGNOSTIC_ASSERT(!mManager); 52 MOZ_DIAGNOSTIC_ASSERT(!mVerifier); 53 54 mManager = std::move(aManager); 55 56 // Handle put op 57 if (mOpArgs.type() == CacheOpArgs::TCachePutAllArgs) { 58 MOZ_DIAGNOSTIC_ASSERT(mCacheId != INVALID_CACHE_ID); 59 60 const CachePutAllArgs& args = mOpArgs.get_CachePutAllArgs(); 61 const nsTArray<CacheRequestResponse>& list = args.requestResponseList(); 62 63 AutoTArray<nsCOMPtr<nsIInputStream>, 256> requestStreamList; 64 AutoTArray<nsCOMPtr<nsIInputStream>, 256> responseStreamList; 65 66 for (uint32_t i = 0; i < list.Length(); ++i) { 67 requestStreamList.AppendElement( 68 DeserializeCacheStream(list[i].request().body())); 69 responseStreamList.AppendElement( 70 DeserializeCacheStream(list[i].response().body())); 71 } 72 73 mManager->ExecutePutAll(this, mCacheId, args.requestResponseList(), 74 requestStreamList, responseStreamList); 75 return; 76 } 77 78 // Handle all other cache ops 79 if (mCacheId != INVALID_CACHE_ID) { 80 MOZ_DIAGNOSTIC_ASSERT(mNamespace == INVALID_NAMESPACE); 81 mManager->ExecuteCacheOp(this, mCacheId, mOpArgs); 82 return; 83 } 84 85 // Handle all storage ops 86 MOZ_DIAGNOSTIC_ASSERT(mNamespace != INVALID_NAMESPACE); 87 mManager->ExecuteStorageOp(this, mNamespace, mOpArgs); 88 } 89 90 void CacheOpParent::WaitForVerification(PrincipalVerifier* aVerifier) { 91 NS_ASSERT_OWNINGTHREAD(CacheOpParent); 92 MOZ_DIAGNOSTIC_ASSERT(!mManager); 93 MOZ_DIAGNOSTIC_ASSERT(!mVerifier); 94 95 mVerifier = aVerifier; 96 mVerifier->AddListener(*this); 97 } 98 99 void CacheOpParent::ActorDestroy(ActorDestroyReason aReason) { 100 NS_ASSERT_OWNINGTHREAD(CacheOpParent); 101 102 if (mVerifier) { 103 mVerifier->RemoveListener(*this); 104 mVerifier = nullptr; 105 } 106 107 if (mManager) { 108 mManager->RemoveListener(this); 109 mManager = nullptr; 110 } 111 112 mIpcManager.destroy(); 113 } 114 115 void CacheOpParent::OnPrincipalVerified( 116 nsresult aRv, const SafeRefPtr<ManagerId>& aManagerId) { 117 NS_ASSERT_OWNINGTHREAD(CacheOpParent); 118 119 mVerifier->RemoveListener(*this); 120 mVerifier = nullptr; 121 122 if (NS_WARN_IF(NS_FAILED(aRv))) { 123 (void)Send__delete__(this, CopyableErrorResult(aRv), void_t()); 124 return; 125 } 126 127 Execute(aManagerId); 128 } 129 130 void CacheOpParent::OnOpComplete(ErrorResult&& aRv, 131 const CacheOpResult& aResult, 132 CacheId aOpenedCacheId, 133 const Maybe<StreamInfo>& aStreamInfo) { 134 NS_ASSERT_OWNINGTHREAD(CacheOpParent); 135 MOZ_DIAGNOSTIC_ASSERT(mIpcManager.isSome()); 136 MOZ_DIAGNOSTIC_ASSERT(mManager); 137 138 // Never send an op-specific result if we have an error. Instead, send 139 // void_t() to ensure that we don't leak actors on the child side. 140 if (NS_WARN_IF(aRv.Failed())) { 141 (void)Send__delete__(this, CopyableErrorResult(std::move(aRv)), void_t()); 142 return; 143 } 144 145 if (aStreamInfo.isSome()) { 146 ProcessCrossOriginResourcePolicyHeader(aRv, 147 aStreamInfo->mSavedResponseList); 148 if (NS_WARN_IF(aRv.Failed())) { 149 (void)Send__delete__(this, CopyableErrorResult(std::move(aRv)), void_t()); 150 return; 151 } 152 } 153 154 uint32_t entryCount = 155 std::max(1lu, aStreamInfo ? static_cast<unsigned long>(std::max( 156 aStreamInfo->mSavedResponseList.Length(), 157 aStreamInfo->mSavedRequestList.Length())) 158 : 0lu); 159 160 // The result must contain the appropriate type at this point. It may 161 // or may not contain the additional result data yet. For types that 162 // do not need special processing, it should already be set. If the 163 // result requires actor-specific operations, then we do that below. 164 // If the type and data types don't match, then we will trigger an 165 // assertion in AutoParentOpResult::Add(). 166 AutoParentOpResult result(mIpcManager.ref(), aResult, entryCount); 167 168 if (aOpenedCacheId != INVALID_CACHE_ID) { 169 result.Add(aOpenedCacheId, mManager.clonePtr()); 170 } 171 172 if (aStreamInfo) { 173 const auto& streamInfo = *aStreamInfo; 174 175 for (const auto& savedResponse : streamInfo.mSavedResponseList) { 176 result.Add(savedResponse, streamInfo.mStreamList); 177 } 178 179 for (const auto& savedRequest : streamInfo.mSavedRequestList) { 180 result.Add(savedRequest, streamInfo.mStreamList); 181 } 182 } 183 184 (void)Send__delete__(this, CopyableErrorResult(std::move(aRv)), 185 result.SendAsOpResult()); 186 } 187 188 already_AddRefed<nsIInputStream> CacheOpParent::DeserializeCacheStream( 189 const Maybe<CacheReadStream>& aMaybeStream) { 190 if (aMaybeStream.isNothing()) { 191 return nullptr; 192 } 193 194 nsCOMPtr<nsIInputStream> stream; 195 const CacheReadStream& readStream = aMaybeStream.ref(); 196 197 // Option 1: One of our own ReadStreams was passed back to us with a stream 198 // control actor. 199 stream = ReadStream::Create(readStream); 200 if (stream) { 201 return stream.forget(); 202 } 203 204 // Option 2: A stream was serialized using normal methods or passed 205 // as a DataPipe. Use the standard method for extracting the 206 // resulting stream. 207 return DeserializeIPCStream(readStream.stream()); 208 } 209 210 void CacheOpParent::ProcessCrossOriginResourcePolicyHeader( 211 ErrorResult& aRv, const nsTArray<SavedResponse>& aResponses) { 212 if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) { 213 return; 214 } 215 // Only checking for match/matchAll. 216 nsILoadInfo::CrossOriginEmbedderPolicy loadingCOEP = 217 nsILoadInfo::EMBEDDER_POLICY_NULL; 218 Maybe<mozilla::ipc::PrincipalInfo> principalInfo; 219 switch (mOpArgs.type()) { 220 case CacheOpArgs::TCacheMatchArgs: { 221 const auto& request = mOpArgs.get_CacheMatchArgs().request(); 222 loadingCOEP = request.loadingEmbedderPolicy(); 223 principalInfo = request.principalInfo(); 224 break; 225 } 226 case CacheOpArgs::TCacheMatchAllArgs: { 227 if (mOpArgs.get_CacheMatchAllArgs().maybeRequest().isSome()) { 228 const auto& request = 229 mOpArgs.get_CacheMatchAllArgs().maybeRequest().ref(); 230 loadingCOEP = request.loadingEmbedderPolicy(); 231 principalInfo = request.principalInfo(); 232 } 233 break; 234 } 235 default: { 236 return; 237 } 238 } 239 240 // skip checking if the request has no principal for same-origin/same-site 241 // checking. 242 if (principalInfo.isNothing() || 243 principalInfo.ref().type() != 244 mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) { 245 return; 246 } 247 const mozilla::ipc::ContentPrincipalInfo& contentPrincipalInfo = 248 principalInfo.ref().get_ContentPrincipalInfo(); 249 250 for (auto it = aResponses.cbegin(); it != aResponses.cend(); ++it) { 251 if (it->mValue.type() != ResponseType::Opaque && 252 it->mValue.type() != ResponseType::Opaqueredirect) { 253 continue; 254 } 255 256 const auto& headers = it->mValue.headers(); 257 const RequestCredentials credentials = it->mValue.credentials(); 258 const auto corpHeaderIt = 259 std::find_if(headers.cbegin(), headers.cend(), [](const auto& header) { 260 return header.name().EqualsLiteral("Cross-Origin-Resource-Policy"); 261 }); 262 263 // According to https://github.com/w3c/ServiceWorker/issues/1490, the cache 264 // response is expected with CORP header, otherwise, throw the type error. 265 // Note that this is different with the CORP checking for fetch metioned in 266 // https://wicg.github.io/cross-origin-embedder-policy/#corp-check. 267 // For fetch, if the response has no CORP header, "same-origin" checking 268 // will be performed. 269 if (corpHeaderIt == headers.cend() && 270 loadingCOEP == nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP) { 271 aRv.ThrowTypeError("Response is expected with CORP header."); 272 return; 273 } 274 275 // Skip the case if the response has no principal for same-origin/same-site 276 // checking. 277 if (it->mValue.principalInfo().isNothing() || 278 it->mValue.principalInfo().ref().type() != 279 mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) { 280 continue; 281 } 282 283 const mozilla::ipc::ContentPrincipalInfo& responseContentPrincipalInfo = 284 it->mValue.principalInfo().ref().get_ContentPrincipalInfo(); 285 286 nsCString corp = 287 corpHeaderIt == headers.cend() ? EmptyCString() : corpHeaderIt->value(); 288 289 if (corp.IsEmpty()) { 290 if (loadingCOEP == nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS) { 291 // This means the request of this request doesn't have 292 // credentials, so it's safe for us to return. 293 if (credentials == RequestCredentials::Omit) { 294 return; 295 } 296 corp = "same-origin"; 297 } 298 } 299 300 if (corp.EqualsLiteral("same-origin")) { 301 if (responseContentPrincipalInfo != contentPrincipalInfo) { 302 aRv.ThrowTypeError("Response is expected from same origin."); 303 return; 304 } 305 } else if (corp.EqualsLiteral("same-site")) { 306 if (!responseContentPrincipalInfo.baseDomain().Equals( 307 contentPrincipalInfo.baseDomain())) { 308 aRv.ThrowTypeError("Response is expected from same site."); 309 return; 310 } 311 } 312 } 313 } 314 315 } // namespace mozilla::dom::cache