lgogdownloader/src/ziputil.cpp

627 lines
18 KiB
C++
Raw Normal View History

/* This program is free software. It comes without any warranty, to
* the extent permitted by applicable law. You can redistribute it
* and/or modify it under the terms of the Do What The Fuck You Want
* To Public License, Version 2, as published by Sam Hocevar. See
* http://www.wtfpl.net/ for more details. */
#include "ziputil.h"
#include <sstream>
#include <sys/stat.h>
#include <boost/iostreams/filtering_streambuf.hpp>
#include <boost/iostreams/copy.hpp>
#include <boost/iostreams/filter/zlib.hpp>
#include <boost/regex.hpp>
off_t ZipUtil::getMojoSetupScriptSize(std::stringstream *stream)
{
off_t mojosetup_script_size = -1;
int script_lines = 0;
boost::regex re("offset=`head -n (\\d+?) \"\\$0\"", boost::regex::perl | boost::regex::icase);
boost::match_results<std::string::const_iterator> what;
if (boost::regex_search(stream->str(), what, re))
{
script_lines = std::stoi(what[1]);
}
std::string script;
for (int i = 0; i < script_lines; ++i)
{
std::string line;
std::getline(*stream, line);
script += line + "\n";
}
mojosetup_script_size = script.size();
return mojosetup_script_size;
}
off_t ZipUtil::getMojoSetupInstallerSize(std::stringstream *stream)
{
off_t mojosetup_installer_size = -1;
boost::regex re("filesizes=\"(\\d+?)\"", boost::regex::perl | boost::regex::icase);
boost::match_results<std::string::const_iterator> what;
if (boost::regex_search(stream->str(), what, re))
{
mojosetup_installer_size = std::stoll(what[1]);
}
return mojosetup_installer_size;
}
struct tm ZipUtil::date_time_to_tm(uint64_t date, uint64_t time)
{
/* DOS date time format
* Y|Y|Y|Y|Y|Y|Y|M| |M|M|M|D|D|D|D|D| |h|h|h|h|h|m|m|m| |m|m|m|s|s|s|s|s
*
* second is divided by 2
* month starts at 1
* https://msdn.microsoft.com/en-us/library/windows/desktop/ms724247%28v=vs.85%29.aspx */
uint64_t local_time_base_year = 1900;
uint64_t dos_time_base_year = 1980;
struct tm timeinfo;
timeinfo.tm_year = (uint16_t)(((date & 0xFE00) >> 9) - local_time_base_year + dos_time_base_year);
timeinfo.tm_mon = (uint16_t)(((date & 0x1E0) >> 5) - 1);
timeinfo.tm_mday = (uint16_t)(date & 0x1F);
timeinfo.tm_hour = (uint16_t)((time & 0xF800) >> 11);
timeinfo.tm_min = (uint16_t)((time & 0x7E0) >> 5);
timeinfo.tm_sec = (uint16_t)(2 * (time & 0x1F));
timeinfo.tm_isdst = -1;
return timeinfo;
}
bool ZipUtil::isValidDate(struct tm timeinfo)
{
if (!(timeinfo.tm_year >= 0 && timeinfo.tm_year <= 207))
return false;
if (!(timeinfo.tm_mon >= 0 && timeinfo.tm_mon <= 11))
return false;
if (!(timeinfo.tm_mday >= 1 && timeinfo.tm_mday <= 31))
return false;
if (!(timeinfo.tm_hour >= 0 && timeinfo.tm_hour <= 23))
return false;
if (!(timeinfo.tm_min >= 0 && timeinfo.tm_min <= 59))
return false;
if (!(timeinfo.tm_sec >= 0 && timeinfo.tm_sec <= 59))
return false;
return true;
}
uint64_t ZipUtil::readValue(std::istream *stream, uint32_t len)
{
uint64_t value = 0;
for (uint32_t i = 0; i < len; i++)
{
value |= ((uint64_t)(stream->get() & 0xFF)) << (i * 8);
}
return value;
}
uint64_t ZipUtil::readUInt64(std::istream *stream)
{
uint64_t value = (uint64_t)readValue(stream, sizeof(uint64_t));
return value;
}
uint32_t ZipUtil::readUInt32(std::istream *stream)
{
uint32_t value = (uint32_t)readValue(stream, sizeof(uint32_t));
return value;
}
uint16_t ZipUtil::readUInt16(std::istream *stream)
{
uint16_t value = (uint16_t)readValue(stream, sizeof(uint16_t));
return value;
}
uint8_t ZipUtil::readUInt8(std::istream *stream)
{
uint8_t value = (uint8_t)readValue(stream, sizeof(uint8_t));
return value;
}
off_t ZipUtil::getZipEOCDOffsetSignature(std::istream *stream, const uint32_t& signature)
{
off_t offset = -1;
stream->seekg(0, stream->end);
off_t stream_length = stream->tellg();
for (off_t i = 4; i <= stream_length; i++)
{
off_t pos = stream_length - i;
stream->seekg(pos, stream->beg);
if (readUInt32(stream) == signature)
{
offset = stream->tellg();
offset -= 4;
break;
}
}
return offset;
}
off_t ZipUtil::getZipEOCDOffset(std::istream *stream)
{
return getZipEOCDOffsetSignature(stream, ZIP_EOCD_HEADER_SIGNATURE);
}
off_t ZipUtil::getZip64EOCDOffset(std::istream *stream)
{
return getZipEOCDOffsetSignature(stream, ZIP_EOCD_HEADER_SIGNATURE64);
}
zipEOCD ZipUtil::readZipEOCDStruct(std::istream *stream, const off_t& eocd_start_pos)
{
zipEOCD eocd;
stream->seekg(eocd_start_pos, stream->beg);
// end of central dir signature <4 bytes>
eocd.header = readUInt32(stream);
// number of this disk <2 bytes>
eocd.disk = readUInt16(stream); // Number of this disk
// number of the disk with the start of the central directory <2 bytes>
eocd.cd_start_disk = readUInt16(stream);
// total number of entries in the central directory on this disk <2 bytes>
eocd.cd_records = readUInt16(stream);
// total number of entries in the central directory <2 bytes>
eocd.total_cd_records = readUInt16(stream);
// size of the central directory <4 bytes>
eocd.cd_size = readUInt32(stream);
// offset of start of central directory with respect to the starting disk number <4 bytes>
eocd.cd_start_offset = readUInt32(stream);
// .ZIP file comment length <2 bytes>
eocd.comment_length = readUInt16(stream);
// .ZIP file comment <variable size>
if (eocd.comment_length > 0)
{
char *buf = new char[eocd.comment_length + 1];
stream->read(buf, eocd.comment_length);
eocd.comment = std::string(buf, eocd.comment_length);
delete[] buf;
}
return eocd;
}
zip64EOCD ZipUtil::readZip64EOCDStruct(std::istream *stream, const off_t& eocd_start_pos)
{
zip64EOCD eocd;
stream->seekg(eocd_start_pos, stream->beg);
// zip64 end of central dir signature <4 bytes>
eocd.header = readUInt32(stream);
// size of zip64 end of central directory record <8 bytes>
eocd.directory_record_size = readUInt64(stream);
/* The value stored into the "size of zip64 end of central
* directory record" should be the size of the remaining
* record and should not include the leading 12 bytes.
*
* Size = SizeOfFixedFields + SizeOfVariableData - 12 */
// version made by <2 bytes>
eocd.version_made_by = readUInt16(stream);
// version needed to extract <2 bytes>
eocd.version_needed = readUInt16(stream);
// number of this disk <4 bytes>
eocd.cd = readUInt32(stream);
// number of the disk with the start of the central directory <8 bytes>
eocd.cd_start = readUInt32(stream);
// total number of entries in the central directory on this disk <8 bytes>
eocd.cd_total_disk = readUInt64(stream);
// total number of entries in the central directory <8 bytes>
eocd.cd_total = readUInt64(stream);
// size of the central directory <8 bytes>
eocd.cd_size = readUInt64(stream);
// offset of start of central directory with respect to the starting disk number <8 bytes>
eocd.cd_offset = readUInt64(stream);
// zip64 extensible data sector <variable size>
// This is data is not needed for our purposes so we just ignore this data
return eocd;
}
zipCDEntry ZipUtil::readZipCDEntry(std::istream *stream)
{
zipCDEntry cd;
char *buf;
// file header signature <4 bytes>
cd.header = readUInt32(stream);
cd.isLocalCDEntry = (cd.header == ZIP_LOCAL_HEADER_SIGNATURE);
if (!cd.isLocalCDEntry)
{
// version made by <2 bytes>
cd.version_made_by = readUInt16(stream);
}
// version needed to extract <2 bytes>
cd.version_needed = readUInt16(stream);
// general purpose bit flag <2 bytes>
cd.flag = readUInt16(stream);
// compression method <2 bytes>
cd.compression_method = readUInt16(stream);
// last mod file time <2 bytes>
cd.mod_time = readUInt16(stream);
// last mod file date <2 bytes>
cd.mod_date = readUInt16(stream);
// crc-32 <4 bytes>
cd.crc32 = readUInt32(stream);
// compressed size <4 bytes>
cd.comp_size = readUInt32(stream);
// uncompressed size <4 bytes>
cd.uncomp_size = readUInt32(stream);
// file name length <2 bytes>
cd.filename_length = readUInt16(stream);
// extra field length <2 bytes>
cd.extra_length = readUInt16(stream);
if (!cd.isLocalCDEntry)
{
// file comment length <2 bytes>
cd.comment_length = readUInt16(stream);
// disk number start <2 bytes>
cd.disk_num = readUInt16(stream);
// internal file attributes <2 bytes>
cd.internal_file_attr = readUInt16(stream);
// external file attributes <4 bytes>
cd.external_file_attr = readUInt32(stream);
// relative offset of local header <4 bytes>
cd.disk_offset = readUInt32(stream);
}
// file name <variable size>
buf = new char[cd.filename_length + 1];
stream->read(buf, cd.filename_length);
cd.filename = std::string(buf, cd.filename_length);
delete[] buf;
// extra field <variable size>
buf = new char[cd.extra_length + 1];
stream->read(buf, cd.extra_length);
cd.extra = std::string(buf, cd.extra_length);
delete[] buf;
std::stringstream extra_stream(cd.extra);
cd.timestamp = 0;
struct tm timeinfo = date_time_to_tm(cd.mod_date, cd.mod_time);
if (isValidDate(timeinfo))
cd.timestamp = mktime(&timeinfo);
// Read extra fields
off_t i = 0;
while (i < cd.extra_length)
{
/* Extra field
* <2 bytes> signature
* <2 bytes> size of extra field data
* <variable> extra field data */
uint16_t header_id = readUInt16(&extra_stream);
uint16_t extra_data_size = readUInt16(&extra_stream);
if (header_id == ZIP_EXTENSION_ZIP64)
{
/* Zip64 Extended Information Extra Field
* <8 bytes> size of uncompressed file
* <8 bytes> size of compressed data
* <8 bytes> offset of local header record
* <4 bytes> number of the disk on which this file starts
*
* The fields only appear if the corresponding Local or Central
* directory record field is set to UINT16_MAX or UINT32_MAX */
if (cd.uncomp_size == UINT32_MAX)
cd.uncomp_size = readUInt64(&extra_stream);
if (cd.comp_size == UINT32_MAX)
cd.comp_size = readUInt64(&extra_stream);
if (cd.disk_offset == UINT32_MAX)
cd.disk_offset = readUInt64(&extra_stream);
if (cd.disk_num == UINT16_MAX)
cd.disk_num = readUInt32(&extra_stream);
}
else if (header_id == ZIP_EXTENDED_TIMESTAMP)
{
/* Extended Timestamp Extra Field
*
* Local header version
* <1 byte> info bits
* <4 bytes> modification time
* <4 bytes> access time
* <4 bytes> creation time
*
* Central header version
* <1 byte> info bits
* <4 bytes> modification time
*
* The lower three info bits in both headers indicate
* which timestamps are present in the local extra field
* bit 0 if set, modification time is present
* bit 1 if set, access time is present
* bit 2 if set, creation time is present
* bits 3-7 reserved for additional timestamps; not set
*
* If info bits indicate that modification time is present
* in the local header field, it must be present in the
* central header field.
* Those times that are present will appear in the order
* indicated, but any combination of times may be omitted. */
uint32_t modification_time = 0;
uint32_t access_time = 0;
uint32_t creation_time = 0;
uint8_t flags = readUInt8(&extra_stream);
if (flags & 0x1) // modification time is present
{
modification_time = readUInt32(&extra_stream);
cd.timestamp = modification_time;
}
if (cd.isLocalCDEntry)
{
if (flags & 0x2) // access time is present
{
access_time = readUInt32(&extra_stream);
}
if (flags & 0x4) // creation time is present
{
creation_time = readUInt32(&extra_stream);
}
}
// access time and creation time are unused currently
// suppress -Wunused-but-set-variable messages by casting these variables to void
(void) access_time;
(void) creation_time;
}
else if (header_id == ZIP_INFOZIP_UNIX_NEW)
{
/* Info-ZIP New Unix Extra Field
* <1 byte> version
* <1 byte> size of uid
* <variable> uid
* <1 byte> size of gid
* <variable> gid
*
* Currently Version is set to the number 1. If there is a need
* to change this field, the version will be incremented.
* UID and GID entries are stored in standard little endian format */
uint8_t version = readUInt8(&extra_stream);
if (version == 1)
{
uint64_t uid = 0;
uint64_t gid = 0;
uint8_t uid_size = readUInt8(&extra_stream);
for (uint8_t i = 0; i < uid_size; i++)
{
uid |= ((uint64_t)extra_stream.get()) << (i * 8);
}
uint8_t gid_size = readUInt8(&extra_stream);
for (uint8_t i = 0; i < gid_size; i++)
{
gid |= ((uint64_t)extra_stream.get()) << (i * 8);
}
}
else
{
// Unknown version
// Skip the rest of this field
extra_stream.seekg(extra_data_size - 1, extra_stream.cur);
}
}
else
{
// Skip over unknown/unimplemented extra field
extra_stream.seekg(extra_data_size, extra_stream.cur);
}
i += 4 + extra_data_size;
}
// file comment <variable size>
buf = new char[cd.comment_length + 1];
stream->read(buf, cd.comment_length);
cd.comment = std::string(buf, cd.comment_length);
delete[] buf;
return cd;
}
/* Extract file
returns 0 if successful
returns 1 if input file could not be opened
returns 2 if compression method is unsupported
returns 3 if output file could not be created
returns 4 if zlib error
*/
int ZipUtil::extractFile(const std::string& input_file_path, const std::string& output_file_path)
{
std::ifstream input_file(input_file_path, std::ifstream::in | std::ifstream::binary);
if (!input_file)
{
// Could not open input file
return 1;
}
// Read header
zipCDEntry cd = readZipCDEntry(&input_file);
if (!(cd.compression_method == boost::iostreams::zlib::deflated || cd.compression_method == boost::iostreams::zlib::no_compression))
{
// Unsupported compression method
return 2;
}
boost::iostreams::zlib_params p;
p.window_bits = 15;
p.noheader = true; // zlib header and trailing adler-32 checksum is omitted
std::ofstream output_file(output_file_path, std::ofstream::out | std::ofstream::binary);
if (!output_file)
{
// Failed to create output file
return 3;
}
// Uncompress
boost::iostreams::filtering_streambuf<boost::iostreams::input> in;
if (cd.compression_method == boost::iostreams::zlib::deflated)
in.push(boost::iostreams::zlib_decompressor(p));
in.push(input_file);
try
{
boost::iostreams::copy(in, output_file);
}
catch(boost::iostreams::zlib_error & e)
{
// zlib error
return 4;
}
input_file.close();
output_file.close();
if (cd.timestamp > 0)
boost::filesystem::last_write_time(output_file_path, cd.timestamp);
return 0;
}
/* Extract stream to stream
returns 0 if successful
returns 1 if input stream is not valid
returns 2 if compression method is unsupported
returns 3 if output stream is not valid
returns 4 if zlib error
*/
int ZipUtil::extractStream(std::istream* input_stream, std::ostream* output_stream)
{
if (!input_stream)
{
// Input stream not valid
return 1;
}
// Read header
zipCDEntry cd = readZipCDEntry(input_stream);
if (!(cd.compression_method == boost::iostreams::zlib::deflated || cd.compression_method == boost::iostreams::zlib::no_compression))
{
// Unsupported compression method
return 2;
}
boost::iostreams::zlib_params p;
p.window_bits = 15;
p.noheader = true; // zlib header and trailing adler-32 checksum is omitted
if (!output_stream)
{
// Output stream not valid
return 3;
}
// Uncompress
boost::iostreams::filtering_streambuf<boost::iostreams::input> in;
if (cd.compression_method == boost::iostreams::zlib::deflated)
in.push(boost::iostreams::zlib_decompressor(p));
//in.push(compressed);
in.push(*input_stream);
try
{
boost::iostreams::copy(in, *output_stream);
}
catch(boost::iostreams::zlib_error & e)
{
// zlib error
return 4;
}
return 0;
}
boost::filesystem::perms ZipUtil::getBoostFilePermission(const uint16_t& attributes)
{
boost::filesystem::perms perms = boost::filesystem::no_perms;
if (attributes & S_IRUSR)
perms |= boost::filesystem::owner_read;
if (attributes & S_IWUSR)
perms |= boost::filesystem::owner_write;
if (attributes & S_IXUSR)
perms |= boost::filesystem::owner_exe;
if (attributes & S_IRGRP)
perms |= boost::filesystem::group_read;
if (attributes & S_IWGRP)
perms |= boost::filesystem::group_write;
if (attributes & S_IXGRP)
perms |= boost::filesystem::group_exe;
if (attributes & S_IROTH)
perms |= boost::filesystem::others_read;
if (attributes & S_IWOTH)
perms |= boost::filesystem::others_write;
if (attributes & S_IXOTH)
perms |= boost::filesystem::others_exe;
return perms;
}
bool ZipUtil::isSymlink(const uint16_t& attributes)
{
bool bSymlink = ((attributes & S_IFMT) == S_IFLNK);
return bSymlink;
}