/* -*- Mode: C++; c-default-style: "k&r"; indent-tabs-mode: nil; tab-width: 2; c-basic-offset: 2 -*- */
/* libmwaw: tools
* Version: MPL 2.0 / LGPLv2+
*
* The contents of this file are subject to the Mozilla Public License Version
* 2.0 (the "License"); you may not use this file except in compliance with
* the License or as specified alternatively below. You may obtain a copy of
* the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* Major Contributor(s):
* Copyright (C) 2011, 2012 Alonso Laurent (alonso@loria.fr)
*
*
* All Rights Reserved.
*
* For minor contributions see the git repository.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU Lesser General Public License Version 2 or later (the "LGPLv2+"),
* in which case the provisions of the LGPLv2+ are applicable
* instead of those above.
*/
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <zlib.h>
#include <fstream>
#include <iostream>
#include "input.h"
#include "zip.h"
#include "zip_internal.h"
namespace libmwaw_zip
{
static void writeU16(char *ptr, uint16_t data)
{
*(ptr++)=char(data&0xFF);
*(ptr++)=char(data>>8);
}
static void writeU32(char *ptr, uint32_t data)
{
*(ptr++)=char(data&0xFF);
*(ptr++)=char((data>>8)&0xFF);
*(ptr++)=char((data>>16)&0xFF);
*(ptr++)=char((data>>24)&0xFF);
}
static void writeTimeDate(char *ptr)
{
time_t now = time(0L);
struct tm timeinfo = *(localtime(&now));
auto time=uint16_t((timeinfo.tm_hour<<11)|(timeinfo.tm_min<<5));
auto date=uint16_t((timeinfo.tm_mon<<5)|timeinfo.tm_mday);
int year = 1900+timeinfo.tm_year;
if (year > 1980)
date |= uint16_t((year-1980)<<9);
writeU16(ptr, time);
writeU16(ptr+2, date);
}
// ------------------------------------------------------------
// File implementation
// ------------------------------------------------------------
Zip::File::File(std::string const &base, std::string const &dir)
: m_base(base)
, m_dir(dir)
, m_uncompressedLength(0)
, m_compressedLength(0)
, m_deflate(false)
, m_crc32(0)
, m_offsetInFile(0)
{
}
bool Zip::File::write(std::shared_ptr<InputStream> input, std::ostream &output)
{
if (!m_base.length())
return false;
if (!input || input->length()<0) {
MWAW_DEBUG_MSG(("Zip::File::write: called without input\n"));
return false;
}
// read the file
auto numBytes = static_cast<unsigned long>(input->length());
input->seek(0, InputStream::SK_SET);
unsigned long numBytesRead=0;
unsigned char const *buf= numBytes==0 ? 0 : input->read(numBytes, numBytesRead);
if (numBytesRead != static_cast<unsigned long>(numBytes) || (numBytesRead && !buf)) {
MWAW_DEBUG_MSG(("Zip::File::write: can not read the input\n"));
return false;
}
// crc32
m_uncompressedLength = m_compressedLength = uInt(numBytes);
m_crc32 = crc32(0L, Z_NULL, 0);
if (numBytes)
m_crc32 = crc32(m_crc32, reinterpret_cast<const Bytef *>(buf), m_uncompressedLength);
std::vector<unsigned char> buffer;
if (numBytes>10) {
buffer.resize(size_t(numBytes)-10);
z_stream zip;
zip.next_in = reinterpret_cast<Bytef *>(const_cast<unsigned char *>(buf));
zip.avail_in = m_uncompressedLength;
zip.total_in = 0;
zip.next_out = &reinterpret_cast<Bytef &>(buffer[0]);
zip.avail_out = uInt(numBytes)-10;
zip.total_out = 0;
zip.msg=0;
zip.zalloc=0;
zip.zfree=0;
zip.opaque=0;
/* optimize for speed and set all to default: expected negative
WindowBits value which supresses the zlib header (and checksum)
from the stream. */
#if defined(__clang__)
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wold-style-cast"
#endif
if (deflateInit2(&zip, 1, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY)!=Z_OK ||
deflate(&zip,Z_FINISH)!=Z_STREAM_END)
buffer.resize(0);
else
m_compressedLength = uInt(zip.total_out);
#if defined(__clang__)
# pragma clang diagnostic pop
#endif
deflateEnd(&zip);
}
// write the data
m_offsetInFile = uLong(output.tellp());
char localHeader[] = {
0x50, 0x4b, 0x3, 0x4 /* 0:magic */, 0x14, 0 /* 4:version */,
0, 0 /* 6:flags */, 0, 0 /* 8:compression */,
0, 0 /* 10:time */, 0, 0 /* 12:date */, 0, 0, 0, 0 /* 14:checksum */,
0, 0, 0, 0 /* 18:compressed size */, 0, 0, 0, 0 /* 22:uncompressed size */,
0, 0 /* 26:file name size */, 0, 0 /* 28:extra size */
};
if (buffer.size()) {
m_deflate = true;
localHeader[8]=0x8; // deflate
}
std::string fName("");
if (m_dir.length() && m_dir[0]=='/')
fName = m_dir.substr(1)+m_base;
else
fName = m_dir+m_base;
writeTimeDate(localHeader+10);
writeU32(localHeader+14, uint32_t(m_crc32));
writeU32(localHeader+18, uint32_t(m_compressedLength));
writeU32(localHeader+22, uint32_t(m_uncompressedLength));
writeU16(localHeader+26, uint16_t(fName.length()));
output.write(localHeader, 30);
if (fName.length())
output.write(fName.c_str(), std::streamsize(fName.length()));
if (buffer.size())
output.write(reinterpret_cast<const char *>(&buffer[0]), std::streamsize(m_compressedLength));
else if (numBytes)
output.write(reinterpret_cast<const char *>(buf), std::streamsize(numBytes));
return true;
}
bool Zip::File::writeCentralInformation(std::ostream &output) const
{
if (!m_base.length())
return false;
char fileHeader[] = {
0x50, 0x4b, 0x1, 0x2 /* 0:magic */,
0x14, 0 /* 4:version */, 0x14, 0 /* 6:version needed*/,
0, 0 /* 8:flags */, 0, 0 /* 10:compression */,
0, 0 /* 12:time */, 0, 0 /* 14:date */, 0, 0, 0, 0 /* 16:checksum */,
0, 0, 0, 0 /* 20:compressed size */, 0, 0, 0, 0 /* 24:uncompressed size */,
0, 0 /* 28:file name size */, 0, 0 /* 30:extra size */,
0, 0 /* 32:comment size */,
0, 0 /* 34: disk # start */, 0, 0 /*36: ascii/text file */,
0, 0, 0, 0 /* 38:external attribute */, 0, 0, 0, 0/*42: offset of local header*/
};
if (m_deflate)
fileHeader[10]=0x8;
std::string fName("");
if (m_dir.length() && m_dir[0]=='/')
fName = m_dir.substr(1)+m_base;
else
fName = m_dir+m_base;
writeTimeDate(fileHeader+12);
writeU32(fileHeader+16, uint32_t(m_crc32));
writeU32(fileHeader+20, uint32_t(m_compressedLength));
writeU32(fileHeader+24, uint32_t(m_uncompressedLength));
writeU16(fileHeader+28, uint16_t(fName.length()));
writeU32(fileHeader+42, uint32_t(m_offsetInFile));
output.write(fileHeader, 46);
if (fName.length())
output.write(fName.c_str(), std::streamsize(fName.length()));
return true;
}
// ------------------------------------------------------------
// Directory implementation
// ------------------------------------------------------------
Zip::Directory::Directory(std::string const &dir)
: m_dir(dir)
, m_nameFileMap()
{
}
bool Zip::Directory::add(std::shared_ptr<InputStream> input, char const *base, std::ostream &output)
{
if (!base || !input) {
MWAW_DEBUG_MSG(("Zip::Directory::add: called without input\n"));
return false;
}
std::string name(base);
// first clean name
size_t len=name.length();
if (base[0]=='\\' || base[0]=='/') {
if (len==1) {
name="";
MWAW_DEBUG_MSG(("Zip::Directory::add: called with name <</>>\n"));
return false;
}
name=base+1;
}
if (m_nameFileMap.find(name) != m_nameFileMap.end()) {
MWAW_DEBUG_MSG(("Zip::Directory::add: a file was already added with this name: %s\n", name.c_str()));
return false;
}
File file(name, m_dir);
if (!file.write(input,output))
return false;
m_nameFileMap.insert(std::map<std::string, File>::value_type(name, file));
return true;
}
int Zip::Directory::writeCentralInformation(std::ostream &output) const
{
int num=0;
for (auto it : m_nameFileMap) {
File const &file=it.second;
if (file.writeCentralInformation(output))
num++;
}
return num;
}
// ------------------------------------------------------------
// Zip implementation
// ------------------------------------------------------------
Zip::Zip()
: m_output()
, m_nameDirectoryMap()
{
}
Zip::~Zip()
{
if (m_output.is_open())
close();
}
bool Zip::open(char const *filename)
{
if (!filename) {
MWAW_DEBUG_MSG(("Zip::open: called without any name\n"));
return false;
}
if (m_output.is_open()) {
MWAW_DEBUG_MSG(("Zip::open: oops, output is already opened\n"));
return false;
}
m_output.open(filename, std::ios::out | std::ios::binary);
if (!m_output.is_open()) {
MWAW_DEBUG_MSG(("Zip::open: can not opend %s\n", filename));
return false;
}
return true;
}
bool Zip::close()
{
if (!m_output.is_open()) {
MWAW_DEBUG_MSG(("Zip::close: the output is already closed\n"));
return false;
}
auto pos=uLong(m_output.tellp());
int numEntries = 0;
for (auto it : m_nameDirectoryMap) {
Directory const &dir=it.second;
numEntries += dir.writeCentralInformation(m_output);
}
char endHeader[] = {
0x50, 0x4b, 0x5, 0x6 /* 0:magic */,
0, 0 /*4: disk number */, 0, 0 /* 6: disk w/cd */,
0, 0 /*8: disk entries */, 0, 0 /* 10: total entries */,
0, 0, 0, 0 /*12: central directory size */,
0, 0, 0, 0 /*16: begin of central directory */,
0, 0 /* 20: comment length */
};
writeU16(endHeader+8, uint16_t(numEntries));
writeU16(endHeader+10, uint16_t(numEntries));
writeU32(endHeader+12, uint32_t(uLong(m_output.tellp())-pos));
writeU32(endHeader+16, uint32_t(pos));
m_output.write(endHeader,22);
m_output.close();
return true;
}
bool Zip::add(std::shared_ptr<InputStream> input, char const *base, char const *path)
{
if (!m_output.is_open())
return false;
if (!base || !input) {
MWAW_DEBUG_MSG(("Zip::add: called without input\n"));
return false;
}
// clean the directory name
std::string dir(path ? path : "");
size_t len = dir.length();
for (size_t c=0; c < len; c++) {
if (dir[c]=='\\')
dir[c]='/';
}
if (!len || (path && path[len-1]!='/'))
dir += '/';
if (m_nameDirectoryMap.find(dir)==m_nameDirectoryMap.end())
m_nameDirectoryMap.insert(std::map<std::string, Directory>::value_type(dir, Directory(dir)));
Directory &direct=m_nameDirectoryMap.find(dir)->second;
return direct.add(input, base, m_output);
}
}
// vim: set filetype=cpp tabstop=2 shiftwidth=2 cindent autoindent smartindent noexpandtab: