1//
2// Compress.cpp
3//
4// Library: Zip
5// Package: Zip
6// Module: Compress
7//
8// Copyright (c) 2007, Applied Informatics Software Engineering GmbH.
9// and Contributors.
10//
11// SPDX-License-Identifier: BSL-1.0
12//
13
14
15#include "Poco/Zip/Compress.h"
16#include "Poco/Zip/ZipLocalFileHeader.h"
17#include "Poco/Zip/ZipStream.h"
18#include "Poco/Zip/ZipArchiveInfo.h"
19#include "Poco/Zip/ZipDataInfo.h"
20#include "Poco/Zip/ZipException.h"
21#include "Poco/StreamCopier.h"
22#include "Poco/File.h"
23#include "Poco/FileStream.h"
24#include "Poco/String.h"
25
26
27namespace Poco {
28namespace Zip {
29
30
31Compress::Compress(std::ostream& out, bool seekableOut, bool forceZip64):
32 _out(out),
33 _seekableOut(seekableOut),
34 _forceZip64(forceZip64),
35 _files(),
36 _infos(),
37 _dirs(),
38 _offset(0)
39{
40 _storeExtensions.insert("gif");
41 _storeExtensions.insert("png");
42 _storeExtensions.insert("jpg");
43 _storeExtensions.insert("jpeg");
44}
45
46
47Compress::~Compress()
48{
49}
50
51
52void Compress::addEntry(std::istream& in, const Poco::DateTime& lastModifiedAt, const Poco::Path& fileName, ZipCommon::CompressionMethod cm, ZipCommon::CompressionLevel cl)
53{
54 if (cm == ZipCommon::CM_AUTO)
55 {
56 std::string ext = Poco::toLower(fileName.getExtension());
57 if (_storeExtensions.find(ext) != _storeExtensions.end())
58 {
59 cm = ZipCommon::CM_STORE;
60 cl = ZipCommon::CL_NORMAL;
61 }
62 else
63 {
64 cm = ZipCommon::CM_DEFLATE;
65 }
66 }
67
68 std::string fn = ZipUtil::validZipEntryFileName(fileName);
69
70 if (!in.good())
71 throw ZipException("Invalid input stream");
72
73 // Check if stream is empty.
74 // In this case, we have to set compression to STORE, otherwise
75 // extraction will fail with various tools.
76 const int eof = std::char_traits<char>::eof();
77 int firstChar = in.get();
78 if (firstChar == eof)
79 {
80 cm = ZipCommon::CM_STORE;
81 cl = ZipCommon::CL_NORMAL;
82 }
83
84 std::streamoff localHeaderOffset = _offset;
85 ZipLocalFileHeader hdr(fileName, lastModifiedAt, cm, cl, _forceZip64);
86 hdr.setStartPos(localHeaderOffset);
87
88 ZipOutputStream zipOut(_out, hdr, _seekableOut);
89 if (firstChar != eof)
90 {
91 zipOut.put(static_cast<char>(firstChar));
92 Poco::StreamCopier::copyStream(in, zipOut);
93 }
94 Poco::UInt64 extraDataSize;
95 zipOut.close(extraDataSize);
96 _offset = hdr.getEndPos();
97 _offset += extraDataSize;
98 _files.insert(std::make_pair(fileName.toString(Poco::Path::PATH_UNIX), hdr));
99 if (!_out) throw Poco::IOException("Bad output stream");
100 ZipFileInfo nfo(hdr);
101 nfo.setOffset(localHeaderOffset);
102 nfo.setZip64Data();
103 _infos.insert(std::make_pair(fileName.toString(Poco::Path::PATH_UNIX), nfo));
104 EDone.notify(this, hdr);
105}
106
107
108void Compress::addFileRaw(std::istream& in, const ZipLocalFileHeader& h, const Poco::Path& fileName)
109{
110 if (!in.good())
111 throw ZipException("Invalid input stream");
112
113 std::string fn = ZipUtil::validZipEntryFileName(fileName);
114 //bypass the header of the input stream and point to the first byte of the data payload
115 in.seekg(h.getDataStartPos(), std::ios_base::beg);
116 if (!in.good()) throw Poco::IOException("Failed to seek on input stream");
117
118 std::streamoff localHeaderOffset = _offset;
119 ZipLocalFileHeader hdr(h);
120 hdr.setFileName(fn, h.isDirectory());
121 hdr.setStartPos(localHeaderOffset);
122 if (hdr.needsZip64())
123 hdr.setZip64Data();
124 //bypass zipoutputstream
125 //write the header directly
126 std::string header = hdr.createHeader();
127 _out.write(header.c_str(), static_cast<std::streamsize>(header.size()));
128 // now fwd the payload to _out in chunks of size CHUNKSIZE
129 Poco::UInt64 totalSize = hdr.getCompressedSize();
130 if (totalSize > 0)
131 {
132 Poco::Buffer<char> buffer(COMPRESS_CHUNK_SIZE);
133 Poco::UInt64 remaining = totalSize;
134 while (remaining > 0)
135 {
136 if (remaining > COMPRESS_CHUNK_SIZE)
137 {
138 in.read(buffer.begin(), COMPRESS_CHUNK_SIZE);
139 std::streamsize n = in.gcount();
140 poco_assert_dbg (n == COMPRESS_CHUNK_SIZE);
141 _out.write(buffer.begin(), n);
142 remaining -= COMPRESS_CHUNK_SIZE;
143 }
144 else
145 {
146 in.read(buffer.begin(), remaining);
147 std::streamsize n = in.gcount();
148 poco_assert_dbg (static_cast<Poco::UInt64>(n) == remaining);
149 _out.write(buffer.begin(), n);
150 remaining = 0;
151 }
152 }
153 }
154 hdr.setStartPos(localHeaderOffset); // This resets EndPos now that compressed Size is known
155 _offset = hdr.getEndPos();
156 //write optional block afterwards
157 if (hdr.searchCRCAndSizesAfterData())
158 {
159 if (hdr.needsZip64())
160 {
161 ZipDataInfo64 info(in, false);
162 _out.write(info.getRawHeader(), static_cast<std::streamsize>(info.getFullHeaderSize()));
163 _offset += ZipDataInfo::getFullHeaderSize();
164 }
165 else
166 {
167 ZipDataInfo info(in, false);
168 _out.write(info.getRawHeader(), static_cast<std::streamsize>(info.getFullHeaderSize()));
169 _offset += ZipDataInfo::getFullHeaderSize();
170 }
171 }
172 else
173 {
174 if (hdr.hasExtraField()) // Update sizes in header extension.
175 hdr.setZip64Data();
176 _out.seekp(hdr.getStartPos(), std::ios_base::beg);
177 std::string header2 = hdr.createHeader();
178 _out.write(header2.c_str(), static_cast<std::streamsize>(header2.size()));
179 _out.seekp(0, std::ios_base::end);
180 }
181
182 _files.insert(std::make_pair(fileName.toString(Poco::Path::PATH_UNIX), hdr));
183 if (!_out) throw Poco::IOException("Bad output stream");
184 ZipFileInfo nfo(hdr);
185 nfo.setOffset(localHeaderOffset);
186 nfo.setZip64Data();
187 _infos.insert(std::make_pair(fileName.toString(Poco::Path::PATH_UNIX), nfo));
188 EDone.notify(this, hdr);
189}
190
191
192void Compress::addFile(std::istream& in, const Poco::DateTime& lastModifiedAt, const Poco::Path& fileName, ZipCommon::CompressionMethod cm, ZipCommon::CompressionLevel cl)
193{
194 if (!fileName.isFile())
195 throw ZipException("Not a file: "+ fileName.toString());
196
197 if (fileName.depth() > 1)
198 {
199 addDirectory(fileName.parent(), lastModifiedAt);
200 }
201 addEntry(in, lastModifiedAt, fileName, cm, cl);
202}
203
204
205void Compress::addFile(const Poco::Path& file, const Poco::Path& fileName, ZipCommon::CompressionMethod cm, ZipCommon::CompressionLevel cl)
206{
207 Poco::File aFile(file);
208 Poco::FileInputStream in(file.toString());
209 if (fileName.depth() > 1)
210 {
211 Poco::File aParent(file.parent());
212 addDirectory(fileName.parent(), aParent.getLastModified());
213 }
214 addFile(in, aFile.getLastModified(), fileName, cm, cl);
215}
216
217
218void Compress::addDirectory(const Poco::Path& entryName, const Poco::DateTime& lastModifiedAt)
219{
220 if (!entryName.isDirectory())
221 throw ZipException("Not a directory: "+ entryName.toString());
222
223 std::string fileStr = entryName.toString(Poco::Path::PATH_UNIX);
224 if (_files.find(fileStr) != _files.end())
225 return; // ignore duplicate add
226 if (fileStr == "/")
227 throw ZipException("Illegal entry name /");
228 if (fileStr.empty())
229 throw ZipException("Illegal empty entry name");
230 if (!ZipCommon::isValidPath(fileStr))
231 throw ZipException("Illegal entry name " + fileStr + " containing parent directory reference");
232
233 if (entryName.depth() > 1)
234 {
235 addDirectory(entryName.parent(), lastModifiedAt);
236 }
237
238 std::streamoff localHeaderOffset = _offset;
239 ZipCommon::CompressionMethod cm = ZipCommon::CM_STORE;
240 ZipCommon::CompressionLevel cl = ZipCommon::CL_NORMAL;
241 ZipLocalFileHeader hdr(entryName, lastModifiedAt, cm, cl);
242 hdr.setStartPos(localHeaderOffset);
243 ZipOutputStream zipOut(_out, hdr, _seekableOut);
244 Poco::UInt64 extraDataSize;
245 zipOut.close(extraDataSize);
246 hdr.setStartPos(localHeaderOffset); // reset again now that compressed Size is known
247 _offset = hdr.getEndPos();
248 if (hdr.searchCRCAndSizesAfterData())
249 _offset += extraDataSize;
250 _files.insert(std::make_pair(entryName.toString(Poco::Path::PATH_UNIX), hdr));
251 if (!_out) throw Poco::IOException("Bad output stream");
252 ZipFileInfo nfo(hdr);
253 nfo.setOffset(localHeaderOffset);
254 nfo.setZip64Data();
255 _infos.insert(std::make_pair(entryName.toString(Poco::Path::PATH_UNIX), nfo));
256 EDone.notify(this, hdr);
257}
258
259
260void Compress::addRecursive(const Poco::Path& entry, ZipCommon::CompressionLevel cl, bool excludeRoot, const Poco::Path& name)
261{
262 addRecursive(entry, ZipCommon::CM_DEFLATE, cl, excludeRoot, name);
263}
264
265
266void Compress::addRecursive(const Poco::Path& entry, ZipCommon::CompressionMethod cm, ZipCommon::CompressionLevel cl, bool excludeRoot, const Poco::Path& name)
267{
268 Poco::File aFile(entry);
269 if (!aFile.isDirectory())
270 throw ZipException("Not a directory: "+ entry.toString());
271 Poco::Path aName(name);
272 aName.makeDirectory();
273 if (!excludeRoot)
274 {
275 if (aName.depth() == 0)
276 {
277 Poco::Path tmp(entry);
278 tmp.makeAbsolute(); // eliminate ../
279 aName = Poco::Path(tmp[tmp.depth()-1]);
280 aName.makeDirectory();
281 }
282
283 addDirectory(aName, aFile.getLastModified());
284 }
285
286 // iterate over children
287 std::vector<std::string> children;
288 aFile.list(children);
289 std::vector<std::string>::const_iterator it = children.begin();
290 std::vector<std::string>::const_iterator itEnd = children.end();
291 for (; it != itEnd; ++it)
292 {
293 Poco::Path realFile(entry, *it);
294 Poco::Path renamedFile(aName, *it);
295 Poco::File aFile2(realFile);
296 if (aFile2.isDirectory())
297 {
298 realFile.makeDirectory();
299 renamedFile.makeDirectory();
300 addRecursive(realFile, cm, cl, false, renamedFile);
301 }
302 else
303 {
304 realFile.makeFile();
305 renamedFile.makeFile();
306 addFile(realFile, renamedFile, cm, cl);
307 }
308 }
309}
310
311
312ZipArchive Compress::close()
313{
314 if (!_dirs.empty() || ! _dirs64.empty())
315 return ZipArchive(_files, _infos, _dirs, _dirs64);
316
317 poco_assert (_infos.size() == _files.size());
318 Poco::UInt64 centralDirSize64 = 0;
319 Poco::UInt64 centralDirStart64 = _offset;
320 // write all infos
321 ZipArchive::FileInfos::const_iterator it = _infos.begin();
322 ZipArchive::FileInfos::const_iterator itEnd = _infos.end();
323 bool needZip64 = _forceZip64;
324 needZip64 = needZip64 || _files.size() >= ZipCommon::ZIP64_MAGIC_SHORT || centralDirStart64 >= ZipCommon::ZIP64_MAGIC;
325 for (; it != itEnd; ++it)
326 {
327 const ZipFileInfo& nfo = it->second;
328 needZip64 = needZip64 || nfo.needsZip64();
329
330 std::string info(nfo.createHeader());
331 _out.write(info.c_str(), static_cast<std::streamsize>(info.size()));
332 Poco::UInt32 entrySize = static_cast<Poco::UInt32>(info.size());
333 centralDirSize64 += entrySize;
334 _offset += entrySize;
335 }
336 if (!_out) throw Poco::IOException("Bad output stream");
337
338 Poco::UInt64 numEntries64 = _infos.size();
339 needZip64 = needZip64 || _offset >= ZipCommon::ZIP64_MAGIC;
340 if (needZip64)
341 {
342 ZipArchiveInfo64 central;
343 central.setCentralDirectorySize(centralDirSize64);
344 central.setCentralDirectoryOffset(centralDirStart64);
345 central.setNumberOfEntries(numEntries64);
346 central.setTotalNumberOfEntries(numEntries64);
347 central.setHeaderOffset(_offset);
348 central.setTotalNumberOfDisks(1);
349 std::string centr(central.createHeader());
350 _out.write(centr.c_str(), static_cast<std::streamsize>(centr.size()));
351 _out.flush();
352 _offset += centr.size();
353 _dirs64.insert(std::make_pair(0, central));
354 }
355
356 Poco::UInt16 numEntries = numEntries64 >= ZipCommon::ZIP64_MAGIC_SHORT ? ZipCommon::ZIP64_MAGIC_SHORT : static_cast<Poco::UInt16>(numEntries64);
357 Poco::UInt32 centralDirStart = centralDirStart64 >= ZipCommon::ZIP64_MAGIC ? ZipCommon::ZIP64_MAGIC : static_cast<Poco::UInt32>(centralDirStart64);
358 Poco::UInt32 centralDirSize = centralDirSize64 >= ZipCommon::ZIP64_MAGIC ? ZipCommon::ZIP64_MAGIC : static_cast<Poco::UInt32>(centralDirSize64);
359 Poco::UInt32 offset = _offset >= ZipCommon::ZIP64_MAGIC ? ZipCommon::ZIP64_MAGIC : static_cast<Poco::UInt32>(_offset);
360 ZipArchiveInfo central;
361 central.setCentralDirectorySize(centralDirSize);
362 central.setCentralDirectoryOffset(centralDirStart);
363 central.setNumberOfEntries(numEntries);
364 central.setTotalNumberOfEntries(numEntries);
365 central.setHeaderOffset(offset);
366 if (!_comment.empty() && _comment.size() <= 65535)
367 {
368 central.setZipComment(_comment);
369 }
370 std::string centr(central.createHeader());
371 _out.write(centr.c_str(), static_cast<std::streamsize>(centr.size()));
372 _out.flush();
373 _offset += centr.size();
374 _dirs.insert(std::make_pair(0, central));
375 return ZipArchive(_files, _infos, _dirs, _dirs64);
376}
377
378
379void Compress::setStoreExtensions(const std::set<std::string>& extensions)
380{
381 _storeExtensions.clear();
382 for (std::set<std::string>::const_iterator it = extensions.begin(); it != extensions.end(); ++it)
383 {
384 _storeExtensions.insert(Poco::toLower(*it));
385 }
386}
387
388
389} } // namespace Poco::Zip
390