Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/image/imgRequest.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2
 *
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 "imgRequest.h"
8
#include "ImageLogging.h"
9
10
#include "imgLoader.h"
11
#include "imgRequestProxy.h"
12
#include "DecodePool.h"
13
#include "ProgressTracker.h"
14
#include "ImageFactory.h"
15
#include "Image.h"
16
#include "MultipartImage.h"
17
#include "RasterImage.h"
18
19
#include "nsIChannel.h"
20
#include "nsICacheInfoChannel.h"
21
#include "nsIDocument.h"
22
#include "nsIThreadRetargetableRequest.h"
23
#include "nsIInputStream.h"
24
#include "nsIMultiPartChannel.h"
25
#include "nsIHttpChannel.h"
26
#include "nsIApplicationCache.h"
27
#include "nsIApplicationCacheChannel.h"
28
#include "nsMimeTypes.h"
29
30
#include "nsIInterfaceRequestorUtils.h"
31
#include "nsISupportsPrimitives.h"
32
#include "nsIScriptSecurityManager.h"
33
#include "nsContentUtils.h"
34
35
#include "plstr.h" // PL_strcasestr(...)
36
#include "prtime.h" // for PR_Now
37
#include "nsNetUtil.h"
38
#include "nsIProtocolHandler.h"
39
#include "imgIRequest.h"
40
#include "nsProperties.h"
41
42
#include "mozilla/IntegerPrintfMacros.h"
43
#include "mozilla/Telemetry.h"
44
45
using namespace mozilla;
46
using namespace mozilla::image;
47
48
0
#define LOG_TEST(level) (MOZ_LOG_TEST(gImgLog, (level)))
49
50
NS_IMPL_ISUPPORTS(imgRequest,
51
                  nsIStreamListener, nsIRequestObserver,
52
                  nsIThreadRetargetableStreamListener,
53
                  nsIChannelEventSink,
54
                  nsIInterfaceRequestor,
55
                  nsIAsyncVerifyRedirectCallback)
56
57
imgRequest::imgRequest(imgLoader* aLoader, const ImageCacheKey& aCacheKey)
58
 : mLoader(aLoader)
59
 , mCacheKey(aCacheKey)
60
 , mLoadId(nullptr)
61
 , mFirstProxy(nullptr)
62
 , mValidator(nullptr)
63
 , mInnerWindowId(0)
64
 , mCORSMode(imgIRequest::CORS_NONE)
65
 , mReferrerPolicy(mozilla::net::RP_Unset)
66
 , mImageErrorCode(NS_OK)
67
 , mMutex("imgRequest")
68
 , mProgressTracker(new ProgressTracker())
69
 , mIsMultiPartChannel(false)
70
 , mIsInCache(false)
71
 , mDecodeRequested(false)
72
 , mNewPartPending(false)
73
 , mHadInsecureRedirect(false)
74
0
{
75
0
  LOG_FUNC(gImgLog, "imgRequest::imgRequest()");
76
0
}
77
78
imgRequest::~imgRequest()
79
0
{
80
0
  if (mLoader) {
81
0
    mLoader->RemoveFromUncachedImages(this);
82
0
  }
83
0
  if (mURI) {
84
0
    LOG_FUNC_WITH_PARAM(gImgLog, "imgRequest::~imgRequest()",
85
0
                        "keyuri", mURI);
86
0
  } else
87
0
    LOG_FUNC(gImgLog, "imgRequest::~imgRequest()");
88
0
}
89
90
nsresult
91
imgRequest::Init(nsIURI *aURI,
92
                 nsIURI *aFinalURI,
93
                 bool aHadInsecureRedirect,
94
                 nsIRequest *aRequest,
95
                 nsIChannel *aChannel,
96
                 imgCacheEntry *aCacheEntry,
97
                 nsISupports* aCX,
98
                 nsIPrincipal* aTriggeringPrincipal,
99
                 int32_t aCORSMode,
100
                 ReferrerPolicy aReferrerPolicy)
101
0
{
102
0
  MOZ_ASSERT(NS_IsMainThread(), "Cannot use nsIURI off main thread!");
103
0
104
0
  LOG_FUNC(gImgLog, "imgRequest::Init");
105
0
106
0
  MOZ_ASSERT(!mImage, "Multiple calls to init");
107
0
  MOZ_ASSERT(aURI, "No uri");
108
0
  MOZ_ASSERT(aFinalURI, "No final uri");
109
0
  MOZ_ASSERT(aRequest, "No request");
110
0
  MOZ_ASSERT(aChannel, "No channel");
111
0
112
0
  mProperties = new nsProperties();
113
0
  mURI = aURI;
114
0
  mFinalURI = aFinalURI;
115
0
  mRequest = aRequest;
116
0
  mChannel = aChannel;
117
0
  mTimedChannel = do_QueryInterface(mChannel);
118
0
  mTriggeringPrincipal = aTriggeringPrincipal;
119
0
  mCORSMode = aCORSMode;
120
0
  mReferrerPolicy = aReferrerPolicy;
121
0
122
0
  // If the original URI and the final URI are different, check whether the
123
0
  // original URI is secure. We deliberately don't take the final URI into
124
0
  // account, as it needs to be handled using more complicated rules than
125
0
  // earlier elements of the redirect chain.
126
0
  if (aURI != aFinalURI) {
127
0
    bool isHttps = false;
128
0
    bool isChrome = false;
129
0
    bool schemeLocal = false;
130
0
    if (NS_FAILED(aURI->SchemeIs("https", &isHttps)) ||
131
0
        NS_FAILED(aURI->SchemeIs("chrome", &isChrome)) ||
132
0
        NS_FAILED(NS_URIChainHasFlags(
133
0
                  aURI,
134
0
                  nsIProtocolHandler::URI_IS_LOCAL_RESOURCE , &schemeLocal))  ||
135
0
        (!isHttps && !isChrome && !schemeLocal)) {
136
0
      mHadInsecureRedirect = true;
137
0
    }
138
0
  }
139
0
140
0
  // imgCacheValidator may have handled redirects before we were created, so we
141
0
  // allow the caller to let us know if any redirects were insecure.
142
0
  mHadInsecureRedirect = mHadInsecureRedirect || aHadInsecureRedirect;
143
0
144
0
  mChannel->GetNotificationCallbacks(getter_AddRefs(mPrevChannelSink));
145
0
146
0
  NS_ASSERTION(mPrevChannelSink != this,
147
0
               "Initializing with a channel that already calls back to us!");
148
0
149
0
  mChannel->SetNotificationCallbacks(this);
150
0
151
0
  mCacheEntry = aCacheEntry;
152
0
  mCacheEntry->UpdateLoadTime();
153
0
154
0
  SetLoadId(aCX);
155
0
156
0
  // Grab the inner window ID of the loading document, if possible.
157
0
  nsCOMPtr<nsIDocument> doc = do_QueryInterface(aCX);
158
0
  if (doc) {
159
0
    mInnerWindowId = doc->InnerWindowID();
160
0
  }
161
0
162
0
  return NS_OK;
163
0
}
164
165
void
166
0
imgRequest::ClearLoader() {
167
0
  mLoader = nullptr;
168
0
}
169
170
already_AddRefed<ProgressTracker>
171
imgRequest::GetProgressTracker() const
172
0
{
173
0
  MutexAutoLock lock(mMutex);
174
0
175
0
  if (mImage) {
176
0
    MOZ_ASSERT(!mProgressTracker,
177
0
               "Should have given mProgressTracker to mImage");
178
0
    return mImage->GetProgressTracker();
179
0
  }
180
0
  MOZ_ASSERT(mProgressTracker,
181
0
             "Should have mProgressTracker until we create mImage");
182
0
  RefPtr<ProgressTracker> progressTracker = mProgressTracker;
183
0
  MOZ_ASSERT(progressTracker);
184
0
  return progressTracker.forget();
185
0
}
186
187
void
188
imgRequest::SetCacheEntry(imgCacheEntry* entry)
189
0
{
190
0
  mCacheEntry = entry;
191
0
}
192
193
bool
194
imgRequest::HasCacheEntry() const
195
0
{
196
0
  return mCacheEntry != nullptr;
197
0
}
198
199
void
200
imgRequest::ResetCacheEntry()
201
0
{
202
0
  if (HasCacheEntry()) {
203
0
    mCacheEntry->SetDataSize(0);
204
0
  }
205
0
}
206
207
void
208
imgRequest::AddProxy(imgRequestProxy* proxy)
209
0
{
210
0
  MOZ_ASSERT(proxy, "null imgRequestProxy passed in");
211
0
  LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::AddProxy", "proxy", proxy);
212
0
213
0
  if (!mFirstProxy) {
214
0
    // Save a raw pointer to the first proxy we see, for use in the network
215
0
    // priority logic.
216
0
    mFirstProxy = proxy;
217
0
  }
218
0
219
0
  // If we're empty before adding, we have to tell the loader we now have
220
0
  // proxies.
221
0
  RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
222
0
  if (progressTracker->ObserverCount() == 0) {
223
0
    MOZ_ASSERT(mURI, "Trying to SetHasProxies without key uri.");
224
0
    if (mLoader) {
225
0
      mLoader->SetHasProxies(this);
226
0
    }
227
0
  }
228
0
229
0
  progressTracker->AddObserver(proxy);
230
0
}
231
232
nsresult
233
imgRequest::RemoveProxy(imgRequestProxy* proxy, nsresult aStatus)
234
0
{
235
0
  LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::RemoveProxy", "proxy", proxy);
236
0
237
0
  // This will remove our animation consumers, so after removing
238
0
  // this proxy, we don't end up without proxies with observers, but still
239
0
  // have animation consumers.
240
0
  proxy->ClearAnimationConsumers();
241
0
242
0
  // Let the status tracker do its thing before we potentially call Cancel()
243
0
  // below, because Cancel() may result in OnStopRequest being called back
244
0
  // before Cancel() returns, leaving the image in a different state then the
245
0
  // one it was in at this point.
246
0
  RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
247
0
  if (!progressTracker->RemoveObserver(proxy)) {
248
0
    return NS_OK;
249
0
  }
250
0
251
0
  if (progressTracker->ObserverCount() == 0) {
252
0
    // If we have no observers, there's nothing holding us alive. If we haven't
253
0
    // been cancelled and thus removed from the cache, tell the image loader so
254
0
    // we can be evicted from the cache.
255
0
    if (mCacheEntry) {
256
0
      MOZ_ASSERT(mURI, "Removing last observer without key uri.");
257
0
258
0
      if (mLoader) {
259
0
        mLoader->SetHasNoProxies(this, mCacheEntry);
260
0
      }
261
0
    } else {
262
0
      LOG_MSG_WITH_PARAM(gImgLog,
263
0
                         "imgRequest::RemoveProxy no cache entry",
264
0
                         "uri", mURI);
265
0
    }
266
0
267
0
    /* If |aStatus| is a failure code, then cancel the load if it is still in
268
0
       progress.  Otherwise, let the load continue, keeping 'this' in the cache
269
0
       with no observers.  This way, if a proxy is destroyed without calling
270
0
       cancel on it, it won't leak and won't leave a bad pointer in the observer
271
0
       list.
272
0
     */
273
0
    if (!(progressTracker->GetProgress() & FLAG_LAST_PART_COMPLETE) &&
274
0
        NS_FAILED(aStatus)) {
275
0
      LOG_MSG(gImgLog, "imgRequest::RemoveProxy",
276
0
              "load in progress.  canceling");
277
0
278
0
      this->Cancel(NS_BINDING_ABORTED);
279
0
    }
280
0
281
0
    /* break the cycle from the cache entry. */
282
0
    mCacheEntry = nullptr;
283
0
  }
284
0
285
0
  return NS_OK;
286
0
}
287
288
void
289
imgRequest::CancelAndAbort(nsresult aStatus)
290
0
{
291
0
  LOG_SCOPE(gImgLog, "imgRequest::CancelAndAbort");
292
0
293
0
  Cancel(aStatus);
294
0
295
0
  // It's possible for the channel to fail to open after we've set our
296
0
  // notification callbacks. In that case, make sure to break the cycle between
297
0
  // the channel and us, because it won't.
298
0
  if (mChannel) {
299
0
    mChannel->SetNotificationCallbacks(mPrevChannelSink);
300
0
    mPrevChannelSink = nullptr;
301
0
  }
302
0
}
303
304
class imgRequestMainThreadCancel : public Runnable
305
{
306
public:
307
  imgRequestMainThreadCancel(imgRequest* aImgRequest, nsresult aStatus)
308
    : Runnable("imgRequestMainThreadCancel")
309
    , mImgRequest(aImgRequest)
310
    , mStatus(aStatus)
311
0
  {
312
0
    MOZ_ASSERT(!NS_IsMainThread(), "Create me off main thread only!");
313
0
    MOZ_ASSERT(aImgRequest);
314
0
  }
315
316
  NS_IMETHOD Run() override
317
0
  {
318
0
    MOZ_ASSERT(NS_IsMainThread(), "I should be running on the main thread!");
319
0
    mImgRequest->ContinueCancel(mStatus);
320
0
    return NS_OK;
321
0
  }
322
private:
323
  RefPtr<imgRequest> mImgRequest;
324
  nsresult mStatus;
325
};
326
327
void
328
imgRequest::Cancel(nsresult aStatus)
329
0
{
330
0
  /* The Cancel() method here should only be called by this class. */
331
0
  LOG_SCOPE(gImgLog, "imgRequest::Cancel");
332
0
333
0
  if (NS_IsMainThread()) {
334
0
    ContinueCancel(aStatus);
335
0
  } else {
336
0
    RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
337
0
    nsCOMPtr<nsIEventTarget> eventTarget = progressTracker->GetEventTarget();
338
0
    nsCOMPtr<nsIRunnable> ev = new imgRequestMainThreadCancel(this, aStatus);
339
0
    eventTarget->Dispatch(ev.forget(), NS_DISPATCH_NORMAL);
340
0
  }
341
0
}
342
343
void
344
imgRequest::ContinueCancel(nsresult aStatus)
345
0
{
346
0
  MOZ_ASSERT(NS_IsMainThread());
347
0
348
0
  RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
349
0
  progressTracker->SyncNotifyProgress(FLAG_HAS_ERROR);
350
0
351
0
  RemoveFromCache();
352
0
353
0
  if (mRequest && !(progressTracker->GetProgress() & FLAG_LAST_PART_COMPLETE)) {
354
0
     mRequest->Cancel(aStatus);
355
0
  }
356
0
}
357
358
class imgRequestMainThreadEvict : public Runnable
359
{
360
public:
361
  explicit imgRequestMainThreadEvict(imgRequest* aImgRequest)
362
    : Runnable("imgRequestMainThreadEvict")
363
    , mImgRequest(aImgRequest)
364
0
  {
365
0
    MOZ_ASSERT(!NS_IsMainThread(), "Create me off main thread only!");
366
0
    MOZ_ASSERT(aImgRequest);
367
0
  }
368
369
  NS_IMETHOD Run() override
370
0
  {
371
0
    MOZ_ASSERT(NS_IsMainThread(), "I should be running on the main thread!");
372
0
    mImgRequest->ContinueEvict();
373
0
    return NS_OK;
374
0
  }
375
private:
376
  RefPtr<imgRequest> mImgRequest;
377
};
378
379
// EvictFromCache() is written to allowed to get called from any thread
380
void
381
imgRequest::EvictFromCache()
382
0
{
383
0
  /* The EvictFromCache() method here should only be called by this class. */
384
0
  LOG_SCOPE(gImgLog, "imgRequest::EvictFromCache");
385
0
386
0
  if (NS_IsMainThread()) {
387
0
    ContinueEvict();
388
0
  } else {
389
0
    NS_DispatchToMainThread(new imgRequestMainThreadEvict(this));
390
0
  }
391
0
}
392
393
// Helper-method used by EvictFromCache()
394
void
395
imgRequest::ContinueEvict()
396
0
{
397
0
  MOZ_ASSERT(NS_IsMainThread());
398
0
399
0
  RemoveFromCache();
400
0
}
401
402
void
403
imgRequest::StartDecoding()
404
0
{
405
0
  MutexAutoLock lock(mMutex);
406
0
  mDecodeRequested = true;
407
0
}
408
409
bool
410
imgRequest::IsDecodeRequested() const
411
0
{
412
0
  MutexAutoLock lock(mMutex);
413
0
  return mDecodeRequested;
414
0
}
415
416
nsresult imgRequest::GetURI(nsIURI** aURI)
417
0
{
418
0
  MOZ_ASSERT(aURI);
419
0
420
0
  LOG_FUNC(gImgLog, "imgRequest::GetURI");
421
0
422
0
  if (mURI) {
423
0
    *aURI = mURI;
424
0
    NS_ADDREF(*aURI);
425
0
    return NS_OK;
426
0
  }
427
0
428
0
  return NS_ERROR_FAILURE;
429
0
}
430
431
nsresult
432
imgRequest::GetFinalURI(nsIURI** aURI)
433
0
{
434
0
  MOZ_ASSERT(aURI);
435
0
436
0
  LOG_FUNC(gImgLog, "imgRequest::GetFinalURI");
437
0
438
0
  if (mFinalURI) {
439
0
    *aURI = mFinalURI;
440
0
    NS_ADDREF(*aURI);
441
0
    return NS_OK;
442
0
  }
443
0
444
0
  return NS_ERROR_FAILURE;
445
0
}
446
447
bool
448
imgRequest::IsScheme(const char* aScheme) const
449
0
{
450
0
  MOZ_ASSERT(aScheme);
451
0
  bool isScheme = false;
452
0
  if (NS_WARN_IF(NS_FAILED(mURI->SchemeIs(aScheme, &isScheme)))) {
453
0
    return false;
454
0
  }
455
0
  return isScheme;
456
0
}
457
458
bool
459
imgRequest::IsChrome() const
460
0
{
461
0
  return IsScheme("chrome");
462
0
}
463
464
bool
465
imgRequest::IsData() const
466
0
{
467
0
  return IsScheme("data");
468
0
}
469
470
nsresult
471
imgRequest::GetImageErrorCode()
472
0
{
473
0
  return mImageErrorCode;
474
0
}
475
476
void
477
imgRequest::RemoveFromCache()
478
0
{
479
0
  LOG_SCOPE(gImgLog, "imgRequest::RemoveFromCache");
480
0
481
0
  bool isInCache = false;
482
0
483
0
  {
484
0
    MutexAutoLock lock(mMutex);
485
0
    isInCache = mIsInCache;
486
0
  }
487
0
488
0
  if (isInCache && mLoader) {
489
0
    // mCacheEntry is nulled out when we have no more observers.
490
0
    if (mCacheEntry) {
491
0
      mLoader->RemoveFromCache(mCacheEntry);
492
0
    } else {
493
0
      mLoader->RemoveFromCache(mCacheKey);
494
0
    }
495
0
  }
496
0
497
0
  mCacheEntry = nullptr;
498
0
}
499
500
bool
501
imgRequest::HasConsumers() const
502
0
{
503
0
  RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
504
0
  return progressTracker && progressTracker->ObserverCount() > 0;
505
0
}
506
507
already_AddRefed<image::Image>
508
imgRequest::GetImage() const
509
0
{
510
0
  MutexAutoLock lock(mMutex);
511
0
  RefPtr<image::Image> image = mImage;
512
0
  return image.forget();
513
0
}
514
515
int32_t imgRequest::Priority() const
516
0
{
517
0
  int32_t priority = nsISupportsPriority::PRIORITY_NORMAL;
518
0
  nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mRequest);
519
0
  if (p) {
520
0
    p->GetPriority(&priority);
521
0
  }
522
0
  return priority;
523
0
}
524
525
void
526
imgRequest::AdjustPriority(imgRequestProxy* proxy, int32_t delta)
527
0
{
528
0
  // only the first proxy is allowed to modify the priority of this image load.
529
0
  //
530
0
  // XXX(darin): this is probably not the most optimal algorithm as we may want
531
0
  // to increase the priority of requests that have a lot of proxies.  the key
532
0
  // concern though is that image loads remain lower priority than other pieces
533
0
  // of content such as link clicks, CSS, and JS.
534
0
  //
535
0
  if (!mFirstProxy || proxy != mFirstProxy) {
536
0
    return;
537
0
  }
538
0
539
0
  AdjustPriorityInternal(delta);
540
0
}
541
542
void
543
imgRequest::AdjustPriorityInternal(int32_t aDelta)
544
0
{
545
0
  nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mChannel);
546
0
  if (p) {
547
0
    p->AdjustPriority(aDelta);
548
0
  }
549
0
}
550
551
void
552
imgRequest::BoostPriority(uint32_t aCategory)
553
0
{
554
0
  if (!gfxPrefs::ImageLayoutNetworkPriority()) {
555
0
    return;
556
0
  }
557
0
558
0
  uint32_t newRequestedCategory =
559
0
    (mBoostCategoriesRequested & aCategory) ^ aCategory;
560
0
  if (!newRequestedCategory) {
561
0
    // priority boost for each category can only apply once.
562
0
    return;
563
0
  }
564
0
565
0
  MOZ_LOG(gImgLog, LogLevel::Debug,
566
0
         ("[this=%p] imgRequest::BoostPriority for category %x",
567
0
          this, newRequestedCategory));
568
0
569
0
  int32_t delta = 0;
570
0
571
0
  if (newRequestedCategory & imgIRequest::CATEGORY_FRAME_INIT) {
572
0
    --delta;
573
0
  }
574
0
575
0
  if (newRequestedCategory & imgIRequest::CATEGORY_SIZE_QUERY) {
576
0
    --delta;
577
0
  }
578
0
579
0
  if (newRequestedCategory & imgIRequest::CATEGORY_DISPLAY) {
580
0
    delta += nsISupportsPriority::PRIORITY_HIGH;
581
0
  }
582
0
583
0
  AdjustPriorityInternal(delta);
584
0
  mBoostCategoriesRequested |= newRequestedCategory;
585
0
}
586
587
void
588
imgRequest::SetIsInCache(bool aInCache)
589
0
{
590
0
  LOG_FUNC_WITH_PARAM(gImgLog,
591
0
                      "imgRequest::SetIsCacheable", "aInCache", aInCache);
592
0
  MutexAutoLock lock(mMutex);
593
0
  mIsInCache = aInCache;
594
0
}
595
596
void
597
imgRequest::UpdateCacheEntrySize()
598
0
{
599
0
  if (!mCacheEntry) {
600
0
    return;
601
0
  }
602
0
603
0
  RefPtr<Image> image = GetImage();
604
0
  SizeOfState state(moz_malloc_size_of);
605
0
  size_t size = image->SizeOfSourceWithComputedFallback(state);
606
0
  mCacheEntry->SetDataSize(size);
607
0
}
608
609
void
610
imgRequest::SetCacheValidation(imgCacheEntry* aCacheEntry, nsIRequest* aRequest)
611
0
{
612
0
  /* get the expires info */
613
0
  if (aCacheEntry) {
614
0
    // Expiration time defaults to 0. We set the expiration time on our
615
0
    // entry if it hasn't been set yet.
616
0
    if (aCacheEntry->GetExpiryTime() == 0) {
617
0
      uint32_t expiration = 0;
618
0
      nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(aRequest));
619
0
      if (cacheChannel) {
620
0
        /* get the expiration time from the caching channel's token */
621
0
        cacheChannel->GetCacheTokenExpirationTime(&expiration);
622
0
      }
623
0
      if (expiration == 0) {
624
0
        // If the channel doesn't support caching, then ensure this expires the
625
0
        // next time it is used.
626
0
        expiration = imgCacheEntry::SecondsFromPRTime(PR_Now()) - 1;
627
0
      }
628
0
      aCacheEntry->SetExpiryTime(expiration);
629
0
    }
630
0
631
0
    // Determine whether the cache entry must be revalidated when we try to use
632
0
    // it. Currently, only HTTP specifies this information...
633
0
    nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aRequest));
634
0
    if (httpChannel) {
635
0
      bool bMustRevalidate = false;
636
0
637
0
      Unused << httpChannel->IsNoStoreResponse(&bMustRevalidate);
638
0
639
0
      if (!bMustRevalidate) {
640
0
        Unused << httpChannel->IsNoCacheResponse(&bMustRevalidate);
641
0
      }
642
0
643
0
      if (!bMustRevalidate) {
644
0
        nsAutoCString cacheHeader;
645
0
646
0
        Unused << httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Cache-Control"),
647
0
                                                 cacheHeader);
648
0
        if (PL_strcasestr(cacheHeader.get(), "must-revalidate")) {
649
0
          bMustRevalidate = true;
650
0
        }
651
0
      }
652
0
653
0
      // Cache entries default to not needing to validate. We ensure that
654
0
      // multiple calls to this function don't override an earlier decision to
655
0
      // validate by making validation a one-way decision.
656
0
      if (bMustRevalidate) {
657
0
        aCacheEntry->SetMustValidate(bMustRevalidate);
658
0
      }
659
0
    }
660
0
  }
661
0
}
662
663
namespace {
664
665
already_AddRefed<nsIApplicationCache>
666
GetApplicationCache(nsIRequest* aRequest)
667
0
{
668
0
  nsresult rv;
669
0
670
0
  nsCOMPtr<nsIApplicationCacheChannel> appCacheChan =
671
0
    do_QueryInterface(aRequest);
672
0
  if (!appCacheChan) {
673
0
    return nullptr;
674
0
  }
675
0
676
0
  bool fromAppCache;
677
0
  rv = appCacheChan->GetLoadedFromApplicationCache(&fromAppCache);
678
0
  NS_ENSURE_SUCCESS(rv, nullptr);
679
0
680
0
  if (!fromAppCache) {
681
0
    return nullptr;
682
0
  }
683
0
684
0
  nsCOMPtr<nsIApplicationCache> appCache;
685
0
  rv = appCacheChan->GetApplicationCache(getter_AddRefs(appCache));
686
0
  NS_ENSURE_SUCCESS(rv, nullptr);
687
0
688
0
  return appCache.forget();
689
0
}
690
691
} // namespace
692
693
bool
694
imgRequest::CacheChanged(nsIRequest* aNewRequest)
695
0
{
696
0
  nsCOMPtr<nsIApplicationCache> newAppCache = GetApplicationCache(aNewRequest);
697
0
698
0
  // Application cache not involved at all or the same app cache involved
699
0
  // in both of the loads (original and new).
700
0
  if (newAppCache == mApplicationCache) {
701
0
    return false;
702
0
  }
703
0
704
0
  // In a rare case it may happen that two objects still refer
705
0
  // the same application cache version.
706
0
  if (newAppCache && mApplicationCache) {
707
0
    nsresult rv;
708
0
709
0
    nsAutoCString oldAppCacheClientId, newAppCacheClientId;
710
0
    rv = mApplicationCache->GetClientID(oldAppCacheClientId);
711
0
    NS_ENSURE_SUCCESS(rv, true);
712
0
    rv = newAppCache->GetClientID(newAppCacheClientId);
713
0
    NS_ENSURE_SUCCESS(rv, true);
714
0
715
0
    if (oldAppCacheClientId == newAppCacheClientId) {
716
0
      return false;
717
0
    }
718
0
  }
719
0
720
0
  // When we get here, app caches differ or app cache is involved
721
0
  // just in one of the loads what we also consider as a change
722
0
  // in a loading cache.
723
0
  return true;
724
0
}
725
726
bool
727
imgRequest::GetMultipart() const
728
0
{
729
0
  MutexAutoLock lock(mMutex);
730
0
  return mIsMultiPartChannel;
731
0
}
732
733
bool
734
imgRequest::HadInsecureRedirect() const
735
0
{
736
0
  MutexAutoLock lock(mMutex);
737
0
  return mHadInsecureRedirect;
738
0
}
739
740
/** nsIRequestObserver methods **/
741
742
NS_IMETHODIMP
743
imgRequest::OnStartRequest(nsIRequest* aRequest, nsISupports* ctxt)
744
0
{
745
0
  LOG_SCOPE(gImgLog, "imgRequest::OnStartRequest");
746
0
747
0
  RefPtr<Image> image;
748
0
749
0
  // Figure out if we're multipart.
750
0
  nsCOMPtr<nsIMultiPartChannel> multiPartChannel = do_QueryInterface(aRequest);
751
0
  MOZ_ASSERT(multiPartChannel || !mIsMultiPartChannel,
752
0
             "Stopped being multipart?"); {
753
0
    MutexAutoLock lock(mMutex);
754
0
    mNewPartPending = true;
755
0
    image = mImage;
756
0
    mIsMultiPartChannel = bool(multiPartChannel);
757
0
  }
758
0
759
0
  // If we're not multipart, we shouldn't have an image yet.
760
0
  if (image && !multiPartChannel) {
761
0
    MOZ_ASSERT_UNREACHABLE("Already have an image for a non-multipart request");
762
0
    Cancel(NS_IMAGELIB_ERROR_FAILURE);
763
0
    return NS_ERROR_FAILURE;
764
0
  }
765
0
766
0
  /*
767
0
   * If mRequest is null here, then we need to set it so that we'll be able to
768
0
   * cancel it if our Cancel() method is called.  Note that this can only
769
0
   * happen for multipart channels.  We could simply not null out mRequest for
770
0
   * non-last parts, if GetIsLastPart() were reliable, but it's not.  See
771
0
   * https://bugzilla.mozilla.org/show_bug.cgi?id=339610
772
0
   */
773
0
  if (!mRequest) {
774
0
    MOZ_ASSERT(multiPartChannel, "Should have mRequest unless we're multipart");
775
0
    nsCOMPtr<nsIChannel> baseChannel;
776
0
    multiPartChannel->GetBaseChannel(getter_AddRefs(baseChannel));
777
0
    mRequest = baseChannel;
778
0
  }
779
0
780
0
  nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
781
0
  if (channel) {
782
0
    /* Get our principal */
783
0
    nsCOMPtr<nsIScriptSecurityManager>
784
0
      secMan = nsContentUtils::GetSecurityManager();
785
0
    if (secMan) {
786
0
      nsresult rv =
787
0
        secMan->GetChannelResultPrincipal(channel, getter_AddRefs(mPrincipal));
788
0
      if (NS_FAILED(rv)) {
789
0
        return rv;
790
0
      }
791
0
    }
792
0
  }
793
0
794
0
  SetCacheValidation(mCacheEntry, aRequest);
795
0
796
0
  mApplicationCache = GetApplicationCache(aRequest);
797
0
798
0
  // Shouldn't we be dead already if this gets hit?
799
0
  // Probably multipart/x-mixed-replace...
800
0
  RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
801
0
  if (progressTracker->ObserverCount() == 0) {
802
0
    this->Cancel(NS_IMAGELIB_ERROR_FAILURE);
803
0
  }
804
0
805
0
  // Try to retarget OnDataAvailable to a decode thread. We must process data
806
0
  // URIs synchronously as per the spec however.
807
0
  if (!channel || IsData()) {
808
0
    return NS_OK;
809
0
  }
810
0
811
0
  nsCOMPtr<nsIThreadRetargetableRequest> retargetable =
812
0
    do_QueryInterface(aRequest);
813
0
  if (retargetable) {
814
0
    nsAutoCString mimeType;
815
0
    nsresult rv = channel->GetContentType(mimeType);
816
0
    if (NS_SUCCEEDED(rv) && !mimeType.EqualsLiteral(IMAGE_SVG_XML)) {
817
0
      // Retarget OnDataAvailable to the DecodePool's IO thread.
818
0
      nsCOMPtr<nsIEventTarget> target =
819
0
        DecodePool::Singleton()->GetIOEventTarget();
820
0
      rv = retargetable->RetargetDeliveryTo(target);
821
0
    }
822
0
    MOZ_LOG(gImgLog, LogLevel::Warning,
823
0
           ("[this=%p] imgRequest::OnStartRequest -- "
824
0
            "RetargetDeliveryTo rv %" PRIu32 "=%s\n",
825
0
            this, static_cast<uint32_t>(rv), NS_SUCCEEDED(rv) ? "succeeded" : "failed"));
826
0
  }
827
0
828
0
  return NS_OK;
829
0
}
830
831
NS_IMETHODIMP
832
imgRequest::OnStopRequest(nsIRequest* aRequest,
833
                          nsISupports* ctxt, nsresult status)
834
0
{
835
0
  LOG_FUNC(gImgLog, "imgRequest::OnStopRequest");
836
0
  MOZ_ASSERT(NS_IsMainThread(), "Can't send notifications off-main-thread");
837
0
838
0
  RefPtr<Image> image = GetImage();
839
0
840
0
  RefPtr<imgRequest> strongThis = this;
841
0
842
0
  if (mIsMultiPartChannel && mNewPartPending) {
843
0
    OnDataAvailable(aRequest, ctxt, nullptr, 0, 0);
844
0
  }
845
0
846
0
  // XXXldb What if this is a non-last part of a multipart request?
847
0
  // xxx before we release our reference to mRequest, lets
848
0
  // save the last status that we saw so that the
849
0
  // imgRequestProxy will have access to it.
850
0
  if (mRequest) {
851
0
    mRequest = nullptr;  // we no longer need the request
852
0
  }
853
0
854
0
  // stop holding a ref to the channel, since we don't need it anymore
855
0
  if (mChannel) {
856
0
    mChannel->SetNotificationCallbacks(mPrevChannelSink);
857
0
    mPrevChannelSink = nullptr;
858
0
    mChannel = nullptr;
859
0
  }
860
0
861
0
  bool lastPart = true;
862
0
  nsCOMPtr<nsIMultiPartChannel> mpchan(do_QueryInterface(aRequest));
863
0
  if (mpchan) {
864
0
    mpchan->GetIsLastPart(&lastPart);
865
0
  }
866
0
867
0
  bool isPartial = false;
868
0
  if (image && (status == NS_ERROR_NET_PARTIAL_TRANSFER)) {
869
0
    isPartial = true;
870
0
    status = NS_OK; // fake happy face
871
0
  }
872
0
873
0
  // Tell the image that it has all of the source data. Note that this can
874
0
  // trigger a failure, since the image might be waiting for more non-optional
875
0
  // data and this is the point where we break the news that it's not coming.
876
0
  if (image) {
877
0
    nsresult rv = image->OnImageDataComplete(aRequest, ctxt, status, lastPart);
878
0
879
0
    // If we got an error in the OnImageDataComplete() call, we don't want to
880
0
    // proceed as if nothing bad happened. However, we also want to give
881
0
    // precedence to failure status codes from necko, since presumably they're
882
0
    // more meaningful.
883
0
    if (NS_FAILED(rv) && NS_SUCCEEDED(status)) {
884
0
      status = rv;
885
0
    }
886
0
  }
887
0
888
0
  // If the request went through, update the cache entry size. Otherwise,
889
0
  // cancel the request, which removes us from the cache.
890
0
  if (image && NS_SUCCEEDED(status) && !isPartial) {
891
0
    // We update the cache entry size here because this is where we finish
892
0
    // loading compressed source data, which is part of our size calculus.
893
0
    UpdateCacheEntrySize();
894
0
895
0
  } else if (isPartial) {
896
0
    // Remove the partial image from the cache.
897
0
    this->EvictFromCache();
898
0
899
0
  } else {
900
0
    mImageErrorCode = status;
901
0
902
0
    // if the error isn't "just" a partial transfer
903
0
    // stops animations, removes from cache
904
0
    this->Cancel(status);
905
0
  }
906
0
907
0
  if (!image) {
908
0
    // We have to fire the OnStopRequest notifications ourselves because there's
909
0
    // no image capable of doing so.
910
0
    Progress progress =
911
0
      LoadCompleteProgress(lastPart, /* aError = */ false, status);
912
0
913
0
    RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
914
0
    progressTracker->SyncNotifyProgress(progress);
915
0
  }
916
0
917
0
  mTimedChannel = nullptr;
918
0
  return NS_OK;
919
0
}
920
921
struct mimetype_closure
922
{
923
  nsACString* newType;
924
  uint32_t segmentSize;
925
};
926
927
/* prototype for these defined below */
928
static nsresult
929
sniff_mimetype_callback(nsIInputStream* in, void* closure,
930
                        const char* fromRawSegment, uint32_t toOffset,
931
                        uint32_t count, uint32_t* writeCount);
932
933
/** nsThreadRetargetableStreamListener methods **/
934
NS_IMETHODIMP
935
imgRequest::CheckListenerChain()
936
0
{
937
0
  // TODO Might need more checking here.
938
0
  NS_ASSERTION(NS_IsMainThread(), "Should be on the main thread!");
939
0
  return NS_OK;
940
0
}
941
942
/** nsIStreamListener methods **/
943
944
struct NewPartResult final
945
{
946
  explicit NewPartResult(image::Image* aExistingImage)
947
    : mImage(aExistingImage)
948
    , mIsFirstPart(!aExistingImage)
949
    , mSucceeded(false)
950
    , mShouldResetCacheEntry(false)
951
0
  { }
952
953
  nsAutoCString mContentType;
954
  nsAutoCString mContentDisposition;
955
  RefPtr<image::Image> mImage;
956
  const bool mIsFirstPart;
957
  bool mSucceeded;
958
  bool mShouldResetCacheEntry;
959
};
960
961
static NewPartResult
962
PrepareForNewPart(nsIRequest* aRequest, nsIInputStream* aInStr, uint32_t aCount,
963
                  nsIURI* aURI, bool aIsMultipart, image::Image* aExistingImage,
964
                  ProgressTracker* aProgressTracker, uint32_t aInnerWindowId)
965
0
{
966
0
  NewPartResult result(aExistingImage);
967
0
968
0
  if (aInStr) {
969
0
    mimetype_closure closure;
970
0
    closure.newType = &result.mContentType;
971
0
    closure.segmentSize = 0;
972
0
973
0
    // Look at the first few bytes and see if we can tell what the data is from
974
0
    // that since servers tend to lie. :(
975
0
    uint32_t out;
976
0
    aInStr->ReadSegments(sniff_mimetype_callback, &closure, aCount, &out);
977
0
978
0
    // We don't support WebP but we are getting reports of Firefox being served
979
0
    // WebP content in the wild. In particular this appears to be a problem on
980
0
    // Fennec where content authors assume Android implies WebP support. The
981
0
    // telemetry below is intended to get a sense of how prevalent this is.
982
0
    //
983
0
    // From the Google WebP FAQ example and the Modernizr library, websites may
984
0
    // supply a tiny WebP image to probe for feature support using scripts. The
985
0
    // probes are implemented as data URIs thus we should have all the content
986
0
    // upfront. We don't want to consider a probe as having observed WebP since
987
0
    // in theory the client should do the right thing when we fail to decode it.
988
0
    // See https://developers.google.com/speed/webp/faq for details.
989
0
    bool webp = result.mContentType.EqualsLiteral(IMAGE_WEBP);
990
0
    bool webpProbe = false;
991
0
    if (webp) {
992
0
      // The probes from the example/library are all < 90 bytes. Round it up
993
0
      // just in case.
994
0
      const uint32_t kMaxProbeSize = 100;
995
0
      if (closure.segmentSize < kMaxProbeSize &&
996
0
          NS_FAILED(aURI->SchemeIs("data", &webpProbe))) {
997
0
        webpProbe = false;
998
0
      }
999
0
1000
0
      if (webpProbe) {
1001
0
        Telemetry::ScalarSet(Telemetry::ScalarID::IMAGES_WEBP_PROBE_OBSERVED,
1002
0
                             true);
1003
0
      } else {
1004
0
        Telemetry::ScalarSet(Telemetry::ScalarID::IMAGES_WEBP_CONTENT_OBSERVED,
1005
0
                             true);
1006
0
      }
1007
0
    }
1008
0
1009
0
    if (!webpProbe) {
1010
0
      Telemetry::ScalarAdd(Telemetry::ScalarID::IMAGES_WEBP_CONTENT_FREQUENCY,
1011
0
                           webp ? NS_LITERAL_STRING("webp") :
1012
0
                                  NS_LITERAL_STRING("other"), 1);
1013
0
    }
1014
0
  }
1015
0
1016
0
  nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
1017
0
  if (result.mContentType.IsEmpty()) {
1018
0
    nsresult rv = chan ? chan->GetContentType(result.mContentType)
1019
0
                       : NS_ERROR_FAILURE;
1020
0
    if (NS_FAILED(rv)) {
1021
0
      MOZ_LOG(gImgLog,
1022
0
              LogLevel::Error, ("imgRequest::PrepareForNewPart -- "
1023
0
                                "Content type unavailable from the channel\n"));
1024
0
      if (!aIsMultipart) {
1025
0
        return result;
1026
0
      }
1027
0
    }
1028
0
  }
1029
0
1030
0
  if (chan) {
1031
0
    chan->GetContentDispositionHeader(result.mContentDisposition);
1032
0
  }
1033
0
1034
0
  MOZ_LOG(gImgLog, LogLevel::Debug,
1035
0
         ("imgRequest::PrepareForNewPart -- Got content type %s\n",
1036
0
          result.mContentType.get()));
1037
0
1038
0
  // XXX If server lied about mimetype and it's SVG, we may need to copy
1039
0
  // the data and dispatch back to the main thread, AND tell the channel to
1040
0
  // dispatch there in the future.
1041
0
1042
0
  // Create the new image and give it ownership of our ProgressTracker.
1043
0
  if (aIsMultipart) {
1044
0
    // Create the ProgressTracker and image for this part.
1045
0
    RefPtr<ProgressTracker> progressTracker = new ProgressTracker();
1046
0
    RefPtr<image::Image> partImage =
1047
0
      image::ImageFactory::CreateImage(aRequest, progressTracker,
1048
0
                                       result.mContentType,
1049
0
                                       aURI, /* aIsMultipart = */ true,
1050
0
                                       aInnerWindowId);
1051
0
1052
0
    if (result.mIsFirstPart) {
1053
0
      // First part for a multipart channel. Create the MultipartImage wrapper.
1054
0
      MOZ_ASSERT(aProgressTracker, "Shouldn't have given away tracker yet");
1055
0
      aProgressTracker->SetIsMultipart();
1056
0
      result.mImage =
1057
0
        image::ImageFactory::CreateMultipartImage(partImage, aProgressTracker);
1058
0
    } else {
1059
0
      // Transition to the new part.
1060
0
      auto multipartImage = static_cast<MultipartImage*>(aExistingImage);
1061
0
      multipartImage->BeginTransitionToPart(partImage);
1062
0
1063
0
      // Reset our cache entry size so it doesn't keep growing without bound.
1064
0
      result.mShouldResetCacheEntry = true;
1065
0
    }
1066
0
  } else {
1067
0
    MOZ_ASSERT(!aExistingImage, "New part for non-multipart channel?");
1068
0
    MOZ_ASSERT(aProgressTracker, "Shouldn't have given away tracker yet");
1069
0
1070
0
    // Create an image using our progress tracker.
1071
0
    result.mImage =
1072
0
      image::ImageFactory::CreateImage(aRequest, aProgressTracker,
1073
0
                                       result.mContentType,
1074
0
                                       aURI, /* aIsMultipart = */ false,
1075
0
                                       aInnerWindowId);
1076
0
  }
1077
0
1078
0
  MOZ_ASSERT(result.mImage);
1079
0
  if (!result.mImage->HasError() || aIsMultipart) {
1080
0
    // We allow multipart images to fail to initialize (which generally
1081
0
    // indicates a bad content type) without cancelling the load, because
1082
0
    // subsequent parts might be fine.
1083
0
    result.mSucceeded = true;
1084
0
  }
1085
0
1086
0
  return result;
1087
0
}
1088
1089
class FinishPreparingForNewPartRunnable final : public Runnable
1090
{
1091
public:
1092
  FinishPreparingForNewPartRunnable(imgRequest* aImgRequest,
1093
                                    NewPartResult&& aResult)
1094
    : Runnable("FinishPreparingForNewPartRunnable")
1095
    , mImgRequest(aImgRequest)
1096
    , mResult(aResult)
1097
0
  {
1098
0
    MOZ_ASSERT(aImgRequest);
1099
0
  }
1100
1101
  NS_IMETHOD Run() override
1102
0
  {
1103
0
    mImgRequest->FinishPreparingForNewPart(mResult);
1104
0
    return NS_OK;
1105
0
  }
1106
1107
private:
1108
  RefPtr<imgRequest> mImgRequest;
1109
  NewPartResult mResult;
1110
};
1111
1112
void
1113
imgRequest::FinishPreparingForNewPart(const NewPartResult& aResult)
1114
0
{
1115
0
  MOZ_ASSERT(NS_IsMainThread());
1116
0
1117
0
  mContentType = aResult.mContentType;
1118
0
1119
0
  SetProperties(aResult.mContentType, aResult.mContentDisposition);
1120
0
1121
0
  if (aResult.mIsFirstPart) {
1122
0
    // Notify listeners that we have an image.
1123
0
    RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1124
0
    progressTracker->OnImageAvailable();
1125
0
    MOZ_ASSERT(progressTracker->HasImage());
1126
0
  }
1127
0
1128
0
  if (aResult.mShouldResetCacheEntry) {
1129
0
    ResetCacheEntry();
1130
0
  }
1131
0
1132
0
  if (IsDecodeRequested()) {
1133
0
    aResult.mImage->StartDecoding(imgIContainer::FLAG_NONE);
1134
0
  }
1135
0
}
1136
1137
NS_IMETHODIMP
1138
imgRequest::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext,
1139
                            nsIInputStream* aInStr, uint64_t aOffset,
1140
                            uint32_t aCount)
1141
0
{
1142
0
  LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequest::OnDataAvailable",
1143
0
                       "count", aCount);
1144
0
1145
0
  NS_ASSERTION(aRequest, "imgRequest::OnDataAvailable -- no request!");
1146
0
1147
0
  RefPtr<Image> image;
1148
0
  RefPtr<ProgressTracker> progressTracker;
1149
0
  bool isMultipart = false;
1150
0
  bool newPartPending = false;
1151
0
1152
0
  // Retrieve and update our state.
1153
0
  {
1154
0
    MutexAutoLock lock(mMutex);
1155
0
    image = mImage;
1156
0
    progressTracker = mProgressTracker;
1157
0
    isMultipart = mIsMultiPartChannel;
1158
0
    newPartPending = mNewPartPending;
1159
0
    mNewPartPending = false;
1160
0
  }
1161
0
1162
0
  // If this is a new part, we need to sniff its content type and create an
1163
0
  // appropriate image.
1164
0
  if (newPartPending) {
1165
0
    NewPartResult result = PrepareForNewPart(aRequest, aInStr, aCount, mURI,
1166
0
                                             isMultipart, image,
1167
0
                                             progressTracker, mInnerWindowId);
1168
0
    bool succeeded = result.mSucceeded;
1169
0
1170
0
    if (result.mImage) {
1171
0
      image = result.mImage;
1172
0
      nsCOMPtr<nsIEventTarget> eventTarget;
1173
0
1174
0
      // Update our state to reflect this new part.
1175
0
      {
1176
0
        MutexAutoLock lock(mMutex);
1177
0
        mImage = image;
1178
0
1179
0
        // We only get an event target if we are not on the main thread, because
1180
0
        // we have to dispatch in that case. If we are on the main thread, but
1181
0
        // on a different scheduler group than ProgressTracker would give us,
1182
0
        // that is okay because nothing in imagelib requires that, just our
1183
0
        // listeners (which have their own checks).
1184
0
        if (!NS_IsMainThread()) {
1185
0
          eventTarget = mProgressTracker->GetEventTarget();
1186
0
          MOZ_ASSERT(eventTarget);
1187
0
        }
1188
0
1189
0
        mProgressTracker = nullptr;
1190
0
      }
1191
0
1192
0
      // Some property objects are not threadsafe, and we need to send
1193
0
      // OnImageAvailable on the main thread, so finish on the main thread.
1194
0
      if (!eventTarget) {
1195
0
        MOZ_ASSERT(NS_IsMainThread());
1196
0
        FinishPreparingForNewPart(result);
1197
0
      } else {
1198
0
        nsCOMPtr<nsIRunnable> runnable =
1199
0
          new FinishPreparingForNewPartRunnable(this, std::move(result));
1200
0
        eventTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
1201
0
      }
1202
0
    }
1203
0
1204
0
    if (!succeeded) {
1205
0
      // Something went wrong; probably a content type issue.
1206
0
      Cancel(NS_IMAGELIB_ERROR_FAILURE);
1207
0
      return NS_BINDING_ABORTED;
1208
0
    }
1209
0
  }
1210
0
1211
0
  // Notify the image that it has new data.
1212
0
  if (aInStr) {
1213
0
    nsresult rv =
1214
0
      image->OnImageDataAvailable(aRequest, aContext, aInStr, aOffset, aCount);
1215
0
1216
0
    if (NS_FAILED(rv)) {
1217
0
      MOZ_LOG(gImgLog, LogLevel::Warning,
1218
0
             ("[this=%p] imgRequest::OnDataAvailable -- "
1219
0
              "copy to RasterImage failed\n", this));
1220
0
      Cancel(NS_IMAGELIB_ERROR_FAILURE);
1221
0
      return NS_BINDING_ABORTED;
1222
0
    }
1223
0
  }
1224
0
1225
0
  return NS_OK;
1226
0
}
1227
1228
void
1229
imgRequest::SetProperties(const nsACString& aContentType,
1230
                          const nsACString& aContentDisposition)
1231
0
{
1232
0
  /* set our mimetype as a property */
1233
0
  nsCOMPtr<nsISupportsCString> contentType =
1234
0
    do_CreateInstance("@mozilla.org/supports-cstring;1");
1235
0
  if (contentType) {
1236
0
    contentType->SetData(aContentType);
1237
0
    mProperties->Set("type", contentType);
1238
0
  }
1239
0
1240
0
  /* set our content disposition as a property */
1241
0
  if (!aContentDisposition.IsEmpty()) {
1242
0
    nsCOMPtr<nsISupportsCString> contentDisposition =
1243
0
      do_CreateInstance("@mozilla.org/supports-cstring;1");
1244
0
    if (contentDisposition) {
1245
0
      contentDisposition->SetData(aContentDisposition);
1246
0
      mProperties->Set("content-disposition", contentDisposition);
1247
0
    }
1248
0
  }
1249
0
}
1250
1251
static nsresult
1252
sniff_mimetype_callback(nsIInputStream* in,
1253
                        void* data,
1254
                        const char* fromRawSegment,
1255
                        uint32_t toOffset,
1256
                        uint32_t count,
1257
                        uint32_t* writeCount)
1258
0
{
1259
0
  mimetype_closure* closure = static_cast<mimetype_closure*>(data);
1260
0
1261
0
  NS_ASSERTION(closure, "closure is null!");
1262
0
1263
0
  closure->segmentSize = count;
1264
0
  if (count > 0) {
1265
0
    imgLoader::GetMimeTypeFromContent(fromRawSegment, count, *closure->newType);
1266
0
  }
1267
0
1268
0
  *writeCount = 0;
1269
0
  return NS_ERROR_FAILURE;
1270
0
}
1271
1272
1273
/** nsIInterfaceRequestor methods **/
1274
1275
NS_IMETHODIMP
1276
imgRequest::GetInterface(const nsIID & aIID, void** aResult)
1277
0
{
1278
0
  if (!mPrevChannelSink || aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
1279
0
    return QueryInterface(aIID, aResult);
1280
0
  }
1281
0
1282
0
  NS_ASSERTION(mPrevChannelSink != this,
1283
0
    "Infinite recursion - don't keep track of channel sinks that are us!");
1284
0
  return mPrevChannelSink->GetInterface(aIID, aResult);
1285
0
}
1286
1287
/** nsIChannelEventSink methods **/
1288
NS_IMETHODIMP
1289
imgRequest::AsyncOnChannelRedirect(nsIChannel* oldChannel,
1290
                                   nsIChannel* newChannel, uint32_t flags,
1291
                                   nsIAsyncVerifyRedirectCallback* callback)
1292
0
{
1293
0
  NS_ASSERTION(mRequest && mChannel,
1294
0
               "Got a channel redirect after we nulled out mRequest!");
1295
0
  NS_ASSERTION(mChannel == oldChannel,
1296
0
               "Got a channel redirect for an unknown channel!");
1297
0
  NS_ASSERTION(newChannel, "Got a redirect to a NULL channel!");
1298
0
1299
0
  SetCacheValidation(mCacheEntry, oldChannel);
1300
0
1301
0
  // Prepare for callback
1302
0
  mRedirectCallback = callback;
1303
0
  mNewRedirectChannel = newChannel;
1304
0
1305
0
  nsCOMPtr<nsIChannelEventSink> sink(do_GetInterface(mPrevChannelSink));
1306
0
  if (sink) {
1307
0
    nsresult rv = sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags,
1308
0
                                               this);
1309
0
    if (NS_FAILED(rv)) {
1310
0
        mRedirectCallback = nullptr;
1311
0
        mNewRedirectChannel = nullptr;
1312
0
    }
1313
0
    return rv;
1314
0
  }
1315
0
1316
0
  (void) OnRedirectVerifyCallback(NS_OK);
1317
0
  return NS_OK;
1318
0
}
1319
1320
NS_IMETHODIMP
1321
imgRequest::OnRedirectVerifyCallback(nsresult result)
1322
0
{
1323
0
  NS_ASSERTION(mRedirectCallback, "mRedirectCallback not set in callback");
1324
0
  NS_ASSERTION(mNewRedirectChannel, "mNewRedirectChannel not set in callback");
1325
0
1326
0
  if (NS_FAILED(result)) {
1327
0
      mRedirectCallback->OnRedirectVerifyCallback(result);
1328
0
      mRedirectCallback = nullptr;
1329
0
      mNewRedirectChannel = nullptr;
1330
0
      return NS_OK;
1331
0
  }
1332
0
1333
0
  mChannel = mNewRedirectChannel;
1334
0
  mTimedChannel = do_QueryInterface(mChannel);
1335
0
  mNewRedirectChannel = nullptr;
1336
0
1337
0
  if (LOG_TEST(LogLevel::Debug)) {
1338
0
    LOG_MSG_WITH_PARAM(gImgLog,
1339
0
                       "imgRequest::OnChannelRedirect", "old",
1340
0
                       mFinalURI ? mFinalURI->GetSpecOrDefault().get()
1341
0
                                 : "");
1342
0
  }
1343
0
1344
0
  // If the previous URI is a non-HTTPS URI, record that fact for later use by
1345
0
  // security code, which needs to know whether there is an insecure load at any
1346
0
  // point in the redirect chain.
1347
0
  bool isHttps = false;
1348
0
  bool isChrome = false;
1349
0
  bool schemeLocal = false;
1350
0
  if (NS_FAILED(mFinalURI->SchemeIs("https", &isHttps)) ||
1351
0
      NS_FAILED(mFinalURI->SchemeIs("chrome", &isChrome)) ||
1352
0
      NS_FAILED(NS_URIChainHasFlags(mFinalURI,
1353
0
                                    nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
1354
0
                                    &schemeLocal))  ||
1355
0
      (!isHttps && !isChrome && !schemeLocal)) {
1356
0
    MutexAutoLock lock(mMutex);
1357
0
1358
0
    // The csp directive upgrade-insecure-requests performs an internal redirect
1359
0
    // to upgrade all requests from http to https before any data is fetched from
1360
0
    // the network. Do not pollute mHadInsecureRedirect in case of such an internal
1361
0
    // redirect.
1362
0
    nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
1363
0
    bool upgradeInsecureRequests = loadInfo ?
1364
0
                                   loadInfo->GetUpgradeInsecureRequests() ||
1365
0
                                   loadInfo->GetBrowserUpgradeInsecureRequests()
1366
0
                                            : false;
1367
0
    if (!upgradeInsecureRequests) {
1368
0
      mHadInsecureRedirect = true;
1369
0
    }
1370
0
  }
1371
0
1372
0
  // Update the final URI.
1373
0
  mChannel->GetURI(getter_AddRefs(mFinalURI));
1374
0
1375
0
  if (LOG_TEST(LogLevel::Debug)) {
1376
0
    LOG_MSG_WITH_PARAM(gImgLog, "imgRequest::OnChannelRedirect", "new",
1377
0
                       mFinalURI ? mFinalURI->GetSpecOrDefault().get()
1378
0
                                 : "");
1379
0
  }
1380
0
1381
0
  // Make sure we have a protocol that returns data rather than opens an
1382
0
  // external application, e.g. 'mailto:'.
1383
0
  bool doesNotReturnData = false;
1384
0
  nsresult rv =
1385
0
    NS_URIChainHasFlags(mFinalURI,
1386
0
                        nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA,
1387
0
                        &doesNotReturnData);
1388
0
1389
0
  if (NS_SUCCEEDED(rv) && doesNotReturnData) {
1390
0
    rv = NS_ERROR_ABORT;
1391
0
  }
1392
0
1393
0
  if (NS_FAILED(rv)) {
1394
0
    mRedirectCallback->OnRedirectVerifyCallback(rv);
1395
0
    mRedirectCallback = nullptr;
1396
0
    return NS_OK;
1397
0
  }
1398
0
1399
0
  mRedirectCallback->OnRedirectVerifyCallback(NS_OK);
1400
0
  mRedirectCallback = nullptr;
1401
0
  return NS_OK;
1402
0
}