/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef Zip_h
#define Zip_h

#include <cstring>
#include <stdint.h>
#include <vector>
#include <zlib.h>
#include <pthread.h>
#include "Utils.h"
#include "mozilla/Assertions.h"
#include "mozilla/RefCounted.h"
#include "mozilla/RefPtr.h"

/**
 * Helper class wrapping z_stream to avoid malloc() calls during
 * inflate. Do not use for deflate.
 * inflateInit allocates two buffers:
 * - one for its internal state, which is "approximately 10K bytes" according
 *   to inflate.h from zlib.
 * - one for the compression window, which depends on the window size passed
 *   to inflateInit2, but is never greater than 32K (1 << MAX_WBITS).
 * Those buffers are created at instantiation time instead of when calling
 * inflateInit2. When inflateInit2 is called, it will call zxx_stream::Alloc
 * to get both these buffers. zxx_stream::Alloc will choose one of the
 * pre-allocated buffers depending on the requested size.
 */
class zxx_stream: public z_stream
{
public:
  /* Forward declaration */
  class StaticAllocator;

  explicit zxx_stream(StaticAllocator *allocator_=nullptr)
  : allocator(allocator_)
  {
    memset(this, 0, sizeof(z_stream));
    zalloc = Alloc;
    zfree = Free;
    opaque = this;
  }

private:
  static void *Alloc(void *data, uInt items, uInt size)
  {
    zxx_stream *zStream = reinterpret_cast<zxx_stream *>(data);
    if (zStream->allocator) {
      return zStream->allocator->Alloc(items, size);
    }
    size_t buf_size = items * size;
    return ::operator new(buf_size);
  }

  static void Free(void *data, void *ptr)
  {
    zxx_stream *zStream = reinterpret_cast<zxx_stream *>(data);
    if (zStream->allocator) {
      zStream->allocator->Free(ptr);
    } else {
      ::operator delete(ptr);
    }
  }

  /**
   * Helper class for each buffer in StaticAllocator.
   */
  template <size_t Size>
  class ZStreamBuf
  {
  public:
    ZStreamBuf() : inUse(false) { }

    bool get(char*& out)
    {
      if (!inUse) {
        inUse = true;
        out = buf;
        return true;
      } else {
        return false;
      }
    }

    void Release()
    {
      memset(buf, 0, Size);
      inUse = false;
    }

    bool Equals(const void *other) { return other == buf; }

    static const size_t size = Size;

  private:
    char buf[Size];
    bool inUse;
  };

public:
  /**
   * Special allocator that uses static buffers to allocate from.
   */
  class StaticAllocator
  {
  public:
    void *Alloc(uInt items, uInt size)
    {
      if (items == 1 && size <= stateBuf1.size) {
        char* res = nullptr;
        if (stateBuf1.get(res) || stateBuf2.get(res)) {
          return res;
        }
        MOZ_CRASH("ZStreamBuf already in use");
      } else if (items * size == windowBuf1.size) {
        char* res = nullptr;
        if (windowBuf1.get(res) || windowBuf2.get(res)) {
          return res;
        }
        MOZ_CRASH("ZStreamBuf already in use");
      } else {
        MOZ_CRASH("No ZStreamBuf for allocation");
      }
    }

    void Free(void *ptr)
    {
      if (stateBuf1.Equals(ptr)) {
        stateBuf1.Release();
      } else if (stateBuf2.Equals(ptr)) {
        stateBuf2.Release();
      }else if (windowBuf1.Equals(ptr)) {
        windowBuf1.Release();
      } else if (windowBuf2.Equals(ptr)) {
        windowBuf2.Release();
      } else {
        MOZ_CRASH("Pointer doesn't match a ZStreamBuf");
      }
    }

    // 0x3000 is an arbitrary size above 10K.
    ZStreamBuf<0x3000> stateBuf1, stateBuf2;
    ZStreamBuf<1 << MAX_WBITS> windowBuf1, windowBuf2;
  };

private:
  StaticAllocator *allocator;
};

/**
 * Forward declaration
 */
class ZipCollection;

/**
 * Class to handle access to Zip archive streams. The Zip archive is mapped
 * in memory, and streams are direct references to that mapped memory.
 * Zip files are assumed to be correctly formed. No boundary checks are
 * performed, which means hand-crafted malicious Zip archives can make the
 * code fail in bad ways. However, since the only intended use is to load
 * libraries from Zip archives, there is no interest in making this code
 * safe, since the libraries could contain malicious code anyways.
 */
class Zip: public mozilla::external::AtomicRefCounted<Zip>
{
public:
  MOZ_DECLARE_REFCOUNTED_TYPENAME(Zip)
  /**
   * Create a Zip instance for the given file name. Returns nullptr in case
   * of failure.
   */
  static already_AddRefed<Zip> Create(const char *filename);

  /**
   * Create a Zip instance using the given buffer.
   */
  static already_AddRefed<Zip> Create(void *buffer, size_t size) {
    return Create(nullptr, buffer, size);
  }

private:
  static already_AddRefed<Zip> Create(const char *filename,
                                           void *buffer, size_t size);

  /**
   * Private constructor
   */
  Zip(const char *filename, void *buffer, size_t size);

public:
  /**
   * Destructor
   */
  ~Zip();

  /**
   * Class used to access Zip archive item streams
   */
  class Stream
  {
  public:
    /**
     * Stream types
     */
    enum Type {
      STORE = 0,
      DEFLATE = 8
    };

    /**
     * Constructor
     */
    Stream(): compressedBuf(nullptr), compressedSize(0), uncompressedSize(0)
            , CRC32(0)
            , type(STORE) { }

    /**
     * Getters
     */
    const void *GetBuffer() { return compressedBuf; }
    size_t GetSize() { return compressedSize; }
    size_t GetUncompressedSize() { return uncompressedSize; }
    size_t GetCRC32() { return CRC32; }
    Type GetType() { return type; }

    /**
     * Returns a zxx_stream for use with inflate functions using the given
     * buffer as inflate output. The caller is expected to allocate enough
     * memory for the Stream uncompressed size.
     */
    zxx_stream GetZStream(void *buf)
    {
      zxx_stream zStream;
      zStream.avail_in = compressedSize;
      zStream.next_in = reinterpret_cast<Bytef *>(
                        const_cast<void *>(compressedBuf));
      zStream.avail_out = uncompressedSize;
      zStream.next_out = static_cast<Bytef *>(buf);
      return zStream;
    }

  protected:
    friend class Zip;
    const void *compressedBuf;
    size_t compressedSize;
    size_t uncompressedSize;
    size_t CRC32;
    Type type;
  };

  /**
   * Returns a stream from the Zip archive.
   */
  bool GetStream(const char *path, Stream *out) const;

  /**
   * Returns the file name of the archive
   */
  const char *GetName() const
  {
    return name;
  }

private:
  /* File name of the archive */
  char *name;
  /* Address where the Zip archive is mapped */
  void *mapped;
  /* Size of the archive */
  size_t size;

  /**
   * Strings (file names, comments, etc.) in the Zip headers are NOT zero
   * terminated. This class is a helper around them.
   */
  class StringBuf
  {
  public:
    /**
     * Constructor
     */
    StringBuf(const char *buf, size_t length): buf(buf), length(length) { }

    /**
     * Returns whether the string has the same content as the given zero
     * terminated string.
     */
    bool Equals(const char *str) const
    {
      return (strncmp(str, buf, length) == 0 && str[length] == '\0');
    }

  private:
    const char *buf;
    size_t length;
  };

/* All the following types need to be packed */
#pragma pack(1)
public:
  /**
   * A Zip archive is an aggregate of entities which all start with a
   * signature giving their type. This template is to be used as a base
   * class for these entities.
   */
  template <typename T>
  class SignedEntity
  {
  public:
    /**
     * Equivalent to reinterpret_cast<const T *>(buf), with an additional
     * check of the signature.
     */
    static const T *validate(const void *buf)
    {
      const T *ret = static_cast<const T *>(buf);
      if (ret->signature == T::magic)
        return ret;
      return nullptr;
    }

    SignedEntity(uint32_t magic): signature(magic) { }
  private:
    le_uint32 signature;
  };

private:
  /**
   * Header used to describe a Local File entry. The header is followed by
   * the file name and an extra field, then by the data stream.
   */
  struct LocalFile: public SignedEntity<LocalFile>
  {
    /* Signature for a Local File header */
    static const uint32_t magic = 0x04034b50;

    /**
     * Returns the file name
     */
    StringBuf GetName() const
    {
      return StringBuf(reinterpret_cast<const char *>(this) + sizeof(*this),
                       filenameSize);
    }

    /**
     * Returns a pointer to the data associated with this header
     */
    const void *GetData() const
    {
      return reinterpret_cast<const char *>(this) + sizeof(*this)
             + filenameSize + extraFieldSize;
    }

    le_uint16 minVersion;
    le_uint16 generalFlag;
    le_uint16 compression;
    le_uint16 lastModifiedTime;
    le_uint16 lastModifiedDate;
    le_uint32 CRC32;
    le_uint32 compressedSize;
    le_uint32 uncompressedSize;
    le_uint16 filenameSize;
    le_uint16 extraFieldSize;
  };

  /**
   * In some cases, when a zip archive is created, compressed size and CRC
   * are not known when writing the Local File header. In these cases, the
   * 3rd bit of the general flag in the Local File header is set, and there
   * is an additional header following the compressed data.
   */
  struct DataDescriptor: public SignedEntity<DataDescriptor>
  {
    /* Signature for a Data Descriptor header */
    static const uint32_t magic = 0x08074b50;

    le_uint32 CRC32;
    le_uint32 compressedSize;
    le_uint32 uncompressedSize;
  };

  /**
   * Header used to describe a Central Directory Entry. The header is
   * followed by the file name, an extra field, and a comment.
   */
  struct DirectoryEntry: public SignedEntity<DirectoryEntry>
  {
    /* Signature for a Central Directory Entry header */
    static const uint32_t magic = 0x02014b50;

    /**
     * Returns the file name
     */
    StringBuf GetName() const
    {
      return StringBuf(reinterpret_cast<const char *>(this) + sizeof(*this),
                       filenameSize);
    }

    /**
     * Returns  the Central Directory Entry following this one.
     */
    const DirectoryEntry *GetNext() const
    {
      return validate(reinterpret_cast<const char *>(this) + sizeof(*this)
                      + filenameSize + extraFieldSize + fileCommentSize);
    }

    le_uint16 creatorVersion;
    le_uint16 minVersion;
    le_uint16 generalFlag;
    le_uint16 compression;
    le_uint16 lastModifiedTime;
    le_uint16 lastModifiedDate;
    le_uint32 CRC32;
    le_uint32 compressedSize;
    le_uint32 uncompressedSize;
    le_uint16 filenameSize;
    le_uint16 extraFieldSize;
    le_uint16 fileCommentSize;
    le_uint16 diskNum;
    le_uint16 internalAttributes;
    le_uint32 externalAttributes;
    le_uint32 offset;
  };

  /**
   * Header used to describe the End of Central Directory Record.
   */
  struct CentralDirectoryEnd: public SignedEntity<CentralDirectoryEnd>
  {
    /* Signature for the End of Central Directory Record */
    static const uint32_t magic = 0x06054b50;

    le_uint16 diskNum;
    le_uint16 startDisk;
    le_uint16 recordsOnDisk;
    le_uint16 records;
    le_uint32 size;
    le_uint32 offset;
    le_uint16 commentSize;
  };
#pragma pack()

  /**
   * Returns the first Directory entry
   */
  const DirectoryEntry *GetFirstEntry() const;

  /* Pointer to the Local File Entry following the last one GetStream() used.
   * This is used by GetStream to avoid scanning the Directory Entries when the
   * requested entry is that one. */
  mutable const LocalFile *nextFile;

  /* Likewise for the next Directory entry */
  mutable const DirectoryEntry *nextDir;

  /* Pointer to the Directory entries */
  mutable const DirectoryEntry *entries;

  mutable pthread_mutex_t mutex;
};

/**
 * Class for bookkeeping Zip instances
 */
class ZipCollection
{
public:
  static ZipCollection Singleton;

  /**
   * Get a Zip instance for the given path. If there is an existing one
   * already, return that one, otherwise create a new one.
   */
  static already_AddRefed<Zip> GetZip(const char *path);

protected:
  friend class Zip;
  /**
   * Register the given Zip instance. This method is meant to be called
   * by Zip::Create.
   */
  static void Register(Zip *zip);

  /**
   * Forget about the given Zip instance. This method is meant to be called
   * by the Zip destructor.
   */
  static void Forget(Zip *zip);

private:
  /* Zip instances bookkept in this collection */
  std::vector<Zip *> zips;
};

#endif /* Zip_h */