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