Files
sousa-gecko/dom/cache/AutoUtils.cpp
T
Nika Layzell e7bf4a196f Bug 2005006 - Store parsed URIs in InternalRequest/InternalResponse, r=dom-core,dom-worker-reviewers,necko-reviewers,smaug,kershaw,edenchuang
This patch updates InternalRequest and InternalResponse such that it parses the
URL passed to it at creation time, rather than when the channel is created.
This should improve the behaviour of these types around Blob URLs, so that
extra state, such as the "revoked" flag, is calculated at the correct time
as-per spec.

While making this change, I noticed that a previous Cache DB migration could
insert an "" URL in the DB when migrating from a single response URL to a URL
list, rather than storing an empty URL list. The choice was made to do
filtering when reading, rather than performing a migration, to avoid migration
first-time start-up overhead.

Differential Revision: https://phabricator.services.mozilla.com/D276047
2026-04-10 19:23:59 +00:00

497 lines
17 KiB
C++

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/cache/AutoUtils.h"
#include "mozilla/dom/InternalHeaders.h"
#include "mozilla/dom/InternalRequest.h"
#include "mozilla/dom/cache/CacheParent.h"
#include "mozilla/dom/cache/CacheStreamControlParent.h"
#include "mozilla/dom/cache/PBoundStorageKeyParent.h"
#include "mozilla/dom/cache/ReadStream.h"
#include "mozilla/dom/cache/SavedTypes.h"
#include "mozilla/dom/cache/StreamList.h"
#include "mozilla/dom/cache/TypeUtils.h"
#include "mozilla/ipc/IPCStreamUtils.h"
#include "mozilla/ipc/PBackgroundParent.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsHttp.h"
using mozilla::Maybe;
using mozilla::dom::cache::CacheReadStream;
using mozilla::ipc::PBackgroundParent;
namespace {
enum CleanupAction { Forget, Delete };
void CleanupChild(CacheReadStream& aReadStream, CleanupAction aAction) {
// fds cleaned up by mStreamCleanupList
}
void CleanupChild(Maybe<CacheReadStream>& aMaybeReadStream,
CleanupAction aAction) {
if (aMaybeReadStream.isNothing()) {
return;
}
CleanupChild(aMaybeReadStream.ref(), aAction);
}
} // namespace
namespace mozilla::dom::cache {
// --------------------------------------------
AutoChildOpArgs::AutoChildOpArgs(TypeUtils* aTypeUtils,
const CacheOpArgs& aOpArgs,
uint32_t aEntryCount)
: mTypeUtils(aTypeUtils), mOpArgs(aOpArgs), mSent(false) {
MOZ_DIAGNOSTIC_ASSERT(mTypeUtils);
MOZ_RELEASE_ASSERT(aEntryCount != 0);
if (mOpArgs.type() == CacheOpArgs::TCachePutAllArgs) {
CachePutAllArgs& args = mOpArgs.get_CachePutAllArgs();
args.requestResponseList().SetCapacity(aEntryCount);
} else {
MOZ_DIAGNOSTIC_ASSERT(aEntryCount == 1);
}
}
AutoChildOpArgs::~AutoChildOpArgs() {
CleanupAction action = mSent ? Forget : Delete;
switch (mOpArgs.type()) {
case CacheOpArgs::TCacheMatchArgs: {
CacheMatchArgs& args = mOpArgs.get_CacheMatchArgs();
CleanupChild(args.request().body(), action);
break;
}
case CacheOpArgs::TCacheMatchAllArgs: {
CacheMatchAllArgs& args = mOpArgs.get_CacheMatchAllArgs();
if (args.maybeRequest().isNothing()) {
break;
}
CleanupChild(args.maybeRequest().ref().body(), action);
break;
}
case CacheOpArgs::TCachePutAllArgs: {
CachePutAllArgs& args = mOpArgs.get_CachePutAllArgs();
auto& list = args.requestResponseList();
for (uint32_t i = 0; i < list.Length(); ++i) {
CleanupChild(list[i].request().body(), action);
CleanupChild(list[i].response().body(), action);
}
break;
}
case CacheOpArgs::TCacheDeleteArgs: {
CacheDeleteArgs& args = mOpArgs.get_CacheDeleteArgs();
CleanupChild(args.request().body(), action);
break;
}
case CacheOpArgs::TCacheKeysArgs: {
CacheKeysArgs& args = mOpArgs.get_CacheKeysArgs();
if (args.maybeRequest().isNothing()) {
break;
}
CleanupChild(args.maybeRequest().ref().body(), action);
break;
}
case CacheOpArgs::TStorageMatchArgs: {
StorageMatchArgs& args = mOpArgs.get_StorageMatchArgs();
CleanupChild(args.request().body(), action);
break;
}
default:
// Other types do not need cleanup
break;
}
}
void AutoChildOpArgs::Add(const InternalRequest& aRequest,
BodyAction aBodyAction, SchemeAction aSchemeAction,
ErrorResult& aRv) {
MOZ_DIAGNOSTIC_ASSERT(!mSent);
switch (mOpArgs.type()) {
case CacheOpArgs::TCacheMatchArgs: {
CacheMatchArgs& args = mOpArgs.get_CacheMatchArgs();
mTypeUtils->ToCacheRequest(args.request(), aRequest, aBodyAction,
aSchemeAction, aRv);
break;
}
case CacheOpArgs::TCacheMatchAllArgs: {
CacheMatchAllArgs& args = mOpArgs.get_CacheMatchAllArgs();
MOZ_DIAGNOSTIC_ASSERT(args.maybeRequest().isNothing());
args.maybeRequest().emplace(CacheRequest());
mTypeUtils->ToCacheRequest(args.maybeRequest().ref(), aRequest,
aBodyAction, aSchemeAction, aRv);
break;
}
case CacheOpArgs::TCacheDeleteArgs: {
CacheDeleteArgs& args = mOpArgs.get_CacheDeleteArgs();
mTypeUtils->ToCacheRequest(args.request(), aRequest, aBodyAction,
aSchemeAction, aRv);
break;
}
case CacheOpArgs::TCacheKeysArgs: {
CacheKeysArgs& args = mOpArgs.get_CacheKeysArgs();
MOZ_DIAGNOSTIC_ASSERT(args.maybeRequest().isNothing());
args.maybeRequest().emplace(CacheRequest());
mTypeUtils->ToCacheRequest(args.maybeRequest().ref(), aRequest,
aBodyAction, aSchemeAction, aRv);
break;
}
case CacheOpArgs::TStorageMatchArgs: {
StorageMatchArgs& args = mOpArgs.get_StorageMatchArgs();
mTypeUtils->ToCacheRequest(args.request(), aRequest, aBodyAction,
aSchemeAction, aRv);
break;
}
default:
MOZ_CRASH("Cache args type cannot send a Request!");
}
}
namespace {
bool MatchInPutList(const InternalRequest& aRequest,
const nsTArray<CacheRequestResponse>& aPutList) {
// This method implements the SW spec QueryCache algorithm against an
// in memory array of Request/Response objects. This essentially the
// same algorithm that is implemented in DBSchema.cpp. Unfortunately
// we cannot unify them because when operating against the real database
// we don't want to load all request/response objects into memory.
// Note, we can skip the check for a invalid request method because
// Cache should only call into here with a GET or HEAD.
#ifdef DEBUG
nsAutoCString method;
aRequest.GetMethod(method);
MOZ_ASSERT(method.LowerCaseEqualsLiteral("get") ||
method.LowerCaseEqualsLiteral("head"));
#endif
RefPtr<InternalHeaders> requestHeaders = aRequest.Headers();
for (uint32_t i = 0; i < aPutList.Length(); ++i) {
const CacheRequest& cachedRequest = aPutList[i].request();
const CacheResponse& cachedResponse = aPutList[i].response();
nsAutoCString url;
MOZ_ALWAYS_SUCCEEDS(aRequest.GetURLWithoutFragment()->GetSpec(url));
nsAutoCString requestUrl(cachedRequest.urlWithoutQuery());
requestUrl.Append(cachedRequest.urlQuery());
// If the URLs don't match, then just skip to the next entry.
if (url != requestUrl) {
continue;
}
RefPtr<InternalHeaders> cachedRequestHeaders =
TypeUtils::ToInternalHeaders(cachedRequest.headers());
RefPtr<InternalHeaders> cachedResponseHeaders =
TypeUtils::ToInternalHeaders(cachedResponse.headers());
nsCString varyHeaders;
ErrorResult rv;
cachedResponseHeaders->Get("vary"_ns, varyHeaders, rv);
MOZ_ALWAYS_TRUE(!rv.Failed());
// Assume the vary headers match until we find a conflict
bool varyHeadersMatch = true;
for (const nsACString& header :
nsCCharSeparatedTokenizer(varyHeaders, NS_HTTP_HEADER_SEP).ToRange()) {
MOZ_DIAGNOSTIC_ASSERT(!header.EqualsLiteral("*"),
"We should have already caught this in "
"TypeUtils::ToPCacheResponseWithoutBody()");
ErrorResult headerRv;
nsAutoCString value;
requestHeaders->Get(header, value, headerRv);
if (NS_WARN_IF(headerRv.Failed())) {
headerRv.SuppressException();
MOZ_DIAGNOSTIC_ASSERT(value.IsEmpty());
}
nsAutoCString cachedValue;
cachedRequestHeaders->Get(header, cachedValue, headerRv);
if (NS_WARN_IF(headerRv.Failed())) {
headerRv.SuppressException();
MOZ_DIAGNOSTIC_ASSERT(cachedValue.IsEmpty());
}
if (value != cachedValue) {
varyHeadersMatch = false;
break;
}
}
// URL was equal and all vary headers match!
if (varyHeadersMatch) {
return true;
}
}
return false;
}
} // namespace
void AutoChildOpArgs::Add(JSContext* aCx, const InternalRequest& aRequest,
BodyAction aBodyAction, SchemeAction aSchemeAction,
Response& aResponse, ErrorResult& aRv) {
MOZ_DIAGNOSTIC_ASSERT(!mSent);
switch (mOpArgs.type()) {
case CacheOpArgs::TCachePutAllArgs: {
CachePutAllArgs& args = mOpArgs.get_CachePutAllArgs();
// Throw an error if a request/response pair would mask another
// request/response pair in the same PutAll operation. This is
// step 2.3.2.3 from the "Batch Cache Operations" spec algorithm.
if (MatchInPutList(aRequest, args.requestResponseList())) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
MOZ_RELEASE_ASSERT(args.requestResponseList().Length() <
args.requestResponseList().Capacity());
// The FileDescriptorSetChild asserts in its destructor that all fds have
// been removed. The copy constructor, however, simply duplicates the
// fds without removing any. This means each temporary and copy must be
// explicitly cleaned up.
//
// Avoid a lot of this hassle by making sure we only create one here. On
// error we remove it.
CacheRequestResponse& pair = *args.requestResponseList().AppendElement();
pair.request().body() = Nothing();
pair.response().body() = Nothing();
mTypeUtils->ToCacheRequest(pair.request(), aRequest, aBodyAction,
aSchemeAction, aRv);
if (!aRv.Failed()) {
mTypeUtils->ToCacheResponse(aCx, pair.response(), aResponse, aRv);
}
if (aRv.Failed()) {
CleanupChild(pair.request().body(), Delete);
args.requestResponseList().RemoveLastElement();
}
break;
}
default:
MOZ_CRASH("Cache args type cannot send a Request/Response pair!");
}
}
const CacheOpArgs& AutoChildOpArgs::SendAsOpArgs() {
MOZ_DIAGNOSTIC_ASSERT(!mSent);
mSent = true;
return mOpArgs;
}
// --------------------------------------------
AutoParentOpResult::AutoParentOpResult(const WeakRefParentType& aManager,
const CacheOpResult& aOpResult,
uint32_t aEntryCount)
: mManager(aManager),
mOpResult(aOpResult),
mStreamControl(nullptr),
mSent(false) {
MOZ_RELEASE_ASSERT(aEntryCount != 0);
if (mOpResult.type() == CacheOpResult::TCacheMatchAllResult) {
CacheMatchAllResult& result = mOpResult.get_CacheMatchAllResult();
result.responseList().SetCapacity(aEntryCount);
} else if (mOpResult.type() == CacheOpResult::TCacheKeysResult) {
CacheKeysResult& result = mOpResult.get_CacheKeysResult();
result.requestList().SetCapacity(aEntryCount);
} else {
MOZ_DIAGNOSTIC_ASSERT(aEntryCount == 1);
}
}
AutoParentOpResult::~AutoParentOpResult() {
CleanupAction action = mSent ? Forget : Delete;
switch (mOpResult.type()) {
case CacheOpResult::TStorageOpenResult: {
StorageOpenResult& result = mOpResult.get_StorageOpenResult();
if (action == Forget || !result.actor()) {
break;
}
QM_WARNONLY_TRY(
OkIf(PCacheParent::Send__delete__(result.actor().AsParent())));
break;
}
default:
// other types do not need additional clean up
break;
}
if (action == Delete && mStreamControl) {
mStreamControl->AssertWillDelete();
QM_WARNONLY_TRY(
OkIf(PCacheStreamControlParent::Send__delete__(mStreamControl)));
}
}
void AutoParentOpResult::Add(CacheId aOpenedCacheId,
SafeRefPtr<Manager> aManager) {
MOZ_DIAGNOSTIC_ASSERT(mOpResult.type() == CacheOpResult::TStorageOpenResult);
MOZ_DIAGNOSTIC_ASSERT(!mOpResult.get_StorageOpenResult().actor());
PCacheParent* cacheParent = nullptr;
if (mManager.is<pPBoundStorageKeyParent>()) {
auto* manager = mManager.as<pPBoundStorageKeyParent>();
MOZ_ASSERT(manager);
cacheParent = manager->SendPCacheConstructor(
new CacheParent(mManager, std::move(aManager), aOpenedCacheId));
} else {
MOZ_ASSERT(mManager.is<pPBackgroundParent>());
auto* manager = mManager.as<pPBackgroundParent>();
MOZ_ASSERT(manager);
cacheParent = manager->SendPCacheConstructor(
new CacheParent(mManager, std::move(aManager), aOpenedCacheId));
}
mOpResult.get_StorageOpenResult().actor() = cacheParent;
}
void AutoParentOpResult::Add(const SavedResponse& aSavedResponse,
StreamList& aStreamList) {
MOZ_DIAGNOSTIC_ASSERT(!mSent);
switch (mOpResult.type()) {
case CacheOpResult::TCacheMatchResult: {
CacheMatchResult& result = mOpResult.get_CacheMatchResult();
MOZ_DIAGNOSTIC_ASSERT(result.maybeResponse().isNothing());
result.maybeResponse().emplace(aSavedResponse.mValue);
SerializeResponseBody(aSavedResponse, aStreamList,
&result.maybeResponse().ref());
break;
}
case CacheOpResult::TCacheMatchAllResult: {
CacheMatchAllResult& result = mOpResult.get_CacheMatchAllResult();
MOZ_RELEASE_ASSERT(result.responseList().Length() <
result.responseList().Capacity());
result.responseList().AppendElement(aSavedResponse.mValue);
SerializeResponseBody(aSavedResponse, aStreamList,
&result.responseList().LastElement());
break;
}
case CacheOpResult::TStorageMatchResult: {
StorageMatchResult& result = mOpResult.get_StorageMatchResult();
MOZ_DIAGNOSTIC_ASSERT(result.maybeResponse().isNothing());
result.maybeResponse().emplace(aSavedResponse.mValue);
SerializeResponseBody(aSavedResponse, aStreamList,
&result.maybeResponse().ref());
break;
}
default:
MOZ_CRASH("Cache result type cannot handle returning a Response!");
}
}
void AutoParentOpResult::Add(const SavedRequest& aSavedRequest,
StreamList& aStreamList) {
MOZ_DIAGNOSTIC_ASSERT(!mSent);
switch (mOpResult.type()) {
case CacheOpResult::TCacheKeysResult: {
CacheKeysResult& result = mOpResult.get_CacheKeysResult();
MOZ_RELEASE_ASSERT(result.requestList().Length() <
result.requestList().Capacity());
result.requestList().AppendElement(aSavedRequest.mValue);
CacheRequest& request = result.requestList().LastElement();
if (!aSavedRequest.mHasBodyId) {
request.body() = Nothing();
break;
}
request.body().emplace(CacheReadStream());
SerializeReadStream(aSavedRequest.mBodyId, aStreamList,
&request.body().ref());
break;
}
default:
MOZ_CRASH("Cache result type cannot handle returning a Request!");
}
}
const CacheOpResult& AutoParentOpResult::SendAsOpResult() {
MOZ_DIAGNOSTIC_ASSERT(!mSent);
mSent = true;
return mOpResult;
}
void AutoParentOpResult::SerializeResponseBody(
const SavedResponse& aSavedResponse, StreamList& aStreamList,
CacheResponse* aResponseOut) {
MOZ_DIAGNOSTIC_ASSERT(aResponseOut);
if (!aSavedResponse.mHasBodyId) {
aResponseOut->body() = Nothing();
return;
}
aResponseOut->body().emplace(CacheReadStream());
SerializeReadStream(aSavedResponse.mBodyId, aStreamList,
&aResponseOut->body().ref());
}
void AutoParentOpResult::SerializeReadStream(const nsID& aId,
StreamList& aStreamList,
CacheReadStream* aReadStreamOut) {
MOZ_DIAGNOSTIC_ASSERT(aReadStreamOut);
MOZ_DIAGNOSTIC_ASSERT(!mSent);
nsCOMPtr<nsIInputStream> stream = aStreamList.Extract(aId);
CacheStreamControlParent* cacheStreamControlParent = nullptr;
if (!mStreamControl) {
if (mManager.is<pPBoundStorageKeyParent>()) {
auto* manager = mManager.as<pPBoundStorageKeyParent>();
cacheStreamControlParent = static_cast<CacheStreamControlParent*>(
manager->SendPCacheStreamControlConstructor(
new CacheStreamControlParent()));
} else {
MOZ_ASSERT(mManager.is<pPBackgroundParent>());
auto* manager = mManager.as<pPBackgroundParent>();
cacheStreamControlParent = static_cast<CacheStreamControlParent*>(
manager->SendPCacheStreamControlConstructor(
new CacheStreamControlParent()));
}
mStreamControl = cacheStreamControlParent;
// If this failed, then the child process is gone. Warn and allow actor
// cleanup to proceed as normal.
if (!mStreamControl) {
NS_WARNING("Cache failed to create stream control actor.");
return;
}
}
aStreamList.SetStreamControl(mStreamControl);
RefPtr<ReadStream> readStream =
ReadStream::Create(mStreamControl, aId, stream);
ErrorResult rv;
readStream->Serialize(aReadStreamOut, rv);
MOZ_DIAGNOSTIC_ASSERT(!rv.Failed());
}
} // namespace mozilla::dom::cache