summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/http/CachePushChecker.cpp
blob: 11dd50984642013e12fab43fcdf46e7147ccde27 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
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