mirror of
https://github.com/fhgwright/SCSI2SD.git
synced 2025-01-24 08:32:52 +00:00
575 lines
13 KiB
C++
575 lines
13 KiB
C++
// Copyright (C) 2011 Michael McMaster <michael@codesrc.com>
|
|
//
|
|
// This file is part of libzipper.
|
|
//
|
|
// libzipper is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// libzipper is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with libzipper. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#include "zipper.hh"
|
|
#include "zip.hh"
|
|
#include "util.hh"
|
|
#include "deflate.hh"
|
|
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <iostream>
|
|
|
|
#include <time.h>
|
|
#include <string.h>
|
|
|
|
#include "config.h"
|
|
|
|
using namespace zipper;
|
|
|
|
namespace
|
|
{
|
|
time_t convertDosDateTime(uint16_t date, uint16_t time)
|
|
{
|
|
struct tm parts;
|
|
memset(&parts, 0, sizeof(parts));
|
|
parts.tm_sec = time & 0x1F;
|
|
parts.tm_min = (time & 0x7E0) >> 5;
|
|
parts.tm_hour = (time >> 11);
|
|
parts.tm_mday = date & 0x1F;
|
|
parts.tm_mon = ((date & 0x1E0) >> 5) - 1;
|
|
parts.tm_year = (date >> 9) + 80;
|
|
return mktime(&parts);
|
|
}
|
|
|
|
void convertDosDateTime(time_t in, uint16_t& date, uint16_t& time)
|
|
{
|
|
struct tm buf;
|
|
struct tm* parts(localtime_r(&in, &buf));
|
|
|
|
time =
|
|
parts->tm_sec +
|
|
(parts->tm_min << 5) +
|
|
(parts->tm_hour << 11);
|
|
|
|
date =
|
|
parts->tm_mday +
|
|
((parts->tm_mon + 1) << 5) +
|
|
((parts->tm_year - 80) << 9);
|
|
}
|
|
|
|
class FileEntry : public CompressedFile
|
|
{
|
|
public:
|
|
FileEntry(
|
|
const ReaderPtr& reader,
|
|
uint16_t versionNeeded,
|
|
uint16_t gpFlag,
|
|
uint16_t compressionMethod,
|
|
uint32_t crc,
|
|
zsize_t compressedSize,
|
|
zsize_t uncompressedSize,
|
|
time_t modTime,
|
|
zsize_t localHeaderOffset,
|
|
std::string fileName
|
|
) :
|
|
m_reader(reader),
|
|
m_versionNeeded(versionNeeded),
|
|
m_gpFlag(gpFlag),
|
|
m_compressionMethod(compressionMethod),
|
|
m_crc(crc),
|
|
m_compressedSize(compressedSize),
|
|
m_uncompressedSize(uncompressedSize),
|
|
m_localHeaderOffset(localHeaderOffset),
|
|
m_fileName(fileName)
|
|
{
|
|
m_modTime.tv_sec = modTime;
|
|
m_modTime.tv_usec = 0;
|
|
}
|
|
|
|
virtual bool isDecompressSupported() const
|
|
{
|
|
return ((m_versionNeeded & 0xf) <= 20) &&
|
|
((m_gpFlag & 0x1) == 0) && // Not encrypted
|
|
((m_compressionMethod == 0) || (m_compressionMethod == 8));
|
|
}
|
|
|
|
virtual const std::string& getPath() const
|
|
{
|
|
return m_fileName;
|
|
}
|
|
|
|
virtual zsize_t getCompressedSize() const { return m_compressedSize; }
|
|
virtual zsize_t getUncompressedSize() const
|
|
{
|
|
return m_uncompressedSize;
|
|
}
|
|
|
|
virtual const timeval& getModificationTime() const { return m_modTime; }
|
|
|
|
virtual void decompress(Writer& writer)
|
|
{
|
|
enum
|
|
{
|
|
Signature = 0x04034b50,
|
|
MinRecordBytes = 30,
|
|
ChunkSize = 64*1024
|
|
};
|
|
|
|
std::vector<uint8_t> localRecord(MinRecordBytes);
|
|
m_reader->readData(
|
|
m_localHeaderOffset, MinRecordBytes, &localRecord[0]
|
|
);
|
|
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_le(localRecord, 26));
|
|
zsize_t extraLength(read16_le(localRecord, 28));
|
|
|
|
zsize_t startCompressedBytes(
|
|
m_localHeaderOffset +
|
|
MinRecordBytes +
|
|
filenameLength +
|
|
extraLength
|
|
);
|
|
|
|
zsize_t endCompressedBytes(
|
|
startCompressedBytes + m_compressedSize
|
|
);
|
|
|
|
if (endCompressedBytes > m_reader->getSize())
|
|
{
|
|
throw FormatException("Compressed file size is too long");
|
|
}
|
|
|
|
switch (m_compressionMethod)
|
|
{
|
|
case 0: // No compression
|
|
{
|
|
for (zsize_t pos(startCompressedBytes);
|
|
pos < endCompressedBytes;
|
|
pos += ChunkSize
|
|
)
|
|
{
|
|
uint8_t buf[ChunkSize];
|
|
zsize_t bytes(
|
|
std::min(zsize_t(ChunkSize), endCompressedBytes - pos)
|
|
);
|
|
m_reader->readData(pos, bytes, &buf[0]);
|
|
writer.writeData(pos, bytes, &buf[0]);
|
|
}
|
|
}; break;
|
|
|
|
case 8: // Deflate
|
|
{
|
|
uint32_t crc(0);
|
|
zsize_t inPos(startCompressedBytes);
|
|
zsize_t outPos(0);
|
|
inflate(
|
|
m_reader,
|
|
writer,
|
|
inPos,
|
|
endCompressedBytes,
|
|
outPos,
|
|
crc);
|
|
|
|
if (m_gpFlag & 0x4) // CRC is after compressed data
|
|
{
|
|
uint8_t dataDescriptor[12];
|
|
m_reader->readData(
|
|
inPos, sizeof(dataDescriptor), &dataDescriptor[0]);
|
|
m_crc = read32_le(dataDescriptor, 0);
|
|
m_compressedSize = read32_le(dataDescriptor, 4);
|
|
m_uncompressedSize = read32_le(dataDescriptor, 8);
|
|
}
|
|
|
|
if (crc != m_crc)
|
|
{
|
|
throw FormatException("Corrupt Data (CRC failure)");
|
|
}
|
|
|
|
}; break;
|
|
default:
|
|
throw UnsupportedException("Unsupported compression scheme");
|
|
};
|
|
}
|
|
|
|
private:
|
|
ReaderPtr m_reader;
|
|
uint16_t m_versionNeeded;
|
|
uint16_t m_gpFlag;
|
|
uint16_t m_compressionMethod;
|
|
uint32_t m_crc;
|
|
zsize_t m_compressedSize;
|
|
zsize_t m_uncompressedSize;
|
|
timeval m_modTime;
|
|
zsize_t m_localHeaderOffset;
|
|
std::string m_fileName;
|
|
};
|
|
|
|
bool readEndCentralDirectory(
|
|
const ReaderPtr& reader,
|
|
zsize_t& centralDirectoryBytes,
|
|
zsize_t& centralDirectoryOffset,
|
|
zsize_t& centralDirectoryEntries
|
|
)
|
|
{
|
|
// Read the end of central directory record. This
|
|
// record enables us to find the remainding
|
|
// records without searching for record signatures.
|
|
|
|
// TODO does not consider the Zip64 entries.
|
|
|
|
enum
|
|
{
|
|
MinRecordBytes = 22, // Minimum size with no comment
|
|
MaxCommentBytes = 65535, // 2 bytes to store comment length
|
|
Signature = 0x06054b50
|
|
};
|
|
|
|
zsize_t providerSize(reader->getSize());
|
|
if (providerSize < MinRecordBytes)
|
|
{
|
|
throw FormatException("Too small");
|
|
}
|
|
|
|
size_t bufSize(
|
|
std::min(zsize_t(MinRecordBytes + MaxCommentBytes), providerSize)
|
|
);
|
|
std::vector<uint8_t> buffer(bufSize);
|
|
reader->readData(providerSize - bufSize, bufSize, &buffer[0]);
|
|
|
|
// Need to search for this record, as it ends in a variable-length
|
|
// comment field. Search backwards, with the assumption that the
|
|
// comment doesn't exist, or is much smaller than the maximum
|
|
// length
|
|
|
|
bool recordFound(false);
|
|
ssize_t pos(bufSize - MinRecordBytes);
|
|
for (; pos >= 0; --pos)
|
|
{
|
|
recordFound = (read32_le(buffer, pos) == Signature);
|
|
break;
|
|
}
|
|
|
|
if (recordFound)
|
|
{
|
|
if (read16_le(buffer, pos + 4) != 0)
|
|
{
|
|
throw UnsupportedException("Spanned disks not supported");
|
|
}
|
|
|
|
centralDirectoryBytes = read32_le(buffer, pos + 12);
|
|
centralDirectoryOffset = read32_le(buffer, pos + 16);
|
|
centralDirectoryEntries = read16_le(buffer, pos + 10);
|
|
}
|
|
return recordFound;
|
|
}
|
|
|
|
std::vector<CompressedFilePtr>
|
|
readCentralDirectory(const ReaderPtr& reader)
|
|
{
|
|
enum Constants
|
|
{
|
|
MinRecordBytes = 46,
|
|
Signature = 0x02014b50
|
|
};
|
|
|
|
zsize_t centralDirectoryBytes(0);
|
|
zsize_t centralDirectoryOffset(0);
|
|
zsize_t centralDirectoryEntries(0);
|
|
bool isZip(
|
|
readEndCentralDirectory(
|
|
reader,
|
|
centralDirectoryBytes,
|
|
centralDirectoryOffset,
|
|
centralDirectoryEntries
|
|
)
|
|
);
|
|
(void) isZip; // Avoid unused warning.
|
|
assert(isZip);
|
|
|
|
std::vector<uint8_t> buffer(centralDirectoryBytes);
|
|
reader->readData(
|
|
centralDirectoryOffset,
|
|
centralDirectoryBytes,
|
|
&buffer[0]
|
|
);
|
|
|
|
zsize_t pos(0);
|
|
std::vector<CompressedFilePtr> entries;
|
|
while ((pos + MinRecordBytes) < buffer.size())
|
|
{
|
|
if (read32_le(buffer, pos) != Signature)
|
|
{
|
|
// Unknown record type.
|
|
pos += 1;
|
|
continue;
|
|
}
|
|
|
|
uint16_t versionNeeded(read16_le(buffer, pos + 6));
|
|
uint16_t gpFlag(read16_le(buffer, pos + 8));
|
|
uint16_t compressionMethod(read16_le(buffer, pos + 10));
|
|
uint16_t modTime(read16_le(buffer, pos + 12));
|
|
uint16_t modDate(read16_le(buffer, pos + 14));
|
|
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()
|
|
)
|
|
{
|
|
throw FormatException("File comments are too long");
|
|
}
|
|
|
|
std::string fileName(
|
|
&buffer[pos + MinRecordBytes],
|
|
&buffer[pos + MinRecordBytes + fileNameLen]
|
|
);
|
|
|
|
entries.push_back(
|
|
CompressedFilePtr(
|
|
new FileEntry(
|
|
reader,
|
|
versionNeeded,
|
|
gpFlag,
|
|
compressionMethod,
|
|
crc,
|
|
compressedSize,
|
|
uncompressedSize,
|
|
convertDosDateTime(modDate, modTime),
|
|
localHeaderOffset,
|
|
fileName
|
|
)
|
|
)
|
|
);
|
|
|
|
pos += MinRecordBytes + fileNameLen + extraLen + commentLen;
|
|
}
|
|
return entries;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
void
|
|
zipper::zip(
|
|
const std::string& filename,
|
|
const Reader& reader,
|
|
const WriterPtr& writer,
|
|
ZipFileRecord& outRecord)
|
|
{
|
|
enum Constants
|
|
{
|
|
ChunkSize = 64*1024,
|
|
WindowBits = 15,
|
|
TimePos = 10
|
|
};
|
|
|
|
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
|
|
deflate(
|
|
reader,
|
|
writer,
|
|
outPos,
|
|
outRecord.uncompressedSize,
|
|
outRecord.compressedSize,
|
|
outRecord.crc32);
|
|
|
|
// Go back and complete the header.
|
|
convertDosDateTime(
|
|
reader.getModTime().tv_sec, outRecord.dosDate, outRecord.dosTime);
|
|
uint8_t trailer[16];
|
|
write16_le(outRecord.dosTime, &trailer[0]);
|
|
write16_le(outRecord.dosDate, &trailer[2]);
|
|
write32_le(outRecord.crc32, &trailer[4]);
|
|
write32_le(outRecord.compressedSize, &trailer[8]);
|
|
write32_le(outRecord.uncompressedSize, &trailer[12]);
|
|
writer->writeData(
|
|
outRecord.localHeaderOffset + TimePos, 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
|
|
};
|
|
|
|
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));
|
|
|
|
write16_le(records[i].dosTime, &buffer[pos]);
|
|
pos += 2;
|
|
write16_le(records[i].dosDate, &buffer[pos]);
|
|
pos += 2;
|
|
|
|
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;
|
|
}
|
|
write16_le(filenameSize, &buffer[pos]);
|
|
pos += 2;
|
|
|
|
write16_le(0, &buffer[pos]); // extra field len
|
|
pos += 2;
|
|
|
|
write16_le(0, &buffer[pos]); // file comment len
|
|
pos += 2;
|
|
|
|
write16_le(0, &buffer[pos]); // disk number
|
|
pos += 2;
|
|
|
|
write16_le(0, &buffer[pos]); // internal file attributes
|
|
pos += 2;
|
|
|
|
write32_le(0, &buffer[pos]); // external file attributes
|
|
pos += 4;
|
|
|
|
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));
|
|
|
|
write16_le(records.size(), &buffer[pos]); // Entries on this disk
|
|
pos += 2;
|
|
write16_le(records.size(), &buffer[pos]); // Total entries
|
|
pos += 2;
|
|
|
|
write32_le(centralDirSize, &buffer[pos]);
|
|
pos += 4;
|
|
write32_le(centralDirOffset, &buffer[pos]);
|
|
pos += 4;
|
|
|
|
write16_le(0, &buffer[pos]); // Zip comment length
|
|
pos += 2;
|
|
|
|
writer->writeData(outPos, pos, &buffer[0]);
|
|
outPos += pos;
|
|
}
|
|
}
|
|
|
|
std::vector<CompressedFilePtr>
|
|
zipper::unzip(const ReaderPtr& reader)
|
|
{
|
|
return readCentralDirectory(reader);
|
|
}
|
|
|
|
bool
|
|
zipper::isZip(const ReaderPtr& reader)
|
|
{
|
|
zsize_t centralDirectoryBytes(0);
|
|
zsize_t centralDirectoryOffset(0);
|
|
zsize_t centralDirectoryEntries(0);
|
|
bool result(
|
|
readEndCentralDirectory(
|
|
reader,
|
|
centralDirectoryBytes,
|
|
centralDirectoryOffset,
|
|
centralDirectoryEntries
|
|
)
|
|
);
|
|
return result;
|
|
}
|
|
|