Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/image/decoders/nsICODecoder.cpp
Line
Count
Source (jump to first uncovered line)
1
/* vim:set tw=80 expandtab softtabstop=2 ts=2 sw=2: */
2
/* This Source Code Form is subject to the terms of the Mozilla Public
3
 * License, v. 2.0. If a copy of the MPL was not distributed with this
4
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6
/* This is a Cross-Platform ICO Decoder, which should work everywhere, including
7
 * Big-Endian machines like the PowerPC. */
8
9
#include "nsICODecoder.h"
10
11
#include <stdlib.h>
12
13
#include "mozilla/EndianUtils.h"
14
#include "mozilla/Move.h"
15
16
#include "mozilla/gfx/Swizzle.h"
17
18
#include "RasterImage.h"
19
20
using namespace mozilla::gfx;
21
22
namespace mozilla {
23
namespace image {
24
25
// Constants.
26
static const uint32_t ICOHEADERSIZE = 6;
27
static const uint32_t BITMAPINFOSIZE = bmp::InfoHeaderLength::WIN_ICO;
28
29
// ----------------------------------------
30
// Actual Data Processing
31
// ----------------------------------------
32
33
// Obtains the number of colors from the bits per pixel
34
uint16_t
35
nsICODecoder::GetNumColors()
36
0
{
37
0
  uint16_t numColors = 0;
38
0
  if (mBPP <= 8) {
39
0
    switch (mBPP) {
40
0
    case 1:
41
0
      numColors = 2;
42
0
      break;
43
0
    case 4:
44
0
      numColors = 16;
45
0
      break;
46
0
    case 8:
47
0
      numColors = 256;
48
0
      break;
49
0
    default:
50
0
      numColors = (uint16_t)-1;
51
0
    }
52
0
  }
53
0
  return numColors;
54
0
}
55
56
nsICODecoder::nsICODecoder(RasterImage* aImage)
57
  : Decoder(aImage)
58
  , mLexer(Transition::To(ICOState::HEADER, ICOHEADERSIZE),
59
           Transition::TerminateSuccess())
60
  , mDirEntry(nullptr)
61
  , mNumIcons(0)
62
  , mCurrIcon(0)
63
  , mBPP(0)
64
  , mMaskRowSize(0)
65
  , mCurrMaskLine(0)
66
  , mIsCursor(false)
67
  , mHasMaskAlpha(false)
68
0
{ }
69
70
nsresult
71
nsICODecoder::FinishInternal()
72
0
{
73
0
  // We shouldn't be called in error cases
74
0
  MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!");
75
0
76
0
  return GetFinalStateFromContainedDecoder();
77
0
}
78
79
nsresult
80
nsICODecoder::FinishWithErrorInternal()
81
0
{
82
0
  // No need to assert !mInFrame here because this condition is enforced by
83
0
  // mContainedDecoder.
84
0
  return GetFinalStateFromContainedDecoder();
85
0
}
86
87
nsresult
88
nsICODecoder::GetFinalStateFromContainedDecoder()
89
0
{
90
0
  if (!mContainedDecoder) {
91
0
    return NS_OK;
92
0
  }
93
0
94
0
  // Let the contained decoder finish up if necessary.
95
0
  FlushContainedDecoder();
96
0
97
0
  // Make our state the same as the state of the contained decoder.
98
0
  mDecodeDone = mContainedDecoder->GetDecodeDone();
99
0
  mProgress |= mContainedDecoder->TakeProgress();
100
0
  mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect());
101
0
  mCurrentFrame = mContainedDecoder->GetCurrentFrameRef();
102
0
103
0
  // Finalize the frame which we deferred to ensure we could modify the final
104
0
  // result (e.g. to apply the BMP mask).
105
0
  MOZ_ASSERT(!mContainedDecoder->GetFinalizeFrames());
106
0
  if (mCurrentFrame) {
107
0
    mCurrentFrame->FinalizeSurface();
108
0
  }
109
0
110
0
  // Propagate errors.
111
0
  nsresult rv = HasError() || mContainedDecoder->HasError()
112
0
              ? NS_ERROR_FAILURE
113
0
              : NS_OK;
114
0
115
0
  MOZ_ASSERT(NS_FAILED(rv) || !mCurrentFrame || mCurrentFrame->IsFinished());
116
0
  return rv;
117
0
}
118
119
LexerTransition<ICOState>
120
nsICODecoder::ReadHeader(const char* aData)
121
0
{
122
0
  // If the third byte is 1, this is an icon. If 2, a cursor.
123
0
  if ((aData[2] != 1) && (aData[2] != 2)) {
124
0
    return Transition::TerminateFailure();
125
0
  }
126
0
  mIsCursor = (aData[2] == 2);
127
0
128
0
  // The fifth and sixth bytes specify the number of resources in the file.
129
0
  mNumIcons = LittleEndian::readUint16(aData + 4);
130
0
  if (mNumIcons == 0) {
131
0
    return Transition::TerminateSuccess(); // Nothing to do.
132
0
  }
133
0
134
0
  // Downscale-during-decode can end up decoding different resources in the ICO
135
0
  // file depending on the target size. Since the resources are not necessarily
136
0
  // scaled versions of the same image, some may be transparent and some may not
137
0
  // be. We could be precise about transparency if we decoded the metadata of
138
0
  // every resource, but for now we don't and it's safest to assume that
139
0
  // transparency could be present.
140
0
  PostHasTransparency();
141
0
142
0
  return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE);
143
0
}
144
145
size_t
146
nsICODecoder::FirstResourceOffset() const
147
0
{
148
0
  MOZ_ASSERT(mNumIcons > 0,
149
0
             "Calling FirstResourceOffset before processing header");
150
0
151
0
  // The first resource starts right after the directory, which starts right
152
0
  // after the ICO header.
153
0
  return ICOHEADERSIZE + mNumIcons * ICODIRENTRYSIZE;
154
0
}
155
156
LexerTransition<ICOState>
157
nsICODecoder::ReadDirEntry(const char* aData)
158
0
{
159
0
  mCurrIcon++;
160
0
161
0
  // Ensure the resource has an offset past the ICO headers.
162
0
  uint32_t offset = LittleEndian::readUint32(aData + 12);
163
0
  if (offset >= FirstResourceOffset()) {
164
0
    // Read the directory entry.
165
0
    IconDirEntryEx e;
166
0
    e.mWidth       = aData[0];
167
0
    e.mHeight      = aData[1];
168
0
    e.mColorCount  = aData[2];
169
0
    e.mReserved    = aData[3];
170
0
    e.mPlanes      = LittleEndian::readUint16(aData + 4);
171
0
    e.mBitCount    = LittleEndian::readUint16(aData + 6);
172
0
    e.mBytesInRes  = LittleEndian::readUint32(aData + 8);
173
0
    e.mImageOffset = offset;
174
0
    e.mSize        = IntSize(e.mWidth, e.mHeight);
175
0
176
0
    // Only accept entries with sufficient resource data to actually contain
177
0
    // some image data.
178
0
    if (e.mBytesInRes > BITMAPINFOSIZE) {
179
0
      if (e.mWidth == 0 || e.mHeight == 0) {
180
0
        mUnsizedDirEntries.AppendElement(e);
181
0
      } else {
182
0
        mDirEntries.AppendElement(e);
183
0
      }
184
0
    }
185
0
  }
186
0
187
0
  if (mCurrIcon == mNumIcons) {
188
0
    if (mUnsizedDirEntries.IsEmpty()) {
189
0
      return Transition::To(ICOState::FINISHED_DIR_ENTRY, 0);
190
0
    }
191
0
    return Transition::To(ICOState::ITERATE_UNSIZED_DIR_ENTRY, 0);
192
0
  }
193
0
194
0
  return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE);
195
0
}
196
197
LexerTransition<ICOState>
198
nsICODecoder::IterateUnsizedDirEntry()
199
0
{
200
0
  MOZ_ASSERT(!mUnsizedDirEntries.IsEmpty());
201
0
202
0
  if (!mDirEntry) {
203
0
    // The first time we are here, there is no entry selected. We must prepare a
204
0
    // new iterator for the contained decoder to advance as it wills. Cloning at
205
0
    // this point ensures it will begin at the end of the dir entries.
206
0
    mReturnIterator = mLexer.Clone(*mIterator, SIZE_MAX);
207
0
    if (mReturnIterator.isNothing()) {
208
0
      // If we cannot read further than this point, then there is no resource
209
0
      // data to read.
210
0
      return Transition::TerminateFailure();
211
0
    }
212
0
  } else {
213
0
    // We have already selected an entry which means a metadata decoder has
214
0
    // finished. Verify the size is valid and if so, add to the discovered
215
0
    // resources.
216
0
    if (mDirEntry->mSize.width > 0 && mDirEntry->mSize.height > 0) {
217
0
      mDirEntries.AppendElement(*mDirEntry);
218
0
    }
219
0
220
0
    // Remove the entry from the unsized list either way.
221
0
    mDirEntry = nullptr;
222
0
    mUnsizedDirEntries.RemoveElementAt(0);
223
0
224
0
    // Our iterator is at an unknown point, so reset it to the point that we
225
0
    // saved.
226
0
    mIterator = mLexer.Clone(*mReturnIterator, SIZE_MAX);
227
0
    if (mIterator.isNothing()) {
228
0
      MOZ_ASSERT_UNREACHABLE("Cannot re-clone return iterator");
229
0
      return Transition::TerminateFailure();
230
0
    }
231
0
  }
232
0
233
0
  // There are no more unsized entries, so we can finally decide which entry to
234
0
  // select for decoding.
235
0
  if (mUnsizedDirEntries.IsEmpty()) {
236
0
    mReturnIterator.reset();
237
0
    return Transition::To(ICOState::FINISHED_DIR_ENTRY, 0);
238
0
  }
239
0
240
0
  // Move to the resource data to start metadata decoding.
241
0
  mDirEntry = &mUnsizedDirEntries[0];
242
0
  size_t offsetToResource = mDirEntry->mImageOffset - FirstResourceOffset();
243
0
  return Transition::ToUnbuffered(ICOState::FOUND_RESOURCE,
244
0
                                  ICOState::SKIP_TO_RESOURCE,
245
0
                                  offsetToResource);
246
0
}
247
248
LexerTransition<ICOState>
249
nsICODecoder::FinishDirEntry()
250
0
{
251
0
  MOZ_ASSERT(!mDirEntry);
252
0
253
0
  if (mDirEntries.IsEmpty()) {
254
0
    return Transition::TerminateFailure();
255
0
  }
256
0
257
0
  // If an explicit output size was specified, we'll try to select the resource
258
0
  // that matches it best below.
259
0
  const Maybe<IntSize> desiredSize = ExplicitOutputSize();
260
0
261
0
  // Determine the biggest resource. We always use the biggest resource for the
262
0
  // intrinsic size, and if we don't have a specific desired size, we select it
263
0
  // as the best resource as well.
264
0
  int32_t bestDelta = INT32_MIN;
265
0
  IconDirEntryEx* biggestEntry = nullptr;
266
0
267
0
  for (size_t i = 0; i < mDirEntries.Length(); ++i) {
268
0
    IconDirEntryEx& e = mDirEntries[i];
269
0
    mImageMetadata.AddNativeSize(e.mSize);
270
0
271
0
    if (!biggestEntry ||
272
0
        (e.mBitCount >= biggestEntry->mBitCount &&
273
0
         e.mSize.width * e.mSize.height >=
274
0
           biggestEntry->mSize.width * biggestEntry->mSize.height)) {
275
0
      biggestEntry = &e;
276
0
277
0
      if (!desiredSize) {
278
0
        mDirEntry = &e;
279
0
      }
280
0
    }
281
0
282
0
    if (desiredSize) {
283
0
      // Calculate the delta between this resource's size and the desired size, so
284
0
      // we can see if it is better than our current-best option.  In the case of
285
0
      // several equally-good resources, we use the last one. "Better" in this
286
0
      // case is determined by |delta|, a measure of the difference in size
287
0
      // between the entry we've found and the desired size. We will choose the
288
0
      // smallest resource that is greater than or equal to the desired size (i.e.
289
0
      // we assume it's better to downscale a larger icon than to upscale a
290
0
      // smaller one).
291
0
      int32_t delta = std::min(e.mSize.width - desiredSize->width,
292
0
                               e.mSize.height - desiredSize->height);
293
0
      if (!mDirEntry ||
294
0
          (e.mBitCount >= mDirEntry->mBitCount &&
295
0
           ((bestDelta < 0 && delta >= bestDelta) ||
296
0
            (delta >= 0 && delta <= bestDelta)))) {
297
0
        mDirEntry = &e;
298
0
        bestDelta = delta;
299
0
      }
300
0
    }
301
0
  }
302
0
303
0
  MOZ_ASSERT(mDirEntry);
304
0
  MOZ_ASSERT(biggestEntry);
305
0
306
0
  // If this is a cursor, set the hotspot. We use the hotspot from the biggest
307
0
  // resource since we also use that resource for the intrinsic size.
308
0
  if (mIsCursor) {
309
0
    mImageMetadata.SetHotspot(biggestEntry->mXHotspot,
310
0
                              biggestEntry->mYHotspot);
311
0
  }
312
0
313
0
  // We always report the biggest resource's size as the intrinsic size; this
314
0
  // is necessary for downscale-during-decode to work since we won't even
315
0
  // attempt to *upscale* while decoding.
316
0
  PostSize(biggestEntry->mSize.width, biggestEntry->mSize.height);
317
0
  if (HasError()) {
318
0
    return Transition::TerminateFailure();
319
0
  }
320
0
321
0
  if (IsMetadataDecode()) {
322
0
    return Transition::TerminateSuccess();
323
0
  }
324
0
325
0
  // If the resource we selected matches the output size perfectly, we don't
326
0
  // need to do any downscaling.
327
0
  if (mDirEntry->mSize == OutputSize()) {
328
0
    MOZ_ASSERT_IF(desiredSize, mDirEntry->mSize == *desiredSize);
329
0
    MOZ_ASSERT_IF(!desiredSize, mDirEntry->mSize == Size());
330
0
    mDownscaler.reset();
331
0
  }
332
0
333
0
  size_t offsetToResource = mDirEntry->mImageOffset - FirstResourceOffset();
334
0
  return Transition::ToUnbuffered(ICOState::FOUND_RESOURCE,
335
0
                                  ICOState::SKIP_TO_RESOURCE,
336
0
                                  offsetToResource);
337
0
}
338
339
LexerTransition<ICOState>
340
nsICODecoder::SniffResource(const char* aData)
341
0
{
342
0
  MOZ_ASSERT(mDirEntry);
343
0
344
0
  // We have BITMAPINFOSIZE bytes buffered at this point. We know an embedded
345
0
  // BMP will have at least that many bytes by definition. We can also infer
346
0
  // that any valid embedded PNG will contain that many bytes as well because:
347
0
  //    BITMAPINFOSIZE
348
0
  //      <
349
0
  //    signature (8 bytes) +
350
0
  //    IHDR (12 bytes header + 13 bytes data)
351
0
  //    IDAT (12 bytes header)
352
0
353
0
  // We use the first PNGSIGNATURESIZE bytes to determine whether this resource
354
0
  // is a PNG or a BMP.
355
0
  bool isPNG = !memcmp(aData, nsPNGDecoder::pngSignatureBytes,
356
0
                       PNGSIGNATURESIZE);
357
0
  if (isPNG) {
358
0
    if (mDirEntry->mBytesInRes <= BITMAPINFOSIZE) {
359
0
      return Transition::TerminateFailure();
360
0
    }
361
0
362
0
    // Prepare a new iterator for the contained decoder to advance as it wills.
363
0
    // Cloning at the point ensures it will begin at the resource offset.
364
0
    Maybe<SourceBufferIterator> containedIterator
365
0
      = mLexer.Clone(*mIterator, mDirEntry->mBytesInRes);
366
0
    if (containedIterator.isNothing()) {
367
0
      return Transition::TerminateFailure();
368
0
    }
369
0
370
0
    // Create a PNG decoder which will do the rest of the work for us.
371
0
    bool metadataDecode = mReturnIterator.isSome();
372
0
    Maybe<IntSize> expectedSize = metadataDecode ? Nothing()
373
0
                                                 : Some(mDirEntry->mSize);
374
0
    mContainedDecoder =
375
0
      DecoderFactory::CreateDecoderForICOResource(DecoderType::PNG,
376
0
                                                  std::move(containedIterator.ref()),
377
0
                                                  WrapNotNull(this),
378
0
                                                  metadataDecode,
379
0
                                                  expectedSize);
380
0
381
0
    // Read in the rest of the PNG unbuffered.
382
0
    size_t toRead = mDirEntry->mBytesInRes - BITMAPINFOSIZE;
383
0
    return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE,
384
0
                                    ICOState::READ_RESOURCE,
385
0
                                    toRead);
386
0
  } else {
387
0
    // Make sure we have a sane size for the bitmap information header.
388
0
    int32_t bihSize = LittleEndian::readUint32(aData);
389
0
    if (bihSize != static_cast<int32_t>(BITMAPINFOSIZE)) {
390
0
      return Transition::TerminateFailure();
391
0
    }
392
0
393
0
    // Read in the rest of the bitmap information header.
394
0
    return ReadBIH(aData);
395
0
  }
396
0
}
397
398
LexerTransition<ICOState>
399
nsICODecoder::ReadResource()
400
0
{
401
0
  if (!FlushContainedDecoder()) {
402
0
    return Transition::TerminateFailure();
403
0
  }
404
0
405
0
  return Transition::ContinueUnbuffered(ICOState::READ_RESOURCE);
406
0
}
407
408
LexerTransition<ICOState>
409
nsICODecoder::ReadBIH(const char* aData)
410
0
{
411
0
  MOZ_ASSERT(mDirEntry);
412
0
413
0
  // Extract the BPP from the BIH header; it should be trusted over the one
414
0
  // we have from the ICO header which is usually set to 0.
415
0
  mBPP = LittleEndian::readUint16(aData + 14);
416
0
417
0
  // Check to make sure we have valid color settings.
418
0
  uint16_t numColors = GetNumColors();
419
0
  if (numColors == uint16_t(-1)) {
420
0
    return Transition::TerminateFailure();
421
0
  }
422
0
423
0
  // The color table is present only if BPP is <= 8.
424
0
  MOZ_ASSERT_IF(mBPP > 8, numColors == 0);
425
0
426
0
  // The ICO format when containing a BMP does not include the 14 byte
427
0
  // bitmap file header. So we create the BMP decoder via the constructor that
428
0
  // tells it to skip this, and pass in the required data (dataOffset) that
429
0
  // would have been present in the header.
430
0
  uint32_t dataOffset = bmp::FILE_HEADER_LENGTH + BITMAPINFOSIZE + 4 * numColors;
431
0
432
0
  // Prepare a new iterator for the contained decoder to advance as it wills.
433
0
  // Cloning at the point ensures it will begin at the resource offset.
434
0
  Maybe<SourceBufferIterator> containedIterator
435
0
    = mLexer.Clone(*mIterator, mDirEntry->mBytesInRes);
436
0
  if (containedIterator.isNothing()) {
437
0
    return Transition::TerminateFailure();
438
0
  }
439
0
440
0
  // Create a BMP decoder which will do most of the work for us; the exception
441
0
  // is the AND mask, which isn't present in standalone BMPs.
442
0
  bool metadataDecode = mReturnIterator.isSome();
443
0
  Maybe<IntSize> expectedSize = metadataDecode ? Nothing()
444
0
                                               : Some(mDirEntry->mSize);
445
0
  mContainedDecoder =
446
0
    DecoderFactory::CreateDecoderForICOResource(DecoderType::BMP,
447
0
                                                std::move(containedIterator.ref()),
448
0
                                                WrapNotNull(this),
449
0
                                                metadataDecode,
450
0
                                                expectedSize,
451
0
                                                Some(dataOffset));
452
0
453
0
  RefPtr<nsBMPDecoder> bmpDecoder =
454
0
    static_cast<nsBMPDecoder*>(mContainedDecoder.get());
455
0
456
0
  // Ensure the decoder has parsed at least the BMP's bitmap info header.
457
0
  if (!FlushContainedDecoder()) {
458
0
    return Transition::TerminateFailure();
459
0
  }
460
0
461
0
  // If this is a metadata decode, FinishResource will any necessary checks.
462
0
  if (mContainedDecoder->IsMetadataDecode()) {
463
0
    return Transition::To(ICOState::FINISHED_RESOURCE, 0);
464
0
  }
465
0
466
0
  // Do we have an AND mask on this BMP? If so, we need to read it after we read
467
0
  // the BMP data itself.
468
0
  uint32_t bmpDataLength = bmpDecoder->GetCompressedImageSize() + 4 * numColors;
469
0
  bool hasANDMask = (BITMAPINFOSIZE + bmpDataLength) < mDirEntry->mBytesInRes;
470
0
  ICOState afterBMPState = hasANDMask ? ICOState::PREPARE_FOR_MASK
471
0
                                      : ICOState::FINISHED_RESOURCE;
472
0
473
0
  // Read in the rest of the BMP unbuffered.
474
0
  return Transition::ToUnbuffered(afterBMPState,
475
0
                                  ICOState::READ_RESOURCE,
476
0
                                  bmpDataLength);
477
0
}
478
479
LexerTransition<ICOState>
480
nsICODecoder::PrepareForMask()
481
0
{
482
0
  MOZ_ASSERT(mDirEntry);
483
0
  MOZ_ASSERT(mContainedDecoder->GetDecodeDone());
484
0
485
0
  // We have received all of the data required by the BMP decoder so flushing
486
0
  // here guarantees the decode has finished.
487
0
  if (!FlushContainedDecoder()) {
488
0
    return Transition::TerminateFailure();
489
0
  }
490
0
491
0
  MOZ_ASSERT(mContainedDecoder->GetDecodeDone());
492
0
493
0
  RefPtr<nsBMPDecoder> bmpDecoder =
494
0
    static_cast<nsBMPDecoder*>(mContainedDecoder.get());
495
0
496
0
  uint16_t numColors = GetNumColors();
497
0
  MOZ_ASSERT(numColors != uint16_t(-1));
498
0
499
0
  // Determine the length of the AND mask.
500
0
  uint32_t bmpLengthWithHeader =
501
0
    BITMAPINFOSIZE + bmpDecoder->GetCompressedImageSize() + 4 * numColors;
502
0
  MOZ_ASSERT(bmpLengthWithHeader < mDirEntry->mBytesInRes);
503
0
  uint32_t maskLength = mDirEntry->mBytesInRes - bmpLengthWithHeader;
504
0
505
0
  // If the BMP provides its own transparency, we ignore the AND mask.
506
0
  if (bmpDecoder->HasTransparency()) {
507
0
    return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE,
508
0
                                    ICOState::SKIP_MASK,
509
0
                                    maskLength);
510
0
  }
511
0
512
0
  // Compute the row size for the mask.
513
0
  mMaskRowSize = ((mDirEntry->mSize.width + 31) / 32) * 4; // + 31 to round up
514
0
515
0
  // If the expected size of the AND mask is larger than its actual size, then
516
0
  // we must have a truncated (and therefore corrupt) AND mask.
517
0
  uint32_t expectedLength = mMaskRowSize * mDirEntry->mSize.height;
518
0
  if (maskLength < expectedLength) {
519
0
    return Transition::TerminateFailure();
520
0
  }
521
0
522
0
  // If we're downscaling, the mask is the wrong size for the surface we've
523
0
  // produced, so we need to downscale the mask into a temporary buffer and then
524
0
  // combine the mask's alpha values with the color values from the image.
525
0
  if (mDownscaler) {
526
0
    MOZ_ASSERT(bmpDecoder->GetImageDataLength() ==
527
0
                 mDownscaler->TargetSize().width *
528
0
                 mDownscaler->TargetSize().height *
529
0
                 sizeof(uint32_t));
530
0
    mMaskBuffer = MakeUnique<uint8_t[]>(bmpDecoder->GetImageDataLength());
531
0
    nsresult rv = mDownscaler->BeginFrame(mDirEntry->mSize, Nothing(),
532
0
                                          mMaskBuffer.get(),
533
0
                                          /* aHasAlpha = */ true,
534
0
                                          /* aFlipVertically = */ true);
535
0
    if (NS_FAILED(rv)) {
536
0
      return Transition::TerminateFailure();
537
0
    }
538
0
  }
539
0
540
0
  mCurrMaskLine = mDirEntry->mSize.height;
541
0
  return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize);
542
0
}
543
544
545
LexerTransition<ICOState>
546
nsICODecoder::ReadMaskRow(const char* aData)
547
0
{
548
0
  MOZ_ASSERT(mDirEntry);
549
0
550
0
  mCurrMaskLine--;
551
0
552
0
  uint8_t sawTransparency = 0;
553
0
554
0
  // Get the mask row we're reading.
555
0
  const uint8_t* mask = reinterpret_cast<const uint8_t*>(aData);
556
0
  const uint8_t* maskRowEnd = mask + mMaskRowSize;
557
0
558
0
  // Get the corresponding row of the mask buffer (if we're downscaling) or the
559
0
  // decoded image data (if we're not).
560
0
  uint32_t* decoded = nullptr;
561
0
  if (mDownscaler) {
562
0
    // Initialize the row to all white and fully opaque.
563
0
    memset(mDownscaler->RowBuffer(), 0xFF, mDirEntry->mSize.width * sizeof(uint32_t));
564
0
565
0
    decoded = reinterpret_cast<uint32_t*>(mDownscaler->RowBuffer());
566
0
  } else {
567
0
    RefPtr<nsBMPDecoder> bmpDecoder =
568
0
      static_cast<nsBMPDecoder*>(mContainedDecoder.get());
569
0
    uint32_t* imageData = bmpDecoder->GetImageData();
570
0
    if (!imageData) {
571
0
      return Transition::TerminateFailure();
572
0
    }
573
0
574
0
    decoded = imageData + mCurrMaskLine * mDirEntry->mSize.width;
575
0
  }
576
0
577
0
  MOZ_ASSERT(decoded);
578
0
  uint32_t* decodedRowEnd = decoded + mDirEntry->mSize.width;
579
0
580
0
  // Iterate simultaneously through the AND mask and the image data.
581
0
  while (mask < maskRowEnd) {
582
0
    uint8_t idx = *mask++;
583
0
    sawTransparency |= idx;
584
0
    for (uint8_t bit = 0x80; bit && decoded < decodedRowEnd; bit >>= 1) {
585
0
      // Clear pixel completely for transparency.
586
0
      if (idx & bit) {
587
0
        *decoded = 0;
588
0
      }
589
0
      decoded++;
590
0
    }
591
0
  }
592
0
593
0
  if (mDownscaler) {
594
0
    mDownscaler->CommitRow();
595
0
  }
596
0
597
0
  // If any bits are set in sawTransparency, then we know at least one pixel was
598
0
  // transparent.
599
0
  if (sawTransparency) {
600
0
    mHasMaskAlpha = true;
601
0
  }
602
0
603
0
  if (mCurrMaskLine == 0) {
604
0
    return Transition::To(ICOState::FINISH_MASK, 0);
605
0
  }
606
0
607
0
  return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize);
608
0
}
609
610
LexerTransition<ICOState>
611
nsICODecoder::FinishMask()
612
0
{
613
0
  // If we're downscaling, we now have the appropriate alpha values in
614
0
  // mMaskBuffer. We just need to transfer them to the image.
615
0
  if (mDownscaler) {
616
0
    // Retrieve the image data.
617
0
    RefPtr<nsBMPDecoder> bmpDecoder =
618
0
      static_cast<nsBMPDecoder*>(mContainedDecoder.get());
619
0
    uint8_t* imageData = reinterpret_cast<uint8_t*>(bmpDecoder->GetImageData());
620
0
    if (!imageData) {
621
0
      return Transition::TerminateFailure();
622
0
    }
623
0
624
0
    // Iterate through the alpha values, copying from mask to image.
625
0
    MOZ_ASSERT(mMaskBuffer);
626
0
    MOZ_ASSERT(bmpDecoder->GetImageDataLength() > 0);
627
0
    for (size_t i = 3 ; i < bmpDecoder->GetImageDataLength() ; i += 4) {
628
0
      imageData[i] = mMaskBuffer[i];
629
0
    }
630
0
    int32_t stride = mDownscaler->TargetSize().width * sizeof(uint32_t);
631
0
    DebugOnly<bool> ret =
632
0
    // We know the format is B8G8R8A8 because we always assume bmp's inside
633
0
    // ico's are transparent.
634
0
      PremultiplyData(imageData, stride, SurfaceFormat::B8G8R8A8,
635
0
        imageData, stride, SurfaceFormat::B8G8R8A8, mDownscaler->TargetSize());
636
0
    MOZ_ASSERT(ret);
637
0
  }
638
0
639
0
  return Transition::To(ICOState::FINISHED_RESOURCE, 0);
640
0
}
641
642
LexerTransition<ICOState>
643
nsICODecoder::FinishResource()
644
0
{
645
0
  MOZ_ASSERT(mDirEntry);
646
0
647
0
  // We have received all of the data required by the PNG/BMP decoder so
648
0
  // flushing here guarantees the decode has finished.
649
0
  if (!FlushContainedDecoder()) {
650
0
    return Transition::TerminateFailure();
651
0
  }
652
0
653
0
  MOZ_ASSERT(mContainedDecoder->GetDecodeDone());
654
0
655
0
  // If it is a metadata decode, all we were trying to get was the size
656
0
  // information missing from the dir entry.
657
0
  if (mContainedDecoder->IsMetadataDecode()) {
658
0
    if (mContainedDecoder->HasSize()) {
659
0
      mDirEntry->mSize = mContainedDecoder->Size();
660
0
    }
661
0
    return Transition::To(ICOState::ITERATE_UNSIZED_DIR_ENTRY, 0);
662
0
  }
663
0
664
0
  // Raymond Chen says that 32bpp only are valid PNG ICOs
665
0
  // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
666
0
  if (!mContainedDecoder->IsValidICOResource()) {
667
0
    return Transition::TerminateFailure();
668
0
  }
669
0
670
0
  // This size from the resource should match that from the dir entry.
671
0
  MOZ_ASSERT_IF(mContainedDecoder->HasSize(),
672
0
                mContainedDecoder->Size() == mDirEntry->mSize);
673
0
674
0
  return Transition::TerminateSuccess();
675
0
}
676
677
LexerResult
678
nsICODecoder::DoDecode(SourceBufferIterator& aIterator, IResumable* aOnResume)
679
0
{
680
0
  MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
681
0
682
0
  return mLexer.Lex(aIterator, aOnResume,
683
0
                    [=](ICOState aState, const char* aData, size_t aLength) {
684
0
    switch (aState) {
685
0
      case ICOState::HEADER:
686
0
        return ReadHeader(aData);
687
0
      case ICOState::DIR_ENTRY:
688
0
        return ReadDirEntry(aData);
689
0
      case ICOState::FINISHED_DIR_ENTRY:
690
0
        return FinishDirEntry();
691
0
      case ICOState::ITERATE_UNSIZED_DIR_ENTRY:
692
0
        return IterateUnsizedDirEntry();
693
0
      case ICOState::SKIP_TO_RESOURCE:
694
0
        return Transition::ContinueUnbuffered(ICOState::SKIP_TO_RESOURCE);
695
0
      case ICOState::FOUND_RESOURCE:
696
0
        return Transition::To(ICOState::SNIFF_RESOURCE, BITMAPINFOSIZE);
697
0
      case ICOState::SNIFF_RESOURCE:
698
0
        return SniffResource(aData);
699
0
      case ICOState::READ_RESOURCE:
700
0
        return ReadResource();
701
0
      case ICOState::PREPARE_FOR_MASK:
702
0
        return PrepareForMask();
703
0
      case ICOState::READ_MASK_ROW:
704
0
        return ReadMaskRow(aData);
705
0
      case ICOState::FINISH_MASK:
706
0
        return FinishMask();
707
0
      case ICOState::SKIP_MASK:
708
0
        return Transition::ContinueUnbuffered(ICOState::SKIP_MASK);
709
0
      case ICOState::FINISHED_RESOURCE:
710
0
        return FinishResource();
711
0
      default:
712
0
        MOZ_CRASH("Unknown ICOState");
713
0
    }
714
0
  });
715
0
}
716
717
bool
718
nsICODecoder::FlushContainedDecoder()
719
0
{
720
0
  MOZ_ASSERT(mContainedDecoder);
721
0
722
0
  bool succeeded = true;
723
0
724
0
  // If we run out of data, the ICO decoder will get resumed when there's more
725
0
  // data available, as usual, so we don't need the contained decoder to get
726
0
  // resumed too. To avoid that, we provide an IResumable which just does
727
0
  // nothing. All the caller needs to do is flush when there is new data.
728
0
  LexerResult result = mContainedDecoder->Decode();
729
0
  if (result == LexerResult(TerminalState::FAILURE)) {
730
0
    succeeded = false;
731
0
  }
732
0
733
0
  MOZ_ASSERT(result != LexerResult(Yield::OUTPUT_AVAILABLE),
734
0
             "Unexpected yield");
735
0
736
0
  // Make our state the same as the state of the contained decoder, and
737
0
  // propagate errors.
738
0
  mProgress |= mContainedDecoder->TakeProgress();
739
0
  mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect());
740
0
  if (mContainedDecoder->HasError()) {
741
0
    succeeded = false;
742
0
  }
743
0
744
0
  return succeeded;
745
0
}
746
747
} // namespace image
748
} // namespace mozilla