/*
 * 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);
}

// Whitelist 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 IsPngChunkWhitelisted(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 (IsPngChunkWhitelisted(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
