AutoUtils.cpp (17289B)
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/AutoUtils.h" 8 9 #include "mozilla/dom/InternalHeaders.h" 10 #include "mozilla/dom/InternalRequest.h" 11 #include "mozilla/dom/cache/CacheParent.h" 12 #include "mozilla/dom/cache/CacheStreamControlParent.h" 13 #include "mozilla/dom/cache/PBoundStorageKeyParent.h" 14 #include "mozilla/dom/cache/ReadStream.h" 15 #include "mozilla/dom/cache/SavedTypes.h" 16 #include "mozilla/dom/cache/StreamList.h" 17 #include "mozilla/dom/cache/TypeUtils.h" 18 #include "mozilla/ipc/IPCStreamUtils.h" 19 #include "mozilla/ipc/PBackgroundParent.h" 20 #include "nsCharSeparatedTokenizer.h" 21 #include "nsHttp.h" 22 23 using mozilla::Maybe; 24 using mozilla::dom::cache::CacheReadStream; 25 using mozilla::ipc::PBackgroundParent; 26 27 namespace { 28 29 enum CleanupAction { Forget, Delete }; 30 31 void CleanupChild(CacheReadStream& aReadStream, CleanupAction aAction) { 32 // fds cleaned up by mStreamCleanupList 33 } 34 35 void CleanupChild(Maybe<CacheReadStream>& aMaybeReadStream, 36 CleanupAction aAction) { 37 if (aMaybeReadStream.isNothing()) { 38 return; 39 } 40 41 CleanupChild(aMaybeReadStream.ref(), aAction); 42 } 43 44 } // namespace 45 46 namespace mozilla::dom::cache { 47 48 // -------------------------------------------- 49 50 AutoChildOpArgs::AutoChildOpArgs(TypeUtils* aTypeUtils, 51 const CacheOpArgs& aOpArgs, 52 uint32_t aEntryCount) 53 : mTypeUtils(aTypeUtils), mOpArgs(aOpArgs), mSent(false) { 54 MOZ_DIAGNOSTIC_ASSERT(mTypeUtils); 55 MOZ_RELEASE_ASSERT(aEntryCount != 0); 56 if (mOpArgs.type() == CacheOpArgs::TCachePutAllArgs) { 57 CachePutAllArgs& args = mOpArgs.get_CachePutAllArgs(); 58 args.requestResponseList().SetCapacity(aEntryCount); 59 } else { 60 MOZ_DIAGNOSTIC_ASSERT(aEntryCount == 1); 61 } 62 } 63 64 AutoChildOpArgs::~AutoChildOpArgs() { 65 CleanupAction action = mSent ? Forget : Delete; 66 67 switch (mOpArgs.type()) { 68 case CacheOpArgs::TCacheMatchArgs: { 69 CacheMatchArgs& args = mOpArgs.get_CacheMatchArgs(); 70 CleanupChild(args.request().body(), action); 71 break; 72 } 73 case CacheOpArgs::TCacheMatchAllArgs: { 74 CacheMatchAllArgs& args = mOpArgs.get_CacheMatchAllArgs(); 75 if (args.maybeRequest().isNothing()) { 76 break; 77 } 78 CleanupChild(args.maybeRequest().ref().body(), action); 79 break; 80 } 81 case CacheOpArgs::TCachePutAllArgs: { 82 CachePutAllArgs& args = mOpArgs.get_CachePutAllArgs(); 83 auto& list = args.requestResponseList(); 84 for (uint32_t i = 0; i < list.Length(); ++i) { 85 CleanupChild(list[i].request().body(), action); 86 CleanupChild(list[i].response().body(), action); 87 } 88 break; 89 } 90 case CacheOpArgs::TCacheDeleteArgs: { 91 CacheDeleteArgs& args = mOpArgs.get_CacheDeleteArgs(); 92 CleanupChild(args.request().body(), action); 93 break; 94 } 95 case CacheOpArgs::TCacheKeysArgs: { 96 CacheKeysArgs& args = mOpArgs.get_CacheKeysArgs(); 97 if (args.maybeRequest().isNothing()) { 98 break; 99 } 100 CleanupChild(args.maybeRequest().ref().body(), action); 101 break; 102 } 103 case CacheOpArgs::TStorageMatchArgs: { 104 StorageMatchArgs& args = mOpArgs.get_StorageMatchArgs(); 105 CleanupChild(args.request().body(), action); 106 break; 107 } 108 default: 109 // Other types do not need cleanup 110 break; 111 } 112 } 113 114 void AutoChildOpArgs::Add(const InternalRequest& aRequest, 115 BodyAction aBodyAction, SchemeAction aSchemeAction, 116 ErrorResult& aRv) { 117 MOZ_DIAGNOSTIC_ASSERT(!mSent); 118 119 switch (mOpArgs.type()) { 120 case CacheOpArgs::TCacheMatchArgs: { 121 CacheMatchArgs& args = mOpArgs.get_CacheMatchArgs(); 122 mTypeUtils->ToCacheRequest(args.request(), aRequest, aBodyAction, 123 aSchemeAction, aRv); 124 break; 125 } 126 case CacheOpArgs::TCacheMatchAllArgs: { 127 CacheMatchAllArgs& args = mOpArgs.get_CacheMatchAllArgs(); 128 MOZ_DIAGNOSTIC_ASSERT(args.maybeRequest().isNothing()); 129 args.maybeRequest().emplace(CacheRequest()); 130 mTypeUtils->ToCacheRequest(args.maybeRequest().ref(), aRequest, 131 aBodyAction, aSchemeAction, aRv); 132 break; 133 } 134 case CacheOpArgs::TCacheDeleteArgs: { 135 CacheDeleteArgs& args = mOpArgs.get_CacheDeleteArgs(); 136 mTypeUtils->ToCacheRequest(args.request(), aRequest, aBodyAction, 137 aSchemeAction, aRv); 138 break; 139 } 140 case CacheOpArgs::TCacheKeysArgs: { 141 CacheKeysArgs& args = mOpArgs.get_CacheKeysArgs(); 142 MOZ_DIAGNOSTIC_ASSERT(args.maybeRequest().isNothing()); 143 args.maybeRequest().emplace(CacheRequest()); 144 mTypeUtils->ToCacheRequest(args.maybeRequest().ref(), aRequest, 145 aBodyAction, aSchemeAction, aRv); 146 break; 147 } 148 case CacheOpArgs::TStorageMatchArgs: { 149 StorageMatchArgs& args = mOpArgs.get_StorageMatchArgs(); 150 mTypeUtils->ToCacheRequest(args.request(), aRequest, aBodyAction, 151 aSchemeAction, aRv); 152 break; 153 } 154 default: 155 MOZ_CRASH("Cache args type cannot send a Request!"); 156 } 157 } 158 159 namespace { 160 161 bool MatchInPutList(const InternalRequest& aRequest, 162 const nsTArray<CacheRequestResponse>& aPutList) { 163 // This method implements the SW spec QueryCache algorithm against an 164 // in memory array of Request/Response objects. This essentially the 165 // same algorithm that is implemented in DBSchema.cpp. Unfortunately 166 // we cannot unify them because when operating against the real database 167 // we don't want to load all request/response objects into memory. 168 169 // Note, we can skip the check for a invalid request method because 170 // Cache should only call into here with a GET or HEAD. 171 #ifdef DEBUG 172 nsAutoCString method; 173 aRequest.GetMethod(method); 174 MOZ_ASSERT(method.LowerCaseEqualsLiteral("get") || 175 method.LowerCaseEqualsLiteral("head")); 176 #endif 177 178 RefPtr<InternalHeaders> requestHeaders = aRequest.Headers(); 179 180 for (uint32_t i = 0; i < aPutList.Length(); ++i) { 181 const CacheRequest& cachedRequest = aPutList[i].request(); 182 const CacheResponse& cachedResponse = aPutList[i].response(); 183 184 nsAutoCString url; 185 aRequest.GetURL(url); 186 187 nsAutoCString requestUrl(cachedRequest.urlWithoutQuery()); 188 requestUrl.Append(cachedRequest.urlQuery()); 189 190 // If the URLs don't match, then just skip to the next entry. 191 if (url != requestUrl) { 192 continue; 193 } 194 195 RefPtr<InternalHeaders> cachedRequestHeaders = 196 TypeUtils::ToInternalHeaders(cachedRequest.headers()); 197 198 RefPtr<InternalHeaders> cachedResponseHeaders = 199 TypeUtils::ToInternalHeaders(cachedResponse.headers()); 200 201 nsCString varyHeaders; 202 ErrorResult rv; 203 cachedResponseHeaders->Get("vary"_ns, varyHeaders, rv); 204 MOZ_ALWAYS_TRUE(!rv.Failed()); 205 206 // Assume the vary headers match until we find a conflict 207 bool varyHeadersMatch = true; 208 209 for (const nsACString& header : 210 nsCCharSeparatedTokenizer(varyHeaders, NS_HTTP_HEADER_SEP).ToRange()) { 211 MOZ_DIAGNOSTIC_ASSERT(!header.EqualsLiteral("*"), 212 "We should have already caught this in " 213 "TypeUtils::ToPCacheResponseWithoutBody()"); 214 215 ErrorResult headerRv; 216 nsAutoCString value; 217 requestHeaders->Get(header, value, headerRv); 218 if (NS_WARN_IF(headerRv.Failed())) { 219 headerRv.SuppressException(); 220 MOZ_DIAGNOSTIC_ASSERT(value.IsEmpty()); 221 } 222 223 nsAutoCString cachedValue; 224 cachedRequestHeaders->Get(header, cachedValue, headerRv); 225 if (NS_WARN_IF(headerRv.Failed())) { 226 headerRv.SuppressException(); 227 MOZ_DIAGNOSTIC_ASSERT(cachedValue.IsEmpty()); 228 } 229 230 if (value != cachedValue) { 231 varyHeadersMatch = false; 232 break; 233 } 234 } 235 236 // URL was equal and all vary headers match! 237 if (varyHeadersMatch) { 238 return true; 239 } 240 } 241 242 return false; 243 } 244 245 } // namespace 246 247 void AutoChildOpArgs::Add(JSContext* aCx, const InternalRequest& aRequest, 248 BodyAction aBodyAction, SchemeAction aSchemeAction, 249 Response& aResponse, ErrorResult& aRv) { 250 MOZ_DIAGNOSTIC_ASSERT(!mSent); 251 252 switch (mOpArgs.type()) { 253 case CacheOpArgs::TCachePutAllArgs: { 254 CachePutAllArgs& args = mOpArgs.get_CachePutAllArgs(); 255 256 // Throw an error if a request/response pair would mask another 257 // request/response pair in the same PutAll operation. This is 258 // step 2.3.2.3 from the "Batch Cache Operations" spec algorithm. 259 if (MatchInPutList(aRequest, args.requestResponseList())) { 260 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 261 return; 262 } 263 264 MOZ_RELEASE_ASSERT(args.requestResponseList().Length() < 265 args.requestResponseList().Capacity()); 266 267 // The FileDescriptorSetChild asserts in its destructor that all fds have 268 // been removed. The copy constructor, however, simply duplicates the 269 // fds without removing any. This means each temporary and copy must be 270 // explicitly cleaned up. 271 // 272 // Avoid a lot of this hassle by making sure we only create one here. On 273 // error we remove it. 274 CacheRequestResponse& pair = *args.requestResponseList().AppendElement(); 275 pair.request().body() = Nothing(); 276 pair.response().body() = Nothing(); 277 278 mTypeUtils->ToCacheRequest(pair.request(), aRequest, aBodyAction, 279 aSchemeAction, aRv); 280 if (!aRv.Failed()) { 281 mTypeUtils->ToCacheResponse(aCx, pair.response(), aResponse, aRv); 282 } 283 284 if (aRv.Failed()) { 285 CleanupChild(pair.request().body(), Delete); 286 args.requestResponseList().RemoveLastElement(); 287 } 288 289 break; 290 } 291 default: 292 MOZ_CRASH("Cache args type cannot send a Request/Response pair!"); 293 } 294 } 295 296 const CacheOpArgs& AutoChildOpArgs::SendAsOpArgs() { 297 MOZ_DIAGNOSTIC_ASSERT(!mSent); 298 mSent = true; 299 return mOpArgs; 300 } 301 302 // -------------------------------------------- 303 304 AutoParentOpResult::AutoParentOpResult(const WeakRefParentType& aManager, 305 const CacheOpResult& aOpResult, 306 uint32_t aEntryCount) 307 : mManager(aManager), 308 mOpResult(aOpResult), 309 mStreamControl(nullptr), 310 mSent(false) { 311 MOZ_RELEASE_ASSERT(aEntryCount != 0); 312 if (mOpResult.type() == CacheOpResult::TCacheMatchAllResult) { 313 CacheMatchAllResult& result = mOpResult.get_CacheMatchAllResult(); 314 result.responseList().SetCapacity(aEntryCount); 315 } else if (mOpResult.type() == CacheOpResult::TCacheKeysResult) { 316 CacheKeysResult& result = mOpResult.get_CacheKeysResult(); 317 result.requestList().SetCapacity(aEntryCount); 318 } else { 319 MOZ_DIAGNOSTIC_ASSERT(aEntryCount == 1); 320 } 321 } 322 323 AutoParentOpResult::~AutoParentOpResult() { 324 CleanupAction action = mSent ? Forget : Delete; 325 326 switch (mOpResult.type()) { 327 case CacheOpResult::TStorageOpenResult: { 328 StorageOpenResult& result = mOpResult.get_StorageOpenResult(); 329 if (action == Forget || !result.actor()) { 330 break; 331 } 332 333 QM_WARNONLY_TRY( 334 OkIf(PCacheParent::Send__delete__(result.actor().AsParent()))); 335 break; 336 } 337 default: 338 // other types do not need additional clean up 339 break; 340 } 341 342 if (action == Delete && mStreamControl) { 343 mStreamControl->AssertWillDelete(); 344 QM_WARNONLY_TRY( 345 OkIf(PCacheStreamControlParent::Send__delete__(mStreamControl))); 346 } 347 } 348 349 void AutoParentOpResult::Add(CacheId aOpenedCacheId, 350 SafeRefPtr<Manager> aManager) { 351 MOZ_DIAGNOSTIC_ASSERT(mOpResult.type() == CacheOpResult::TStorageOpenResult); 352 MOZ_DIAGNOSTIC_ASSERT(!mOpResult.get_StorageOpenResult().actor()); 353 354 PCacheParent* cacheParent = nullptr; 355 if (mManager.is<pPBoundStorageKeyParent>()) { 356 auto* manager = mManager.as<pPBoundStorageKeyParent>(); 357 MOZ_ASSERT(manager); 358 359 cacheParent = manager->SendPCacheConstructor( 360 new CacheParent(mManager, std::move(aManager), aOpenedCacheId)); 361 } else { 362 MOZ_ASSERT(mManager.is<pPBackgroundParent>()); 363 auto* manager = mManager.as<pPBackgroundParent>(); 364 MOZ_ASSERT(manager); 365 366 cacheParent = manager->SendPCacheConstructor( 367 new CacheParent(mManager, std::move(aManager), aOpenedCacheId)); 368 } 369 370 mOpResult.get_StorageOpenResult().actor() = cacheParent; 371 } 372 373 void AutoParentOpResult::Add(const SavedResponse& aSavedResponse, 374 StreamList& aStreamList) { 375 MOZ_DIAGNOSTIC_ASSERT(!mSent); 376 377 switch (mOpResult.type()) { 378 case CacheOpResult::TCacheMatchResult: { 379 CacheMatchResult& result = mOpResult.get_CacheMatchResult(); 380 MOZ_DIAGNOSTIC_ASSERT(result.maybeResponse().isNothing()); 381 result.maybeResponse().emplace(aSavedResponse.mValue); 382 SerializeResponseBody(aSavedResponse, aStreamList, 383 &result.maybeResponse().ref()); 384 break; 385 } 386 case CacheOpResult::TCacheMatchAllResult: { 387 CacheMatchAllResult& result = mOpResult.get_CacheMatchAllResult(); 388 MOZ_RELEASE_ASSERT(result.responseList().Length() < 389 result.responseList().Capacity()); 390 result.responseList().AppendElement(aSavedResponse.mValue); 391 SerializeResponseBody(aSavedResponse, aStreamList, 392 &result.responseList().LastElement()); 393 break; 394 } 395 case CacheOpResult::TStorageMatchResult: { 396 StorageMatchResult& result = mOpResult.get_StorageMatchResult(); 397 MOZ_DIAGNOSTIC_ASSERT(result.maybeResponse().isNothing()); 398 result.maybeResponse().emplace(aSavedResponse.mValue); 399 SerializeResponseBody(aSavedResponse, aStreamList, 400 &result.maybeResponse().ref()); 401 break; 402 } 403 default: 404 MOZ_CRASH("Cache result type cannot handle returning a Response!"); 405 } 406 } 407 408 void AutoParentOpResult::Add(const SavedRequest& aSavedRequest, 409 StreamList& aStreamList) { 410 MOZ_DIAGNOSTIC_ASSERT(!mSent); 411 412 switch (mOpResult.type()) { 413 case CacheOpResult::TCacheKeysResult: { 414 CacheKeysResult& result = mOpResult.get_CacheKeysResult(); 415 MOZ_RELEASE_ASSERT(result.requestList().Length() < 416 result.requestList().Capacity()); 417 result.requestList().AppendElement(aSavedRequest.mValue); 418 CacheRequest& request = result.requestList().LastElement(); 419 420 if (!aSavedRequest.mHasBodyId) { 421 request.body() = Nothing(); 422 break; 423 } 424 425 request.body().emplace(CacheReadStream()); 426 SerializeReadStream(aSavedRequest.mBodyId, aStreamList, 427 &request.body().ref()); 428 break; 429 } 430 default: 431 MOZ_CRASH("Cache result type cannot handle returning a Request!"); 432 } 433 } 434 435 const CacheOpResult& AutoParentOpResult::SendAsOpResult() { 436 MOZ_DIAGNOSTIC_ASSERT(!mSent); 437 mSent = true; 438 return mOpResult; 439 } 440 441 void AutoParentOpResult::SerializeResponseBody( 442 const SavedResponse& aSavedResponse, StreamList& aStreamList, 443 CacheResponse* aResponseOut) { 444 MOZ_DIAGNOSTIC_ASSERT(aResponseOut); 445 446 if (!aSavedResponse.mHasBodyId) { 447 aResponseOut->body() = Nothing(); 448 return; 449 } 450 451 aResponseOut->body().emplace(CacheReadStream()); 452 SerializeReadStream(aSavedResponse.mBodyId, aStreamList, 453 &aResponseOut->body().ref()); 454 } 455 456 void AutoParentOpResult::SerializeReadStream(const nsID& aId, 457 StreamList& aStreamList, 458 CacheReadStream* aReadStreamOut) { 459 MOZ_DIAGNOSTIC_ASSERT(aReadStreamOut); 460 MOZ_DIAGNOSTIC_ASSERT(!mSent); 461 462 nsCOMPtr<nsIInputStream> stream = aStreamList.Extract(aId); 463 464 CacheStreamControlParent* cacheStreamControlParent = nullptr; 465 if (!mStreamControl) { 466 if (mManager.is<pPBoundStorageKeyParent>()) { 467 auto* manager = mManager.as<pPBoundStorageKeyParent>(); 468 cacheStreamControlParent = static_cast<CacheStreamControlParent*>( 469 manager->SendPCacheStreamControlConstructor( 470 new CacheStreamControlParent())); 471 } else { 472 MOZ_ASSERT(mManager.is<pPBackgroundParent>()); 473 474 auto* manager = mManager.as<pPBackgroundParent>(); 475 cacheStreamControlParent = static_cast<CacheStreamControlParent*>( 476 manager->SendPCacheStreamControlConstructor( 477 new CacheStreamControlParent())); 478 } 479 mStreamControl = cacheStreamControlParent; 480 481 // If this failed, then the child process is gone. Warn and allow actor 482 // cleanup to proceed as normal. 483 if (!mStreamControl) { 484 NS_WARNING("Cache failed to create stream control actor."); 485 return; 486 } 487 } 488 489 aStreamList.SetStreamControl(mStreamControl); 490 491 RefPtr<ReadStream> readStream = 492 ReadStream::Create(mStreamControl, aId, stream); 493 ErrorResult rv; 494 readStream->Serialize(aReadStreamOut, rv); 495 MOZ_DIAGNOSTIC_ASSERT(!rv.Failed()); 496 } 497 498 } // namespace mozilla::dom::cache