blob: 4db2392b4eabb1a1221243445743a3ac4e9d98bb [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "compile/Png.h"
#include "android-base/stringprintf.h"
#include "androidfw/StringPiece.h"
#include "io/Io.h"
using ::android::StringPiece;
using ::android::base::StringPrintf;
namespace aapt {
static constexpr const char* kPngSignature = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a";
// Useful helper function that encodes individual bytes into a uint32
// at compile time.
constexpr uint32_t u32(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
return (((uint32_t)a) << 24) | (((uint32_t)b) << 16) | (((uint32_t)c) << 8) |
((uint32_t)d);
}
// Allow list of PNG chunk types that we want to keep in the resulting PNG.
enum PngChunkTypes {
kPngChunkIHDR = u32(73, 72, 68, 82),
kPngChunkIDAT = u32(73, 68, 65, 84),
kPngChunkIEND = u32(73, 69, 78, 68),
kPngChunkPLTE = u32(80, 76, 84, 69),
kPngChunktRNS = u32(116, 82, 78, 83),
kPngChunksRGB = u32(115, 82, 71, 66),
};
static uint32_t Peek32LE(const char* data) {
uint32_t word = ((uint32_t)data[0]) & 0x000000ff;
word <<= 8;
word |= ((uint32_t)data[1]) & 0x000000ff;
word <<= 8;
word |= ((uint32_t)data[2]) & 0x000000ff;
word <<= 8;
word |= ((uint32_t)data[3]) & 0x000000ff;
return word;
}
static bool IsPngChunkAllowed(uint32_t type) {
switch (type) {
case kPngChunkIHDR:
case kPngChunkIDAT:
case kPngChunkIEND:
case kPngChunkPLTE:
case kPngChunktRNS:
case kPngChunksRGB:
return true;
default:
return false;
}
}
PngChunkFilter::PngChunkFilter(const StringPiece& data) : data_(data) {
if (util::StartsWith(data_, kPngSignature)) {
window_start_ = 0;
window_end_ = kPngSignatureSize;
} else {
error_msg_ = "file does not start with PNG signature";
}
}
bool PngChunkFilter::ConsumeWindow(const void** buffer, size_t* len) {
if (window_start_ != window_end_) {
// We have bytes to give from our window.
const size_t bytes_read = window_end_ - window_start_;
*buffer = data_.data() + window_start_;
*len = bytes_read;
window_start_ = window_end_;
return true;
}
return false;
}
bool PngChunkFilter::Next(const void** buffer, size_t* len) {
if (HadError()) {
return false;
}
// In case BackUp was called, we must consume the window.
if (ConsumeWindow(buffer, len)) {
return true;
}
// Advance the window as far as possible (until we meet a chunk that
// we want to strip).
while (window_end_ < data_.size()) {
// Chunk length (4 bytes) + type (4 bytes) + crc32 (4 bytes) = 12 bytes.
const size_t kMinChunkHeaderSize = 3 * sizeof(uint32_t);
// Is there enough room for a chunk header?
if (data_.size() - window_end_ < kMinChunkHeaderSize) {
error_msg_ = StringPrintf("Not enough space for a PNG chunk @ byte %zu/%zu", window_end_,
data_.size());
return false;
}
// Verify the chunk length.
const uint32_t chunk_len = Peek32LE(data_.data() + window_end_);
if ((size_t)chunk_len > data_.size() - window_end_ - kMinChunkHeaderSize) {
// Overflow.
const uint32_t chunk_type = Peek32LE(data_.data() + window_end_ + sizeof(uint32_t));
error_msg_ = StringPrintf(
"PNG chunk type %08x is too large: chunk length is %zu but chunk "
"starts at byte %zu/%zu",
chunk_type, (size_t)chunk_len, window_end_ + kMinChunkHeaderSize, data_.size());
return false;
}
// Do we strip this chunk?
const uint32_t chunk_type = Peek32LE(data_.data() + window_end_ + sizeof(uint32_t));
if (IsPngChunkAllowed(chunk_type)) {
// Advance the window to include this chunk.
window_end_ += kMinChunkHeaderSize + chunk_len;
// Special case the IEND chunk, which MUST appear last and libpng stops parsing once it hits
// such a chunk (let's do the same).
if (chunk_type == kPngChunkIEND) {
// Truncate the data to the end of this chunk. There may be garbage trailing after
// (b/38169876)
data_ = data_.substr(0, window_end_);
break;
}
} else {
// We want to strip this chunk. If we accumulated a window,
// we must return the window now.
if (window_start_ != window_end_) {
break;
}
// The window is empty, so we can advance past this chunk
// and keep looking for the next good chunk,
window_end_ += kMinChunkHeaderSize + chunk_len;
window_start_ = window_end_;
}
}
if (ConsumeWindow(buffer, len)) {
return true;
}
return false;
}
void PngChunkFilter::BackUp(size_t count) {
if (HadError()) {
return;
}
window_start_ -= count;
}
bool PngChunkFilter::Rewind() {
if (HadError()) {
return false;
}
window_start_ = 0;
window_end_ = kPngSignatureSize;
return true;
}
} // namespace aapt