Bug 1814798 - pt 2. Add a PHCManager component to control PHC r=glandium,emilio
[gecko.git] / netwerk / protocol / http / CachePushChecker.cpp
blob9f48aa7eda07a2d18d500d1262e676ea6676b704
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set sw=2 ts=8 et 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/. */
7 #include "CachePushChecker.h"
9 #include "LoadContextInfo.h"
10 #include "mozilla/ScopeExit.h"
11 #include "mozilla/net/SocketProcessChild.h"
12 #include "nsICacheEntry.h"
13 #include "nsICacheStorageService.h"
14 #include "nsICacheStorage.h"
15 #include "nsThreadUtils.h"
16 #include "CacheControlParser.h"
17 #include "nsHttpHandler.h"
19 namespace mozilla {
20 namespace net {
22 NS_IMPL_ISUPPORTS(CachePushChecker, nsICacheEntryOpenCallback);
24 CachePushChecker::CachePushChecker(nsIURI* aPushedURL,
25 const OriginAttributes& aOriginAttributes,
26 const nsACString& aRequestString,
27 std::function<void(bool)>&& aCallback)
28 : mPushedURL(aPushedURL),
29 mOriginAttributes(aOriginAttributes),
30 mRequestString(aRequestString),
31 mCallback(std::move(aCallback)),
32 mCurrentEventTarget(GetCurrentSerialEventTarget()) {}
34 nsresult CachePushChecker::DoCheck() {
35 if (XRE_IsSocketProcess()) {
36 RefPtr<CachePushChecker> self = this;
37 return NS_DispatchToMainThread(
38 NS_NewRunnableFunction(
39 "CachePushChecker::DoCheck",
40 [self]() {
41 if (SocketProcessChild* child =
42 SocketProcessChild::GetSingleton()) {
43 child
44 ->SendCachePushCheck(self->mPushedURL,
45 self->mOriginAttributes,
46 self->mRequestString)
47 ->Then(
48 GetCurrentSerialEventTarget(), __func__,
49 [self](bool aResult) { self->InvokeCallback(aResult); },
50 [](const mozilla::ipc::ResponseRejectReason) {});
52 }),
53 NS_DISPATCH_NORMAL);
56 nsresult rv;
57 nsCOMPtr<nsICacheStorageService> css =
58 do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
59 if (NS_FAILED(rv)) {
60 return rv;
63 RefPtr<LoadContextInfo> lci = GetLoadContextInfo(false, mOriginAttributes);
64 nsCOMPtr<nsICacheStorage> ds;
65 rv = css->DiskCacheStorage(lci, getter_AddRefs(ds));
66 if (NS_FAILED(rv)) {
67 return rv;
70 return ds->AsyncOpenURI(
71 mPushedURL, ""_ns,
72 nsICacheStorage::OPEN_READONLY | nsICacheStorage::OPEN_SECRETLY, this);
75 NS_IMETHODIMP
76 CachePushChecker::OnCacheEntryCheck(nsICacheEntry* entry, uint32_t* result) {
77 MOZ_ASSERT(XRE_IsParentProcess());
79 // We never care to fully open the entry, since we won't actually use it.
80 // We just want to be able to do all our checks to see if a future channel can
81 // use this entry, or if we need to accept the push.
82 *result = nsICacheEntryOpenCallback::ENTRY_NOT_WANTED;
84 bool isForcedValid = false;
85 entry->GetIsForcedValid(&isForcedValid);
87 nsHttpRequestHead requestHead;
88 requestHead.ParseHeaderSet(mRequestString.BeginReading());
89 nsHttpResponseHead cachedResponseHead;
90 bool acceptPush = true;
91 auto onExitGuard = MakeScopeExit([&] { InvokeCallback(acceptPush); });
93 nsresult rv =
94 nsHttp::GetHttpResponseHeadFromCacheEntry(entry, &cachedResponseHead);
95 if (NS_FAILED(rv)) {
96 // Couldn't make sense of what's in the cache entry, go ahead and accept
97 // the push.
98 return NS_OK;
101 if ((cachedResponseHead.Status() / 100) != 2) {
102 // Assume the push is sending us a success, while we don't have one in the
103 // cache, so we'll accept the push.
104 return NS_OK;
107 // Get the method that was used to generate the cached response
108 nsCString buf;
109 rv = entry->GetMetaDataElement("request-method", getter_Copies(buf));
110 if (NS_FAILED(rv)) {
111 // Can't check request method, accept the push
112 return NS_OK;
114 nsAutoCString pushedMethod;
115 requestHead.Method(pushedMethod);
116 if (!buf.Equals(pushedMethod)) {
117 // Methods don't match, accept the push
118 return NS_OK;
121 int64_t size, contentLength;
122 rv = nsHttp::CheckPartial(entry, &size, &contentLength, &cachedResponseHead);
123 if (NS_FAILED(rv)) {
124 // Couldn't figure out if this was partial or not, accept the push.
125 return NS_OK;
128 if (size == int64_t(-1) || contentLength != size) {
129 // This is partial content in the cache, accept the push.
130 return NS_OK;
133 nsAutoCString requestedETag;
134 if (NS_FAILED(requestHead.GetHeader(nsHttp::If_Match, requestedETag))) {
135 // Can't check etag
136 return NS_OK;
138 if (!requestedETag.IsEmpty()) {
139 nsAutoCString cachedETag;
140 if (NS_FAILED(cachedResponseHead.GetHeader(nsHttp::ETag, cachedETag))) {
141 // Can't check etag
142 return NS_OK;
144 if (!requestedETag.Equals(cachedETag)) {
145 // ETags don't match, accept the push.
146 return NS_OK;
150 nsAutoCString imsString;
151 Unused << requestHead.GetHeader(nsHttp::If_Modified_Since, imsString);
152 if (!buf.IsEmpty()) {
153 uint32_t ims = buf.ToInteger(&rv);
154 uint32_t lm;
155 rv = cachedResponseHead.GetLastModifiedValue(&lm);
156 if (NS_SUCCEEDED(rv) && lm && lm < ims) {
157 // The push appears to be newer than what's in our cache, accept it.
158 return NS_OK;
162 nsAutoCString cacheControlRequestHeader;
163 Unused << requestHead.GetHeader(nsHttp::Cache_Control,
164 cacheControlRequestHeader);
165 CacheControlParser cacheControlRequest(cacheControlRequestHeader);
166 if (cacheControlRequest.NoStore()) {
167 // Don't use a no-store cache entry, accept the push.
168 return NS_OK;
171 nsCString cachedAuth;
172 rv = entry->GetMetaDataElement("auth", getter_Copies(cachedAuth));
173 if (NS_SUCCEEDED(rv)) {
174 uint32_t lastModifiedTime;
175 rv = entry->GetLastModified(&lastModifiedTime);
176 if (NS_SUCCEEDED(rv)) {
177 if ((gHttpHandler->SessionStartTime() > lastModifiedTime) &&
178 !cachedAuth.IsEmpty()) {
179 // Need to revalidate this, as the auth is old. Accept the push.
180 return NS_OK;
183 if (cachedAuth.IsEmpty() &&
184 requestHead.HasHeader(nsHttp::Authorization)) {
185 // They're pushing us something with auth, but we didn't cache anything
186 // with auth. Accept the push.
187 return NS_OK;
192 bool weaklyFramed, isImmutable;
193 nsHttp::DetermineFramingAndImmutability(entry, &cachedResponseHead, true,
194 &weaklyFramed, &isImmutable);
196 // We'll need this value in later computations...
197 uint32_t lastModifiedTime;
198 rv = entry->GetLastModified(&lastModifiedTime);
199 if (NS_FAILED(rv)) {
200 // Ugh, this really sucks. OK, accept the push.
201 return NS_OK;
204 // Determine if this is the first time that this cache entry
205 // has been accessed during this session.
206 bool fromPreviousSession =
207 (gHttpHandler->SessionStartTime() > lastModifiedTime);
209 bool validationRequired = nsHttp::ValidationRequired(
210 isForcedValid, &cachedResponseHead, 0 /*NWGH: ??? - loadFlags*/, false,
211 false /* forceValidateCacheContent */, isImmutable, false, requestHead,
212 entry, cacheControlRequest, fromPreviousSession);
214 if (validationRequired) {
215 // A real channel would most likely hit the net at this point, so let's
216 // accept the push.
217 return NS_OK;
220 // If we get here, then we would be able to use this cache entry. Cancel the
221 // push so as not to waste any more bandwidth.
222 acceptPush = false;
223 return NS_OK;
226 NS_IMETHODIMP
227 CachePushChecker::OnCacheEntryAvailable(nsICacheEntry* entry, bool isNew,
228 nsresult result) {
229 // Nothing to do here, all the work is in OnCacheEntryCheck.
230 return NS_OK;
233 void CachePushChecker::InvokeCallback(bool aResult) {
234 RefPtr<CachePushChecker> self = this;
235 auto task = [self, aResult]() { self->mCallback(aResult); };
236 if (!mCurrentEventTarget->IsOnCurrentThread()) {
237 mCurrentEventTarget->Dispatch(
238 NS_NewRunnableFunction("CachePushChecker::InvokeCallback",
239 std::move(task)),
240 NS_DISPATCH_NORMAL);
241 return;
244 task();
247 } // namespace net
248 } // namespace mozilla