diff options
Diffstat (limited to '')
-rw-r--r-- | netwerk/protocol/http/CachePushChecker.cpp | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/netwerk/protocol/http/CachePushChecker.cpp b/netwerk/protocol/http/CachePushChecker.cpp new file mode 100644 index 0000000000..9f48aa7eda --- /dev/null +++ b/netwerk/protocol/http/CachePushChecker.cpp @@ -0,0 +1,248 @@ +/* -*- 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 |