// along with libzipper. If not, see <http://www.gnu.org/licenses/>.
#include "zipper.hh"
-#include "Unzip.hh"
+#include "zip.hh"
+#include "util.hh"
#include <zlib.h>
namespace
{
- template<typename T>
- uint32_t
- read32(const T& zipData, size_t pos)
- {
- // Read 4 bytes in little-endian order.
- // Return results in host-endian.
- return uint32_t(
- zipData[pos] |
- (uint32_t(zipData[pos+1]) << 8) |
- (uint32_t(zipData[pos+2]) << 16) |
- (uint32_t(zipData[pos+3]) << 24)
- );
- }
-
- uint16_t
- read16(const std::vector<uint8_t>& zipData, size_t pos)
- {
- // Read 2 bytes in little-endian order.
- // Return results in host-endian.
- return uint16_t(
- zipData[pos] |
- (uint16_t(zipData[pos+1]) << 8)
- );
- }
-
struct InflateDeleter
{
public:
z_stream* m_stream;
};
+ struct DeflateDeleter
+ {
+ public:
+ DeflateDeleter(z_stream* stream) : m_stream(stream) {}
+ ~DeflateDeleter()
+ {
+ deflateEnd(m_stream);
+
+ }
+ private:
+ z_stream* m_stream;
+ };
+
class FileEntry : public CompressedFile
{
public:
m_reader->readData(
m_localHeaderOffset, MinRecordBytes, &localRecord[0]
);
- if (read32(localRecord, 0) != Signature)
+ if (read32_le(localRecord, 0) != Signature)
{
throw FormatException("Invalid local ZIP record");
}
// Don't trust the lengths for filename and extra content read from
// the central records. At least for extra, these DO differ for
// unknown reasons
- zsize_t filenameLength(read16(localRecord, 26));
- zsize_t extraLength(read16(localRecord, 28));
+ zsize_t filenameLength(read16_le(localRecord, 26));
+ zsize_t extraLength(read16_le(localRecord, 28));
zsize_t startCompressedBytes(
m_localHeaderOffset +
12 - stream.avail_in,
&dataDescriptor[stream.avail_in]);
}
- m_crc = read32(dataDescriptor, 0);
- m_compressedSize = read32(dataDescriptor, 4);
- m_uncompressedSize = read32(dataDescriptor, 8);
+ m_crc = read32_le(dataDescriptor, 0);
+ m_compressedSize = read32_le(dataDescriptor, 4);
+ m_uncompressedSize = read32_le(dataDescriptor, 8);
}
if (crc != m_crc)
ssize_t pos(bufSize - MinRecordBytes);
for (; pos >= 0; --pos)
{
- recordFound = (read32(buffer, pos) == Signature);
+ recordFound = (read32_le(buffer, pos) == Signature);
break;
}
if (recordFound)
{
- if (read16(buffer, pos + 4) != 0)
+ if (read16_le(buffer, pos + 4) != 0)
{
throw UnsupportedException("Spanned disks not supported");
}
- centralDirectoryBytes = read32(buffer, pos + 12);
- centralDirectoryOffset = read32(buffer, pos + 16);
- centralDirectoryEntries = read16(buffer, pos + 10);
+ centralDirectoryBytes = read32_le(buffer, pos + 12);
+ centralDirectoryOffset = read32_le(buffer, pos + 16);
+ centralDirectoryEntries = read16_le(buffer, pos + 10);
}
return recordFound;
}
std::vector<CompressedFilePtr> entries;
while ((pos + MinRecordBytes) < buffer.size())
{
- if (read32(buffer, pos) != Signature)
+ if (read32_le(buffer, pos) != Signature)
{
// Unknown record type.
pos += 1;
continue;
}
- uint16_t versionNeeded(read16(buffer, pos + 6));
- uint16_t gpFlag(read16(buffer, pos + 8));
- uint16_t compressionMethod(read16(buffer, pos + 10));
- uint32_t crc(read32(buffer, pos + 16));
- uint32_t compressedSize(read32(buffer, pos + 20));
- uint32_t uncompressedSize(read32(buffer, pos + 24));
- size_t fileNameLen(read16(buffer, pos + 28));
- size_t extraLen(read16(buffer, pos + 30));
- size_t commentLen(read16(buffer, pos + 32));
- uint32_t localHeaderOffset(read32(buffer, pos + 42));
+ uint16_t versionNeeded(read16_le(buffer, pos + 6));
+ uint16_t gpFlag(read16_le(buffer, pos + 8));
+ uint16_t compressionMethod(read16_le(buffer, pos + 10));
+ uint32_t crc(read32_le(buffer, pos + 16));
+ uint32_t compressedSize(read32_le(buffer, pos + 20));
+ uint32_t uncompressedSize(read32_le(buffer, pos + 24));
+ size_t fileNameLen(read16_le(buffer, pos + 28));
+ size_t extraLen(read16_le(buffer, pos + 30));
+ size_t commentLen(read16_le(buffer, pos + 32));
+ uint32_t localHeaderOffset(read32_le(buffer, pos + 42));
if ((fileNameLen + extraLen + commentLen + MinRecordBytes + pos) >
buffer.size()
}
return entries;
}
+
+}
+
+
+void
+zipper::zip(
+ const std::string& filename,
+ const Reader& reader,
+ const WriterPtr& writer,
+ ZipFileRecord& outRecord)
+{
+ enum Constants
+ {
+ ChunkSize = 64*1024,
+ WindowBits = 15,
+ CRC32Pos = 14
+ };
+
+ static uint8_t Header[] =
+ {
+ 0x50, 0x4b, 0x03, 0x04, // Header
+ 20, // Version (2.0)
+ 0, // File attributes
+ 0,0, // gp flag.
+ 8,0, // deflate method
+ 0,0, // file time
+ 0,0, // file date
+ 0,0,0,0, // CRC32
+ 0,0,0,0, // Compressed size
+ 0,0,0,0 // Uncompressed size
+ };
+
+ zsize_t outPos(writer->getSize());
+ outRecord.localHeaderOffset = outPos;
+ outRecord.filename = filename;
+
+ // Write header
+ {
+ uint8_t buffer[ChunkSize];
+ memcpy(buffer, Header, sizeof(Header));
+ zsize_t pos(sizeof(Header));
+
+ std::string::size_type filenameSize(filename.size());
+ if (filenameSize > (ChunkSize - pos))
+ {
+ filenameSize = ChunkSize - pos;
+ }
+ buffer[pos++] = filenameSize & 0xff;
+ buffer[pos++] = (filenameSize >> 8);
+ buffer[pos++] = 0; // extra field len
+ buffer[pos++] = 0; // extra field len
+ memcpy(buffer + pos, filename.data(), filenameSize);
+ pos += filenameSize;
+ writer->writeData(outPos, pos, &buffer[0]);
+ outPos += pos;
+ }
+
+ // Write compressed data
+
+ uint8_t inChunk[ChunkSize];
+ uint8_t outChunk[ChunkSize];
+
+ outRecord.uncompressedSize = 0;
+ outRecord.compressedSize = 0;
+
+ z_stream stream;
+ stream.zalloc = NULL;
+ stream.zfree = NULL;
+ stream.opaque = NULL;
+ int zlibErr(
+ deflateInit2(
+ &stream,
+ Z_DEFAULT_COMPRESSION,
+ Z_DEFLATED,
+ -WindowBits,
+ MAX_MEM_LEVEL,
+ Z_DEFAULT_STRATEGY)
+ );
+
+ assert(zlibErr == Z_OK);
+ DeflateDeleter deleter(&stream);
+ stream.next_in = NULL;
+ stream.avail_in = 0;
+
+ zsize_t pos(0);
+ zsize_t end(reader.getSize());
+ outRecord.crc32 = crc32(0, NULL, 0);
+
+ while (pos < end)
+ {
+ if (stream.avail_in == 0)
+ {
+ stream.avail_in =
+ std::min(zsize_t(ChunkSize), end - pos);
+ reader.readData(
+ pos, stream.avail_in, &inChunk[0]);
+ stream.next_in = reinterpret_cast<Bytef*>(&inChunk);
+ pos += stream.avail_in;
+ outRecord.uncompressedSize += stream.avail_in;
+ outRecord.crc32 =
+ crc32(outRecord.crc32, stream.next_in, stream.avail_in);
+ }
+
+ stream.next_out = reinterpret_cast<Bytef*>(&outChunk);
+ stream.avail_out = sizeof(outChunk);
+
+ zlibErr = deflate(&stream, (pos < end) ? Z_NO_FLUSH : Z_FINISH);
+
+ if (zlibErr == Z_STREAM_END)
+ {
+ if (pos < end)
+ {
+ assert(!"zlib buffer unexpectedly empty");
+ std::terminate();
+ }
+ }
+ else if (zlibErr != Z_OK)
+ {
+ throw FormatException("Corrupt Data");
+ }
+
+ zsize_t bytesToWrite(sizeof(outChunk) - stream.avail_out);
+ writer->writeData(
+ outPos,
+ bytesToWrite,
+ &outChunk[0]);
+ outPos += bytesToWrite;
+ outRecord.compressedSize += bytesToWrite;
+ }
+
+ // Go back and complete the header.
+ uint8_t trailer[12];
+ write32_le(outRecord.crc32, &trailer[0]);
+ write32_le(outRecord.compressedSize, &trailer[4]);
+ write32_le(outRecord.uncompressedSize, &trailer[8]);
+ writer->writeData(
+ outRecord.localHeaderOffset + CRC32Pos, sizeof(trailer), &trailer[0]);
+}
+
+void
+zipper::zipFinalise(
+ const std::vector<ZipFileRecord>& records,
+ const WriterPtr& writer)
+{
+ enum Constants
+ {
+ ChunkSize = 64*1024
+ };
+
+ static uint8_t FileHeader[] =
+ {
+ 0x50, 0x4b, 0x01, 0x02, // Header
+ 20, 0x00, // Version (2.0)
+ 20, 0x00, // Version Needed to extract (2.0)
+ 0,0, // gp flag.
+ 8,0, // deflate method
+ 0,0, // file time
+ 0,0 // file date
+ };
+
+ zsize_t outPos(writer->getSize());
+ uint32_t centralDirOffset(outPos);
+
+ for (size_t i = 0; i < records.size(); ++i)
+ {
+ uint8_t buffer[ChunkSize];
+ memcpy(buffer, FileHeader, sizeof(FileHeader));
+ zsize_t pos(sizeof(FileHeader));
+
+ write32_le(records[i].crc32, &buffer[pos]);
+ pos += 4;
+
+ write32_le(records[i].compressedSize, &buffer[pos]);
+ pos += 4;
+
+ write32_le(records[i].uncompressedSize, &buffer[pos]);
+ pos += 4;
+
+ std::string::size_type filenameSize(records[i].filename.size());
+ if (filenameSize > (ChunkSize - pos))
+ {
+ filenameSize = ChunkSize - pos;
+ }
+ buffer[pos++] = filenameSize & 0xff;
+ buffer[pos++] = (filenameSize >> 8);
+ buffer[pos++] = 0; // extra field len
+ buffer[pos++] = 0; // extra field len
+
+ buffer[pos++] = 0; // file comment len
+ buffer[pos++] = 0; // file comment len
+
+ buffer[pos++] = 0; // disk number
+ buffer[pos++] = 0; // disk number
+
+ buffer[pos++] = 0; // internal file attributes
+ buffer[pos++] = 0; // internal file attributes
+
+ buffer[pos++] = 0; // external file attributes
+ buffer[pos++] = 0; // external file attributes
+ buffer[pos++] = 0; // external file attributes
+ buffer[pos++] = 0; // external file attributes
+
+ write32_le(records[i].localHeaderOffset, &buffer[pos]);
+ pos += 4;
+
+ memcpy(buffer + pos, records[i].filename.data(), filenameSize);
+ pos += filenameSize;
+
+ writer->writeData(outPos, pos, &buffer[0]);
+ outPos += pos;
+ }
+
+ uint32_t centralDirSize(writer->getSize() - centralDirOffset);
+
+ {
+ // End-of-directory record.
+ static uint8_t EndDirectory[] =
+ {
+ 0x50, 0x4b, 0x05, 0x06, // Header
+ 0x00, 0x00, // Disk num
+ 0x00, 0x00 // Disk with central dir
+ };
+ uint8_t buffer[ChunkSize];
+ memcpy(buffer, EndDirectory, sizeof(EndDirectory));
+ zsize_t pos(sizeof(EndDirectory));
+
+ buffer[pos++] = records.size() & 0xff; // Entries on this disk
+ buffer[pos++] = records.size() >> 8;
+ buffer[pos++] = records.size() & 0xff; // Total entries
+ buffer[pos++] = records.size() >> 8;
+
+ write32_le(centralDirSize, &buffer[pos]);
+ pos += 4;
+ write32_le(centralDirOffset, &buffer[pos]);
+ pos += 4;
+
+ buffer[pos++] = 0; // Zip comment length
+ buffer[pos++] = 0; // Zip comment length
+
+ writer->writeData(outPos, pos, &buffer[0]);
+ outPos += pos;
+ }
}
std::vector<CompressedFilePtr>