Index: third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp |
diff --git a/third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp b/third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp |
index 177b58af86dbbebe6f631714da4501214ac11b31..4f9d48a70837fc2e82494d9aeb984307415b8a38 100644 |
--- a/third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp |
+++ b/third_party/WebKit/Source/platform/image-decoders/png/PNGImageReader.cpp |
@@ -38,11 +38,18 @@ |
#include "platform/image-decoders/png/PNGImageReader.h" |
+#include "platform/RuntimeEnabledFeatures.h" |
+#include "platform/image-decoders/FastSharedBufferReader.h" |
+ |
#include "platform/image-decoders/SegmentReader.h" |
#include "png.h" |
#include "wtf/PtrUtil.h" |
+#include "zlib.h" |
#include <memory> |
+#define get16(p) ((p)[0] << 8 | (p)[1]) |
+#define get32(p) ((p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3]) |
+ |
namespace { |
inline blink::PNGImageDecoder* imageDecoder(png_structp png) { |
@@ -75,13 +82,22 @@ namespace blink { |
PNGImageReader::PNGImageReader(PNGImageDecoder* decoder, size_t readOffset) |
: m_decoder(decoder), |
m_readOffset(readOffset), |
- m_currentBufferSize(0), |
- m_decodingSizeOnly(false), |
+ m_parseOffset(readOffset), |
+ m_decodeOffset(readOffset), |
+ m_infoSize(0), |
+ m_isAnimated(false), |
+ m_isParsed(false), |
+ m_width(0), |
+ m_height(0), |
+ m_repetitionCount(cAnimationNone), |
+ m_posterFrame(0), |
+ m_visibleFrames(1), |
m_hasAlpha(false) { |
m_png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, pngFailed, 0); |
m_info = png_create_info_struct(m_png); |
png_set_progressive_read_fn(m_png, m_decoder, pngHeaderAvailable, |
pngRowAvailable, pngComplete); |
+ memset(&m_currentFrame, 0, sizeof(m_currentFrame)); |
} |
PNGImageReader::~PNGImageReader() { |
@@ -91,26 +107,208 @@ PNGImageReader::~PNGImageReader() { |
m_readOffset = 0; |
} |
-bool PNGImageReader::decode(const SegmentReader& data, bool sizeOnly) { |
- m_decodingSizeOnly = sizeOnly; |
+// PNG signature and IHDR size |
+static const size_t headerSize = 33; |
+// fcTL size |
+static const size_t frameControlSize = 34; |
- // We need to do the setjmp here. Otherwise bad things will happen. |
- if (setjmp(JMPBUF(m_png))) |
- return m_decoder->setFailed(); |
- |
- const char* segment; |
- while (size_t segmentLength = data.getSomeData(segment, m_readOffset)) { |
- m_readOffset += segmentLength; |
- m_currentBufferSize = m_readOffset; |
- png_process_data(m_png, m_info, |
- reinterpret_cast<png_bytep>(const_cast<char*>(segment)), |
- segmentLength); |
- if (sizeOnly ? m_decoder->isDecodedSizeAvailable() |
- : m_decoder->frameIsCompleteAtIndex(0)) |
+bool PNGImageReader::parse(SegmentReader& data) { |
+ const unsigned long maxPNGSize = 1000000UL; |
+ size_t inputSize = data.size(); |
+ const png_byte* chunk; |
+ FastSharedBufferReader reader(&data); |
+ char readBuffer[frameControlSize]; |
+ |
+ if (m_parseOffset == m_readOffset) { |
+ if (m_readOffset + headerSize > inputSize) |
+ return false; |
+ |
+ chunk = reinterpret_cast<const png_byte*>(reader.getConsecutiveData( |
+ m_readOffset + 8, headerSize - 8, readBuffer)); |
+ const png_byte* chunkId = chunk + 4; |
+ png_uint_32 chunkSize = get32(chunk); |
+ if (memcmp(chunkId, "IHDR", 4) || chunkSize != 13) |
+ return false; |
+ m_currentFrame.width = m_width = get32(chunk + 8); |
+ m_currentFrame.height = m_height = get32(chunk + 12); |
+ if (m_width > maxPNGSize || m_height > maxPNGSize) |
+ return false; |
+ png_byte bitDepth = chunk[16]; |
+ if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4 && bitDepth != 8 && |
+ bitDepth != 16) |
+ return false; |
+ png_byte colorType = chunk[17]; |
+ if (colorType == 1 || colorType == 5 || colorType > 6) |
+ return false; |
+ png_uint_32 chunkCrc = get32(chunk + 21); |
+ if (crc32(crc32(0, Z_NULL, 0), chunkId, 17) != chunkCrc) |
+ return false; |
+ m_infoSize = m_parseOffset = m_readOffset + headerSize; |
+ } |
+ |
+ while (!m_isParsed) { |
+ if (m_parseOffset + 8 > inputSize) |
+ return false; |
+ |
+ chunk = reinterpret_cast<const png_byte*>( |
+ reader.getConsecutiveData(m_parseOffset, 8, readBuffer)); |
+ const png_byte* chunkId = chunk + 4; |
+ png_uint_32 chunkSize = get32(chunk); |
+ |
+ if (!memcmp(chunkId, "IDAT", 4) && !m_currentFrame.start) { |
+ m_currentFrame.start = m_parseOffset; |
+ if (!m_decoder->setSize(m_width, m_height)) |
+ return false; |
+ png_destroy_read_struct(&m_png, &m_info, 0); |
+ m_png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, pngFailed, 0); |
+ m_info = png_create_info_struct(m_png); |
+ png_set_progressive_read_fn(m_png, m_decoder, pngHeaderAvailable, |
+ pngRowAvailable, pngComplete); |
+ |
+ if (setjmp(JMPBUF(m_png))) |
+ return false; |
+ |
+ processData(data, 0, m_readOffset, m_parseOffset + 8); |
+ } |
+ |
+ size_t chunkEnd = m_parseOffset + chunkSize + 12; |
+ if (chunkEnd > inputSize) |
+ return false; |
+ |
+ if (!memcmp(chunkId, "IEND", 4) && !chunkSize) { |
+ if (!m_currentFrame.start) |
+ return false; |
+ m_isParsed = true; |
+ m_currentFrame.finish = chunkEnd; |
+ m_frames.append(m_currentFrame); |
return true; |
+ } |
+ |
+ if (RuntimeEnabledFeatures::animatedPNGEnabled()) { |
+ if (!memcmp(chunkId, "fdAT", 4) && m_isAnimated && |
+ !m_currentFrame.start) { |
+ m_currentFrame.start = m_parseOffset; |
+ } else if (!memcmp(chunkId, "acTL", 4) && chunkSize == 8 && |
+ !m_isAnimated && !m_currentFrame.start) { |
+ chunk = reinterpret_cast<const png_byte*>( |
+ reader.getConsecutiveData(m_parseOffset, 16, readBuffer)); |
+ m_isAnimated = true; |
+ m_posterFrame = 1; |
+ m_visibleFrames = 0; |
+ m_repetitionCount = static_cast<int>(get32(chunk + 12)) - 1; |
+ } else if (!memcmp(chunkId, "fcTL", 4) && chunkSize == 26 && |
+ (m_isAnimated || !m_currentFrame.start)) { |
+ if (m_currentFrame.start) { |
+ m_currentFrame.finish = chunkEnd; |
+ m_frames.append(m_currentFrame); |
+ m_currentFrame.start = 0; |
+ } else if (m_frames.isEmpty()) { |
+ m_posterFrame = 0; |
+ } |
+ |
+ chunk = reinterpret_cast<const png_byte*>(reader.getConsecutiveData( |
+ m_parseOffset, frameControlSize, readBuffer)); |
+ m_currentFrame.width = get32(chunk + 12); |
+ m_currentFrame.height = get32(chunk + 16); |
+ m_currentFrame.xOffset = get32(chunk + 20); |
+ m_currentFrame.yOffset = get32(chunk + 24); |
+ unsigned numerator = get16(chunk + 28); |
+ unsigned denominator = get16(chunk + 30); |
+ m_currentFrame.duration = !denominator |
+ ? numerator * 10 |
+ : (int)(numerator * 1000.0 / denominator); |
+ m_currentFrame.dispose = chunk[32]; |
+ m_currentFrame.blend = chunk[33]; |
+ |
+ if (m_currentFrame.width > maxPNGSize || |
+ m_currentFrame.height > maxPNGSize || |
+ m_currentFrame.xOffset > maxPNGSize || |
+ m_currentFrame.yOffset > maxPNGSize || |
+ m_currentFrame.xOffset + m_currentFrame.width > m_width || |
+ m_currentFrame.yOffset + m_currentFrame.height > m_height || |
+ m_currentFrame.dispose > 2 || m_currentFrame.blend > 1) |
+ return false; |
+ |
+ if (!m_visibleFrames) { |
+ m_currentFrame.blend = 0; |
+ if (m_currentFrame.dispose == 2) |
+ m_currentFrame.dispose = 1; |
+ } |
+ m_visibleFrames++; |
+ } else if (!m_currentFrame.start && m_frames.isEmpty()) { |
+ m_infoSize = chunkEnd; |
+ } |
+ } |
+ |
+ m_parseOffset = chunkEnd; |
+ } |
+ return true; |
+} |
+ |
+bool PNGImageReader::decode(SegmentReader& data, size_t index) { |
+ static png_byte dataIEND[12] = {0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130}; |
+ |
+ index += m_posterFrame; |
+ if (index && index >= m_frames.size()) |
+ return true; |
+ |
+ if (index || m_decodeOffset == m_readOffset) { |
+ png_destroy_read_struct(&m_png, &m_info, 0); |
+ m_png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, pngFailed, 0); |
+ m_info = png_create_info_struct(m_png); |
+ png_set_progressive_read_fn(m_png, m_decoder, pngHeaderAvailable, |
+ pngRowAvailable, pngComplete); |
} |
- return false; |
+ if (setjmp(JMPBUF(m_png))) |
+ return false; |
+ |
+ if (!index) { |
+ size_t endOffset = !m_frames.isEmpty() ? m_frames[0].finish : 0; |
+ processData(data, index, m_decodeOffset, endOffset); |
+ if (m_decodeOffset == endOffset) |
+ png_process_data(m_png, m_info, dataIEND, 12); |
+ } else { |
+ const png_byte* chunk; |
+ FastSharedBufferReader reader(&data); |
+ char readBuffer[headerSize]; |
+ |
+ m_decodeOffset = m_readOffset; |
+ size_t offset = m_frames[index].start; |
+ size_t endOffset = m_frames[index].finish; |
+ if (data.size() < endOffset) |
+ return true; |
+ |
+ png_set_crc_action(m_png, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE); |
+ if (m_frames[index].width != m_width || |
+ m_frames[index].height != m_height) { |
+ unsigned char header[headerSize]; |
+ chunk = reinterpret_cast<const png_byte*>( |
+ reader.getConsecutiveData(m_readOffset, headerSize, readBuffer)); |
+ memcpy(&header[0], chunk, headerSize); |
+ png_save_uint_32(&header[16], m_frames[index].width); |
+ png_save_uint_32(&header[20], m_frames[index].height); |
+ png_process_data(m_png, m_info, header, headerSize); |
+ processData(data, index, m_readOffset + headerSize, m_infoSize); |
+ } else { |
+ processData(data, index, m_readOffset, m_infoSize); |
+ } |
+ |
+ png_byte dataIDAT[8] = {0, 0, 0, 0, 73, 68, 65, 84}; |
+ while (offset < endOffset) { |
+ chunk = reinterpret_cast<const png_byte*>( |
+ reader.getConsecutiveData(offset, 8, readBuffer)); |
+ png_uint_32 chunkSize = get32(chunk); |
+ if (!memcmp(chunk + 4, "fdAT", 4)) { |
+ png_save_uint_32(&dataIDAT[0], chunkSize - 4); |
+ png_process_data(m_png, m_info, dataIDAT, 8); |
+ processData(data, index, offset + 12, offset + 12 + chunkSize); |
+ } |
+ offset += chunkSize + 12; |
+ } |
+ png_process_data(m_png, m_info, dataIEND, 12); |
+ } |
+ return true; |
} |
} // namespace blink |