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