diff options
Diffstat (limited to 'mozglue/linker/Zip.h')
-rw-r--r-- | mozglue/linker/Zip.h | 500 |
1 files changed, 500 insertions, 0 deletions
diff --git a/mozglue/linker/Zip.h b/mozglue/linker/Zip.h new file mode 100644 index 000000000..29e42592a --- /dev/null +++ b/mozglue/linker/Zip.h @@ -0,0 +1,500 @@ +/* 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 */ |