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"
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",
41 if (SocketProcessChild
* child
=
42 SocketProcessChild::GetSingleton()) {
44 ->SendCachePushCheck(self
->mPushedURL
,
45 self
->mOriginAttributes
,
48 GetCurrentSerialEventTarget(), __func__
,
49 [self
](bool aResult
) { self
->InvokeCallback(aResult
); },
50 [](const mozilla::ipc::ResponseRejectReason
) {});
57 nsCOMPtr
<nsICacheStorageService
> css
=
58 do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv
);
63 RefPtr
<LoadContextInfo
> lci
= GetLoadContextInfo(false, mOriginAttributes
);
64 nsCOMPtr
<nsICacheStorage
> ds
;
65 rv
= css
->DiskCacheStorage(lci
, getter_AddRefs(ds
));
70 return ds
->AsyncOpenURI(
72 nsICacheStorage::OPEN_READONLY
| nsICacheStorage::OPEN_SECRETLY
, this);
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
); });
94 nsHttp::GetHttpResponseHeadFromCacheEntry(entry
, &cachedResponseHead
);
96 // Couldn't make sense of what's in the cache entry, go ahead and accept
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.
107 // Get the method that was used to generate the cached response
109 rv
= entry
->GetMetaDataElement("request-method", getter_Copies(buf
));
111 // Can't check request method, accept the push
114 nsAutoCString pushedMethod
;
115 requestHead
.Method(pushedMethod
);
116 if (!buf
.Equals(pushedMethod
)) {
117 // Methods don't match, accept the push
121 int64_t size
, contentLength
;
122 rv
= nsHttp::CheckPartial(entry
, &size
, &contentLength
, &cachedResponseHead
);
124 // Couldn't figure out if this was partial or not, accept the push.
128 if (size
== int64_t(-1) || contentLength
!= size
) {
129 // This is partial content in the cache, accept the push.
133 nsAutoCString requestedETag
;
134 if (NS_FAILED(requestHead
.GetHeader(nsHttp::If_Match
, requestedETag
))) {
138 if (!requestedETag
.IsEmpty()) {
139 nsAutoCString cachedETag
;
140 if (NS_FAILED(cachedResponseHead
.GetHeader(nsHttp::ETag
, cachedETag
))) {
144 if (!requestedETag
.Equals(cachedETag
)) {
145 // ETags don't match, accept the push.
150 nsAutoCString imsString
;
151 Unused
<< requestHead
.GetHeader(nsHttp::If_Modified_Since
, imsString
);
152 if (!buf
.IsEmpty()) {
153 uint32_t ims
= buf
.ToInteger(&rv
);
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.
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.
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.
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.
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
);
200 // Ugh, this really sucks. OK, accept the push.
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
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.
227 CachePushChecker::OnCacheEntryAvailable(nsICacheEntry
* entry
, bool isNew
,
229 // Nothing to do here, all the work is in OnCacheEntryCheck.
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",
248 } // namespace mozilla