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