tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsICOEncoder.cpp (17072B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #include "nsCRT.h"
      6 #include "mozilla/EndianUtils.h"
      7 #include "nsBMPEncoder.h"
      8 #include "BMPHeaders.h"
      9 #include "nsPNGEncoder.h"
     10 #include "nsICOEncoder.h"
     11 #include "nsString.h"
     12 #include "nsStreamUtils.h"
     13 #include "nsTArray.h"
     14 
     15 using namespace mozilla;
     16 using namespace mozilla::image;
     17 
     18 NS_IMPL_ISUPPORTS(nsICOEncoder, imgIEncoder, nsIInputStream,
     19                  nsIAsyncInputStream)
     20 
     21 nsICOEncoder::nsICOEncoder()
     22    : mICOFileHeader{},
     23      mICODirEntry{},
     24      mImageBufferStart(nullptr),
     25      mImageBufferCurr(0),
     26      mImageBufferSize(0),
     27      mImageBufferReadPoint(0),
     28      mFinished(false),
     29      mUsePNG(true),
     30      mNotifyThreshold(0) {}
     31 
     32 nsICOEncoder::~nsICOEncoder() {
     33  if (mImageBufferStart) {
     34    free(mImageBufferStart);
     35    mImageBufferStart = nullptr;
     36    mImageBufferCurr = nullptr;
     37  }
     38 }
     39 
     40 // nsICOEncoder::InitFromData
     41 // Two output options are supported: format=<png|bmp>;bpp=<bpp_value>
     42 // format specifies whether to use png or bitmap format
     43 // bpp specifies the bits per pixel to use where bpp_value can be 24 or 32
     44 NS_IMETHODIMP
     45 nsICOEncoder::InitFromData(const uint8_t* aData, uint32_t aLength,
     46                           uint32_t aWidth, uint32_t aHeight, uint32_t aStride,
     47                           uint32_t aInputFormat,
     48                           const nsAString& aOutputOptions,
     49                           const nsACString& aRandomizationKey) {
     50  // validate input format
     51  if (aInputFormat != INPUT_FORMAT_RGB && aInputFormat != INPUT_FORMAT_RGBA &&
     52      aInputFormat != INPUT_FORMAT_HOSTARGB) {
     53    return NS_ERROR_INVALID_ARG;
     54  }
     55 
     56  // Stride is the padded width of each row, so it better be longer
     57  if ((aInputFormat == INPUT_FORMAT_RGB && aStride < aWidth * 3) ||
     58      ((aInputFormat == INPUT_FORMAT_RGBA ||
     59        aInputFormat == INPUT_FORMAT_HOSTARGB) &&
     60       aStride < aWidth * 4)) {
     61    NS_WARNING("Invalid stride for InitFromData");
     62    return NS_ERROR_INVALID_ARG;
     63  }
     64 
     65  nsresult rv;
     66  rv = StartImageEncode(aWidth, aHeight, aInputFormat, aOutputOptions);
     67  NS_ENSURE_SUCCESS(rv, rv);
     68 
     69  rv = AddImageFrame(aData, aLength, aWidth, aHeight, aStride, aInputFormat,
     70                     aOutputOptions);
     71  NS_ENSURE_SUCCESS(rv, rv);
     72 
     73  rv = EndImageEncode();
     74  return rv;
     75 }
     76 
     77 // Returns the number of bytes in the image buffer used
     78 // For an ICO file, this is all bytes in the buffer.
     79 NS_IMETHODIMP
     80 nsICOEncoder::GetImageBufferUsed(uint32_t* aOutputSize) {
     81  NS_ENSURE_ARG_POINTER(aOutputSize);
     82  *aOutputSize = mImageBufferSize;
     83  return NS_OK;
     84 }
     85 
     86 // Returns a pointer to the start of the image buffer
     87 NS_IMETHODIMP
     88 nsICOEncoder::GetImageBuffer(char** aOutputBuffer) {
     89  NS_ENSURE_ARG_POINTER(aOutputBuffer);
     90  *aOutputBuffer = reinterpret_cast<char*>(mImageBufferStart);
     91  return NS_OK;
     92 }
     93 
     94 NS_IMETHODIMP
     95 nsICOEncoder::AddImageFrame(const uint8_t* aData, uint32_t aLength,
     96                            uint32_t aWidth, uint32_t aHeight, uint32_t aStride,
     97                            uint32_t aInputFormat,
     98                            const nsAString& aFrameOptions) {
     99  if (mUsePNG) {
    100    mContainedEncoder = new nsPNGEncoder();
    101    nsresult rv;
    102    nsAutoString noParams;
    103    rv = mContainedEncoder->InitFromData(aData, aLength, aWidth, aHeight,
    104                                         aStride, aInputFormat, noParams,
    105                                         VoidCString());
    106    NS_ENSURE_SUCCESS(rv, rv);
    107 
    108    uint32_t PNGImageBufferSize;
    109    mContainedEncoder->GetImageBufferUsed(&PNGImageBufferSize);
    110    mImageBufferSize =
    111        ICONFILEHEADERSIZE + ICODIRENTRYSIZE + PNGImageBufferSize;
    112    mImageBufferStart = static_cast<uint8_t*>(malloc(mImageBufferSize));
    113    if (!mImageBufferStart) {
    114      return NS_ERROR_OUT_OF_MEMORY;
    115    }
    116    mImageBufferCurr = mImageBufferStart;
    117    mICODirEntry.mBytesInRes = PNGImageBufferSize;
    118 
    119    EncodeFileHeader();
    120    EncodeInfoHeader();
    121 
    122    char* imageBuffer;
    123    rv = mContainedEncoder->GetImageBuffer(&imageBuffer);
    124    NS_ENSURE_SUCCESS(rv, rv);
    125    memcpy(mImageBufferCurr, imageBuffer, PNGImageBufferSize);
    126    mImageBufferCurr += PNGImageBufferSize;
    127  } else {
    128    mContainedEncoder = new nsBMPEncoder();
    129    nsresult rv;
    130 
    131    nsAutoString params;
    132    params.AppendLiteral("bpp=");
    133    params.AppendInt(mICODirEntry.mBitCount);
    134 
    135    rv = mContainedEncoder->InitFromData(aData, aLength, aWidth, aHeight,
    136                                         aStride, aInputFormat, params,
    137                                         VoidCString());
    138    NS_ENSURE_SUCCESS(rv, rv);
    139 
    140    uint32_t andMaskSize = ((GetRealWidth() + 31) / 32) * 4 *  // row AND mask
    141                           GetRealHeight();                    // num rows
    142 
    143    uint32_t BMPImageBufferSize;
    144    mContainedEncoder->GetImageBufferUsed(&BMPImageBufferSize);
    145    mImageBufferSize =
    146        ICONFILEHEADERSIZE + ICODIRENTRYSIZE + BMPImageBufferSize + andMaskSize;
    147    mImageBufferStart = static_cast<uint8_t*>(malloc(mImageBufferSize));
    148    if (!mImageBufferStart) {
    149      return NS_ERROR_OUT_OF_MEMORY;
    150    }
    151    mImageBufferCurr = mImageBufferStart;
    152 
    153    // Icon files that wrap a BMP file must not include the BITMAPFILEHEADER
    154    // section at the beginning of the encoded BMP data, so we must skip over
    155    // bmp::FILE_HEADER_LENGTH bytes when adding the BMP content to the icon
    156    // file.
    157    mICODirEntry.mBytesInRes =
    158        BMPImageBufferSize - bmp::FILE_HEADER_LENGTH + andMaskSize;
    159 
    160    // Encode the icon headers
    161    EncodeFileHeader();
    162    EncodeInfoHeader();
    163 
    164    char* imageBuffer;
    165    rv = mContainedEncoder->GetImageBuffer(&imageBuffer);
    166    NS_ENSURE_SUCCESS(rv, rv);
    167    memcpy(mImageBufferCurr, imageBuffer + bmp::FILE_HEADER_LENGTH,
    168           BMPImageBufferSize - bmp::FILE_HEADER_LENGTH);
    169    // We need to fix the BMP height to be *2 for the AND mask
    170    uint32_t fixedHeight = GetRealHeight() * 2;
    171    NativeEndian::swapToLittleEndianInPlace(&fixedHeight, 1);
    172    // The height is stored at an offset of 8 from the DIB header
    173    memcpy(mImageBufferCurr + 8, &fixedHeight, sizeof(fixedHeight));
    174    mImageBufferCurr += BMPImageBufferSize - bmp::FILE_HEADER_LENGTH;
    175 
    176    // Calculate rowsize in DWORD's
    177    uint32_t rowSize = ((GetRealWidth() + 31) / 32) * 4;  // + 31 to round up
    178    int32_t currentLine = GetRealHeight();
    179 
    180    // Write out the AND mask
    181    while (currentLine > 0) {
    182      currentLine--;
    183      uint8_t* encoded = mImageBufferCurr + currentLine * rowSize;
    184      uint8_t* encodedEnd = encoded + rowSize;
    185      while (encoded != encodedEnd) {
    186        *encoded = 0;  // make everything visible
    187        encoded++;
    188      }
    189    }
    190 
    191    mImageBufferCurr += andMaskSize;
    192  }
    193 
    194  return NS_OK;
    195 }
    196 
    197 // See ::InitFromData for other info.
    198 NS_IMETHODIMP
    199 nsICOEncoder::StartImageEncode(uint32_t aWidth, uint32_t aHeight,
    200                               uint32_t aInputFormat,
    201                               const nsAString& aOutputOptions) {
    202  // can't initialize more than once
    203  if (mImageBufferStart || mImageBufferCurr) {
    204    return NS_ERROR_ALREADY_INITIALIZED;
    205  }
    206 
    207  // validate input format
    208  if (aInputFormat != INPUT_FORMAT_RGB && aInputFormat != INPUT_FORMAT_RGBA &&
    209      aInputFormat != INPUT_FORMAT_HOSTARGB) {
    210    return NS_ERROR_INVALID_ARG;
    211  }
    212 
    213  // Icons are only 1 byte, so make sure our bitmap is in range
    214  if (aWidth > 256 || aHeight > 256) {
    215    return NS_ERROR_INVALID_ARG;
    216  }
    217 
    218  // parse and check any provided output options
    219  uint16_t bpp = 24;
    220  bool usePNG = true;
    221  nsresult rv = ParseOptions(aOutputOptions, bpp, usePNG);
    222  NS_ENSURE_SUCCESS(rv, rv);
    223  MOZ_ASSERT(bpp <= 32);
    224 
    225  mUsePNG = usePNG;
    226 
    227  InitFileHeader();
    228  // The width and height are stored as 0 when we have a value of 256
    229  InitInfoHeader(bpp, aWidth == 256 ? 0 : (uint8_t)aWidth,
    230                 aHeight == 256 ? 0 : (uint8_t)aHeight);
    231 
    232  return NS_OK;
    233 }
    234 
    235 NS_IMETHODIMP
    236 nsICOEncoder::EndImageEncode() {
    237  // must be initialized
    238  if (!mImageBufferStart || !mImageBufferCurr) {
    239    return NS_ERROR_NOT_INITIALIZED;
    240  }
    241 
    242  mFinished = true;
    243  NotifyListener();
    244 
    245  // if output callback can't get enough memory, it will free our buffer
    246  if (!mImageBufferStart || !mImageBufferCurr) {
    247    return NS_ERROR_OUT_OF_MEMORY;
    248  }
    249 
    250  return NS_OK;
    251 }
    252 
    253 // Parses the encoder options and sets the bits per pixel to use and PNG or BMP
    254 // See InitFromData for a description of the parse options
    255 nsresult nsICOEncoder::ParseOptions(const nsAString& aOptions,
    256                                    uint16_t& aBppOut, bool& aUsePNGOut) {
    257  // If no parsing options just use the default of 24BPP and PNG yes
    258  if (aOptions.Length() == 0) {
    259    aUsePNGOut = true;
    260    aBppOut = 24;
    261  }
    262 
    263  // Parse the input string into a set of name/value pairs.
    264  // From format: format=<png|bmp>;bpp=<bpp_value>
    265  // to format: [0] = format=<png|bmp>, [1] = bpp=<bpp_value>
    266  nsTArray<nsCString> nameValuePairs;
    267  ParseString(NS_ConvertUTF16toUTF8(aOptions), ';', nameValuePairs);
    268 
    269  // For each name/value pair in the set
    270  for (unsigned i = 0; i < nameValuePairs.Length(); ++i) {
    271    // Split the name value pair [0] = name, [1] = value
    272    nsTArray<nsCString> nameValuePair;
    273    ParseString(nameValuePairs[i], '=', nameValuePair);
    274    if (nameValuePair.Length() != 2) {
    275      return NS_ERROR_INVALID_ARG;
    276    }
    277 
    278    // Parse the format portion of the string format=<png|bmp>;bpp=<bpp_value>
    279    if (nameValuePair[0].Equals("format", nsCaseInsensitiveCStringComparator)) {
    280      if (nameValuePair[1].Equals("png", nsCaseInsensitiveCStringComparator)) {
    281        aUsePNGOut = true;
    282      } else if (nameValuePair[1].Equals("bmp",
    283                                         nsCaseInsensitiveCStringComparator)) {
    284        aUsePNGOut = false;
    285      } else {
    286        return NS_ERROR_INVALID_ARG;
    287      }
    288    }
    289 
    290    // Parse the bpp portion of the string format=<png|bmp>;bpp=<bpp_value>
    291    if (nameValuePair[0].Equals("bpp", nsCaseInsensitiveCStringComparator)) {
    292      if (nameValuePair[1].EqualsLiteral("24")) {
    293        aBppOut = 24;
    294      } else if (nameValuePair[1].EqualsLiteral("32")) {
    295        aBppOut = 32;
    296      } else {
    297        return NS_ERROR_INVALID_ARG;
    298      }
    299    }
    300  }
    301 
    302  return NS_OK;
    303 }
    304 
    305 NS_IMETHODIMP
    306 nsICOEncoder::Close() {
    307  if (mImageBufferStart) {
    308    free(mImageBufferStart);
    309    mImageBufferStart = nullptr;
    310    mImageBufferSize = 0;
    311    mImageBufferReadPoint = 0;
    312    mImageBufferCurr = nullptr;
    313  }
    314 
    315  return NS_OK;
    316 }
    317 
    318 // Obtains the available bytes to read
    319 NS_IMETHODIMP
    320 nsICOEncoder::Available(uint64_t* _retval) {
    321  if (!mImageBufferStart || !mImageBufferCurr) {
    322    return NS_BASE_STREAM_CLOSED;
    323  }
    324 
    325  *_retval = GetCurrentImageBufferOffset() - mImageBufferReadPoint;
    326  return NS_OK;
    327 }
    328 
    329 // Obtains the stream's status
    330 NS_IMETHODIMP
    331 nsICOEncoder::StreamStatus() {
    332  return mImageBufferStart && mImageBufferCurr ? NS_OK : NS_BASE_STREAM_CLOSED;
    333 }
    334 
    335 // [noscript] Reads bytes which are available
    336 NS_IMETHODIMP
    337 nsICOEncoder::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) {
    338  return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval);
    339 }
    340 
    341 // [noscript] Reads segments
    342 NS_IMETHODIMP
    343 nsICOEncoder::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
    344                           uint32_t aCount, uint32_t* _retval) {
    345  uint32_t maxCount = GetCurrentImageBufferOffset() - mImageBufferReadPoint;
    346  if (maxCount == 0) {
    347    *_retval = 0;
    348    return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
    349  }
    350 
    351  if (aCount > maxCount) {
    352    aCount = maxCount;
    353  }
    354 
    355  nsresult rv = aWriter(
    356      this, aClosure,
    357      reinterpret_cast<const char*>(mImageBufferStart + mImageBufferReadPoint),
    358      0, aCount, _retval);
    359  if (NS_SUCCEEDED(rv)) {
    360    NS_ASSERTION(*_retval <= aCount, "bad write count");
    361    mImageBufferReadPoint += *_retval;
    362  }
    363  // errors returned from the writer end here!
    364  return NS_OK;
    365 }
    366 
    367 NS_IMETHODIMP
    368 nsICOEncoder::IsNonBlocking(bool* _retval) {
    369  *_retval = true;
    370  return NS_OK;
    371 }
    372 
    373 NS_IMETHODIMP
    374 nsICOEncoder::AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags,
    375                        uint32_t aRequestedCount, nsIEventTarget* aTarget) {
    376  if (aFlags != 0) {
    377    return NS_ERROR_NOT_IMPLEMENTED;
    378  }
    379 
    380  if (mCallback || mCallbackTarget) {
    381    return NS_ERROR_UNEXPECTED;
    382  }
    383 
    384  mCallbackTarget = aTarget;
    385  // 0 means "any number of bytes except 0"
    386  mNotifyThreshold = aRequestedCount;
    387  if (!aRequestedCount) {
    388    mNotifyThreshold = 1024;  // We don't want to notify incessantly
    389  }
    390 
    391  // We set the callback absolutely last, because NotifyListener uses it to
    392  // determine if someone needs to be notified.  If we don't set it last,
    393  // NotifyListener might try to fire off a notification to a null target
    394  // which will generally cause non-threadsafe objects to be used off the
    395  // main thread
    396  mCallback = aCallback;
    397 
    398  // What we are being asked for may be present already
    399  NotifyListener();
    400  return NS_OK;
    401 }
    402 
    403 NS_IMETHODIMP
    404 nsICOEncoder::CloseWithStatus(nsresult aStatus) { return Close(); }
    405 
    406 void nsICOEncoder::NotifyListener() {
    407  if (mCallback && (GetCurrentImageBufferOffset() - mImageBufferReadPoint >=
    408                        mNotifyThreshold ||
    409                    mFinished)) {
    410    nsCOMPtr<nsIInputStreamCallback> callback;
    411    if (mCallbackTarget) {
    412      callback = NS_NewInputStreamReadyEvent("nsICOEncoder::NotifyListener",
    413                                             mCallback, mCallbackTarget);
    414    } else {
    415      callback = mCallback;
    416    }
    417 
    418    NS_ASSERTION(callback, "Shouldn't fail to make the callback");
    419    // Null the callback first because OnInputStreamReady could reenter
    420    // AsyncWait
    421    mCallback = nullptr;
    422    mCallbackTarget = nullptr;
    423    mNotifyThreshold = 0;
    424 
    425    callback->OnInputStreamReady(this);
    426  }
    427 }
    428 
    429 // Initializes the icon file header mICOFileHeader
    430 void nsICOEncoder::InitFileHeader() {
    431  memset(&mICOFileHeader, 0, sizeof(mICOFileHeader));
    432  mICOFileHeader.mReserved = 0;
    433  mICOFileHeader.mType = 1;
    434  mICOFileHeader.mCount = 1;
    435 }
    436 
    437 // Initializes the icon directory info header mICODirEntry
    438 void nsICOEncoder::InitInfoHeader(uint16_t aBPP, uint8_t aWidth,
    439                                  uint8_t aHeight) {
    440  memset(&mICODirEntry, 0, sizeof(mICODirEntry));
    441  mICODirEntry.mBitCount = aBPP;
    442  mICODirEntry.mBytesInRes = 0;
    443  mICODirEntry.mColorCount = 0;
    444  mICODirEntry.mWidth = aWidth;
    445  mICODirEntry.mHeight = aHeight;
    446  mICODirEntry.mImageOffset = ICONFILEHEADERSIZE + ICODIRENTRYSIZE;
    447  mICODirEntry.mPlanes = 1;
    448  mICODirEntry.mReserved = 0;
    449 }
    450 
    451 // Encodes the icon file header mICOFileHeader
    452 void nsICOEncoder::EncodeFileHeader() {
    453  IconFileHeader littleEndianIFH = mICOFileHeader;
    454  NativeEndian::swapToLittleEndianInPlace(&littleEndianIFH.mReserved, 1);
    455  NativeEndian::swapToLittleEndianInPlace(&littleEndianIFH.mType, 1);
    456  NativeEndian::swapToLittleEndianInPlace(&littleEndianIFH.mCount, 1);
    457 
    458  memcpy(mImageBufferCurr, &littleEndianIFH.mReserved,
    459         sizeof(littleEndianIFH.mReserved));
    460  mImageBufferCurr += sizeof(littleEndianIFH.mReserved);
    461  memcpy(mImageBufferCurr, &littleEndianIFH.mType,
    462         sizeof(littleEndianIFH.mType));
    463  mImageBufferCurr += sizeof(littleEndianIFH.mType);
    464  memcpy(mImageBufferCurr, &littleEndianIFH.mCount,
    465         sizeof(littleEndianIFH.mCount));
    466  mImageBufferCurr += sizeof(littleEndianIFH.mCount);
    467 }
    468 
    469 // Encodes the icon directory info header mICODirEntry
    470 void nsICOEncoder::EncodeInfoHeader() {
    471  IconDirEntry littleEndianmIDE = mICODirEntry;
    472 
    473  NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mPlanes, 1);
    474  NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mBitCount, 1);
    475  NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mBytesInRes, 1);
    476  NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mImageOffset, 1);
    477 
    478  memcpy(mImageBufferCurr, &littleEndianmIDE.mWidth,
    479         sizeof(littleEndianmIDE.mWidth));
    480  mImageBufferCurr += sizeof(littleEndianmIDE.mWidth);
    481  memcpy(mImageBufferCurr, &littleEndianmIDE.mHeight,
    482         sizeof(littleEndianmIDE.mHeight));
    483  mImageBufferCurr += sizeof(littleEndianmIDE.mHeight);
    484  memcpy(mImageBufferCurr, &littleEndianmIDE.mColorCount,
    485         sizeof(littleEndianmIDE.mColorCount));
    486  mImageBufferCurr += sizeof(littleEndianmIDE.mColorCount);
    487  memcpy(mImageBufferCurr, &littleEndianmIDE.mReserved,
    488         sizeof(littleEndianmIDE.mReserved));
    489  mImageBufferCurr += sizeof(littleEndianmIDE.mReserved);
    490  memcpy(mImageBufferCurr, &littleEndianmIDE.mPlanes,
    491         sizeof(littleEndianmIDE.mPlanes));
    492  mImageBufferCurr += sizeof(littleEndianmIDE.mPlanes);
    493  memcpy(mImageBufferCurr, &littleEndianmIDE.mBitCount,
    494         sizeof(littleEndianmIDE.mBitCount));
    495  mImageBufferCurr += sizeof(littleEndianmIDE.mBitCount);
    496  memcpy(mImageBufferCurr, &littleEndianmIDE.mBytesInRes,
    497         sizeof(littleEndianmIDE.mBytesInRes));
    498  mImageBufferCurr += sizeof(littleEndianmIDE.mBytesInRes);
    499  memcpy(mImageBufferCurr, &littleEndianmIDE.mImageOffset,
    500         sizeof(littleEndianmIDE.mImageOffset));
    501  mImageBufferCurr += sizeof(littleEndianmIDE.mImageOffset);
    502 }