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