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 | |
27 | namespace Poco { |
28 | namespace Zip { |
29 | |
30 | |
31 | Compress::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 | |
47 | Compress::~Compress() |
48 | { |
49 | } |
50 | |
51 | |
52 | void 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 = _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 ; |
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 | |
108 | void Compress::(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 = _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 = 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 = 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 | |
192 | void 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 | |
205 | void 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 | |
218 | void 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 = _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 ; |
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 | |
260 | void 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 | |
266 | void 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 | |
312 | ZipArchive 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 | |
379 | void 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 | |