forked from mirrors/gecko-dev
This only changes the behaviour when called with a TaskQueue or other type using SerialEventTargetGuard on the stack. They are being switched over as the existing GetCurrentEventTarget method is being removed, as it is somewhat confusing, and poorly documented. Callers which need to get the current thread even when on a threadpool or behind a TaskQueue were switched to GetCurrentEventTarget in the previous part. Differential Revision: https://phabricator.services.mozilla.com/D166607
248 lines
8.1 KiB
C++
248 lines
8.1 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set sw=2 ts=8 et tw=80 : */
|
|
/* 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 "CachePushChecker.h"
|
|
|
|
#include "LoadContextInfo.h"
|
|
#include "mozilla/ScopeExit.h"
|
|
#include "mozilla/net/SocketProcessChild.h"
|
|
#include "nsICacheEntry.h"
|
|
#include "nsICacheStorageService.h"
|
|
#include "nsICacheStorage.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "CacheControlParser.h"
|
|
#include "nsHttpHandler.h"
|
|
|
|
namespace mozilla {
|
|
namespace net {
|
|
|
|
NS_IMPL_ISUPPORTS(CachePushChecker, nsICacheEntryOpenCallback);
|
|
|
|
CachePushChecker::CachePushChecker(nsIURI* aPushedURL,
|
|
const OriginAttributes& aOriginAttributes,
|
|
const nsACString& aRequestString,
|
|
std::function<void(bool)>&& aCallback)
|
|
: mPushedURL(aPushedURL),
|
|
mOriginAttributes(aOriginAttributes),
|
|
mRequestString(aRequestString),
|
|
mCallback(std::move(aCallback)),
|
|
mCurrentEventTarget(GetCurrentSerialEventTarget()) {}
|
|
|
|
nsresult CachePushChecker::DoCheck() {
|
|
if (XRE_IsSocketProcess()) {
|
|
RefPtr<CachePushChecker> self = this;
|
|
return NS_DispatchToMainThread(
|
|
NS_NewRunnableFunction(
|
|
"CachePushChecker::DoCheck",
|
|
[self]() {
|
|
if (SocketProcessChild* child =
|
|
SocketProcessChild::GetSingleton()) {
|
|
child
|
|
->SendCachePushCheck(self->mPushedURL,
|
|
self->mOriginAttributes,
|
|
self->mRequestString)
|
|
->Then(
|
|
GetCurrentSerialEventTarget(), __func__,
|
|
[self](bool aResult) { self->InvokeCallback(aResult); },
|
|
[](const mozilla::ipc::ResponseRejectReason) {});
|
|
}
|
|
}),
|
|
NS_DISPATCH_NORMAL);
|
|
}
|
|
|
|
nsresult rv;
|
|
nsCOMPtr<nsICacheStorageService> css =
|
|
do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
RefPtr<LoadContextInfo> lci = GetLoadContextInfo(false, mOriginAttributes);
|
|
nsCOMPtr<nsICacheStorage> ds;
|
|
rv = css->DiskCacheStorage(lci, getter_AddRefs(ds));
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
return ds->AsyncOpenURI(
|
|
mPushedURL, ""_ns,
|
|
nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY, this);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
CachePushChecker::OnCacheEntryCheck(nsICacheEntry* entry, uint32_t* result) {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
|
|
// We never care to fully open the entry, since we won't actually use it.
|
|
// We just want to be able to do all our checks to see if a future channel can
|
|
// use this entry, or if we need to accept the push.
|
|
*result = nsICacheEntryOpenCallback::ENTRY_NOT_WANTED;
|
|
|
|
bool isForcedValid = false;
|
|
entry->GetIsForcedValid(&isForcedValid);
|
|
|
|
nsHttpRequestHead requestHead;
|
|
requestHead.ParseHeaderSet(mRequestString.BeginReading());
|
|
nsHttpResponseHead cachedResponseHead;
|
|
bool acceptPush = true;
|
|
auto onExitGuard = MakeScopeExit([&] { InvokeCallback(acceptPush); });
|
|
|
|
nsresult rv =
|
|
nsHttp::GetHttpResponseHeadFromCacheEntry(entry, &cachedResponseHead);
|
|
if (NS_FAILED(rv)) {
|
|
// Couldn't make sense of what's in the cache entry, go ahead and accept
|
|
// the push.
|
|
return NS_OK;
|
|
}
|
|
|
|
if ((cachedResponseHead.Status() / 100) != 2) {
|
|
// Assume the push is sending us a success, while we don't have one in the
|
|
// cache, so we'll accept the push.
|
|
return NS_OK;
|
|
}
|
|
|
|
// Get the method that was used to generate the cached response
|
|
nsCString buf;
|
|
rv = entry->GetMetaDataElement("request-method", getter_Copies(buf));
|
|
if (NS_FAILED(rv)) {
|
|
// Can't check request method, accept the push
|
|
return NS_OK;
|
|
}
|
|
nsAutoCString pushedMethod;
|
|
requestHead.Method(pushedMethod);
|
|
if (!buf.Equals(pushedMethod)) {
|
|
// Methods don't match, accept the push
|
|
return NS_OK;
|
|
}
|
|
|
|
int64_t size, contentLength;
|
|
rv = nsHttp::CheckPartial(entry, &size, &contentLength, &cachedResponseHead);
|
|
if (NS_FAILED(rv)) {
|
|
// Couldn't figure out if this was partial or not, accept the push.
|
|
return NS_OK;
|
|
}
|
|
|
|
if (size == int64_t(-1) || contentLength != size) {
|
|
// This is partial content in the cache, accept the push.
|
|
return NS_OK;
|
|
}
|
|
|
|
nsAutoCString requestedETag;
|
|
if (NS_FAILED(requestHead.GetHeader(nsHttp::If_Match, requestedETag))) {
|
|
// Can't check etag
|
|
return NS_OK;
|
|
}
|
|
if (!requestedETag.IsEmpty()) {
|
|
nsAutoCString cachedETag;
|
|
if (NS_FAILED(cachedResponseHead.GetHeader(nsHttp::ETag, cachedETag))) {
|
|
// Can't check etag
|
|
return NS_OK;
|
|
}
|
|
if (!requestedETag.Equals(cachedETag)) {
|
|
// ETags don't match, accept the push.
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
nsAutoCString imsString;
|
|
Unused << requestHead.GetHeader(nsHttp::If_Modified_Since, imsString);
|
|
if (!buf.IsEmpty()) {
|
|
uint32_t ims = buf.ToInteger(&rv);
|
|
uint32_t lm;
|
|
rv = cachedResponseHead.GetLastModifiedValue(&lm);
|
|
if (NS_SUCCEEDED(rv) && lm && lm < ims) {
|
|
// The push appears to be newer than what's in our cache, accept it.
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
nsAutoCString cacheControlRequestHeader;
|
|
Unused << requestHead.GetHeader(nsHttp::Cache_Control,
|
|
cacheControlRequestHeader);
|
|
CacheControlParser cacheControlRequest(cacheControlRequestHeader);
|
|
if (cacheControlRequest.NoStore()) {
|
|
// Don't use a no-store cache entry, accept the push.
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCString cachedAuth;
|
|
rv = entry->GetMetaDataElement("auth", getter_Copies(cachedAuth));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
uint32_t lastModifiedTime;
|
|
rv = entry->GetLastModified(&lastModifiedTime);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
if ((gHttpHandler->SessionStartTime() > lastModifiedTime) &&
|
|
!cachedAuth.IsEmpty()) {
|
|
// Need to revalidate this, as the auth is old. Accept the push.
|
|
return NS_OK;
|
|
}
|
|
|
|
if (cachedAuth.IsEmpty() &&
|
|
requestHead.HasHeader(nsHttp::Authorization)) {
|
|
// They're pushing us something with auth, but we didn't cache anything
|
|
// with auth. Accept the push.
|
|
return NS_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool weaklyFramed, isImmutable;
|
|
nsHttp::DetermineFramingAndImmutability(entry, &cachedResponseHead, true,
|
|
&weaklyFramed, &isImmutable);
|
|
|
|
// We'll need this value in later computations...
|
|
uint32_t lastModifiedTime;
|
|
rv = entry->GetLastModified(&lastModifiedTime);
|
|
if (NS_FAILED(rv)) {
|
|
// Ugh, this really sucks. OK, accept the push.
|
|
return NS_OK;
|
|
}
|
|
|
|
// Determine if this is the first time that this cache entry
|
|
// has been accessed during this session.
|
|
bool fromPreviousSession =
|
|
(gHttpHandler->SessionStartTime() > lastModifiedTime);
|
|
|
|
bool validationRequired = nsHttp::ValidationRequired(
|
|
isForcedValid, &cachedResponseHead, 0 /*NWGH: ??? - loadFlags*/, false,
|
|
false /* forceValidateCacheContent */, isImmutable, false, requestHead,
|
|
entry, cacheControlRequest, fromPreviousSession);
|
|
|
|
if (validationRequired) {
|
|
// A real channel would most likely hit the net at this point, so let's
|
|
// accept the push.
|
|
return NS_OK;
|
|
}
|
|
|
|
// If we get here, then we would be able to use this cache entry. Cancel the
|
|
// push so as not to waste any more bandwidth.
|
|
acceptPush = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
CachePushChecker::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
|
|
nsresult result) {
|
|
// Nothing to do here, all the work is in OnCacheEntryCheck.
|
|
return NS_OK;
|
|
}
|
|
|
|
void CachePushChecker::InvokeCallback(bool aResult) {
|
|
RefPtr<CachePushChecker> self = this;
|
|
auto task = [self, aResult]() { self->mCallback(aResult); };
|
|
if (!mCurrentEventTarget->IsOnCurrentThread()) {
|
|
mCurrentEventTarget->Dispatch(
|
|
NS_NewRunnableFunction("CachePushChecker::InvokeCallback",
|
|
std::move(task)),
|
|
NS_DISPATCH_NORMAL);
|
|
return;
|
|
}
|
|
|
|
task();
|
|
}
|
|
|
|
} // namespace net
|
|
} // namespace mozilla
|