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