Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 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 : // Undefine windows version of LoadImage because our code uses that name.
8 : #undef LoadImage
9 :
10 : #include "ImageLogging.h"
11 : #include "imgLoader.h"
12 :
13 : #include "mozilla/Attributes.h"
14 : #include "mozilla/ClearOnShutdown.h"
15 : #include "mozilla/Move.h"
16 : #include "mozilla/Preferences.h"
17 : #include "mozilla/ChaosMode.h"
18 :
19 : #include "nsImageModule.h"
20 : #include "imgRequestProxy.h"
21 :
22 : #include "nsCOMPtr.h"
23 :
24 : #include "nsContentPolicyUtils.h"
25 : #include "nsContentUtils.h"
26 : #include "nsNetUtil.h"
27 : #include "nsNetCID.h"
28 : #include "nsIProtocolHandler.h"
29 : #include "nsMimeTypes.h"
30 : #include "nsStreamUtils.h"
31 : #include "nsIHttpChannel.h"
32 : #include "nsICacheInfoChannel.h"
33 : #include "nsIClassOfService.h"
34 : #include "nsIInterfaceRequestor.h"
35 : #include "nsIInterfaceRequestorUtils.h"
36 : #include "nsIProgressEventSink.h"
37 : #include "nsIChannelEventSink.h"
38 : #include "nsIAsyncVerifyRedirectCallback.h"
39 : #include "nsIFileURL.h"
40 : #include "nsIFile.h"
41 : #include "nsCRT.h"
42 : #include "nsINetworkPredictor.h"
43 : #include "mozilla/dom/ContentParent.h"
44 : #include "mozilla/dom/nsMixedContentBlocker.h"
45 :
46 : #include "nsIApplicationCache.h"
47 : #include "nsIApplicationCacheContainer.h"
48 :
49 : #include "nsIMemoryReporter.h"
50 : #include "DecoderFactory.h"
51 : #include "Image.h"
52 : #include "gfxPrefs.h"
53 : #include "prtime.h"
54 :
55 : // we want to explore making the document own the load group
56 : // so we can associate the document URI with the load group.
57 : // until this point, we have an evil hack:
58 : #include "nsIHttpChannelInternal.h"
59 : #include "nsILoadContext.h"
60 : #include "nsILoadGroupChild.h"
61 : #include "nsIDOMDocument.h"
62 : #include "nsIDocShell.h"
63 :
64 : using namespace mozilla;
65 : using namespace mozilla::dom;
66 : using namespace mozilla::image;
67 : using namespace mozilla::net;
68 :
69 0 : MOZ_DEFINE_MALLOC_SIZE_OF(ImagesMallocSizeOf)
70 :
71 3 : class imgMemoryReporter final : public nsIMemoryReporter
72 : {
73 0 : ~imgMemoryReporter() = default;
74 :
75 : public:
76 : NS_DECL_ISUPPORTS
77 :
78 0 : NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport,
79 : nsISupports* aData, bool aAnonymize) override
80 : {
81 0 : nsTArray<ImageMemoryCounter> chrome;
82 0 : nsTArray<ImageMemoryCounter> content;
83 0 : nsTArray<ImageMemoryCounter> uncached;
84 :
85 0 : for (uint32_t i = 0; i < mKnownLoaders.Length(); i++) {
86 0 : for (auto iter = mKnownLoaders[i]->mChromeCache.Iter(); !iter.Done(); iter.Next()) {
87 0 : imgCacheEntry* entry = iter.UserData();
88 0 : RefPtr<imgRequest> req = entry->GetRequest();
89 0 : RecordCounterForRequest(req, &chrome, !entry->HasNoProxies());
90 : }
91 0 : for (auto iter = mKnownLoaders[i]->mCache.Iter(); !iter.Done(); iter.Next()) {
92 0 : imgCacheEntry* entry = iter.UserData();
93 0 : RefPtr<imgRequest> req = entry->GetRequest();
94 0 : RecordCounterForRequest(req, &content, !entry->HasNoProxies());
95 : }
96 0 : MutexAutoLock lock(mKnownLoaders[i]->mUncachedImagesMutex);
97 0 : for (auto iter = mKnownLoaders[i]->mUncachedImages.Iter();
98 0 : !iter.Done();
99 0 : iter.Next()) {
100 0 : nsPtrHashKey<imgRequest>* entry = iter.Get();
101 0 : RefPtr<imgRequest> req = entry->GetKey();
102 0 : RecordCounterForRequest(req, &uncached, req->HasConsumers());
103 : }
104 : }
105 :
106 : // Note that we only need to anonymize content image URIs.
107 :
108 0 : ReportCounterArray(aHandleReport, aData, chrome, "images/chrome");
109 :
110 0 : ReportCounterArray(aHandleReport, aData, content, "images/content",
111 0 : aAnonymize);
112 :
113 : // Uncached images may be content or chrome, so anonymize them.
114 0 : ReportCounterArray(aHandleReport, aData, uncached, "images/uncached",
115 0 : aAnonymize);
116 :
117 0 : return NS_OK;
118 : }
119 :
120 0 : static int64_t ImagesContentUsedUncompressedDistinguishedAmount()
121 : {
122 0 : size_t n = 0;
123 0 : for (uint32_t i = 0; i < imgLoader::sMemReporter->mKnownLoaders.Length();
124 : i++) {
125 0 : for (auto iter = imgLoader::sMemReporter->mKnownLoaders[i]->mCache.Iter();
126 0 : !iter.Done();
127 0 : iter.Next()) {
128 0 : imgCacheEntry* entry = iter.UserData();
129 0 : if (entry->HasNoProxies()) {
130 0 : continue;
131 : }
132 :
133 0 : RefPtr<imgRequest> req = entry->GetRequest();
134 0 : RefPtr<image::Image> image = req->GetImage();
135 0 : if (!image) {
136 0 : continue;
137 : }
138 :
139 : // Both this and EntryImageSizes measure images/content/raster/used/decoded
140 : // memory. This function's measurement is secondary -- the result doesn't
141 : // go in the "explicit" tree -- so we use moz_malloc_size_of instead of
142 : // ImagesMallocSizeOf to prevent DMD from seeing it reported twice.
143 0 : ImageMemoryCounter counter(image, moz_malloc_size_of, /* aIsUsed = */ true);
144 :
145 0 : n += counter.Values().DecodedHeap();
146 0 : n += counter.Values().DecodedNonHeap();
147 : }
148 : }
149 0 : return n;
150 : }
151 :
152 2 : void RegisterLoader(imgLoader* aLoader)
153 : {
154 2 : mKnownLoaders.AppendElement(aLoader);
155 2 : }
156 :
157 0 : void UnregisterLoader(imgLoader* aLoader)
158 : {
159 0 : mKnownLoaders.RemoveElement(aLoader);
160 0 : }
161 :
162 : private:
163 : nsTArray<imgLoader*> mKnownLoaders;
164 :
165 0 : struct MemoryTotal
166 : {
167 0 : MemoryTotal& operator+=(const ImageMemoryCounter& aImageCounter)
168 : {
169 0 : if (aImageCounter.Type() == imgIContainer::TYPE_RASTER) {
170 0 : if (aImageCounter.IsUsed()) {
171 0 : mUsedRasterCounter += aImageCounter.Values();
172 : } else {
173 0 : mUnusedRasterCounter += aImageCounter.Values();
174 : }
175 0 : } else if (aImageCounter.Type() == imgIContainer::TYPE_VECTOR) {
176 0 : if (aImageCounter.IsUsed()) {
177 0 : mUsedVectorCounter += aImageCounter.Values();
178 : } else {
179 0 : mUnusedVectorCounter += aImageCounter.Values();
180 : }
181 : } else {
182 0 : MOZ_CRASH("Unexpected image type");
183 : }
184 :
185 0 : return *this;
186 : }
187 :
188 0 : const MemoryCounter& UsedRaster() const { return mUsedRasterCounter; }
189 0 : const MemoryCounter& UnusedRaster() const { return mUnusedRasterCounter; }
190 0 : const MemoryCounter& UsedVector() const { return mUsedVectorCounter; }
191 0 : const MemoryCounter& UnusedVector() const { return mUnusedVectorCounter; }
192 :
193 : private:
194 : MemoryCounter mUsedRasterCounter;
195 : MemoryCounter mUnusedRasterCounter;
196 : MemoryCounter mUsedVectorCounter;
197 : MemoryCounter mUnusedVectorCounter;
198 : };
199 :
200 : // Reports all images of a single kind, e.g. all used chrome images.
201 0 : void ReportCounterArray(nsIHandleReportCallback* aHandleReport,
202 : nsISupports* aData,
203 : nsTArray<ImageMemoryCounter>& aCounterArray,
204 : const char* aPathPrefix,
205 : bool aAnonymize = false)
206 : {
207 0 : MemoryTotal summaryTotal;
208 0 : MemoryTotal nonNotableTotal;
209 :
210 : // Report notable images, and compute total and non-notable aggregate sizes.
211 0 : for (uint32_t i = 0; i < aCounterArray.Length(); i++) {
212 0 : ImageMemoryCounter& counter = aCounterArray[i];
213 :
214 0 : if (aAnonymize) {
215 0 : counter.URI().Truncate();
216 0 : counter.URI().AppendPrintf("<anonymized-%u>", i);
217 : } else {
218 : // The URI could be an extremely long data: URI. Truncate if needed.
219 : static const size_t max = 256;
220 0 : if (counter.URI().Length() > max) {
221 0 : counter.URI().Truncate(max);
222 0 : counter.URI().AppendLiteral(" (truncated)");
223 : }
224 0 : counter.URI().ReplaceChar('/', '\\');
225 : }
226 :
227 0 : summaryTotal += counter;
228 :
229 0 : if (counter.IsNotable()) {
230 0 : ReportImage(aHandleReport, aData, aPathPrefix, counter);
231 : } else {
232 0 : nonNotableTotal += counter;
233 : }
234 : }
235 :
236 : // Report non-notable images in aggregate.
237 : ReportTotal(aHandleReport, aData, /* aExplicit = */ true,
238 0 : aPathPrefix, "<non-notable images>/", nonNotableTotal);
239 :
240 : // Report a summary in aggregate, outside of the explicit tree.
241 : ReportTotal(aHandleReport, aData, /* aExplicit = */ false,
242 0 : aPathPrefix, "", summaryTotal);
243 0 : }
244 :
245 0 : static void ReportImage(nsIHandleReportCallback* aHandleReport,
246 : nsISupports* aData,
247 : const char* aPathPrefix,
248 : const ImageMemoryCounter& aCounter)
249 : {
250 0 : nsAutoCString pathPrefix(NS_LITERAL_CSTRING("explicit/"));
251 0 : pathPrefix.Append(aPathPrefix);
252 0 : pathPrefix.Append(aCounter.Type() == imgIContainer::TYPE_RASTER
253 : ? "/raster/"
254 0 : : "/vector/");
255 0 : pathPrefix.Append(aCounter.IsUsed() ? "used/" : "unused/");
256 0 : pathPrefix.Append("image(");
257 0 : pathPrefix.AppendInt(aCounter.IntrinsicSize().width);
258 0 : pathPrefix.Append("x");
259 0 : pathPrefix.AppendInt(aCounter.IntrinsicSize().height);
260 0 : pathPrefix.Append(", ");
261 :
262 0 : if (aCounter.URI().IsEmpty()) {
263 0 : pathPrefix.Append("<unknown URI>");
264 : } else {
265 0 : pathPrefix.Append(aCounter.URI());
266 : }
267 :
268 0 : pathPrefix.Append(")/");
269 :
270 0 : ReportSurfaces(aHandleReport, aData, pathPrefix, aCounter);
271 :
272 0 : ReportSourceValue(aHandleReport, aData, pathPrefix, aCounter.Values());
273 0 : }
274 :
275 0 : static void ReportSurfaces(nsIHandleReportCallback* aHandleReport,
276 : nsISupports* aData,
277 : const nsACString& aPathPrefix,
278 : const ImageMemoryCounter& aCounter)
279 : {
280 0 : for (const SurfaceMemoryCounter& counter : aCounter.Surfaces()) {
281 0 : nsAutoCString surfacePathPrefix(aPathPrefix);
282 0 : surfacePathPrefix.Append(counter.IsLocked() ? "locked/" : "unlocked/");
283 0 : surfacePathPrefix.Append("surface(");
284 0 : surfacePathPrefix.AppendInt(counter.Key().Size().width);
285 0 : surfacePathPrefix.Append("x");
286 0 : surfacePathPrefix.AppendInt(counter.Key().Size().height);
287 :
288 0 : if (counter.Values().SharedHandles() > 0) {
289 0 : surfacePathPrefix.Append(", shared:");
290 0 : surfacePathPrefix.AppendInt(uint32_t(counter.Values().SharedHandles()));
291 : }
292 :
293 0 : if (counter.Type() == SurfaceMemoryCounterType::NORMAL) {
294 0 : PlaybackType playback = counter.Key().Playback();
295 0 : surfacePathPrefix.Append(playback == PlaybackType::eAnimated
296 : ? " (animation)"
297 0 : : "");
298 :
299 0 : if (counter.Key().Flags() != DefaultSurfaceFlags()) {
300 0 : surfacePathPrefix.Append(", flags:");
301 0 : surfacePathPrefix.AppendInt(uint32_t(counter.Key().Flags()),
302 0 : /* aRadix = */ 16);
303 : }
304 0 : } else if (counter.Type() == SurfaceMemoryCounterType::COMPOSITING) {
305 0 : surfacePathPrefix.Append(", compositing frame");
306 0 : } else if (counter.Type() == SurfaceMemoryCounterType::COMPOSITING_PREV) {
307 0 : surfacePathPrefix.Append(", compositing prev frame");
308 : } else {
309 0 : MOZ_ASSERT_UNREACHABLE("Unknown counter type");
310 : }
311 :
312 0 : surfacePathPrefix.Append(")/");
313 :
314 0 : ReportValues(aHandleReport, aData, surfacePathPrefix, counter.Values());
315 : }
316 0 : }
317 :
318 0 : static void ReportTotal(nsIHandleReportCallback* aHandleReport,
319 : nsISupports* aData,
320 : bool aExplicit,
321 : const char* aPathPrefix,
322 : const char* aPathInfix,
323 : const MemoryTotal& aTotal)
324 : {
325 0 : nsAutoCString pathPrefix;
326 0 : if (aExplicit) {
327 0 : pathPrefix.Append("explicit/");
328 : }
329 0 : pathPrefix.Append(aPathPrefix);
330 :
331 0 : nsAutoCString rasterUsedPrefix(pathPrefix);
332 0 : rasterUsedPrefix.Append("/raster/used/");
333 0 : rasterUsedPrefix.Append(aPathInfix);
334 0 : ReportValues(aHandleReport, aData, rasterUsedPrefix, aTotal.UsedRaster());
335 :
336 0 : nsAutoCString rasterUnusedPrefix(pathPrefix);
337 0 : rasterUnusedPrefix.Append("/raster/unused/");
338 0 : rasterUnusedPrefix.Append(aPathInfix);
339 0 : ReportValues(aHandleReport, aData, rasterUnusedPrefix,
340 0 : aTotal.UnusedRaster());
341 :
342 0 : nsAutoCString vectorUsedPrefix(pathPrefix);
343 0 : vectorUsedPrefix.Append("/vector/used/");
344 0 : vectorUsedPrefix.Append(aPathInfix);
345 0 : ReportValues(aHandleReport, aData, vectorUsedPrefix, aTotal.UsedVector());
346 :
347 0 : nsAutoCString vectorUnusedPrefix(pathPrefix);
348 0 : vectorUnusedPrefix.Append("/vector/unused/");
349 0 : vectorUnusedPrefix.Append(aPathInfix);
350 0 : ReportValues(aHandleReport, aData, vectorUnusedPrefix,
351 0 : aTotal.UnusedVector());
352 0 : }
353 :
354 0 : static void ReportValues(nsIHandleReportCallback* aHandleReport,
355 : nsISupports* aData,
356 : const nsACString& aPathPrefix,
357 : const MemoryCounter& aCounter)
358 : {
359 0 : ReportSourceValue(aHandleReport, aData, aPathPrefix, aCounter);
360 :
361 0 : ReportValue(aHandleReport, aData, KIND_HEAP, aPathPrefix,
362 : "decoded-heap",
363 : "Decoded image data which is stored on the heap.",
364 0 : aCounter.DecodedHeap());
365 :
366 0 : ReportValue(aHandleReport, aData, KIND_NONHEAP, aPathPrefix,
367 : "decoded-nonheap",
368 : "Decoded image data which isn't stored on the heap.",
369 0 : aCounter.DecodedNonHeap());
370 0 : }
371 :
372 0 : static void ReportSourceValue(nsIHandleReportCallback* aHandleReport,
373 : nsISupports* aData,
374 : const nsACString& aPathPrefix,
375 : const MemoryCounter& aCounter)
376 : {
377 0 : ReportValue(aHandleReport, aData, KIND_HEAP, aPathPrefix,
378 : "source",
379 : "Raster image source data and vector image documents.",
380 0 : aCounter.Source());
381 0 : }
382 :
383 0 : static void ReportValue(nsIHandleReportCallback* aHandleReport,
384 : nsISupports* aData,
385 : int32_t aKind,
386 : const nsACString& aPathPrefix,
387 : const char* aPathSuffix,
388 : const char* aDescription,
389 : size_t aValue)
390 : {
391 0 : if (aValue == 0) {
392 0 : return;
393 : }
394 :
395 0 : nsAutoCString desc(aDescription);
396 0 : nsAutoCString path(aPathPrefix);
397 0 : path.Append(aPathSuffix);
398 :
399 0 : aHandleReport->Callback(EmptyCString(), path, aKind, UNITS_BYTES,
400 0 : aValue, desc, aData);
401 : }
402 :
403 0 : static void RecordCounterForRequest(imgRequest* aRequest,
404 : nsTArray<ImageMemoryCounter>* aArray,
405 : bool aIsUsed)
406 : {
407 0 : RefPtr<image::Image> image = aRequest->GetImage();
408 0 : if (!image) {
409 0 : return;
410 : }
411 :
412 0 : ImageMemoryCounter counter(image, ImagesMallocSizeOf, aIsUsed);
413 :
414 0 : aArray->AppendElement(Move(counter));
415 : }
416 : };
417 :
418 41 : NS_IMPL_ISUPPORTS(imgMemoryReporter, nsIMemoryReporter)
419 :
420 984 : NS_IMPL_ISUPPORTS(nsProgressNotificationProxy,
421 : nsIProgressEventSink,
422 : nsIChannelEventSink,
423 : nsIInterfaceRequestor)
424 :
425 : NS_IMETHODIMP
426 41 : nsProgressNotificationProxy::OnProgress(nsIRequest* request,
427 : nsISupports* ctxt,
428 : int64_t progress,
429 : int64_t progressMax)
430 : {
431 82 : nsCOMPtr<nsILoadGroup> loadGroup;
432 41 : request->GetLoadGroup(getter_AddRefs(loadGroup));
433 :
434 82 : nsCOMPtr<nsIProgressEventSink> target;
435 41 : NS_QueryNotificationCallbacks(mOriginalCallbacks,
436 : loadGroup,
437 : NS_GET_IID(nsIProgressEventSink),
438 82 : getter_AddRefs(target));
439 41 : if (!target) {
440 0 : return NS_OK;
441 : }
442 41 : return target->OnProgress(mImageRequest, ctxt, progress, progressMax);
443 : }
444 :
445 : NS_IMETHODIMP
446 1 : nsProgressNotificationProxy::OnStatus(nsIRequest* request,
447 : nsISupports* ctxt,
448 : nsresult status,
449 : const char16_t* statusArg)
450 : {
451 2 : nsCOMPtr<nsILoadGroup> loadGroup;
452 1 : request->GetLoadGroup(getter_AddRefs(loadGroup));
453 :
454 2 : nsCOMPtr<nsIProgressEventSink> target;
455 1 : NS_QueryNotificationCallbacks(mOriginalCallbacks,
456 : loadGroup,
457 : NS_GET_IID(nsIProgressEventSink),
458 2 : getter_AddRefs(target));
459 1 : if (!target) {
460 0 : return NS_OK;
461 : }
462 1 : return target->OnStatus(mImageRequest, ctxt, status, statusArg);
463 : }
464 :
465 : NS_IMETHODIMP
466 0 : nsProgressNotificationProxy::
467 : AsyncOnChannelRedirect(nsIChannel* oldChannel,
468 : nsIChannel* newChannel,
469 : uint32_t flags,
470 : nsIAsyncVerifyRedirectCallback* cb)
471 : {
472 : // Tell the original original callbacks about it too
473 0 : nsCOMPtr<nsILoadGroup> loadGroup;
474 0 : newChannel->GetLoadGroup(getter_AddRefs(loadGroup));
475 0 : nsCOMPtr<nsIChannelEventSink> target;
476 0 : NS_QueryNotificationCallbacks(mOriginalCallbacks,
477 : loadGroup,
478 : NS_GET_IID(nsIChannelEventSink),
479 0 : getter_AddRefs(target));
480 0 : if (!target) {
481 0 : cb->OnRedirectVerifyCallback(NS_OK);
482 0 : return NS_OK;
483 : }
484 :
485 : // Delegate to |target| if set, reusing |cb|
486 0 : return target->AsyncOnChannelRedirect(oldChannel, newChannel, flags, cb);
487 : }
488 :
489 : NS_IMETHODIMP
490 124 : nsProgressNotificationProxy::GetInterface(const nsIID& iid,
491 : void** result)
492 : {
493 124 : if (iid.Equals(NS_GET_IID(nsIProgressEventSink))) {
494 41 : *result = static_cast<nsIProgressEventSink*>(this);
495 41 : NS_ADDREF_THIS();
496 41 : return NS_OK;
497 : }
498 83 : if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
499 0 : *result = static_cast<nsIChannelEventSink*>(this);
500 0 : NS_ADDREF_THIS();
501 0 : return NS_OK;
502 : }
503 83 : if (mOriginalCallbacks) {
504 83 : return mOriginalCallbacks->GetInterface(iid, result);
505 : }
506 0 : return NS_NOINTERFACE;
507 : }
508 :
509 : static void
510 41 : NewRequestAndEntry(bool aForcePrincipalCheckForCacheEntry, imgLoader* aLoader,
511 : const ImageCacheKey& aKey,
512 : imgRequest** aRequest, imgCacheEntry** aEntry)
513 : {
514 82 : RefPtr<imgRequest> request = new imgRequest(aLoader, aKey);
515 : RefPtr<imgCacheEntry> entry =
516 123 : new imgCacheEntry(aLoader, request, aForcePrincipalCheckForCacheEntry);
517 41 : aLoader->AddToUncachedImages(request);
518 41 : request.forget(aRequest);
519 41 : entry.forget(aEntry);
520 41 : }
521 :
522 : static bool
523 0 : ShouldRevalidateEntry(imgCacheEntry* aEntry,
524 : nsLoadFlags aFlags,
525 : bool aHasExpired)
526 : {
527 0 : bool bValidateEntry = false;
528 :
529 0 : if (aFlags & nsIRequest::LOAD_BYPASS_CACHE) {
530 0 : return false;
531 : }
532 :
533 0 : if (aFlags & nsIRequest::VALIDATE_ALWAYS) {
534 0 : bValidateEntry = true;
535 0 : } else if (aEntry->GetMustValidate()) {
536 0 : bValidateEntry = true;
537 0 : } else if (aHasExpired) {
538 : // The cache entry has expired... Determine whether the stale cache
539 : // entry can be used without validation...
540 0 : if (aFlags & (nsIRequest::VALIDATE_NEVER |
541 : nsIRequest::VALIDATE_ONCE_PER_SESSION)) {
542 : // VALIDATE_NEVER and VALIDATE_ONCE_PER_SESSION allow stale cache
543 : // entries to be used unless they have been explicitly marked to
544 : // indicate that revalidation is necessary.
545 0 : bValidateEntry = false;
546 :
547 0 : } else if (!(aFlags & nsIRequest::LOAD_FROM_CACHE)) {
548 : // LOAD_FROM_CACHE allows a stale cache entry to be used... Otherwise,
549 : // the entry must be revalidated.
550 0 : bValidateEntry = true;
551 : }
552 : }
553 :
554 0 : return bValidateEntry;
555 : }
556 :
557 : /* Call content policies on cached images that went through a redirect */
558 : static bool
559 4 : ShouldLoadCachedImage(imgRequest* aImgRequest,
560 : nsISupports* aLoadingContext,
561 : nsIPrincipal* aLoadingPrincipal,
562 : nsContentPolicyType aPolicyType)
563 : {
564 : /* Call content policies on cached images - Bug 1082837
565 : * Cached images are keyed off of the first uri in a redirect chain.
566 : * Hence content policies don't get a chance to test the intermediate hops
567 : * or the final desitnation. Here we test the final destination using
568 : * mCurrentURI off of the imgRequest and passing it into content policies.
569 : * For Mixed Content Blocker, we do an additional check to determine if any
570 : * of the intermediary hops went through an insecure redirect with the
571 : * mHadInsecureRedirect flag
572 : */
573 4 : bool insecureRedirect = aImgRequest->HadInsecureRedirect();
574 8 : nsCOMPtr<nsIURI> contentLocation;
575 4 : aImgRequest->GetCurrentURI(getter_AddRefs(contentLocation));
576 : nsresult rv;
577 :
578 4 : int16_t decision = nsIContentPolicy::REJECT_REQUEST;
579 8 : rv = NS_CheckContentLoadPolicy(aPolicyType,
580 : contentLocation,
581 : aLoadingPrincipal,
582 : aLoadingContext,
583 4 : EmptyCString(), //mime guess
584 : nullptr, //aExtra
585 : &decision,
586 : nsContentUtils::GetContentPolicy(),
587 4 : nsContentUtils::GetSecurityManager());
588 4 : if (NS_FAILED(rv) || !NS_CP_ACCEPTED(decision)) {
589 0 : return false;
590 : }
591 :
592 : // We call all Content Policies above, but we also have to call mcb
593 : // individually to check the intermediary redirect hops are secure.
594 4 : if (insecureRedirect) {
595 : // Bug 1314356: If the image ended up in the cache upgraded by HSTS and the page
596 : // uses upgrade-inscure-requests it had an insecure redirect (http->https).
597 : // We need to invalidate the image and reload it because mixed content blocker
598 : // only bails if upgrade-insecure-requests is set on the doc and the resource
599 : // load is http: which would result in an incorrect mixed content warning.
600 0 : nsCOMPtr<nsIDocShell> docShell = NS_CP_GetDocShellFromContext(aLoadingContext);
601 0 : if (docShell) {
602 0 : nsIDocument* document = docShell->GetDocument();
603 0 : if (document && document->GetUpgradeInsecureRequests(false)) {
604 0 : return false;
605 : }
606 : }
607 :
608 0 : if (!nsContentUtils::IsSystemPrincipal(aLoadingPrincipal)) {
609 : // Set the requestingLocation from the aLoadingPrincipal.
610 0 : nsCOMPtr<nsIURI> requestingLocation;
611 0 : if (aLoadingPrincipal) {
612 0 : rv = aLoadingPrincipal->GetURI(getter_AddRefs(requestingLocation));
613 0 : NS_ENSURE_SUCCESS(rv, false);
614 : }
615 :
616 : // reset the decision for mixed content blocker check
617 0 : decision = nsIContentPolicy::REJECT_REQUEST;
618 0 : rv = nsMixedContentBlocker::ShouldLoad(insecureRedirect,
619 : aPolicyType,
620 : contentLocation,
621 : requestingLocation,
622 : aLoadingContext,
623 0 : EmptyCString(), //mime guess
624 : nullptr,
625 : aLoadingPrincipal,
626 0 : &decision);
627 0 : if (NS_FAILED(rv) || !NS_CP_ACCEPTED(decision)) {
628 0 : return false;
629 : }
630 : }
631 : }
632 :
633 4 : bool sendPriming = false;
634 4 : bool mixedContentWouldBlock = false;
635 4 : rv = nsMixedContentBlocker::GetHSTSPrimingFromRequestingContext(contentLocation,
636 4 : aLoadingContext, &sendPriming, &mixedContentWouldBlock);
637 4 : if (NS_FAILED(rv)) {
638 0 : return false;
639 : }
640 4 : if (sendPriming && mixedContentWouldBlock) {
641 : // if either of the securty checks above would cause a priming request, we
642 : // can't load this image from the cache, so go ahead and return false here
643 0 : return false;
644 : }
645 :
646 4 : return true;
647 : }
648 :
649 : // Returns true if this request is compatible with the given CORS mode on the
650 : // given loading principal, and false if the request may not be reused due
651 : // to CORS. Also checks the Referrer Policy, since requests with different
652 : // referrers/policies may generate different responses.
653 : static bool
654 4 : ValidateSecurityInfo(imgRequest* request, bool forcePrincipalCheck,
655 : int32_t corsmode, nsIPrincipal* loadingPrincipal,
656 : nsISupports* aCX, nsContentPolicyType aPolicyType,
657 : ReferrerPolicy referrerPolicy)
658 : {
659 : // If the entry's Referrer Policy doesn't match, we can't use this request.
660 : // XXX: this will return false if an image has different referrer attributes,
661 : // i.e. we currently don't use the cached image but reload the image with
662 : // the new referrer policy bug 1174921
663 4 : if (referrerPolicy != request->GetReferrerPolicy()) {
664 0 : return false;
665 : }
666 :
667 : // If the entry's CORS mode doesn't match, or the CORS mode matches but the
668 : // document principal isn't the same, we can't use this request.
669 4 : if (request->GetCORSMode() != corsmode) {
670 0 : return false;
671 : }
672 4 : if (request->GetCORSMode() != imgIRequest::CORS_NONE ||
673 : forcePrincipalCheck) {
674 0 : nsCOMPtr<nsIPrincipal> otherprincipal = request->GetLoadingPrincipal();
675 :
676 : // If we previously had a principal, but we don't now, we can't use this
677 : // request.
678 0 : if (otherprincipal && !loadingPrincipal) {
679 0 : return false;
680 : }
681 :
682 0 : if (otherprincipal && loadingPrincipal) {
683 0 : bool equals = false;
684 0 : otherprincipal->Equals(loadingPrincipal, &equals);
685 0 : if (!equals) {
686 0 : return false;
687 : }
688 : }
689 : }
690 :
691 : // Content Policy Check on Cached Images
692 4 : return ShouldLoadCachedImage(request, aCX, loadingPrincipal, aPolicyType);
693 : }
694 :
695 : static nsresult
696 41 : NewImageChannel(nsIChannel** aResult,
697 : // If aForcePrincipalCheckForCacheEntry is true, then we will
698 : // force a principal check even when not using CORS before
699 : // assuming we have a cache hit on a cache entry that we
700 : // create for this channel. This is an out param that should
701 : // be set to true if this channel ends up depending on
702 : // aLoadingPrincipal and false otherwise.
703 : bool* aForcePrincipalCheckForCacheEntry,
704 : nsIURI* aURI,
705 : nsIURI* aInitialDocumentURI,
706 : int32_t aCORSMode,
707 : nsIURI* aReferringURI,
708 : ReferrerPolicy aReferrerPolicy,
709 : nsILoadGroup* aLoadGroup,
710 : const nsCString& aAcceptHeader,
711 : nsLoadFlags aLoadFlags,
712 : nsContentPolicyType aPolicyType,
713 : nsIPrincipal* aLoadingPrincipal,
714 : nsISupports* aRequestingContext,
715 : bool aRespectPrivacy)
716 : {
717 41 : MOZ_ASSERT(aResult);
718 :
719 : nsresult rv;
720 82 : nsCOMPtr<nsIHttpChannel> newHttpChannel;
721 :
722 82 : nsCOMPtr<nsIInterfaceRequestor> callbacks;
723 :
724 41 : if (aLoadGroup) {
725 : // Get the notification callbacks from the load group for the new channel.
726 : //
727 : // XXX: This is not exactly correct, because the network request could be
728 : // referenced by multiple windows... However, the new channel needs
729 : // something. So, using the 'first' notification callbacks is better
730 : // than nothing...
731 : //
732 41 : aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
733 : }
734 :
735 : // Pass in a nullptr loadgroup because this is the underlying network
736 : // request. This request may be referenced by several proxy image requests
737 : // (possibly in different documents).
738 : // If all of the proxy requests are canceled then this request should be
739 : // canceled too.
740 : //
741 41 : aLoadFlags |= nsIChannel::LOAD_CLASSIFY_URI;
742 :
743 82 : nsCOMPtr<nsINode> requestingNode = do_QueryInterface(aRequestingContext);
744 :
745 : nsSecurityFlags securityFlags =
746 : aCORSMode == imgIRequest::CORS_NONE
747 41 : ? nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS
748 41 : : nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
749 41 : if (aCORSMode == imgIRequest::CORS_ANONYMOUS) {
750 0 : securityFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
751 41 : } else if (aCORSMode == imgIRequest::CORS_USE_CREDENTIALS) {
752 0 : securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
753 : }
754 41 : securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
755 :
756 : // Note we are calling NS_NewChannelWithTriggeringPrincipal() here with a
757 : // node and a principal. This is for things like background images that are
758 : // specified by user stylesheets, where the document is being styled, but
759 : // the principal is that of the user stylesheet.
760 41 : if (requestingNode && aLoadingPrincipal) {
761 41 : rv = NS_NewChannelWithTriggeringPrincipal(aResult,
762 : aURI,
763 : requestingNode,
764 : aLoadingPrincipal,
765 : securityFlags,
766 : aPolicyType,
767 : nullptr, // loadGroup
768 : callbacks,
769 41 : aLoadFlags);
770 :
771 41 : if (NS_FAILED(rv)) {
772 0 : return rv;
773 : }
774 :
775 41 : if (aPolicyType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
776 : // If this is a favicon loading, we will use the originAttributes from the
777 : // loadingPrincipal as the channel's originAttributes. This allows the favicon
778 : // loading from XUL will use the correct originAttributes.
779 :
780 2 : nsCOMPtr<nsILoadInfo> loadInfo = (*aResult)->GetLoadInfo();
781 1 : if (loadInfo) {
782 : rv =
783 1 : loadInfo->SetOriginAttributes(aLoadingPrincipal->OriginAttributesRef());
784 : }
785 : }
786 : } else {
787 : // either we are loading something inside a document, in which case
788 : // we should always have a requestingNode, or we are loading something
789 : // outside a document, in which case the loadingPrincipal and
790 : // triggeringPrincipal should always be the systemPrincipal.
791 : // However, there are exceptions: one is Notifications which create a
792 : // channel in the parent prcoess in which case we can't get a requestingNode.
793 0 : rv = NS_NewChannel(aResult,
794 : aURI,
795 : nsContentUtils::GetSystemPrincipal(),
796 : securityFlags,
797 : aPolicyType,
798 : nullptr, // loadGroup
799 : callbacks,
800 0 : aLoadFlags);
801 :
802 0 : if (NS_FAILED(rv)) {
803 0 : return rv;
804 : }
805 :
806 : // Use the OriginAttributes from the loading principal, if one is available,
807 : // and adjust the private browsing ID based on what kind of load the caller
808 : // has asked us to perform.
809 0 : OriginAttributes attrs;
810 0 : if (aLoadingPrincipal) {
811 0 : attrs = aLoadingPrincipal->OriginAttributesRef();
812 : }
813 0 : attrs.mPrivateBrowsingId = aRespectPrivacy ? 1 : 0;
814 :
815 0 : nsCOMPtr<nsILoadInfo> loadInfo = (*aResult)->GetLoadInfo();
816 0 : if (loadInfo) {
817 0 : rv = loadInfo->SetOriginAttributes(attrs);
818 : }
819 : }
820 :
821 41 : if (NS_FAILED(rv)) {
822 0 : return rv;
823 : }
824 :
825 : // only inherit if we have a principal
826 41 : *aForcePrincipalCheckForCacheEntry =
827 82 : aLoadingPrincipal &&
828 41 : nsContentUtils::ChannelShouldInheritPrincipal(
829 : aLoadingPrincipal,
830 : aURI,
831 : /* aInheritForAboutBlank */ false,
832 : /* aForceInherit */ false);
833 :
834 : // Initialize HTTP-specific attributes
835 41 : newHttpChannel = do_QueryInterface(*aResult);
836 41 : if (newHttpChannel) {
837 4 : rv = newHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
838 : aAcceptHeader,
839 3 : false);
840 1 : MOZ_ASSERT(NS_SUCCEEDED(rv));
841 :
842 : nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
843 2 : do_QueryInterface(newHttpChannel);
844 1 : NS_ENSURE_TRUE(httpChannelInternal, NS_ERROR_UNEXPECTED);
845 1 : rv = httpChannelInternal->SetDocumentURI(aInitialDocumentURI);
846 1 : MOZ_ASSERT(NS_SUCCEEDED(rv));
847 1 : rv = newHttpChannel->SetReferrerWithPolicy(aReferringURI, aReferrerPolicy);
848 1 : MOZ_ASSERT(NS_SUCCEEDED(rv));
849 : }
850 :
851 : // Image channels are loaded by default with reduced priority.
852 82 : nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(*aResult);
853 41 : if (p) {
854 1 : uint32_t priority = nsISupportsPriority::PRIORITY_LOW;
855 :
856 1 : if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) {
857 0 : ++priority; // further reduce priority for background loads
858 : }
859 :
860 1 : p->AdjustPriority(priority);
861 : }
862 :
863 : // Create a new loadgroup for this new channel, using the old group as
864 : // the parent. The indirection keeps the channel insulated from cancels,
865 : // but does allow a way for this revalidation to be associated with at
866 : // least one base load group for scheduling/caching purposes.
867 :
868 82 : nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
869 82 : nsCOMPtr<nsILoadGroupChild> childLoadGroup = do_QueryInterface(loadGroup);
870 41 : if (childLoadGroup) {
871 41 : childLoadGroup->SetParentLoadGroup(aLoadGroup);
872 : }
873 41 : (*aResult)->SetLoadGroup(loadGroup);
874 :
875 41 : return NS_OK;
876 : }
877 :
878 : static uint32_t
879 132 : SecondsFromPRTime(PRTime prTime)
880 : {
881 132 : return uint32_t(int64_t(prTime) / int64_t(PR_USEC_PER_SEC));
882 : }
883 :
884 41 : imgCacheEntry::imgCacheEntry(imgLoader* loader, imgRequest* request,
885 41 : bool forcePrincipalCheck)
886 : : mLoader(loader),
887 : mRequest(request),
888 : mDataSize(0),
889 41 : mTouchedTime(SecondsFromPRTime(PR_Now())),
890 41 : mLoadTime(SecondsFromPRTime(PR_Now())),
891 : mExpiryTime(0),
892 : mMustValidate(false),
893 : // We start off as evicted so we don't try to update the cache. PutIntoCache
894 : // will set this to false.
895 : mEvicted(true),
896 : mHasNoProxies(true),
897 123 : mForcePrincipalCheck(forcePrincipalCheck)
898 41 : { }
899 :
900 2 : imgCacheEntry::~imgCacheEntry()
901 : {
902 1 : LOG_FUNC(gImgLog, "imgCacheEntry::~imgCacheEntry()");
903 1 : }
904 :
905 : void
906 5 : imgCacheEntry::Touch(bool updateTime /* = true */)
907 : {
908 10 : LOG_SCOPE(gImgLog, "imgCacheEntry::Touch");
909 :
910 5 : if (updateTime) {
911 5 : mTouchedTime = SecondsFromPRTime(PR_Now());
912 : }
913 :
914 5 : UpdateCache();
915 5 : }
916 :
917 : void
918 45 : imgCacheEntry::UpdateCache(int32_t diff /* = 0 */)
919 : {
920 : // Don't update the cache if we've been removed from it or it doesn't care
921 : // about our size or usage.
922 45 : if (!Evicted() && HasNoProxies()) {
923 0 : mLoader->CacheEntriesChanged(mRequest->IsChrome(), diff);
924 : }
925 45 : }
926 :
927 41 : void imgCacheEntry::UpdateLoadTime()
928 : {
929 41 : mLoadTime = SecondsFromPRTime(PR_Now());
930 41 : }
931 :
932 : void
933 41 : imgCacheEntry::SetHasNoProxies(bool hasNoProxies)
934 : {
935 41 : if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
936 0 : if (hasNoProxies) {
937 0 : LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheEntry::SetHasNoProxies true",
938 0 : "uri", mRequest->CacheKey().Spec());
939 : } else {
940 0 : LOG_FUNC_WITH_PARAM(gImgLog, "imgCacheEntry::SetHasNoProxies false",
941 0 : "uri", mRequest->CacheKey().Spec());
942 : }
943 : }
944 :
945 41 : mHasNoProxies = hasNoProxies;
946 41 : }
947 :
948 4 : imgCacheQueue::imgCacheQueue()
949 : : mDirty(false),
950 4 : mSize(0)
951 4 : { }
952 :
953 : void
954 0 : imgCacheQueue::UpdateSize(int32_t diff)
955 : {
956 0 : mSize += diff;
957 0 : }
958 :
959 : uint32_t
960 0 : imgCacheQueue::GetSize() const
961 : {
962 0 : return mSize;
963 : }
964 :
965 : #include <algorithm>
966 : using namespace std;
967 :
968 : void
969 41 : imgCacheQueue::Remove(imgCacheEntry* entry)
970 : {
971 41 : auto it = find(mQueue.begin(), mQueue.end(), entry);
972 41 : if (it != mQueue.end()) {
973 41 : mSize -= (*it)->GetDataSize();
974 41 : mQueue.erase(it);
975 41 : MarkDirty();
976 : }
977 41 : }
978 :
979 : void
980 41 : imgCacheQueue::Push(imgCacheEntry* entry)
981 : {
982 41 : mSize += entry->GetDataSize();
983 :
984 82 : RefPtr<imgCacheEntry> refptr(entry);
985 41 : mQueue.push_back(refptr);
986 41 : MarkDirty();
987 41 : }
988 :
989 : already_AddRefed<imgCacheEntry>
990 0 : imgCacheQueue::Pop()
991 : {
992 0 : if (mQueue.empty()) {
993 0 : return nullptr;
994 : }
995 0 : if (IsDirty()) {
996 0 : Refresh();
997 : }
998 :
999 0 : RefPtr<imgCacheEntry> entry = mQueue[0];
1000 0 : std::pop_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
1001 0 : mQueue.pop_back();
1002 :
1003 0 : mSize -= entry->GetDataSize();
1004 0 : return entry.forget();
1005 : }
1006 :
1007 : void
1008 0 : imgCacheQueue::Refresh()
1009 : {
1010 0 : std::make_heap(mQueue.begin(), mQueue.end(), imgLoader::CompareCacheEntries);
1011 0 : mDirty = false;
1012 0 : }
1013 :
1014 : void
1015 82 : imgCacheQueue::MarkDirty()
1016 : {
1017 82 : mDirty = true;
1018 82 : }
1019 :
1020 : bool
1021 0 : imgCacheQueue::IsDirty()
1022 : {
1023 0 : return mDirty;
1024 : }
1025 :
1026 : uint32_t
1027 172 : imgCacheQueue::GetNumElements() const
1028 : {
1029 172 : return mQueue.size();
1030 : }
1031 :
1032 : imgCacheQueue::iterator
1033 0 : imgCacheQueue::begin()
1034 : {
1035 0 : return mQueue.begin();
1036 : }
1037 :
1038 : imgCacheQueue::const_iterator
1039 0 : imgCacheQueue::begin() const
1040 : {
1041 0 : return mQueue.begin();
1042 : }
1043 :
1044 : imgCacheQueue::iterator
1045 0 : imgCacheQueue::end()
1046 : {
1047 0 : return mQueue.end();
1048 : }
1049 :
1050 : imgCacheQueue::const_iterator
1051 0 : imgCacheQueue::end() const
1052 : {
1053 0 : return mQueue.end();
1054 : }
1055 :
1056 : nsresult
1057 45 : imgLoader::CreateNewProxyForRequest(imgRequest* aRequest,
1058 : nsILoadGroup* aLoadGroup,
1059 : imgINotificationObserver* aObserver,
1060 : nsLoadFlags aLoadFlags,
1061 : imgRequestProxy** _retval)
1062 : {
1063 90 : LOG_SCOPE_WITH_PARAM(gImgLog, "imgLoader::CreateNewProxyForRequest",
1064 : "imgRequest", aRequest);
1065 :
1066 : /* XXX If we move decoding onto separate threads, we should save off the
1067 : calling thread here and pass it off to |proxyRequest| so that it call
1068 : proxy calls to |aObserver|.
1069 : */
1070 :
1071 90 : RefPtr<imgRequestProxy> proxyRequest = new imgRequestProxy();
1072 :
1073 : /* It is important to call |SetLoadFlags()| before calling |Init()| because
1074 : |Init()| adds the request to the loadgroup.
1075 : */
1076 45 : proxyRequest->SetLoadFlags(aLoadFlags);
1077 :
1078 90 : RefPtr<ImageURL> uri;
1079 45 : aRequest->GetURI(getter_AddRefs(uri));
1080 :
1081 : // init adds itself to imgRequest's list of observers
1082 45 : nsresult rv = proxyRequest->Init(aRequest, aLoadGroup, uri, aObserver);
1083 45 : if (NS_WARN_IF(NS_FAILED(rv))) {
1084 0 : return rv;
1085 : }
1086 :
1087 45 : proxyRequest.forget(_retval);
1088 45 : return NS_OK;
1089 : }
1090 :
1091 0 : class imgCacheExpirationTracker final
1092 : : public nsExpirationTracker<imgCacheEntry, 3>
1093 : {
1094 : enum { TIMEOUT_SECONDS = 10 };
1095 : public:
1096 : imgCacheExpirationTracker();
1097 :
1098 : protected:
1099 : void NotifyExpired(imgCacheEntry* entry) override;
1100 : };
1101 :
1102 2 : imgCacheExpirationTracker::imgCacheExpirationTracker()
1103 : : nsExpirationTracker<imgCacheEntry, 3>(TIMEOUT_SECONDS * 1000,
1104 2 : "imgCacheExpirationTracker")
1105 2 : { }
1106 :
1107 : void
1108 0 : imgCacheExpirationTracker::NotifyExpired(imgCacheEntry* entry)
1109 : {
1110 : // Hold on to a reference to this entry, because the expiration tracker
1111 : // mechanism doesn't.
1112 0 : RefPtr<imgCacheEntry> kungFuDeathGrip(entry);
1113 :
1114 0 : if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
1115 0 : RefPtr<imgRequest> req = entry->GetRequest();
1116 0 : if (req) {
1117 0 : LOG_FUNC_WITH_PARAM(gImgLog,
1118 : "imgCacheExpirationTracker::NotifyExpired",
1119 0 : "entry", req->CacheKey().Spec());
1120 : }
1121 : }
1122 :
1123 : // We can be called multiple times on the same entry. Don't do work multiple
1124 : // times.
1125 0 : if (!entry->Evicted()) {
1126 0 : entry->Loader()->RemoveFromCache(entry);
1127 : }
1128 :
1129 0 : entry->Loader()->VerifyCacheSizes();
1130 0 : }
1131 :
1132 :
1133 : ///////////////////////////////////////////////////////////////////////////////
1134 : // imgLoader
1135 : ///////////////////////////////////////////////////////////////////////////////
1136 :
1137 : double imgLoader::sCacheTimeWeight;
1138 : uint32_t imgLoader::sCacheMaxSize;
1139 : imgMemoryReporter* imgLoader::sMemReporter;
1140 :
1141 41 : NS_IMPL_ISUPPORTS(imgLoader, imgILoader, nsIContentSniffer, imgICache,
1142 : nsISupportsWeakReference, nsIObserver)
1143 :
1144 : static imgLoader* gNormalLoader = nullptr;
1145 : static imgLoader* gPrivateBrowsingLoader = nullptr;
1146 :
1147 : /* static */ already_AddRefed<imgLoader>
1148 1 : imgLoader::CreateImageLoader()
1149 : {
1150 : // In some cases, such as xpctests, XPCOM modules are not automatically
1151 : // initialized. We need to make sure that our module is initialized before
1152 : // we hand out imgLoader instances and code starts using them.
1153 1 : mozilla::image::EnsureModuleInitialized();
1154 :
1155 2 : RefPtr<imgLoader> loader = new imgLoader();
1156 1 : loader->Init();
1157 :
1158 2 : return loader.forget();
1159 : }
1160 :
1161 : imgLoader*
1162 56 : imgLoader::NormalLoader()
1163 : {
1164 56 : if (!gNormalLoader) {
1165 1 : gNormalLoader = CreateImageLoader().take();
1166 : }
1167 56 : return gNormalLoader;
1168 : }
1169 :
1170 : imgLoader*
1171 0 : imgLoader::PrivateBrowsingLoader()
1172 : {
1173 0 : if (!gPrivateBrowsingLoader) {
1174 0 : gPrivateBrowsingLoader = CreateImageLoader().take();
1175 0 : gPrivateBrowsingLoader->RespectPrivacyNotifications();
1176 : }
1177 0 : return gPrivateBrowsingLoader;
1178 : }
1179 :
1180 2 : imgLoader::imgLoader()
1181 2 : : mUncachedImagesMutex("imgLoader::UncachedImages"), mRespectPrivacy(false)
1182 : {
1183 2 : sMemReporter->AddRef();
1184 2 : sMemReporter->RegisterLoader(this);
1185 2 : }
1186 :
1187 0 : imgLoader::~imgLoader()
1188 : {
1189 0 : ClearChromeImageCache();
1190 0 : ClearImageCache();
1191 : {
1192 : // If there are any of our imgRequest's left they are in the uncached
1193 : // images set, so clear their pointer to us.
1194 0 : MutexAutoLock lock(mUncachedImagesMutex);
1195 0 : for (auto iter = mUncachedImages.Iter(); !iter.Done(); iter.Next()) {
1196 0 : nsPtrHashKey<imgRequest>* entry = iter.Get();
1197 0 : RefPtr<imgRequest> req = entry->GetKey();
1198 0 : req->ClearLoader();
1199 : }
1200 : }
1201 0 : sMemReporter->UnregisterLoader(this);
1202 0 : sMemReporter->Release();
1203 0 : }
1204 :
1205 : void
1206 86 : imgLoader::VerifyCacheSizes()
1207 : {
1208 : #ifdef DEBUG
1209 86 : if (!mCacheTracker) {
1210 0 : return;
1211 : }
1212 :
1213 86 : uint32_t cachesize = mCache.Count() + mChromeCache.Count();
1214 : uint32_t queuesize =
1215 86 : mCacheQueue.GetNumElements() + mChromeCacheQueue.GetNumElements();
1216 86 : uint32_t trackersize = 0;
1217 254 : for (nsExpirationTracker<imgCacheEntry, 3>::Iterator it(mCacheTracker.get());
1218 127 : it.Next(); ){
1219 41 : trackersize++;
1220 : }
1221 86 : MOZ_ASSERT(queuesize == trackersize, "Queue and tracker sizes out of sync!");
1222 86 : MOZ_ASSERT(queuesize <= cachesize, "Queue has more elements than cache!");
1223 : #endif
1224 : }
1225 :
1226 : imgLoader::imgCacheTable&
1227 128 : imgLoader::GetCache(bool aForChrome)
1228 : {
1229 128 : return aForChrome ? mChromeCache : mCache;
1230 : }
1231 :
1232 : imgLoader::imgCacheTable&
1233 128 : imgLoader::GetCache(const ImageCacheKey& aKey)
1234 : {
1235 128 : return GetCache(aKey.IsChrome());
1236 : }
1237 :
1238 : imgCacheQueue&
1239 83 : imgLoader::GetCacheQueue(bool aForChrome)
1240 : {
1241 83 : return aForChrome ? mChromeCacheQueue : mCacheQueue;
1242 :
1243 : }
1244 :
1245 : imgCacheQueue&
1246 83 : imgLoader::GetCacheQueue(const ImageCacheKey& aKey)
1247 : {
1248 83 : return GetCacheQueue(aKey.IsChrome());
1249 :
1250 : }
1251 :
1252 3 : void imgLoader::GlobalInit()
1253 : {
1254 3 : sCacheTimeWeight = gfxPrefs::ImageCacheTimeWeight() / 1000.0;
1255 3 : int32_t cachesize = gfxPrefs::ImageCacheSize();
1256 3 : sCacheMaxSize = cachesize > 0 ? cachesize : 0;
1257 :
1258 3 : sMemReporter = new imgMemoryReporter();
1259 3 : RegisterStrongMemoryReporter(sMemReporter);
1260 : RegisterImagesContentUsedUncompressedDistinguishedAmount(
1261 3 : imgMemoryReporter::ImagesContentUsedUncompressedDistinguishedAmount);
1262 3 : }
1263 :
1264 0 : void imgLoader::ShutdownMemoryReporter()
1265 : {
1266 0 : UnregisterImagesContentUsedUncompressedDistinguishedAmount();
1267 0 : UnregisterStrongMemoryReporter(sMemReporter);
1268 0 : }
1269 :
1270 : nsresult
1271 2 : imgLoader::InitCache()
1272 : {
1273 4 : nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
1274 2 : if (!os) {
1275 0 : return NS_ERROR_FAILURE;
1276 : }
1277 :
1278 2 : os->AddObserver(this, "memory-pressure", false);
1279 2 : os->AddObserver(this, "chrome-flush-skin-caches", false);
1280 2 : os->AddObserver(this, "chrome-flush-caches", false);
1281 2 : os->AddObserver(this, "last-pb-context-exited", false);
1282 2 : os->AddObserver(this, "profile-before-change", false);
1283 2 : os->AddObserver(this, "xpcom-shutdown", false);
1284 :
1285 2 : mCacheTracker = MakeUnique<imgCacheExpirationTracker>();
1286 :
1287 2 : return NS_OK;
1288 : }
1289 :
1290 : nsresult
1291 2 : imgLoader::Init()
1292 : {
1293 2 : InitCache();
1294 :
1295 2 : ReadAcceptHeaderPref();
1296 :
1297 2 : Preferences::AddWeakObserver(this, "image.http.accept");
1298 :
1299 2 : return NS_OK;
1300 : }
1301 :
1302 : NS_IMETHODIMP
1303 0 : imgLoader::RespectPrivacyNotifications()
1304 : {
1305 0 : mRespectPrivacy = true;
1306 0 : return NS_OK;
1307 : }
1308 :
1309 : NS_IMETHODIMP
1310 0 : imgLoader::Observe(nsISupports* aSubject, const char* aTopic,
1311 : const char16_t* aData)
1312 : {
1313 : // We listen for pref change notifications...
1314 0 : if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
1315 0 : if (!NS_strcmp(aData, u"image.http.accept")) {
1316 0 : ReadAcceptHeaderPref();
1317 : }
1318 :
1319 0 : } else if (strcmp(aTopic, "memory-pressure") == 0) {
1320 0 : MinimizeCaches();
1321 0 : } else if (strcmp(aTopic, "chrome-flush-skin-caches") == 0 ||
1322 0 : strcmp(aTopic, "chrome-flush-caches") == 0) {
1323 0 : MinimizeCaches();
1324 0 : ClearChromeImageCache();
1325 0 : } else if (strcmp(aTopic, "last-pb-context-exited") == 0) {
1326 0 : if (mRespectPrivacy) {
1327 0 : ClearImageCache();
1328 0 : ClearChromeImageCache();
1329 : }
1330 0 : } else if (strcmp(aTopic, "profile-before-change") == 0) {
1331 0 : mCacheTracker = nullptr;
1332 0 : } else if (strcmp(aTopic, "xpcom-shutdown") == 0) {
1333 0 : mCacheTracker = nullptr;
1334 0 : ShutdownMemoryReporter();
1335 :
1336 : } else {
1337 : // (Nothing else should bring us here)
1338 0 : MOZ_ASSERT(0, "Invalid topic received");
1339 : }
1340 :
1341 0 : return NS_OK;
1342 : }
1343 :
1344 2 : void imgLoader::ReadAcceptHeaderPref()
1345 : {
1346 4 : nsAdoptingCString accept = Preferences::GetCString("image.http.accept");
1347 2 : if (accept) {
1348 2 : mAcceptHeader = accept;
1349 : } else {
1350 : mAcceptHeader =
1351 0 : IMAGE_PNG "," IMAGE_WILDCARD ";q=0.8," ANY_WILDCARD ";q=0.5";
1352 : }
1353 2 : }
1354 :
1355 : NS_IMETHODIMP
1356 0 : imgLoader::ClearCache(bool chrome)
1357 : {
1358 0 : if (XRE_IsParentProcess()) {
1359 0 : bool privateLoader = this == gPrivateBrowsingLoader;
1360 0 : for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
1361 0 : Unused << cp->SendClearImageCache(privateLoader, chrome);
1362 : }
1363 : }
1364 :
1365 0 : if (chrome) {
1366 0 : return ClearChromeImageCache();
1367 : }
1368 0 : return ClearImageCache();
1369 :
1370 : }
1371 :
1372 : NS_IMETHODIMP
1373 0 : imgLoader::RemoveEntry(nsIURI* aURI,
1374 : nsIDOMDocument* aDOMDoc)
1375 : {
1376 0 : nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDOMDoc);
1377 0 : if (aURI) {
1378 0 : OriginAttributes attrs;
1379 0 : if (doc) {
1380 0 : nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
1381 0 : if (principal) {
1382 0 : attrs = principal->OriginAttributesRef();
1383 : }
1384 : }
1385 :
1386 0 : nsresult rv = NS_OK;
1387 0 : ImageCacheKey key(aURI, attrs, doc, rv);
1388 0 : if (NS_SUCCEEDED(rv) && RemoveFromCache(key)) {
1389 0 : return NS_OK;
1390 : }
1391 : }
1392 0 : return NS_ERROR_NOT_AVAILABLE;
1393 : }
1394 :
1395 : NS_IMETHODIMP
1396 0 : imgLoader::FindEntryProperties(nsIURI* uri,
1397 : nsIDOMDocument* aDOMDoc,
1398 : nsIProperties** _retval)
1399 : {
1400 0 : *_retval = nullptr;
1401 :
1402 0 : nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDOMDoc);
1403 :
1404 0 : OriginAttributes attrs;
1405 0 : if (doc) {
1406 0 : nsCOMPtr<nsIPrincipal> principal = doc->NodePrincipal();
1407 0 : if (principal) {
1408 0 : attrs = principal->OriginAttributesRef();
1409 : }
1410 : }
1411 :
1412 : nsresult rv;
1413 0 : ImageCacheKey key(uri, attrs, doc, rv);
1414 0 : NS_ENSURE_SUCCESS(rv, rv);
1415 0 : imgCacheTable& cache = GetCache(key);
1416 :
1417 0 : RefPtr<imgCacheEntry> entry;
1418 0 : if (cache.Get(key, getter_AddRefs(entry)) && entry) {
1419 0 : if (mCacheTracker && entry->HasNoProxies()) {
1420 0 : mCacheTracker->MarkUsed(entry);
1421 : }
1422 :
1423 0 : RefPtr<imgRequest> request = entry->GetRequest();
1424 0 : if (request) {
1425 0 : nsCOMPtr<nsIProperties> properties = request->Properties();
1426 0 : properties.forget(_retval);
1427 : }
1428 : }
1429 :
1430 0 : return NS_OK;
1431 : }
1432 :
1433 : NS_IMETHODIMP_(void)
1434 0 : imgLoader::ClearCacheForControlledDocument(nsIDocument* aDoc)
1435 : {
1436 0 : MOZ_ASSERT(aDoc);
1437 0 : AutoTArray<RefPtr<imgCacheEntry>, 128> entriesToBeRemoved;
1438 0 : imgCacheTable& cache = GetCache(false);
1439 0 : for (auto iter = cache.Iter(); !iter.Done(); iter.Next()) {
1440 0 : auto& key = iter.Key();
1441 0 : if (key.ControlledDocument() == aDoc) {
1442 0 : entriesToBeRemoved.AppendElement(iter.Data());
1443 : }
1444 : }
1445 0 : for (auto& entry : entriesToBeRemoved) {
1446 0 : if (!RemoveFromCache(entry)) {
1447 0 : NS_WARNING("Couldn't remove an entry from the cache in ClearCacheForControlledDocument()\n");
1448 : }
1449 : }
1450 0 : }
1451 :
1452 : void
1453 0 : imgLoader::Shutdown()
1454 : {
1455 0 : NS_IF_RELEASE(gNormalLoader);
1456 0 : gNormalLoader = nullptr;
1457 0 : NS_IF_RELEASE(gPrivateBrowsingLoader);
1458 0 : gPrivateBrowsingLoader = nullptr;
1459 0 : }
1460 :
1461 : nsresult
1462 0 : imgLoader::ClearChromeImageCache()
1463 : {
1464 0 : return EvictEntries(mChromeCache);
1465 : }
1466 :
1467 : nsresult
1468 0 : imgLoader::ClearImageCache()
1469 : {
1470 0 : return EvictEntries(mCache);
1471 : }
1472 :
1473 : void
1474 0 : imgLoader::MinimizeCaches()
1475 : {
1476 0 : EvictEntries(mCacheQueue);
1477 0 : EvictEntries(mChromeCacheQueue);
1478 0 : }
1479 :
1480 : bool
1481 41 : imgLoader::PutIntoCache(const ImageCacheKey& aKey, imgCacheEntry* entry)
1482 : {
1483 41 : imgCacheTable& cache = GetCache(aKey);
1484 :
1485 : LOG_STATIC_FUNC_WITH_PARAM(gImgLog,
1486 41 : "imgLoader::PutIntoCache", "uri", aKey.Spec());
1487 :
1488 : // Check to see if this request already exists in the cache. If so, we'll
1489 : // replace the old version.
1490 82 : RefPtr<imgCacheEntry> tmpCacheEntry;
1491 41 : if (cache.Get(aKey, getter_AddRefs(tmpCacheEntry)) && tmpCacheEntry) {
1492 0 : MOZ_LOG(gImgLog, LogLevel::Debug,
1493 : ("[this=%p] imgLoader::PutIntoCache -- Element already in the cache",
1494 : nullptr));
1495 0 : RefPtr<imgRequest> tmpRequest = tmpCacheEntry->GetRequest();
1496 :
1497 : // If it already exists, and we're putting the same key into the cache, we
1498 : // should remove the old version.
1499 0 : MOZ_LOG(gImgLog, LogLevel::Debug,
1500 : ("[this=%p] imgLoader::PutIntoCache -- Replacing cached element",
1501 : nullptr));
1502 :
1503 0 : RemoveFromCache(aKey);
1504 : } else {
1505 41 : MOZ_LOG(gImgLog, LogLevel::Debug,
1506 : ("[this=%p] imgLoader::PutIntoCache --"
1507 : " Element NOT already in the cache", nullptr));
1508 : }
1509 :
1510 41 : cache.Put(aKey, entry);
1511 :
1512 : // We can be called to resurrect an evicted entry.
1513 41 : if (entry->Evicted()) {
1514 41 : entry->SetEvicted(false);
1515 : }
1516 :
1517 : // If we're resurrecting an entry with no proxies, put it back in the
1518 : // tracker and queue.
1519 41 : if (entry->HasNoProxies()) {
1520 41 : nsresult addrv = NS_OK;
1521 :
1522 41 : if (mCacheTracker) {
1523 41 : addrv = mCacheTracker->AddObject(entry);
1524 : }
1525 :
1526 41 : if (NS_SUCCEEDED(addrv)) {
1527 41 : imgCacheQueue& queue = GetCacheQueue(aKey);
1528 41 : queue.Push(entry);
1529 : }
1530 : }
1531 :
1532 82 : RefPtr<imgRequest> request = entry->GetRequest();
1533 41 : request->SetIsInCache(true);
1534 41 : RemoveFromUncachedImages(request);
1535 :
1536 82 : return true;
1537 : }
1538 :
1539 : bool
1540 0 : imgLoader::SetHasNoProxies(imgRequest* aRequest, imgCacheEntry* aEntry)
1541 : {
1542 0 : LOG_STATIC_FUNC_WITH_PARAM(gImgLog,
1543 : "imgLoader::SetHasNoProxies", "uri",
1544 0 : aRequest->CacheKey().Spec());
1545 :
1546 0 : aEntry->SetHasNoProxies(true);
1547 :
1548 0 : if (aEntry->Evicted()) {
1549 0 : return false;
1550 : }
1551 :
1552 0 : imgCacheQueue& queue = GetCacheQueue(aRequest->IsChrome());
1553 :
1554 0 : nsresult addrv = NS_OK;
1555 :
1556 0 : if (mCacheTracker) {
1557 0 : addrv = mCacheTracker->AddObject(aEntry);
1558 : }
1559 :
1560 0 : if (NS_SUCCEEDED(addrv)) {
1561 0 : queue.Push(aEntry);
1562 : }
1563 :
1564 0 : imgCacheTable& cache = GetCache(aRequest->IsChrome());
1565 0 : CheckCacheLimits(cache, queue);
1566 :
1567 0 : return true;
1568 : }
1569 :
1570 : bool
1571 41 : imgLoader::SetHasProxies(imgRequest* aRequest)
1572 : {
1573 41 : VerifyCacheSizes();
1574 :
1575 41 : const ImageCacheKey& key = aRequest->CacheKey();
1576 41 : imgCacheTable& cache = GetCache(key);
1577 :
1578 : LOG_STATIC_FUNC_WITH_PARAM(gImgLog,
1579 41 : "imgLoader::SetHasProxies", "uri", key.Spec());
1580 :
1581 82 : RefPtr<imgCacheEntry> entry;
1582 41 : if (cache.Get(key, getter_AddRefs(entry)) && entry) {
1583 : // Make sure the cache entry is for the right request
1584 41 : RefPtr<imgRequest> entryRequest = entry->GetRequest();
1585 41 : if (entryRequest == aRequest && entry->HasNoProxies()) {
1586 41 : imgCacheQueue& queue = GetCacheQueue(key);
1587 41 : queue.Remove(entry);
1588 :
1589 41 : if (mCacheTracker) {
1590 41 : mCacheTracker->RemoveObject(entry);
1591 : }
1592 :
1593 41 : entry->SetHasNoProxies(false);
1594 :
1595 41 : return true;
1596 : }
1597 : }
1598 :
1599 0 : return false;
1600 : }
1601 :
1602 : void
1603 0 : imgLoader::CacheEntriesChanged(bool aForChrome, int32_t aSizeDiff /* = 0 */)
1604 : {
1605 0 : imgCacheQueue& queue = GetCacheQueue(aForChrome);
1606 0 : queue.MarkDirty();
1607 0 : queue.UpdateSize(aSizeDiff);
1608 0 : }
1609 :
1610 : void
1611 0 : imgLoader::CheckCacheLimits(imgCacheTable& cache, imgCacheQueue& queue)
1612 : {
1613 0 : if (queue.GetNumElements() == 0) {
1614 0 : NS_ASSERTION(queue.GetSize() == 0,
1615 : "imgLoader::CheckCacheLimits -- incorrect cache size");
1616 : }
1617 :
1618 : // Remove entries from the cache until we're back at our desired max size.
1619 0 : while (queue.GetSize() > sCacheMaxSize) {
1620 : // Remove the first entry in the queue.
1621 0 : RefPtr<imgCacheEntry> entry(queue.Pop());
1622 :
1623 0 : NS_ASSERTION(entry, "imgLoader::CheckCacheLimits -- NULL entry pointer");
1624 :
1625 0 : if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
1626 0 : RefPtr<imgRequest> req = entry->GetRequest();
1627 0 : if (req) {
1628 0 : LOG_STATIC_FUNC_WITH_PARAM(gImgLog,
1629 : "imgLoader::CheckCacheLimits",
1630 0 : "entry", req->CacheKey().Spec());
1631 : }
1632 : }
1633 :
1634 0 : if (entry) {
1635 0 : RemoveFromCache(entry);
1636 : }
1637 : }
1638 0 : }
1639 :
1640 : bool
1641 0 : imgLoader::ValidateRequestWithNewChannel(imgRequest* request,
1642 : nsIURI* aURI,
1643 : nsIURI* aInitialDocumentURI,
1644 : nsIURI* aReferrerURI,
1645 : ReferrerPolicy aReferrerPolicy,
1646 : nsILoadGroup* aLoadGroup,
1647 : imgINotificationObserver* aObserver,
1648 : nsISupports* aCX,
1649 : nsLoadFlags aLoadFlags,
1650 : nsContentPolicyType aLoadPolicyType,
1651 : imgRequestProxy** aProxyRequest,
1652 : nsIPrincipal* aLoadingPrincipal,
1653 : int32_t aCORSMode)
1654 : {
1655 : // now we need to insert a new channel request object inbetween the real
1656 : // request and the proxy that basically delays loading the image until it
1657 : // gets a 304 or figures out that this needs to be a new request
1658 :
1659 : nsresult rv;
1660 :
1661 : // If we're currently in the middle of validating this request, just hand
1662 : // back a proxy to it; the required work will be done for us.
1663 0 : if (request->GetValidator()) {
1664 : rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver,
1665 0 : aLoadFlags, aProxyRequest);
1666 0 : if (NS_FAILED(rv)) {
1667 0 : return false;
1668 : }
1669 :
1670 0 : if (*aProxyRequest) {
1671 0 : imgRequestProxy* proxy = static_cast<imgRequestProxy*>(*aProxyRequest);
1672 :
1673 : // We will send notifications from imgCacheValidator::OnStartRequest().
1674 : // In the mean time, we must defer notifications because we are added to
1675 : // the imgRequest's proxy list, and we can get extra notifications
1676 : // resulting from methods such as StartDecoding(). See bug 579122.
1677 0 : proxy->SetNotificationsDeferred(true);
1678 :
1679 : // Attach the proxy without notifying
1680 0 : request->GetValidator()->AddProxy(proxy);
1681 : }
1682 :
1683 0 : return NS_SUCCEEDED(rv);
1684 :
1685 : }
1686 : // We will rely on Necko to cache this request when it's possible, and to
1687 : // tell imgCacheValidator::OnStartRequest whether the request came from its
1688 : // cache.
1689 0 : nsCOMPtr<nsIChannel> newChannel;
1690 : bool forcePrincipalCheck;
1691 0 : rv = NewImageChannel(getter_AddRefs(newChannel),
1692 : &forcePrincipalCheck,
1693 : aURI,
1694 : aInitialDocumentURI,
1695 : aCORSMode,
1696 : aReferrerURI,
1697 : aReferrerPolicy,
1698 : aLoadGroup,
1699 : mAcceptHeader,
1700 : aLoadFlags,
1701 : aLoadPolicyType,
1702 : aLoadingPrincipal,
1703 : aCX,
1704 0 : mRespectPrivacy);
1705 0 : if (NS_FAILED(rv)) {
1706 0 : return false;
1707 : }
1708 :
1709 0 : RefPtr<imgRequestProxy> req;
1710 0 : rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver,
1711 0 : aLoadFlags, getter_AddRefs(req));
1712 0 : if (NS_FAILED(rv)) {
1713 0 : return false;
1714 : }
1715 :
1716 : // Make sure that OnStatus/OnProgress calls have the right request set...
1717 : RefPtr<nsProgressNotificationProxy> progressproxy =
1718 0 : new nsProgressNotificationProxy(newChannel, req);
1719 0 : if (!progressproxy) {
1720 0 : return false;
1721 : }
1722 :
1723 : RefPtr<imgCacheValidator> hvc =
1724 : new imgCacheValidator(progressproxy, this, request, aCX,
1725 0 : forcePrincipalCheck);
1726 :
1727 : // Casting needed here to get past multiple inheritance.
1728 : nsCOMPtr<nsIStreamListener> listener =
1729 0 : do_QueryInterface(static_cast<nsIThreadRetargetableStreamListener*>(hvc));
1730 0 : NS_ENSURE_TRUE(listener, false);
1731 :
1732 : // We must set the notification callbacks before setting up the
1733 : // CORS listener, because that's also interested inthe
1734 : // notification callbacks.
1735 0 : newChannel->SetNotificationCallbacks(hvc);
1736 :
1737 0 : request->SetValidator(hvc);
1738 :
1739 : // We will send notifications from imgCacheValidator::OnStartRequest().
1740 : // In the mean time, we must defer notifications because we are added to
1741 : // the imgRequest's proxy list, and we can get extra notifications
1742 : // resulting from methods such as StartDecoding(). See bug 579122.
1743 0 : req->SetNotificationsDeferred(true);
1744 :
1745 : // Add the proxy without notifying
1746 0 : hvc->AddProxy(req);
1747 :
1748 : mozilla::net::PredictorLearn(aURI, aInitialDocumentURI,
1749 0 : nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, aLoadGroup);
1750 0 : rv = newChannel->AsyncOpen2(listener);
1751 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
1752 0 : req->CancelAndForgetObserver(rv);
1753 0 : return false;
1754 : }
1755 :
1756 0 : req.forget(aProxyRequest);
1757 0 : return true;
1758 : }
1759 :
1760 : bool
1761 4 : imgLoader::ValidateEntry(imgCacheEntry* aEntry,
1762 : nsIURI* aURI,
1763 : nsIURI* aInitialDocumentURI,
1764 : nsIURI* aReferrerURI,
1765 : ReferrerPolicy aReferrerPolicy,
1766 : nsILoadGroup* aLoadGroup,
1767 : imgINotificationObserver* aObserver,
1768 : nsISupports* aCX,
1769 : nsLoadFlags aLoadFlags,
1770 : nsContentPolicyType aLoadPolicyType,
1771 : bool aCanMakeNewChannel,
1772 : imgRequestProxy** aProxyRequest,
1773 : nsIPrincipal* aLoadingPrincipal,
1774 : int32_t aCORSMode)
1775 : {
1776 8 : LOG_SCOPE(gImgLog, "imgLoader::ValidateEntry");
1777 :
1778 : bool hasExpired;
1779 4 : uint32_t expirationTime = aEntry->GetExpiryTime();
1780 4 : if (expirationTime <= SecondsFromPRTime(PR_Now())) {
1781 4 : hasExpired = true;
1782 : } else {
1783 0 : hasExpired = false;
1784 : }
1785 :
1786 : nsresult rv;
1787 :
1788 : // Special treatment for file URLs - aEntry has expired if file has changed
1789 8 : nsCOMPtr<nsIFileURL> fileUrl(do_QueryInterface(aURI));
1790 4 : if (fileUrl) {
1791 0 : uint32_t lastModTime = aEntry->GetLoadTime();
1792 :
1793 0 : nsCOMPtr<nsIFile> theFile;
1794 0 : rv = fileUrl->GetFile(getter_AddRefs(theFile));
1795 0 : if (NS_SUCCEEDED(rv)) {
1796 : PRTime fileLastMod;
1797 0 : rv = theFile->GetLastModifiedTime(&fileLastMod);
1798 0 : if (NS_SUCCEEDED(rv)) {
1799 : // nsIFile uses millisec, NSPR usec
1800 0 : fileLastMod *= 1000;
1801 0 : hasExpired = SecondsFromPRTime((PRTime)fileLastMod) > lastModTime;
1802 : }
1803 : }
1804 : }
1805 :
1806 8 : RefPtr<imgRequest> request(aEntry->GetRequest());
1807 :
1808 4 : if (!request) {
1809 0 : return false;
1810 : }
1811 :
1812 4 : if (!ValidateSecurityInfo(request, aEntry->ForcePrincipalCheck(),
1813 : aCORSMode, aLoadingPrincipal,
1814 : aCX, aLoadPolicyType, aReferrerPolicy))
1815 0 : return false;
1816 :
1817 : // data URIs are immutable and by their nature can't leak data, so we can
1818 : // just return true in that case. Doing so would mean that shift-reload
1819 : // doesn't reload data URI documents/images though (which is handy for
1820 : // debugging during gecko development) so we make an exception in that case.
1821 8 : nsAutoCString scheme;
1822 4 : aURI->GetScheme(scheme);
1823 4 : if (scheme.EqualsLiteral("data") &&
1824 0 : !(aLoadFlags & nsIRequest::LOAD_BYPASS_CACHE)) {
1825 0 : return true;
1826 : }
1827 :
1828 4 : bool validateRequest = false;
1829 :
1830 : // If the request's loadId is the same as the aCX, then it is ok to use
1831 : // this one because it has already been validated for this context.
1832 : //
1833 : // XXX: nullptr seems to be a 'special' key value that indicates that NO
1834 : // validation is required.
1835 : //
1836 4 : void *key = (void*) aCX;
1837 4 : if (request->LoadId() != key) {
1838 : // If we would need to revalidate this entry, but we're being told to
1839 : // bypass the cache, we don't allow this entry to be used.
1840 0 : if (aLoadFlags & nsIRequest::LOAD_BYPASS_CACHE) {
1841 0 : return false;
1842 : }
1843 :
1844 0 : if (MOZ_UNLIKELY(ChaosMode::isActive(ChaosFeature::ImageCache))) {
1845 0 : if (ChaosMode::randomUint32LessThan(4) < 1) {
1846 0 : return false;
1847 : }
1848 : }
1849 :
1850 : // Determine whether the cache aEntry must be revalidated...
1851 0 : validateRequest = ShouldRevalidateEntry(aEntry, aLoadFlags, hasExpired);
1852 :
1853 0 : MOZ_LOG(gImgLog, LogLevel::Debug,
1854 : ("imgLoader::ValidateEntry validating cache entry. "
1855 : "validateRequest = %d", validateRequest));
1856 4 : } else if (!key && MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
1857 0 : MOZ_LOG(gImgLog, LogLevel::Debug,
1858 : ("imgLoader::ValidateEntry BYPASSING cache validation for %s "
1859 : "because of NULL LoadID", aURI->GetSpecOrDefault().get()));
1860 : }
1861 :
1862 : // We can't use a cached request if it comes from a different
1863 : // application cache than this load is expecting.
1864 8 : nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer;
1865 8 : nsCOMPtr<nsIApplicationCache> requestAppCache;
1866 8 : nsCOMPtr<nsIApplicationCache> groupAppCache;
1867 4 : if ((appCacheContainer = do_GetInterface(request->GetRequest()))) {
1868 0 : appCacheContainer->GetApplicationCache(getter_AddRefs(requestAppCache));
1869 : }
1870 4 : if ((appCacheContainer = do_QueryInterface(aLoadGroup))) {
1871 0 : appCacheContainer->GetApplicationCache(getter_AddRefs(groupAppCache));
1872 : }
1873 :
1874 4 : if (requestAppCache != groupAppCache) {
1875 0 : MOZ_LOG(gImgLog, LogLevel::Debug,
1876 : ("imgLoader::ValidateEntry - Unable to use cached imgRequest "
1877 : "[request=%p] because of mismatched application caches\n",
1878 : address_of(request)));
1879 0 : return false;
1880 : }
1881 :
1882 4 : if (validateRequest && aCanMakeNewChannel) {
1883 0 : LOG_SCOPE(gImgLog,
1884 : "imgLoader::ValidateRequest |cache hit| must validate");
1885 :
1886 0 : return ValidateRequestWithNewChannel(request, aURI, aInitialDocumentURI,
1887 : aReferrerURI, aReferrerPolicy,
1888 : aLoadGroup, aObserver,
1889 : aCX, aLoadFlags, aLoadPolicyType,
1890 : aProxyRequest, aLoadingPrincipal,
1891 0 : aCORSMode);
1892 : }
1893 :
1894 4 : return !validateRequest;
1895 : }
1896 :
1897 : bool
1898 0 : imgLoader::RemoveFromCache(const ImageCacheKey& aKey)
1899 : {
1900 : LOG_STATIC_FUNC_WITH_PARAM(gImgLog,
1901 0 : "imgLoader::RemoveFromCache", "uri", aKey.Spec());
1902 :
1903 0 : imgCacheTable& cache = GetCache(aKey);
1904 0 : imgCacheQueue& queue = GetCacheQueue(aKey);
1905 :
1906 0 : RefPtr<imgCacheEntry> entry;
1907 0 : cache.Remove(aKey, getter_AddRefs(entry));
1908 0 : if (entry) {
1909 0 : MOZ_ASSERT(!entry->Evicted(), "Evicting an already-evicted cache entry!");
1910 :
1911 : // Entries with no proxies are in the tracker.
1912 0 : if (entry->HasNoProxies()) {
1913 0 : if (mCacheTracker) {
1914 0 : mCacheTracker->RemoveObject(entry);
1915 : }
1916 0 : queue.Remove(entry);
1917 : }
1918 :
1919 0 : entry->SetEvicted(true);
1920 :
1921 0 : RefPtr<imgRequest> request = entry->GetRequest();
1922 0 : request->SetIsInCache(false);
1923 0 : AddToUncachedImages(request);
1924 :
1925 0 : return true;
1926 : }
1927 0 : return false;
1928 : }
1929 :
1930 : bool
1931 1 : imgLoader::RemoveFromCache(imgCacheEntry* entry)
1932 : {
1933 1 : LOG_STATIC_FUNC(gImgLog, "imgLoader::RemoveFromCache entry");
1934 :
1935 2 : RefPtr<imgRequest> request = entry->GetRequest();
1936 1 : if (request) {
1937 1 : const ImageCacheKey& key = request->CacheKey();
1938 1 : imgCacheTable& cache = GetCache(key);
1939 1 : imgCacheQueue& queue = GetCacheQueue(key);
1940 :
1941 : LOG_STATIC_FUNC_WITH_PARAM(gImgLog,
1942 : "imgLoader::RemoveFromCache", "entry's uri",
1943 1 : key.Spec());
1944 :
1945 1 : cache.Remove(key);
1946 :
1947 1 : if (entry->HasNoProxies()) {
1948 : LOG_STATIC_FUNC(gImgLog,
1949 0 : "imgLoader::RemoveFromCache removing from tracker");
1950 0 : if (mCacheTracker) {
1951 0 : mCacheTracker->RemoveObject(entry);
1952 : }
1953 0 : queue.Remove(entry);
1954 : }
1955 :
1956 1 : entry->SetEvicted(true);
1957 1 : request->SetIsInCache(false);
1958 1 : AddToUncachedImages(request);
1959 :
1960 1 : return true;
1961 : }
1962 :
1963 0 : return false;
1964 : }
1965 :
1966 : nsresult
1967 0 : imgLoader::EvictEntries(imgCacheTable& aCacheToClear)
1968 : {
1969 0 : LOG_STATIC_FUNC(gImgLog, "imgLoader::EvictEntries table");
1970 :
1971 : // We have to make a temporary, since RemoveFromCache removes the element
1972 : // from the queue, invalidating iterators.
1973 0 : nsTArray<RefPtr<imgCacheEntry> > entries;
1974 0 : for (auto iter = aCacheToClear.Iter(); !iter.Done(); iter.Next()) {
1975 0 : RefPtr<imgCacheEntry>& data = iter.Data();
1976 0 : entries.AppendElement(data);
1977 : }
1978 :
1979 0 : for (uint32_t i = 0; i < entries.Length(); ++i) {
1980 0 : if (!RemoveFromCache(entries[i])) {
1981 0 : return NS_ERROR_FAILURE;
1982 : }
1983 : }
1984 :
1985 0 : MOZ_ASSERT(aCacheToClear.Count() == 0);
1986 :
1987 0 : return NS_OK;
1988 : }
1989 :
1990 : nsresult
1991 0 : imgLoader::EvictEntries(imgCacheQueue& aQueueToClear)
1992 : {
1993 0 : LOG_STATIC_FUNC(gImgLog, "imgLoader::EvictEntries queue");
1994 :
1995 : // We have to make a temporary, since RemoveFromCache removes the element
1996 : // from the queue, invalidating iterators.
1997 0 : nsTArray<RefPtr<imgCacheEntry> > entries(aQueueToClear.GetNumElements());
1998 0 : for (imgCacheQueue::const_iterator i = aQueueToClear.begin();
1999 0 : i != aQueueToClear.end(); ++i) {
2000 0 : entries.AppendElement(*i);
2001 : }
2002 :
2003 0 : for (uint32_t i = 0; i < entries.Length(); ++i) {
2004 0 : if (!RemoveFromCache(entries[i])) {
2005 0 : return NS_ERROR_FAILURE;
2006 : }
2007 : }
2008 :
2009 0 : MOZ_ASSERT(aQueueToClear.GetNumElements() == 0);
2010 :
2011 0 : return NS_OK;
2012 : }
2013 :
2014 : void
2015 42 : imgLoader::AddToUncachedImages(imgRequest* aRequest)
2016 : {
2017 84 : MutexAutoLock lock(mUncachedImagesMutex);
2018 42 : mUncachedImages.PutEntry(aRequest);
2019 42 : }
2020 :
2021 : void
2022 42 : imgLoader::RemoveFromUncachedImages(imgRequest* aRequest)
2023 : {
2024 84 : MutexAutoLock lock(mUncachedImagesMutex);
2025 42 : mUncachedImages.RemoveEntry(aRequest);
2026 42 : }
2027 :
2028 :
2029 : #define LOAD_FLAGS_CACHE_MASK (nsIRequest::LOAD_BYPASS_CACHE | \
2030 : nsIRequest::LOAD_FROM_CACHE)
2031 :
2032 : #define LOAD_FLAGS_VALIDATE_MASK (nsIRequest::VALIDATE_ALWAYS | \
2033 : nsIRequest::VALIDATE_NEVER | \
2034 : nsIRequest::VALIDATE_ONCE_PER_SESSION)
2035 :
2036 : NS_IMETHODIMP
2037 0 : imgLoader::LoadImageXPCOM(nsIURI* aURI,
2038 : nsIURI* aInitialDocumentURI,
2039 : nsIURI* aReferrerURI,
2040 : const nsAString& aReferrerPolicy,
2041 : nsIPrincipal* aLoadingPrincipal,
2042 : nsILoadGroup* aLoadGroup,
2043 : imgINotificationObserver* aObserver,
2044 : nsISupports* aCX,
2045 : nsLoadFlags aLoadFlags,
2046 : nsISupports* aCacheKey,
2047 : nsContentPolicyType aContentPolicyType,
2048 : imgIRequest** _retval)
2049 : {
2050 : // Optional parameter, so defaults to 0 (== TYPE_INVALID)
2051 0 : if (!aContentPolicyType) {
2052 0 : aContentPolicyType = nsIContentPolicy::TYPE_INTERNAL_IMAGE;
2053 : }
2054 : imgRequestProxy* proxy;
2055 0 : ReferrerPolicy refpol = ReferrerPolicyFromString(aReferrerPolicy);
2056 0 : nsCOMPtr<nsINode> node = do_QueryInterface(aCX);
2057 0 : nsCOMPtr<nsIDocument> doc = do_QueryInterface(aCX);
2058 0 : nsresult rv = LoadImage(aURI,
2059 : aInitialDocumentURI,
2060 : aReferrerURI,
2061 : refpol,
2062 : aLoadingPrincipal,
2063 : aLoadGroup,
2064 : aObserver,
2065 : node,
2066 : doc,
2067 : aLoadFlags,
2068 : aCacheKey,
2069 : aContentPolicyType,
2070 0 : EmptyString(),
2071 : /* aUseUrgentStartForChannel */ false,
2072 0 : &proxy);
2073 0 : *_retval = proxy;
2074 0 : return rv;
2075 : }
2076 :
2077 : nsresult
2078 45 : imgLoader::LoadImage(nsIURI* aURI,
2079 : nsIURI* aInitialDocumentURI,
2080 : nsIURI* aReferrerURI,
2081 : ReferrerPolicy aReferrerPolicy,
2082 : nsIPrincipal* aLoadingPrincipal,
2083 : nsILoadGroup* aLoadGroup,
2084 : imgINotificationObserver* aObserver,
2085 : nsINode *aContext,
2086 : nsIDocument* aLoadingDocument,
2087 : nsLoadFlags aLoadFlags,
2088 : nsISupports* aCacheKey,
2089 : nsContentPolicyType aContentPolicyType,
2090 : const nsAString& initiatorType,
2091 : bool aUseUrgentStartForChannel,
2092 : imgRequestProxy** _retval)
2093 : {
2094 45 : VerifyCacheSizes();
2095 :
2096 45 : NS_ASSERTION(aURI, "imgLoader::LoadImage -- NULL URI pointer");
2097 :
2098 45 : if (!aURI) {
2099 0 : return NS_ERROR_NULL_POINTER;
2100 : }
2101 :
2102 90 : LOG_SCOPE_WITH_PARAM(gImgLog, "imgLoader::LoadImage", "aURI",
2103 : aURI->GetSpecOrDefault().get());
2104 :
2105 45 : *_retval = nullptr;
2106 :
2107 90 : RefPtr<imgRequest> request;
2108 :
2109 : nsresult rv;
2110 45 : nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL;
2111 :
2112 : #ifdef DEBUG
2113 45 : bool isPrivate = false;
2114 :
2115 45 : if (aLoadGroup) {
2116 90 : nsCOMPtr<nsIInterfaceRequestor> callbacks;
2117 45 : aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
2118 45 : if (callbacks) {
2119 90 : nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
2120 45 : isPrivate = loadContext && loadContext->UsePrivateBrowsing();
2121 : }
2122 : }
2123 45 : MOZ_ASSERT(isPrivate == mRespectPrivacy);
2124 : #endif
2125 :
2126 : // Get the default load flags from the loadgroup (if possible)...
2127 45 : if (aLoadGroup) {
2128 45 : aLoadGroup->GetLoadFlags(&requestFlags);
2129 : }
2130 : //
2131 : // Merge the default load flags with those passed in via aLoadFlags.
2132 : // Currently, *only* the caching, validation and background load flags
2133 : // are merged...
2134 : //
2135 : // The flags in aLoadFlags take precedence over the default flags!
2136 : //
2137 45 : if (aLoadFlags & LOAD_FLAGS_CACHE_MASK) {
2138 : // Override the default caching flags...
2139 2 : requestFlags = (requestFlags & ~LOAD_FLAGS_CACHE_MASK) |
2140 1 : (aLoadFlags & LOAD_FLAGS_CACHE_MASK);
2141 : }
2142 45 : if (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK) {
2143 : // Override the default validation flags...
2144 2 : requestFlags = (requestFlags & ~LOAD_FLAGS_VALIDATE_MASK) |
2145 1 : (aLoadFlags & LOAD_FLAGS_VALIDATE_MASK);
2146 : }
2147 45 : if (aLoadFlags & nsIRequest::LOAD_BACKGROUND) {
2148 : // Propagate background loading...
2149 0 : requestFlags |= nsIRequest::LOAD_BACKGROUND;
2150 : }
2151 :
2152 45 : int32_t corsmode = imgIRequest::CORS_NONE;
2153 45 : if (aLoadFlags & imgILoader::LOAD_CORS_ANONYMOUS) {
2154 0 : corsmode = imgIRequest::CORS_ANONYMOUS;
2155 45 : } else if (aLoadFlags & imgILoader::LOAD_CORS_USE_CREDENTIALS) {
2156 0 : corsmode = imgIRequest::CORS_USE_CREDENTIALS;
2157 : }
2158 :
2159 90 : RefPtr<imgCacheEntry> entry;
2160 :
2161 : // Look in the cache for our URI, and then validate it.
2162 : // XXX For now ignore aCacheKey. We will need it in the future
2163 : // for correctly dealing with image load requests that are a result
2164 : // of post data.
2165 90 : OriginAttributes attrs;
2166 45 : if (aLoadingPrincipal) {
2167 45 : attrs = aLoadingPrincipal->OriginAttributesRef();
2168 : }
2169 90 : ImageCacheKey key(aURI, attrs, aLoadingDocument, rv);
2170 45 : NS_ENSURE_SUCCESS(rv, rv);
2171 45 : imgCacheTable& cache = GetCache(key);
2172 :
2173 45 : if (cache.Get(key, getter_AddRefs(entry)) && entry) {
2174 4 : if (ValidateEntry(entry, aURI, aInitialDocumentURI, aReferrerURI,
2175 : aReferrerPolicy, aLoadGroup, aObserver, aLoadingDocument,
2176 : requestFlags, aContentPolicyType, true, _retval,
2177 : aLoadingPrincipal, corsmode)) {
2178 4 : request = entry->GetRequest();
2179 :
2180 : // If this entry has no proxies, its request has no reference to the
2181 : // entry.
2182 4 : if (entry->HasNoProxies()) {
2183 : LOG_FUNC_WITH_PARAM(gImgLog,
2184 0 : "imgLoader::LoadImage() adding proxyless entry", "uri", key.Spec());
2185 0 : MOZ_ASSERT(!request->HasCacheEntry(),
2186 : "Proxyless entry's request has cache entry!");
2187 0 : request->SetCacheEntry(entry);
2188 :
2189 0 : if (mCacheTracker && entry->GetExpirationState()->IsTracked()) {
2190 0 : mCacheTracker->MarkUsed(entry);
2191 : }
2192 : }
2193 :
2194 4 : entry->Touch();
2195 :
2196 : } else {
2197 : // We can't use this entry. We'll try to load it off the network, and if
2198 : // successful, overwrite the old entry in the cache with a new one.
2199 0 : entry = nullptr;
2200 : }
2201 : }
2202 :
2203 : // Keep the channel in this scope, so we can adjust its notificationCallbacks
2204 : // later when we create the proxy.
2205 90 : nsCOMPtr<nsIChannel> newChannel;
2206 : // If we didn't get a cache hit, we need to load from the network.
2207 45 : if (!request) {
2208 82 : LOG_SCOPE(gImgLog, "imgLoader::LoadImage |cache miss|");
2209 :
2210 : bool forcePrincipalCheck;
2211 41 : rv = NewImageChannel(getter_AddRefs(newChannel),
2212 : &forcePrincipalCheck,
2213 : aURI,
2214 : aInitialDocumentURI,
2215 : corsmode,
2216 : aReferrerURI,
2217 : aReferrerPolicy,
2218 : aLoadGroup,
2219 : mAcceptHeader,
2220 : requestFlags,
2221 : aContentPolicyType,
2222 : aLoadingPrincipal,
2223 : aContext,
2224 41 : mRespectPrivacy);
2225 41 : if (NS_FAILED(rv)) {
2226 0 : return NS_ERROR_FAILURE;
2227 : }
2228 :
2229 41 : MOZ_ASSERT(NS_UsePrivateBrowsing(newChannel) == mRespectPrivacy);
2230 :
2231 82 : NewRequestAndEntry(forcePrincipalCheck, this, key,
2232 82 : getter_AddRefs(request),
2233 123 : getter_AddRefs(entry));
2234 :
2235 41 : MOZ_LOG(gImgLog, LogLevel::Debug,
2236 : ("[this=%p] imgLoader::LoadImage -- Created new imgRequest"
2237 : " [request=%p]\n", this, request.get()));
2238 :
2239 82 : nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(newChannel));
2240 41 : if (cos && aUseUrgentStartForChannel) {
2241 0 : cos->AddClassFlags(nsIClassOfService::UrgentStart);
2242 : }
2243 :
2244 82 : nsCOMPtr<nsILoadGroup> channelLoadGroup;
2245 41 : newChannel->GetLoadGroup(getter_AddRefs(channelLoadGroup));
2246 82 : rv = request->Init(aURI, aURI, /* aHadInsecureRedirect = */ false,
2247 : channelLoadGroup, newChannel, entry, aLoadingDocument,
2248 41 : aLoadingPrincipal, corsmode, aReferrerPolicy);
2249 41 : if (NS_FAILED(rv)) {
2250 0 : return NS_ERROR_FAILURE;
2251 : }
2252 :
2253 : // Add the initiator type for this image load
2254 82 : nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(newChannel);
2255 41 : if (timedChannel) {
2256 1 : timedChannel->SetInitiatorType(initiatorType);
2257 : }
2258 :
2259 : // create the proxy listener
2260 123 : nsCOMPtr<nsIStreamListener> listener = new ProxyListener(request.get());
2261 :
2262 41 : MOZ_LOG(gImgLog, LogLevel::Debug,
2263 : ("[this=%p] imgLoader::LoadImage -- Calling channel->AsyncOpen2()\n",
2264 : this));
2265 :
2266 : mozilla::net::PredictorLearn(aURI, aInitialDocumentURI,
2267 41 : nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE, aLoadGroup);
2268 :
2269 41 : nsresult openRes = newChannel->AsyncOpen2(listener);
2270 :
2271 41 : if (NS_FAILED(openRes)) {
2272 0 : MOZ_LOG(gImgLog, LogLevel::Debug,
2273 : ("[this=%p] imgLoader::LoadImage -- AsyncOpen2() failed: 0x%" PRIx32 "\n",
2274 : this, static_cast<uint32_t>(openRes)));
2275 0 : request->CancelAndAbort(openRes);
2276 0 : return openRes;
2277 : }
2278 :
2279 : // Try to add the new request into the cache.
2280 41 : PutIntoCache(key, entry);
2281 : } else {
2282 4 : LOG_MSG_WITH_PARAM(gImgLog,
2283 4 : "imgLoader::LoadImage |cache hit|", "request", request);
2284 : }
2285 :
2286 :
2287 : // If we didn't get a proxy when validating the cache entry, we need to
2288 : // create one.
2289 45 : if (!*_retval) {
2290 : // ValidateEntry() has three return values: "Is valid," "might be valid --
2291 : // validating over network", and "not valid." If we don't have a _retval,
2292 : // we know ValidateEntry is not validating over the network, so it's safe
2293 : // to SetLoadId here because we know this request is valid for this context.
2294 : //
2295 : // Note, however, that this doesn't guarantee the behaviour we want (one
2296 : // URL maps to the same image on a page) if we load the same image in a
2297 : // different tab (see bug 528003), because its load id will get re-set, and
2298 : // that'll cause us to validate over the network.
2299 45 : request->SetLoadId(aLoadingDocument);
2300 :
2301 45 : LOG_MSG(gImgLog, "imgLoader::LoadImage", "creating proxy request.");
2302 45 : rv = CreateNewProxyForRequest(request, aLoadGroup, aObserver,
2303 : requestFlags, _retval);
2304 45 : if (NS_FAILED(rv)) {
2305 0 : return rv;
2306 : }
2307 :
2308 45 : imgRequestProxy* proxy = *_retval;
2309 :
2310 : // Make sure that OnStatus/OnProgress calls have the right request set, if
2311 : // we did create a channel here.
2312 45 : if (newChannel) {
2313 : nsCOMPtr<nsIInterfaceRequestor> requestor(
2314 123 : new nsProgressNotificationProxy(newChannel, proxy));
2315 41 : if (!requestor) {
2316 0 : return NS_ERROR_OUT_OF_MEMORY;
2317 : }
2318 41 : newChannel->SetNotificationCallbacks(requestor);
2319 : }
2320 :
2321 : // Note that it's OK to add here even if the request is done. If it is,
2322 : // it'll send a OnStopRequest() to the proxy in imgRequestProxy::Notify and
2323 : // the proxy will be removed from the loadgroup.
2324 45 : proxy->AddToLoadGroup();
2325 :
2326 : // If we're loading off the network, explicitly don't notify our proxy,
2327 : // because necko (or things called from necko, such as imgCacheValidator)
2328 : // are going to call our notifications asynchronously, and we can't make it
2329 : // further asynchronous because observers might rely on imagelib completing
2330 : // its work between the channel's OnStartRequest and OnStopRequest.
2331 45 : if (!newChannel) {
2332 4 : proxy->NotifyListener();
2333 : }
2334 :
2335 45 : return rv;
2336 : }
2337 :
2338 0 : NS_ASSERTION(*_retval, "imgLoader::LoadImage -- no return value");
2339 :
2340 0 : return NS_OK;
2341 : }
2342 :
2343 : NS_IMETHODIMP
2344 0 : imgLoader::LoadImageWithChannelXPCOM(nsIChannel* channel,
2345 : imgINotificationObserver* aObserver,
2346 : nsISupports* aCX,
2347 : nsIStreamListener** listener,
2348 : imgIRequest** _retval)
2349 : {
2350 : nsresult result;
2351 : imgRequestProxy* proxy;
2352 : result = LoadImageWithChannel(channel,
2353 : aObserver,
2354 : aCX,
2355 : listener,
2356 0 : &proxy);
2357 0 : *_retval = proxy;
2358 0 : return result;
2359 : }
2360 :
2361 : nsresult
2362 0 : imgLoader::LoadImageWithChannel(nsIChannel* channel,
2363 : imgINotificationObserver* aObserver,
2364 : nsISupports* aCX,
2365 : nsIStreamListener** listener,
2366 : imgRequestProxy** _retval)
2367 : {
2368 0 : NS_ASSERTION(channel,
2369 : "imgLoader::LoadImageWithChannel -- NULL channel pointer");
2370 :
2371 0 : MOZ_ASSERT(NS_UsePrivateBrowsing(channel) == mRespectPrivacy);
2372 :
2373 0 : RefPtr<imgRequest> request;
2374 :
2375 0 : nsCOMPtr<nsIURI> uri;
2376 0 : channel->GetURI(getter_AddRefs(uri));
2377 0 : nsCOMPtr<nsIDocument> doc = do_QueryInterface(aCX);
2378 :
2379 0 : NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE);
2380 0 : nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
2381 :
2382 0 : OriginAttributes attrs;
2383 0 : if (loadInfo) {
2384 0 : attrs = loadInfo->GetOriginAttributes();
2385 : }
2386 :
2387 : nsresult rv;
2388 0 : ImageCacheKey key(uri, attrs, doc, rv);
2389 0 : NS_ENSURE_SUCCESS(rv, rv);
2390 :
2391 0 : nsLoadFlags requestFlags = nsIRequest::LOAD_NORMAL;
2392 0 : channel->GetLoadFlags(&requestFlags);
2393 :
2394 0 : RefPtr<imgCacheEntry> entry;
2395 :
2396 0 : if (requestFlags & nsIRequest::LOAD_BYPASS_CACHE) {
2397 0 : RemoveFromCache(key);
2398 : } else {
2399 : // Look in the cache for our URI, and then validate it.
2400 : // XXX For now ignore aCacheKey. We will need it in the future
2401 : // for correctly dealing with image load requests that are a result
2402 : // of post data.
2403 0 : imgCacheTable& cache = GetCache(key);
2404 0 : if (cache.Get(key, getter_AddRefs(entry)) && entry) {
2405 : // We don't want to kick off another network load. So we ask
2406 : // ValidateEntry to only do validation without creating a new proxy. If
2407 : // it says that the entry isn't valid any more, we'll only use the entry
2408 : // we're getting if the channel is loading from the cache anyways.
2409 : //
2410 : // XXX -- should this be changed? it's pretty much verbatim from the old
2411 : // code, but seems nonsensical.
2412 : //
2413 : // Since aCanMakeNewChannel == false, we don't need to pass content policy
2414 : // type/principal/etc
2415 :
2416 0 : nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
2417 : // if there is a loadInfo, use the right contentType, otherwise
2418 : // default to the internal image type
2419 : nsContentPolicyType policyType = loadInfo
2420 0 : ? loadInfo->InternalContentPolicyType()
2421 0 : : nsIContentPolicy::TYPE_INTERNAL_IMAGE;
2422 :
2423 0 : if (ValidateEntry(entry, uri, nullptr, nullptr, RP_Unset,
2424 : nullptr, aObserver, aCX, requestFlags,
2425 : policyType, false, nullptr,
2426 : nullptr, imgIRequest::CORS_NONE)) {
2427 0 : request = entry->GetRequest();
2428 : } else {
2429 0 : nsCOMPtr<nsICacheInfoChannel> cacheChan(do_QueryInterface(channel));
2430 : bool bUseCacheCopy;
2431 :
2432 0 : if (cacheChan) {
2433 0 : cacheChan->IsFromCache(&bUseCacheCopy);
2434 : } else {
2435 0 : bUseCacheCopy = false;
2436 : }
2437 :
2438 0 : if (!bUseCacheCopy) {
2439 0 : entry = nullptr;
2440 : } else {
2441 0 : request = entry->GetRequest();
2442 : }
2443 : }
2444 :
2445 0 : if (request && entry) {
2446 : // If this entry has no proxies, its request has no reference to
2447 : // the entry.
2448 0 : if (entry->HasNoProxies()) {
2449 : LOG_FUNC_WITH_PARAM(gImgLog,
2450 : "imgLoader::LoadImageWithChannel() adding proxyless entry",
2451 0 : "uri", key.Spec());
2452 0 : MOZ_ASSERT(!request->HasCacheEntry(),
2453 : "Proxyless entry's request has cache entry!");
2454 0 : request->SetCacheEntry(entry);
2455 :
2456 0 : if (mCacheTracker && entry->GetExpirationState()->IsTracked()) {
2457 0 : mCacheTracker->MarkUsed(entry);
2458 : }
2459 : }
2460 : }
2461 : }
2462 : }
2463 :
2464 0 : nsCOMPtr<nsILoadGroup> loadGroup;
2465 0 : channel->GetLoadGroup(getter_AddRefs(loadGroup));
2466 :
2467 : // Filter out any load flags not from nsIRequest
2468 0 : requestFlags &= nsIRequest::LOAD_REQUESTMASK;
2469 :
2470 0 : rv = NS_OK;
2471 0 : if (request) {
2472 : // we have this in our cache already.. cancel the current (document) load
2473 :
2474 : // this should fire an OnStopRequest
2475 0 : channel->Cancel(NS_ERROR_PARSED_DATA_CACHED);
2476 :
2477 0 : *listener = nullptr; // give them back a null nsIStreamListener
2478 :
2479 0 : rv = CreateNewProxyForRequest(request, loadGroup, aObserver,
2480 : requestFlags, _retval);
2481 0 : static_cast<imgRequestProxy*>(*_retval)->NotifyListener();
2482 : } else {
2483 : // We use originalURI here to fulfil the imgIRequest contract on GetURI.
2484 0 : nsCOMPtr<nsIURI> originalURI;
2485 0 : channel->GetOriginalURI(getter_AddRefs(originalURI));
2486 :
2487 : // XXX(seth): We should be able to just use |key| here, except that |key| is
2488 : // constructed above with the *current URI* and not the *original URI*. I'm
2489 : // pretty sure this is a bug, and it's preventing us from ever getting a
2490 : // cache hit in LoadImageWithChannel when redirects are involved.
2491 0 : ImageCacheKey originalURIKey(originalURI, attrs, doc, rv);
2492 0 : NS_ENSURE_SUCCESS(rv, rv);
2493 :
2494 : // Default to doing a principal check because we don't know who
2495 : // started that load and whether their principal ended up being
2496 : // inherited on the channel.
2497 0 : NewRequestAndEntry(/* aForcePrincipalCheckForCacheEntry = */ true,
2498 : this, originalURIKey,
2499 0 : getter_AddRefs(request),
2500 0 : getter_AddRefs(entry));
2501 :
2502 : // No principal specified here, because we're not passed one.
2503 : // In LoadImageWithChannel, the redirects that may have been
2504 : // assoicated with this load would have gone through necko.
2505 : // We only have the final URI in ImageLib and hence don't know
2506 : // if the request went through insecure redirects. But if it did,
2507 : // the necko cache should have handled that (since all necko cache hits
2508 : // including the redirects will go through content policy). Hence, we
2509 : // can set aHadInsecureRedirect to false here.
2510 0 : rv = request->Init(originalURI, uri, /* aHadInsecureRedirect = */ false,
2511 : channel, channel, entry, aCX, nullptr,
2512 : imgIRequest::CORS_NONE, RP_Unset);
2513 0 : NS_ENSURE_SUCCESS(rv, rv);
2514 :
2515 : RefPtr<ProxyListener> pl =
2516 0 : new ProxyListener(static_cast<nsIStreamListener*>(request.get()));
2517 0 : pl.forget(listener);
2518 :
2519 : // Try to add the new request into the cache.
2520 0 : PutIntoCache(originalURIKey, entry);
2521 :
2522 0 : rv = CreateNewProxyForRequest(request, loadGroup, aObserver,
2523 : requestFlags, _retval);
2524 :
2525 : // Explicitly don't notify our proxy, because we're loading off the
2526 : // network, and necko (or things called from necko, such as
2527 : // imgCacheValidator) are going to call our notifications asynchronously,
2528 : // and we can't make it further asynchronous because observers might rely
2529 : // on imagelib completing its work between the channel's OnStartRequest and
2530 : // OnStopRequest.
2531 : }
2532 :
2533 0 : return rv;
2534 : }
2535 :
2536 : bool
2537 4 : imgLoader::SupportImageWithMimeType(const char* aMimeType,
2538 : AcceptedMimeTypes aAccept
2539 : /* = AcceptedMimeTypes::IMAGES */)
2540 : {
2541 8 : nsAutoCString mimeType(aMimeType);
2542 4 : ToLowerCase(mimeType);
2543 :
2544 4 : if (aAccept == AcceptedMimeTypes::IMAGES_AND_DOCUMENTS &&
2545 0 : mimeType.EqualsLiteral("image/svg+xml")) {
2546 0 : return true;
2547 : }
2548 :
2549 4 : DecoderType type = DecoderFactory::GetDecoderType(mimeType.get());
2550 4 : return type != DecoderType::UNKNOWN;
2551 : }
2552 :
2553 : NS_IMETHODIMP
2554 1 : imgLoader::GetMIMETypeFromContent(nsIRequest* aRequest,
2555 : const uint8_t* aContents,
2556 : uint32_t aLength,
2557 : nsACString& aContentType)
2558 : {
2559 1 : return GetMimeTypeFromContent((const char*)aContents, aLength, aContentType);
2560 : }
2561 :
2562 : /* static */
2563 : nsresult
2564 42 : imgLoader::GetMimeTypeFromContent(const char* aContents,
2565 : uint32_t aLength,
2566 : nsACString& aContentType)
2567 : {
2568 : /* Is it a GIF? */
2569 84 : if (aLength >= 6 && (!nsCRT::strncmp(aContents, "GIF87a", 6) ||
2570 42 : !nsCRT::strncmp(aContents, "GIF89a", 6))) {
2571 1 : aContentType.AssignLiteral(IMAGE_GIF);
2572 :
2573 : /* or a PNG? */
2574 59 : } else if (aLength >= 8 && ((unsigned char)aContents[0]==0x89 &&
2575 36 : (unsigned char)aContents[1]==0x50 &&
2576 36 : (unsigned char)aContents[2]==0x4E &&
2577 36 : (unsigned char)aContents[3]==0x47 &&
2578 36 : (unsigned char)aContents[4]==0x0D &&
2579 36 : (unsigned char)aContents[5]==0x0A &&
2580 36 : (unsigned char)aContents[6]==0x1A &&
2581 18 : (unsigned char)aContents[7]==0x0A)) {
2582 18 : aContentType.AssignLiteral(IMAGE_PNG);
2583 :
2584 : /* maybe a JPEG (JFIF)? */
2585 : /* JFIF files start with SOI APP0 but older files can start with SOI DQT
2586 : * so we test for SOI followed by any marker, i.e. FF D8 FF
2587 : * this will also work for SPIFF JPEG files if they appear in the future.
2588 : *
2589 : * (JFIF is 0XFF 0XD8 0XFF 0XE0 <skip 2> 0X4A 0X46 0X49 0X46 0X00)
2590 : */
2591 46 : } else if (aLength >= 3 &&
2592 23 : ((unsigned char)aContents[0])==0xFF &&
2593 0 : ((unsigned char)aContents[1])==0xD8 &&
2594 0 : ((unsigned char)aContents[2])==0xFF) {
2595 0 : aContentType.AssignLiteral(IMAGE_JPEG);
2596 :
2597 : /* or how about ART? */
2598 : /* ART begins with JG (4A 47). Major version offset 2.
2599 : * Minor version offset 3. Offset 4 must be nullptr.
2600 : */
2601 46 : } else if (aLength >= 5 &&
2602 23 : ((unsigned char) aContents[0])==0x4a &&
2603 0 : ((unsigned char) aContents[1])==0x47 &&
2604 0 : ((unsigned char) aContents[4])==0x00 ) {
2605 0 : aContentType.AssignLiteral(IMAGE_ART);
2606 :
2607 23 : } else if (aLength >= 2 && !nsCRT::strncmp(aContents, "BM", 2)) {
2608 0 : aContentType.AssignLiteral(IMAGE_BMP);
2609 :
2610 : // ICOs always begin with a 2-byte 0 followed by a 2-byte 1.
2611 : // CURs begin with 2-byte 0 followed by 2-byte 2.
2612 46 : } else if (aLength >= 4 && (!memcmp(aContents, "\000\000\001\000", 4) ||
2613 23 : !memcmp(aContents, "\000\000\002\000", 4))) {
2614 0 : aContentType.AssignLiteral(IMAGE_ICO);
2615 :
2616 : } else {
2617 : /* none of the above? I give up */
2618 23 : return NS_ERROR_NOT_AVAILABLE;
2619 : }
2620 :
2621 19 : return NS_OK;
2622 : }
2623 :
2624 : /**
2625 : * proxy stream listener class used to handle multipart/x-mixed-replace
2626 : */
2627 :
2628 : #include "nsIRequest.h"
2629 : #include "nsIStreamConverterService.h"
2630 :
2631 620 : NS_IMPL_ISUPPORTS(ProxyListener,
2632 : nsIStreamListener,
2633 : nsIThreadRetargetableStreamListener,
2634 : nsIRequestObserver)
2635 :
2636 41 : ProxyListener::ProxyListener(nsIStreamListener* dest) :
2637 41 : mDestListener(dest)
2638 : {
2639 : /* member initializers and constructor code */
2640 41 : }
2641 :
2642 82 : ProxyListener::~ProxyListener()
2643 : {
2644 : /* destructor code */
2645 123 : }
2646 :
2647 :
2648 : /** nsIRequestObserver methods **/
2649 :
2650 : NS_IMETHODIMP
2651 41 : ProxyListener::OnStartRequest(nsIRequest* aRequest, nsISupports* ctxt)
2652 : {
2653 41 : if (!mDestListener) {
2654 0 : return NS_ERROR_FAILURE;
2655 : }
2656 :
2657 82 : nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
2658 41 : if (channel) {
2659 : // We need to set the initiator type for the image load
2660 82 : nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(channel);
2661 41 : if (timedChannel) {
2662 2 : nsAutoString type;
2663 1 : timedChannel->GetInitiatorType(type);
2664 1 : if (type.IsEmpty()) {
2665 1 : timedChannel->SetInitiatorType(NS_LITERAL_STRING("img"));
2666 : }
2667 : }
2668 :
2669 82 : nsAutoCString contentType;
2670 41 : nsresult rv = channel->GetContentType(contentType);
2671 :
2672 41 : if (!contentType.IsEmpty()) {
2673 : /* If multipart/x-mixed-replace content, we'll insert a MIME decoder
2674 : in the pipeline to handle the content and pass it along to our
2675 : original listener.
2676 : */
2677 41 : if (NS_LITERAL_CSTRING("multipart/x-mixed-replace").Equals(contentType)) {
2678 :
2679 : nsCOMPtr<nsIStreamConverterService> convServ(
2680 0 : do_GetService("@mozilla.org/streamConverters;1", &rv));
2681 0 : if (NS_SUCCEEDED(rv)) {
2682 0 : nsCOMPtr<nsIStreamListener> toListener(mDestListener);
2683 0 : nsCOMPtr<nsIStreamListener> fromListener;
2684 :
2685 0 : rv = convServ->AsyncConvertData("multipart/x-mixed-replace",
2686 : "*/*",
2687 : toListener,
2688 : nullptr,
2689 0 : getter_AddRefs(fromListener));
2690 0 : if (NS_SUCCEEDED(rv)) {
2691 0 : mDestListener = fromListener;
2692 : }
2693 : }
2694 : }
2695 : }
2696 : }
2697 :
2698 41 : return mDestListener->OnStartRequest(aRequest, ctxt);
2699 : }
2700 :
2701 : NS_IMETHODIMP
2702 41 : ProxyListener::OnStopRequest(nsIRequest* aRequest,
2703 : nsISupports* ctxt,
2704 : nsresult status)
2705 : {
2706 41 : if (!mDestListener) {
2707 0 : return NS_ERROR_FAILURE;
2708 : }
2709 :
2710 41 : return mDestListener->OnStopRequest(aRequest, ctxt, status);
2711 : }
2712 :
2713 : /** nsIStreamListener methods **/
2714 :
2715 : NS_IMETHODIMP
2716 41 : ProxyListener::OnDataAvailable(nsIRequest* aRequest, nsISupports* ctxt,
2717 : nsIInputStream* inStr, uint64_t sourceOffset,
2718 : uint32_t count)
2719 : {
2720 41 : if (!mDestListener) {
2721 0 : return NS_ERROR_FAILURE;
2722 : }
2723 :
2724 41 : return mDestListener->OnDataAvailable(aRequest, ctxt, inStr,
2725 41 : sourceOffset, count);
2726 : }
2727 :
2728 : /** nsThreadRetargetableStreamListener methods **/
2729 : NS_IMETHODIMP
2730 1 : ProxyListener::CheckListenerChain()
2731 : {
2732 1 : NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
2733 1 : nsresult rv = NS_OK;
2734 : nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
2735 2 : do_QueryInterface(mDestListener, &rv);
2736 1 : if (retargetableListener) {
2737 1 : rv = retargetableListener->CheckListenerChain();
2738 : }
2739 1 : MOZ_LOG(gImgLog, LogLevel::Debug,
2740 : ("ProxyListener::CheckListenerChain %s [this=%p listener=%p rv=%" PRIx32 "]",
2741 : (NS_SUCCEEDED(rv) ? "success" : "failure"),
2742 : this, (nsIStreamListener*)mDestListener, static_cast<uint32_t>(rv)));
2743 2 : return rv;
2744 : }
2745 :
2746 : /**
2747 : * http validate class. check a channel for a 304
2748 : */
2749 :
2750 0 : NS_IMPL_ISUPPORTS(imgCacheValidator, nsIStreamListener, nsIRequestObserver,
2751 : nsIThreadRetargetableStreamListener,
2752 : nsIChannelEventSink, nsIInterfaceRequestor,
2753 : nsIAsyncVerifyRedirectCallback)
2754 :
2755 0 : imgCacheValidator::imgCacheValidator(nsProgressNotificationProxy* progress,
2756 : imgLoader* loader, imgRequest* request,
2757 : nsISupports* aContext,
2758 0 : bool forcePrincipalCheckForCacheEntry)
2759 : : mProgressProxy(progress),
2760 : mRequest(request),
2761 : mContext(aContext),
2762 : mImgLoader(loader),
2763 0 : mHadInsecureRedirect(false)
2764 : {
2765 0 : NewRequestAndEntry(forcePrincipalCheckForCacheEntry, loader,
2766 0 : mRequest->CacheKey(),
2767 0 : getter_AddRefs(mNewRequest),
2768 0 : getter_AddRefs(mNewEntry));
2769 0 : }
2770 :
2771 0 : imgCacheValidator::~imgCacheValidator()
2772 : {
2773 0 : if (mRequest) {
2774 0 : mRequest->SetValidator(nullptr);
2775 : }
2776 0 : }
2777 :
2778 : void
2779 0 : imgCacheValidator::AddProxy(imgRequestProxy* aProxy)
2780 : {
2781 : // aProxy needs to be in the loadgroup since we're validating from
2782 : // the network.
2783 0 : aProxy->AddToLoadGroup();
2784 :
2785 0 : mProxies.AppendObject(aProxy);
2786 0 : }
2787 :
2788 : /** nsIRequestObserver methods **/
2789 :
2790 : NS_IMETHODIMP
2791 0 : imgCacheValidator::OnStartRequest(nsIRequest* aRequest, nsISupports* ctxt)
2792 : {
2793 : // We may be holding on to a document, so ensure that it's released.
2794 0 : nsCOMPtr<nsISupports> context = mContext.forget();
2795 :
2796 : // If for some reason we don't still have an existing request (probably
2797 : // because OnStartRequest got delivered more than once), just bail.
2798 0 : if (!mRequest) {
2799 0 : MOZ_ASSERT_UNREACHABLE("OnStartRequest delivered more than once?");
2800 : aRequest->Cancel(NS_BINDING_ABORTED);
2801 : return NS_ERROR_FAILURE;
2802 : }
2803 :
2804 : // If this request is coming from cache and has the same URI as our
2805 : // imgRequest, the request all our proxies are pointing at is valid, and all
2806 : // we have to do is tell them to notify their listeners.
2807 0 : nsCOMPtr<nsICacheInfoChannel> cacheChan(do_QueryInterface(aRequest));
2808 0 : nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
2809 0 : if (cacheChan && channel && !mRequest->CacheChanged(aRequest)) {
2810 0 : bool isFromCache = false;
2811 0 : cacheChan->IsFromCache(&isFromCache);
2812 :
2813 0 : nsCOMPtr<nsIURI> channelURI;
2814 0 : channel->GetURI(getter_AddRefs(channelURI));
2815 :
2816 0 : nsCOMPtr<nsIURI> currentURI;
2817 0 : mRequest->GetCurrentURI(getter_AddRefs(currentURI));
2818 :
2819 0 : bool sameURI = false;
2820 0 : if (channelURI && currentURI) {
2821 0 : channelURI->Equals(currentURI, &sameURI);
2822 : }
2823 :
2824 0 : if (isFromCache && sameURI) {
2825 0 : uint32_t count = mProxies.Count();
2826 0 : for (int32_t i = count-1; i>=0; i--) {
2827 0 : imgRequestProxy* proxy = static_cast<imgRequestProxy*>(mProxies[i]);
2828 :
2829 : // Proxies waiting on cache validation should be deferring
2830 : // notifications. Undefer them.
2831 0 : MOZ_ASSERT(proxy->NotificationsDeferred(),
2832 : "Proxies waiting on cache validation should be "
2833 : "deferring notifications!");
2834 0 : proxy->SetNotificationsDeferred(false);
2835 :
2836 : // Notify synchronously, because we're already in OnStartRequest, an
2837 : // asynchronously-called function.
2838 0 : proxy->SyncNotifyListener();
2839 : }
2840 :
2841 : // We don't need to load this any more.
2842 0 : aRequest->Cancel(NS_BINDING_ABORTED);
2843 :
2844 0 : mRequest->SetLoadId(context);
2845 0 : mRequest->SetValidator(nullptr);
2846 :
2847 0 : mRequest = nullptr;
2848 :
2849 0 : mNewRequest = nullptr;
2850 0 : mNewEntry = nullptr;
2851 :
2852 0 : return NS_OK;
2853 : }
2854 : }
2855 :
2856 : // We can't load out of cache. We have to create a whole new request for the
2857 : // data that's coming in off the channel.
2858 0 : nsCOMPtr<nsIURI> uri;
2859 : {
2860 0 : RefPtr<ImageURL> imageURL;
2861 0 : mRequest->GetURI(getter_AddRefs(imageURL));
2862 0 : uri = imageURL->ToIURI();
2863 : }
2864 :
2865 0 : if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
2866 0 : LOG_MSG_WITH_PARAM(gImgLog,
2867 : "imgCacheValidator::OnStartRequest creating new request",
2868 0 : "uri", uri->GetSpecOrDefault().get());
2869 : }
2870 :
2871 0 : int32_t corsmode = mRequest->GetCORSMode();
2872 0 : ReferrerPolicy refpol = mRequest->GetReferrerPolicy();
2873 0 : nsCOMPtr<nsIPrincipal> loadingPrincipal = mRequest->GetLoadingPrincipal();
2874 :
2875 : // Doom the old request's cache entry
2876 0 : mRequest->RemoveFromCache();
2877 :
2878 0 : mRequest->SetValidator(nullptr);
2879 0 : mRequest = nullptr;
2880 :
2881 : // We use originalURI here to fulfil the imgIRequest contract on GetURI.
2882 0 : nsCOMPtr<nsIURI> originalURI;
2883 0 : channel->GetOriginalURI(getter_AddRefs(originalURI));
2884 : nsresult rv =
2885 0 : mNewRequest->Init(originalURI, uri, mHadInsecureRedirect, aRequest, channel,
2886 0 : mNewEntry, context, loadingPrincipal, corsmode, refpol);
2887 0 : if (NS_FAILED(rv)) {
2888 0 : return rv;
2889 : }
2890 :
2891 0 : mDestListener = new ProxyListener(mNewRequest);
2892 :
2893 : // Try to add the new request into the cache. Note that the entry must be in
2894 : // the cache before the proxies' ownership changes, because adding a proxy
2895 : // changes the caching behaviour for imgRequests.
2896 0 : mImgLoader->PutIntoCache(mNewRequest->CacheKey(), mNewEntry);
2897 :
2898 0 : uint32_t count = mProxies.Count();
2899 0 : for (int32_t i = count-1; i>=0; i--) {
2900 0 : imgRequestProxy* proxy = static_cast<imgRequestProxy*>(mProxies[i]);
2901 0 : proxy->ChangeOwner(mNewRequest);
2902 :
2903 : // Notify synchronously, because we're already in OnStartRequest, an
2904 : // asynchronously-called function.
2905 0 : proxy->SetNotificationsDeferred(false);
2906 0 : proxy->SyncNotifyListener();
2907 : }
2908 :
2909 0 : mNewRequest = nullptr;
2910 0 : mNewEntry = nullptr;
2911 :
2912 0 : return mDestListener->OnStartRequest(aRequest, ctxt);
2913 : }
2914 :
2915 : NS_IMETHODIMP
2916 0 : imgCacheValidator::OnStopRequest(nsIRequest* aRequest,
2917 : nsISupports* ctxt,
2918 : nsresult status)
2919 : {
2920 : // Be sure we've released the document that we may have been holding on to.
2921 0 : mContext = nullptr;
2922 :
2923 0 : if (!mDestListener) {
2924 0 : return NS_OK;
2925 : }
2926 :
2927 0 : return mDestListener->OnStopRequest(aRequest, ctxt, status);
2928 : }
2929 :
2930 : /** nsIStreamListener methods **/
2931 :
2932 :
2933 : NS_IMETHODIMP
2934 0 : imgCacheValidator::OnDataAvailable(nsIRequest* aRequest, nsISupports* ctxt,
2935 : nsIInputStream* inStr,
2936 : uint64_t sourceOffset, uint32_t count)
2937 : {
2938 0 : if (!mDestListener) {
2939 : // XXX see bug 113959
2940 : uint32_t _retval;
2941 0 : inStr->ReadSegments(NS_DiscardSegment, nullptr, count, &_retval);
2942 0 : return NS_OK;
2943 : }
2944 :
2945 0 : return mDestListener->OnDataAvailable(aRequest, ctxt, inStr, sourceOffset,
2946 0 : count);
2947 : }
2948 :
2949 : /** nsIThreadRetargetableStreamListener methods **/
2950 :
2951 : NS_IMETHODIMP
2952 0 : imgCacheValidator::CheckListenerChain()
2953 : {
2954 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
2955 0 : nsresult rv = NS_OK;
2956 : nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
2957 0 : do_QueryInterface(mDestListener, &rv);
2958 0 : if (retargetableListener) {
2959 0 : rv = retargetableListener->CheckListenerChain();
2960 : }
2961 0 : MOZ_LOG(gImgLog, LogLevel::Debug,
2962 : ("[this=%p] imgCacheValidator::CheckListenerChain -- rv %" PRId32 "=%s",
2963 : this, static_cast<uint32_t>(rv), NS_SUCCEEDED(rv) ? "succeeded" : "failed"));
2964 0 : return rv;
2965 : }
2966 :
2967 : /** nsIInterfaceRequestor methods **/
2968 :
2969 : NS_IMETHODIMP
2970 0 : imgCacheValidator::GetInterface(const nsIID& aIID, void** aResult)
2971 : {
2972 0 : if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
2973 0 : return QueryInterface(aIID, aResult);
2974 : }
2975 :
2976 0 : return mProgressProxy->GetInterface(aIID, aResult);
2977 : }
2978 :
2979 : // These functions are materially the same as the same functions in imgRequest.
2980 : // We duplicate them because we're verifying whether cache loads are necessary,
2981 : // not unconditionally loading.
2982 :
2983 : /** nsIChannelEventSink methods **/
2984 : NS_IMETHODIMP
2985 0 : imgCacheValidator::
2986 : AsyncOnChannelRedirect(nsIChannel* oldChannel,
2987 : nsIChannel* newChannel,
2988 : uint32_t flags,
2989 : nsIAsyncVerifyRedirectCallback* callback)
2990 : {
2991 : // Note all cache information we get from the old channel.
2992 0 : mNewRequest->SetCacheValidation(mNewEntry, oldChannel);
2993 :
2994 : // If the previous URI is a non-HTTPS URI, record that fact for later use by
2995 : // security code, which needs to know whether there is an insecure load at any
2996 : // point in the redirect chain.
2997 0 : nsCOMPtr<nsIURI> oldURI;
2998 0 : bool isHttps = false;
2999 0 : bool isChrome = false;
3000 0 : bool schemeLocal = false;
3001 0 : if (NS_FAILED(oldChannel->GetURI(getter_AddRefs(oldURI))) ||
3002 0 : NS_FAILED(oldURI->SchemeIs("https", &isHttps)) ||
3003 0 : NS_FAILED(oldURI->SchemeIs("chrome", &isChrome)) ||
3004 0 : NS_FAILED(NS_URIChainHasFlags(oldURI,
3005 : nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
3006 0 : &schemeLocal)) ||
3007 0 : (!isHttps && !isChrome && !schemeLocal)) {
3008 0 : mHadInsecureRedirect = true;
3009 : }
3010 :
3011 : // Prepare for callback
3012 0 : mRedirectCallback = callback;
3013 0 : mRedirectChannel = newChannel;
3014 :
3015 0 : return mProgressProxy->AsyncOnChannelRedirect(oldChannel, newChannel, flags,
3016 0 : this);
3017 : }
3018 :
3019 : NS_IMETHODIMP
3020 0 : imgCacheValidator::OnRedirectVerifyCallback(nsresult aResult)
3021 : {
3022 : // If we've already been told to abort, just do so.
3023 0 : if (NS_FAILED(aResult)) {
3024 0 : mRedirectCallback->OnRedirectVerifyCallback(aResult);
3025 0 : mRedirectCallback = nullptr;
3026 0 : mRedirectChannel = nullptr;
3027 0 : return NS_OK;
3028 : }
3029 :
3030 : // make sure we have a protocol that returns data rather than opens
3031 : // an external application, e.g. mailto:
3032 0 : nsCOMPtr<nsIURI> uri;
3033 0 : mRedirectChannel->GetURI(getter_AddRefs(uri));
3034 0 : bool doesNotReturnData = false;
3035 0 : NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA,
3036 0 : &doesNotReturnData);
3037 :
3038 0 : nsresult result = NS_OK;
3039 :
3040 0 : if (doesNotReturnData) {
3041 0 : result = NS_ERROR_ABORT;
3042 : }
3043 :
3044 0 : mRedirectCallback->OnRedirectVerifyCallback(result);
3045 0 : mRedirectCallback = nullptr;
3046 0 : mRedirectChannel = nullptr;
3047 0 : return NS_OK;
3048 : }
|