summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/http/CachePushChecker.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--netwerk/protocol/http/CachePushChecker.cpp249
1 files changed, 249 insertions, 0 deletions
diff --git a/netwerk/protocol/http/CachePushChecker.cpp b/netwerk/protocol/http/CachePushChecker.cpp
new file mode 100644
index 0000000000..11dd509846
--- /dev/null
+++ b/netwerk/protocol/http/CachePushChecker.cpp
@@ -0,0 +1,249 @@
+/* -*- 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"
+
+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(GetCurrentEventTarget()) {}
+
+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, false, 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,
+ nsIApplicationCache* appCache,
+ 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,
+ 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,
+ nsIApplicationCache* appCache,
+ 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