| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574 | //	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;	}}voidzipper::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]);}voidzipper::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);}boolzipper::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;}
 |