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