1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "compile/Png.h" 18 19 #include "android-base/stringprintf.h" 20 #include "androidfw/StringPiece.h" 21 22 #include "io/Io.h" 23 24 using ::android::StringPiece; 25 using ::android::base::StringPrintf; 26 27 namespace aapt { 28 29 static constexpr const char* kPngSignature = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"; 30 31 // Useful helper function that encodes individual bytes into a uint32 32 // at compile time. 33 constexpr uint32_t u32(uint8_t a, uint8_t b, uint8_t c, uint8_t d) { 34 return (((uint32_t)a) << 24) | (((uint32_t)b) << 16) | (((uint32_t)c) << 8) | 35 ((uint32_t)d); 36 } 37 38 // Whitelist of PNG chunk types that we want to keep in the resulting PNG. 39 enum PngChunkTypes { 40 kPngChunkIHDR = u32(73, 72, 68, 82), 41 kPngChunkIDAT = u32(73, 68, 65, 84), 42 kPngChunkIEND = u32(73, 69, 78, 68), 43 kPngChunkPLTE = u32(80, 76, 84, 69), 44 kPngChunktRNS = u32(116, 82, 78, 83), 45 kPngChunksRGB = u32(115, 82, 71, 66), 46 }; 47 48 static uint32_t Peek32LE(const char* data) { 49 uint32_t word = ((uint32_t)data[0]) & 0x000000ff; 50 word <<= 8; 51 word |= ((uint32_t)data[1]) & 0x000000ff; 52 word <<= 8; 53 word |= ((uint32_t)data[2]) & 0x000000ff; 54 word <<= 8; 55 word |= ((uint32_t)data[3]) & 0x000000ff; 56 return word; 57 } 58 59 static bool IsPngChunkWhitelisted(uint32_t type) { 60 switch (type) { 61 case kPngChunkIHDR: 62 case kPngChunkIDAT: 63 case kPngChunkIEND: 64 case kPngChunkPLTE: 65 case kPngChunktRNS: 66 case kPngChunksRGB: 67 return true; 68 default: 69 return false; 70 } 71 } 72 73 PngChunkFilter::PngChunkFilter(const StringPiece& data) : data_(data) { 74 if (util::StartsWith(data_, kPngSignature)) { 75 window_start_ = 0; 76 window_end_ = kPngSignatureSize; 77 } else { 78 error_msg_ = "file does not start with PNG signature"; 79 } 80 } 81 82 bool PngChunkFilter::ConsumeWindow(const void** buffer, size_t* len) { 83 if (window_start_ != window_end_) { 84 // We have bytes to give from our window. 85 const size_t bytes_read = window_end_ - window_start_; 86 *buffer = data_.data() + window_start_; 87 *len = bytes_read; 88 window_start_ = window_end_; 89 return true; 90 } 91 return false; 92 } 93 94 bool PngChunkFilter::Next(const void** buffer, size_t* len) { 95 if (HadError()) { 96 return false; 97 } 98 99 // In case BackUp was called, we must consume the window. 100 if (ConsumeWindow(buffer, len)) { 101 return true; 102 } 103 104 // Advance the window as far as possible (until we meet a chunk that 105 // we want to strip). 106 while (window_end_ < data_.size()) { 107 // Chunk length (4 bytes) + type (4 bytes) + crc32 (4 bytes) = 12 bytes. 108 const size_t kMinChunkHeaderSize = 3 * sizeof(uint32_t); 109 110 // Is there enough room for a chunk header? 111 if (data_.size() - window_end_ < kMinChunkHeaderSize) { 112 error_msg_ = StringPrintf("Not enough space for a PNG chunk @ byte %zu/%zu", window_end_, 113 data_.size()); 114 return false; 115 } 116 117 // Verify the chunk length. 118 const uint32_t chunk_len = Peek32LE(data_.data() + window_end_); 119 if ((size_t)chunk_len > data_.size() - window_end_ - kMinChunkHeaderSize) { 120 // Overflow. 121 const uint32_t chunk_type = Peek32LE(data_.data() + window_end_ + sizeof(uint32_t)); 122 error_msg_ = StringPrintf( 123 "PNG chunk type %08x is too large: chunk length is %zu but chunk " 124 "starts at byte %zu/%zu", 125 chunk_type, (size_t)chunk_len, window_end_ + kMinChunkHeaderSize, data_.size()); 126 return false; 127 } 128 129 // Do we strip this chunk? 130 const uint32_t chunk_type = Peek32LE(data_.data() + window_end_ + sizeof(uint32_t)); 131 if (IsPngChunkWhitelisted(chunk_type)) { 132 // Advance the window to include this chunk. 133 window_end_ += kMinChunkHeaderSize + chunk_len; 134 135 // Special case the IEND chunk, which MUST appear last and libpng stops parsing once it hits 136 // such a chunk (let's do the same). 137 if (chunk_type == kPngChunkIEND) { 138 // Truncate the data to the end of this chunk. There may be garbage trailing after 139 // (b/38169876) 140 data_ = data_.substr(0, window_end_); 141 break; 142 } 143 144 } else { 145 // We want to strip this chunk. If we accumulated a window, 146 // we must return the window now. 147 if (window_start_ != window_end_) { 148 break; 149 } 150 151 // The window is empty, so we can advance past this chunk 152 // and keep looking for the next good chunk, 153 window_end_ += kMinChunkHeaderSize + chunk_len; 154 window_start_ = window_end_; 155 } 156 } 157 158 if (ConsumeWindow(buffer, len)) { 159 return true; 160 } 161 return false; 162 } 163 164 void PngChunkFilter::BackUp(size_t count) { 165 if (HadError()) { 166 return; 167 } 168 window_start_ -= count; 169 } 170 171 bool PngChunkFilter::Rewind() { 172 if (HadError()) { 173 return false; 174 } 175 window_start_ = 0; 176 window_end_ = kPngSignatureSize; 177 return true; 178 } 179 180 } // namespace aapt 181