Thu, 15 Jan 2015 15:59:08 +0100
Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
1 /* vim:set tw=80 expandtab softtabstop=4 ts=4 sw=4: */
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/. */
6 /* This is a Cross-Platform ICO Decoder, which should work everywhere, including
7 * Big-Endian machines like the PowerPC. */
9 #include <stdlib.h>
11 #include "mozilla/Endian.h"
12 #include "nsICODecoder.h"
14 #include "RasterImage.h"
16 namespace mozilla {
17 namespace image {
19 #define ICONCOUNTOFFSET 4
20 #define DIRENTRYOFFSET 6
21 #define BITMAPINFOSIZE 40
22 #define PREFICONSIZE 16
24 // ----------------------------------------
25 // Actual Data Processing
26 // ----------------------------------------
28 uint32_t
29 nsICODecoder::CalcAlphaRowSize()
30 {
31 // Calculate rowsize in DWORD's and then return in # of bytes
32 uint32_t rowSize = (GetRealWidth() + 31) / 32; // + 31 to round up
33 return rowSize * 4; // Return rowSize in bytes
34 }
36 // Obtains the number of colors from the bits per pixel
37 uint16_t
38 nsICODecoder::GetNumColors()
39 {
40 uint16_t numColors = 0;
41 if (mBPP <= 8) {
42 switch (mBPP) {
43 case 1:
44 numColors = 2;
45 break;
46 case 4:
47 numColors = 16;
48 break;
49 case 8:
50 numColors = 256;
51 break;
52 default:
53 numColors = (uint16_t)-1;
54 }
55 }
56 return numColors;
57 }
60 nsICODecoder::nsICODecoder(RasterImage &aImage)
61 : Decoder(aImage)
62 {
63 mPos = mImageOffset = mCurrIcon = mNumIcons = mBPP = mRowBytes = 0;
64 mIsPNG = false;
65 mRow = nullptr;
66 mOldLine = mCurLine = 1; // Otherwise decoder will never start
67 }
69 nsICODecoder::~nsICODecoder()
70 {
71 if (mRow) {
72 moz_free(mRow);
73 }
74 }
76 void
77 nsICODecoder::FinishInternal()
78 {
79 // We shouldn't be called in error cases
80 NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call FinishInternal after error!");
82 // Finish the internally used decoder as well
83 if (mContainedDecoder) {
84 mContainedDecoder->FinishSharedDecoder();
85 mDecodeDone = mContainedDecoder->GetDecodeDone();
86 }
87 }
89 // Returns a buffer filled with the bitmap file header in little endian:
90 // Signature 2 bytes 'BM'
91 // FileSize 4 bytes File size in bytes
92 // reserved 4 bytes unused (=0)
93 // DataOffset 4 bytes File offset to Raster Data
94 // Returns true if successful
95 bool nsICODecoder::FillBitmapFileHeaderBuffer(int8_t *bfh)
96 {
97 memset(bfh, 0, 14);
98 bfh[0] = 'B';
99 bfh[1] = 'M';
100 int32_t dataOffset = 0;
101 int32_t fileSize = 0;
102 dataOffset = BFH_LENGTH + BITMAPINFOSIZE;
104 // The color table is present only if BPP is <= 8
105 if (mDirEntry.mBitCount <= 8) {
106 uint16_t numColors = GetNumColors();
107 if (numColors == (uint16_t)-1) {
108 return false;
109 }
110 dataOffset += 4 * numColors;
111 fileSize = dataOffset + GetRealWidth() * GetRealHeight();
112 } else {
113 fileSize = dataOffset + (mDirEntry.mBitCount * GetRealWidth() *
114 GetRealHeight()) / 8;
115 }
117 NativeEndian::swapToLittleEndianInPlace(&fileSize, 1);
118 memcpy(bfh + 2, &fileSize, sizeof(fileSize));
119 NativeEndian::swapToLittleEndianInPlace(&dataOffset, 1);
120 memcpy(bfh + 10, &dataOffset, sizeof(dataOffset));
121 return true;
122 }
124 // A BMP inside of an ICO has *2 height because of the AND mask
125 // that follows the actual bitmap. The BMP shouldn't know about
126 // this difference though.
127 bool
128 nsICODecoder::FixBitmapHeight(int8_t *bih)
129 {
130 // Get the height from the BMP file information header
131 int32_t height;
132 memcpy(&height, bih + 8, sizeof(height));
133 NativeEndian::swapFromLittleEndianInPlace(&height, 1);
134 // BMPs can be stored inverted by having a negative height
135 height = abs(height);
137 // The bitmap height is by definition * 2 what it should be to account for
138 // the 'AND mask'. It is * 2 even if the `AND mask` is not present.
139 height /= 2;
141 if (height > 256) {
142 return false;
143 }
145 // We should always trust the height from the bitmap itself instead of
146 // the ICO height. So fix the ICO height.
147 if (height == 256) {
148 mDirEntry.mHeight = 0;
149 } else {
150 mDirEntry.mHeight = (int8_t)height;
151 }
153 // Fix the BMP height in the BIH so that the BMP decoder can work properly
154 NativeEndian::swapToLittleEndianInPlace(&height, 1);
155 memcpy(bih + 8, &height, sizeof(height));
156 return true;
157 }
159 // We should always trust the contained resource for the width
160 // information over our own information.
161 bool
162 nsICODecoder::FixBitmapWidth(int8_t *bih)
163 {
164 // Get the width from the BMP file information header
165 int32_t width;
166 memcpy(&width, bih + 4, sizeof(width));
167 NativeEndian::swapFromLittleEndianInPlace(&width, 1);
168 if (width > 256) {
169 return false;
170 }
172 // We should always trust the width from the bitmap itself instead of
173 // the ICO width.
174 if (width == 256) {
175 mDirEntry.mWidth = 0;
176 } else {
177 mDirEntry.mWidth = (int8_t)width;
178 }
179 return true;
180 }
182 // The BMP information header's bits per pixel should be trusted
183 // more than what we have. Usually the ICO's BPP is set to 0
184 int32_t
185 nsICODecoder::ExtractBPPFromBitmap(int8_t *bih)
186 {
187 int32_t bitsPerPixel;
188 memcpy(&bitsPerPixel, bih + 14, sizeof(bitsPerPixel));
189 NativeEndian::swapFromLittleEndianInPlace(&bitsPerPixel, 1);
190 return bitsPerPixel;
191 }
193 int32_t
194 nsICODecoder::ExtractBIHSizeFromBitmap(int8_t *bih)
195 {
196 int32_t headerSize;
197 memcpy(&headerSize, bih, sizeof(headerSize));
198 NativeEndian::swapFromLittleEndianInPlace(&headerSize, 1);
199 return headerSize;
200 }
202 void
203 nsICODecoder::SetHotSpotIfCursor() {
204 if (!mIsCursor) {
205 return;
206 }
208 mImageMetadata.SetHotspot(mDirEntry.mXHotspot, mDirEntry.mYHotspot);
209 }
211 void
212 nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy)
213 {
214 NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
216 if (!aCount) {
217 if (mContainedDecoder) {
218 WriteToContainedDecoder(aBuffer, aCount, aStrategy);
219 }
220 return;
221 }
223 while (aCount && (mPos < ICONCOUNTOFFSET)) { // Skip to the # of icons.
224 if (mPos == 2) { // if the third byte is 1: This is an icon, 2: a cursor
225 if ((*aBuffer != 1) && (*aBuffer != 2)) {
226 PostDataError();
227 return;
228 }
229 mIsCursor = (*aBuffer == 2);
230 }
231 mPos++; aBuffer++; aCount--;
232 }
234 if (mPos == ICONCOUNTOFFSET && aCount >= 2) {
235 mNumIcons = LittleEndian::readUint16(reinterpret_cast<const uint16_t*>(aBuffer));
236 aBuffer += 2;
237 mPos += 2;
238 aCount -= 2;
239 }
241 if (mNumIcons == 0)
242 return; // Nothing to do.
244 uint16_t colorDepth = 0;
245 nsIntSize prefSize = mImage.GetRequestedResolution();
246 if (prefSize.width == 0 && prefSize.height == 0) {
247 prefSize.SizeTo(PREFICONSIZE, PREFICONSIZE);
248 }
250 // A measure of the difference in size between the entry we've found
251 // and the requested size. We will choose the smallest image that is
252 // >= requested size (i.e. we assume it's better to downscale a larger
253 // icon than to upscale a smaller one).
254 int32_t diff = INT_MIN;
256 // Loop through each entry's dir entry
257 while (mCurrIcon < mNumIcons) {
258 if (mPos >= DIRENTRYOFFSET + (mCurrIcon * sizeof(mDirEntryArray)) &&
259 mPos < DIRENTRYOFFSET + ((mCurrIcon + 1) * sizeof(mDirEntryArray))) {
260 uint32_t toCopy = sizeof(mDirEntryArray) -
261 (mPos - DIRENTRYOFFSET - mCurrIcon * sizeof(mDirEntryArray));
262 if (toCopy > aCount) {
263 toCopy = aCount;
264 }
265 memcpy(mDirEntryArray + sizeof(mDirEntryArray) - toCopy, aBuffer, toCopy);
266 mPos += toCopy;
267 aCount -= toCopy;
268 aBuffer += toCopy;
269 }
270 if (aCount == 0)
271 return; // Need more data
273 IconDirEntry e;
274 if (mPos == (DIRENTRYOFFSET + ICODIRENTRYSIZE) +
275 (mCurrIcon * sizeof(mDirEntryArray))) {
276 mCurrIcon++;
277 ProcessDirEntry(e);
278 // We can't use GetRealWidth and GetRealHeight here because those operate
279 // on mDirEntry, here we are going through each item in the directory.
280 // Calculate the delta between this image's size and the desired size,
281 // so we can see if it is better than our current-best option.
282 // In the case of several equally-good images, we use the last one.
283 int32_t delta = (e.mWidth == 0 ? 256 : e.mWidth) - prefSize.width +
284 (e.mHeight == 0 ? 256 : e.mHeight) - prefSize.height;
285 if (e.mBitCount >= colorDepth &&
286 ((diff < 0 && delta >= diff) || (delta >= 0 && delta <= diff))) {
287 diff = delta;
288 mImageOffset = e.mImageOffset;
290 // ensure mImageOffset is >= size of the direntry headers (bug #245631)
291 uint32_t minImageOffset = DIRENTRYOFFSET +
292 mNumIcons * sizeof(mDirEntryArray);
293 if (mImageOffset < minImageOffset) {
294 PostDataError();
295 return;
296 }
298 colorDepth = e.mBitCount;
299 memcpy(&mDirEntry, &e, sizeof(IconDirEntry));
300 }
301 }
302 }
304 if (mPos < mImageOffset) {
305 // Skip to (or at least towards) the desired image offset
306 uint32_t toSkip = mImageOffset - mPos;
307 if (toSkip > aCount)
308 toSkip = aCount;
310 mPos += toSkip;
311 aBuffer += toSkip;
312 aCount -= toSkip;
313 }
315 // If we are within the first PNGSIGNATURESIZE bytes of the image data,
316 // then we have either a BMP or a PNG. We use the first PNGSIGNATURESIZE
317 // bytes to determine which one we have.
318 if (mCurrIcon == mNumIcons && mPos >= mImageOffset &&
319 mPos < mImageOffset + PNGSIGNATURESIZE)
320 {
321 uint32_t toCopy = PNGSIGNATURESIZE - (mPos - mImageOffset);
322 if (toCopy > aCount) {
323 toCopy = aCount;
324 }
326 memcpy(mSignature + (mPos - mImageOffset), aBuffer, toCopy);
327 mPos += toCopy;
328 aCount -= toCopy;
329 aBuffer += toCopy;
331 mIsPNG = !memcmp(mSignature, nsPNGDecoder::pngSignatureBytes,
332 PNGSIGNATURESIZE);
333 if (mIsPNG) {
334 mContainedDecoder = new nsPNGDecoder(mImage);
335 mContainedDecoder->SetObserver(mObserver);
336 mContainedDecoder->SetSizeDecode(IsSizeDecode());
337 mContainedDecoder->InitSharedDecoder(mImageData, mImageDataLength,
338 mColormap, mColormapSize,
339 mCurrentFrame);
340 if (!WriteToContainedDecoder(mSignature, PNGSIGNATURESIZE, aStrategy)) {
341 return;
342 }
343 }
344 }
346 // If we have a PNG, let the PNG decoder do all of the rest of the work
347 if (mIsPNG && mContainedDecoder && mPos >= mImageOffset + PNGSIGNATURESIZE) {
348 if (!WriteToContainedDecoder(aBuffer, aCount, aStrategy)) {
349 return;
350 }
352 if (!HasSize() && mContainedDecoder->HasSize()) {
353 PostSize(mContainedDecoder->GetImageMetadata().GetWidth(),
354 mContainedDecoder->GetImageMetadata().GetHeight());
355 }
357 mPos += aCount;
358 aBuffer += aCount;
359 aCount = 0;
361 // Raymond Chen says that 32bpp only are valid PNG ICOs
362 // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
363 if (!IsSizeDecode() &&
364 !static_cast<nsPNGDecoder*>(mContainedDecoder.get())->IsValidICO()) {
365 PostDataError();
366 }
367 return;
368 }
370 // We've processed all of the icon dir entries and are within the
371 // bitmap info size
372 if (!mIsPNG && mCurrIcon == mNumIcons && mPos >= mImageOffset &&
373 mPos >= mImageOffset + PNGSIGNATURESIZE &&
374 mPos < mImageOffset + BITMAPINFOSIZE) {
376 // As we were decoding, we did not know if we had a PNG signature or the
377 // start of a bitmap information header. At this point we know we had
378 // a bitmap information header and not a PNG signature, so fill the bitmap
379 // information header with the data it should already have.
380 memcpy(mBIHraw, mSignature, PNGSIGNATURESIZE);
382 // We've found the icon.
383 uint32_t toCopy = sizeof(mBIHraw) - (mPos - mImageOffset);
384 if (toCopy > aCount)
385 toCopy = aCount;
387 memcpy(mBIHraw + (mPos - mImageOffset), aBuffer, toCopy);
388 mPos += toCopy;
389 aCount -= toCopy;
390 aBuffer += toCopy;
391 }
393 // If we have a BMP inside the ICO and we have read the BIH header
394 if (!mIsPNG && mPos == mImageOffset + BITMAPINFOSIZE) {
396 // Make sure we have a sane value for the bitmap information header
397 int32_t bihSize = ExtractBIHSizeFromBitmap(reinterpret_cast<int8_t*>(mBIHraw));
398 if (bihSize != BITMAPINFOSIZE) {
399 PostDataError();
400 return;
401 }
402 // We are extracting the BPP from the BIH header as it should be trusted
403 // over the one we have from the icon header
404 mBPP = ExtractBPPFromBitmap(reinterpret_cast<int8_t*>(mBIHraw));
406 // Init the bitmap decoder which will do most of the work for us
407 // It will do everything except the AND mask which isn't present in bitmaps
408 // bmpDecoder is for local scope ease, it will be freed by mContainedDecoder
409 nsBMPDecoder *bmpDecoder = new nsBMPDecoder(mImage);
410 mContainedDecoder = bmpDecoder;
411 bmpDecoder->SetUseAlphaData(true);
412 mContainedDecoder->SetObserver(mObserver);
413 mContainedDecoder->SetSizeDecode(IsSizeDecode());
414 mContainedDecoder->InitSharedDecoder(mImageData, mImageDataLength,
415 mColormap, mColormapSize,
416 mCurrentFrame);
418 // The ICO format when containing a BMP does not include the 14 byte
419 // bitmap file header. To use the code of the BMP decoder we need to
420 // generate this header ourselves and feed it to the BMP decoder.
421 int8_t bfhBuffer[BMPFILEHEADERSIZE];
422 if (!FillBitmapFileHeaderBuffer(bfhBuffer)) {
423 PostDataError();
424 return;
425 }
426 if (!WriteToContainedDecoder((const char*)bfhBuffer, sizeof(bfhBuffer), aStrategy)) {
427 return;
428 }
430 // Setup the cursor hot spot if one is present
431 SetHotSpotIfCursor();
433 // Fix the ICO height from the BIH.
434 // Fix the height on the BIH to be /2 so our BMP decoder will understand.
435 if (!FixBitmapHeight(reinterpret_cast<int8_t*>(mBIHraw))) {
436 PostDataError();
437 return;
438 }
440 // Fix the ICO width from the BIH.
441 if (!FixBitmapWidth(reinterpret_cast<int8_t*>(mBIHraw))) {
442 PostDataError();
443 return;
444 }
446 // Write out the BMP's bitmap info header
447 if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw), aStrategy)) {
448 return;
449 }
451 PostSize(mContainedDecoder->GetImageMetadata().GetWidth(),
452 mContainedDecoder->GetImageMetadata().GetHeight());
454 // We have the size. If we're doing a size decode, we got what
455 // we came for.
456 if (IsSizeDecode())
457 return;
459 // Sometimes the ICO BPP header field is not filled out
460 // so we should trust the contained resource over our own
461 // information.
462 mBPP = bmpDecoder->GetBitsPerPixel();
464 // Check to make sure we have valid color settings
465 uint16_t numColors = GetNumColors();
466 if (numColors == (uint16_t)-1) {
467 PostDataError();
468 return;
469 }
470 }
472 // If we have a BMP
473 if (!mIsPNG && mContainedDecoder && mPos >= mImageOffset + BITMAPINFOSIZE) {
474 uint16_t numColors = GetNumColors();
475 if (numColors == (uint16_t)-1) {
476 PostDataError();
477 return;
478 }
479 // Feed the actual image data (not including headers) into the BMP decoder
480 uint32_t bmpDataOffset = mDirEntry.mImageOffset + BITMAPINFOSIZE;
481 uint32_t bmpDataEnd = mDirEntry.mImageOffset + BITMAPINFOSIZE +
482 static_cast<nsBMPDecoder*>(mContainedDecoder.get())->GetCompressedImageSize() +
483 4 * numColors;
485 // If we are feeding in the core image data, but we have not yet
486 // reached the ICO's 'AND buffer mask'
487 if (mPos >= bmpDataOffset && mPos < bmpDataEnd) {
489 // Figure out how much data the BMP decoder wants
490 uint32_t toFeed = bmpDataEnd - mPos;
491 if (toFeed > aCount) {
492 toFeed = aCount;
493 }
495 if (!WriteToContainedDecoder(aBuffer, toFeed, aStrategy)) {
496 return;
497 }
499 mPos += toFeed;
500 aCount -= toFeed;
501 aBuffer += toFeed;
502 }
504 // If the bitmap is fully processed, treat any left over data as the ICO's
505 // 'AND buffer mask' which appears after the bitmap resource.
506 if (!mIsPNG && mPos >= bmpDataEnd) {
507 // There may be an optional AND bit mask after the data. This is
508 // only used if the alpha data is not already set. The alpha data
509 // is used for 32bpp bitmaps as per the comment in ICODecoder.h
510 // The alpha mask should be checked in all other cases.
511 if (static_cast<nsBMPDecoder*>(mContainedDecoder.get())->GetBitsPerPixel() != 32 ||
512 !static_cast<nsBMPDecoder*>(mContainedDecoder.get())->HasAlphaData()) {
513 uint32_t rowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up
514 if (mPos == bmpDataEnd) {
515 mPos++;
516 mRowBytes = 0;
517 mCurLine = GetRealHeight();
518 mRow = (uint8_t*)moz_realloc(mRow, rowSize);
519 if (!mRow) {
520 PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
521 return;
522 }
523 }
525 // Ensure memory has been allocated before decoding.
526 NS_ABORT_IF_FALSE(mRow, "mRow is null");
527 if (!mRow) {
528 PostDataError();
529 return;
530 }
532 while (mCurLine > 0 && aCount > 0) {
533 uint32_t toCopy = std::min(rowSize - mRowBytes, aCount);
534 if (toCopy) {
535 memcpy(mRow + mRowBytes, aBuffer, toCopy);
536 aCount -= toCopy;
537 aBuffer += toCopy;
538 mRowBytes += toCopy;
539 }
540 if (rowSize == mRowBytes) {
541 mCurLine--;
542 mRowBytes = 0;
544 uint32_t* imageData =
545 static_cast<nsBMPDecoder*>(mContainedDecoder.get())->GetImageData();
546 if (!imageData) {
547 PostDataError();
548 return;
549 }
550 uint32_t* decoded = imageData + mCurLine * GetRealWidth();
551 uint32_t* decoded_end = decoded + GetRealWidth();
552 uint8_t* p = mRow, *p_end = mRow + rowSize;
553 while (p < p_end) {
554 uint8_t idx = *p++;
555 for (uint8_t bit = 0x80; bit && decoded<decoded_end; bit >>= 1) {
556 // Clear pixel completely for transparency.
557 if (idx & bit) {
558 *decoded = 0;
559 }
560 decoded++;
561 }
562 }
563 }
564 }
565 }
566 }
567 }
568 }
570 bool
571 nsICODecoder::WriteToContainedDecoder(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy)
572 {
573 mContainedDecoder->Write(aBuffer, aCount, aStrategy);
574 if (mContainedDecoder->HasDataError()) {
575 mDataError = mContainedDecoder->HasDataError();
576 }
577 if (mContainedDecoder->HasDecoderError()) {
578 PostDecoderError(mContainedDecoder->GetDecoderError());
579 }
580 return !HasError();
581 }
583 void
584 nsICODecoder::ProcessDirEntry(IconDirEntry& aTarget)
585 {
586 memset(&aTarget, 0, sizeof(aTarget));
587 memcpy(&aTarget.mWidth, mDirEntryArray, sizeof(aTarget.mWidth));
588 memcpy(&aTarget.mHeight, mDirEntryArray + 1, sizeof(aTarget.mHeight));
589 memcpy(&aTarget.mColorCount, mDirEntryArray + 2, sizeof(aTarget.mColorCount));
590 memcpy(&aTarget.mReserved, mDirEntryArray + 3, sizeof(aTarget.mReserved));
591 memcpy(&aTarget.mPlanes, mDirEntryArray + 4, sizeof(aTarget.mPlanes));
592 aTarget.mPlanes = LittleEndian::readUint16(&aTarget.mPlanes);
593 memcpy(&aTarget.mBitCount, mDirEntryArray + 6, sizeof(aTarget.mBitCount));
594 aTarget.mBitCount = LittleEndian::readUint16(&aTarget.mBitCount);
595 memcpy(&aTarget.mBytesInRes, mDirEntryArray + 8, sizeof(aTarget.mBytesInRes));
596 aTarget.mBytesInRes = LittleEndian::readUint32(&aTarget.mBytesInRes);
597 memcpy(&aTarget.mImageOffset, mDirEntryArray + 12,
598 sizeof(aTarget.mImageOffset));
599 aTarget.mImageOffset = LittleEndian::readUint32(&aTarget.mImageOffset);
600 }
602 bool
603 nsICODecoder::NeedsNewFrame() const
604 {
605 if (mContainedDecoder) {
606 return mContainedDecoder->NeedsNewFrame();
607 }
609 return Decoder::NeedsNewFrame();
610 }
612 nsresult
613 nsICODecoder::AllocateFrame()
614 {
615 if (mContainedDecoder) {
616 nsresult rv = mContainedDecoder->AllocateFrame();
617 mCurrentFrame = mContainedDecoder->GetCurrentFrame();
618 return rv;
619 }
621 return Decoder::AllocateFrame();
622 }
624 } // namespace image
625 } // namespace mozilla