Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / shell / source / win32 / zipfile / zipfile.cxx
blob13f79a0410184942be4eba4f37b60f1cf3a9cfa2
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include "zipexcptn.hxx"
21 #include <zipfile.hxx>
22 #include <global.hxx>
23 #include <types.hxx>
24 #include <stream_helper.hxx>
26 #include <malloc.h>
27 #include <algorithm>
28 #include <memory>
30 #include <string.h>
32 namespace
35 struct LocalFileHeader
37 unsigned short min_version;
38 unsigned short general_flag;
39 unsigned short compression;
40 unsigned short lastmod_time;
41 unsigned short lastmod_date;
42 unsigned crc32;
43 unsigned compressed_size;
44 unsigned uncompressed_size;
45 unsigned short filename_size;
46 unsigned short extra_field_size;
47 std::string filename;
48 std::string extra_field;
49 LocalFileHeader()
50 : min_version(0), general_flag(0), compression(0), lastmod_time(0), lastmod_date(0),
51 crc32(0), compressed_size(0), uncompressed_size(0), filename_size(0), extra_field_size(0),
52 filename(), extra_field() {}
55 struct CentralDirectoryEntry
57 unsigned short creator_version;
58 unsigned short min_version;
59 unsigned short general_flag;
60 unsigned short compression;
61 unsigned short lastmod_time;
62 unsigned short lastmod_date;
63 unsigned crc32;
64 unsigned compressed_size;
65 unsigned uncompressed_size;
66 unsigned short filename_size;
67 unsigned short extra_field_size;
68 unsigned short file_comment_size;
69 unsigned short disk_num;
70 unsigned short internal_attr;
71 unsigned external_attr;
72 unsigned offset;
73 std::string filename;
74 std::string extra_field;
75 std::string file_comment;
76 CentralDirectoryEntry()
77 : creator_version(0), min_version(0), general_flag(0), compression(0), lastmod_time(0),
78 lastmod_date(0), crc32(0), compressed_size(0), uncompressed_size(0), filename_size(0),
79 extra_field_size(0), file_comment_size(0), disk_num(0), internal_attr(0),
80 external_attr(0), offset(0), filename(), extra_field(), file_comment() {}
83 struct CentralDirectoryEnd
85 unsigned short disk_num;
86 unsigned short cdir_disk;
87 unsigned short disk_entries;
88 unsigned short cdir_entries;
89 unsigned cdir_size;
90 unsigned cdir_offset;
91 unsigned short comment_size;
92 std::string comment;
93 CentralDirectoryEnd()
94 : disk_num(0), cdir_disk(0), disk_entries(0), cdir_entries(0),
95 cdir_size(0), cdir_offset(0), comment_size(0), comment() {}
98 #define CDIR_ENTRY_SIG 0x02014b50
99 #define LOC_FILE_HEADER_SIG 0x04034b50
100 #define CDIR_END_SIG 0x06054b50
102 // This little lot performs in a truly appalling way without
103 // buffering eg. on an IStream.
105 unsigned short readShort(StreamInterface *stream)
107 if (!stream || stream->stell() == -1)
108 throw IOException(-1);
109 unsigned short tmpBuf;
110 unsigned long numBytesRead = stream->sread(
111 reinterpret_cast<unsigned char *>( &tmpBuf ), 2);
112 if (numBytesRead != 2)
113 throw IOException(-1);
114 return tmpBuf;
117 unsigned readInt(StreamInterface *stream)
119 if (!stream || stream->stell() == -1)
120 throw IOException(-1);
121 unsigned tmpBuf;
122 unsigned long numBytesRead = stream->sread(
123 reinterpret_cast<unsigned char *>( &tmpBuf ), 4);
124 if (numBytesRead != 4)
125 throw IOException(-1);
126 return tmpBuf;
129 std::string readString(StreamInterface *stream, unsigned long size)
131 if (!stream || stream->stell() == -1)
132 throw IOException(-1);
133 auto tmp = std::make_unique<unsigned char[]>(size);
134 unsigned long numBytesRead = stream->sread(tmp.get(), size);
135 if (numBytesRead != size)
137 throw IOException(-1);
140 std::string aStr(reinterpret_cast<char *>(tmp.get()), size);
141 return aStr;
144 bool readCentralDirectoryEnd(StreamInterface *stream, CentralDirectoryEnd &end)
148 unsigned signature = readInt(stream);
149 if (signature != CDIR_END_SIG)
150 return false;
152 end.disk_num = readShort(stream);
153 end.cdir_disk = readShort(stream);
154 end.disk_entries = readShort(stream);
155 end.cdir_entries = readShort(stream);
156 end.cdir_size = readInt(stream);
157 end.cdir_offset = readInt(stream);
158 end.comment_size = readShort(stream);
159 end.comment.assign(readString(stream, end.comment_size));
161 catch (...)
163 return false;
165 return true;
168 bool readCentralDirectoryEntry(StreamInterface *stream, CentralDirectoryEntry &entry)
172 unsigned signature = readInt(stream);
173 if (signature != CDIR_ENTRY_SIG)
174 return false;
176 entry.creator_version = readShort(stream);
177 entry.min_version = readShort(stream);
178 entry.general_flag = readShort(stream);
179 entry.compression = readShort(stream);
180 entry.lastmod_time = readShort(stream);
181 entry.lastmod_date = readShort(stream);
182 entry.crc32 = readInt(stream);
183 entry.compressed_size = readInt(stream);
184 entry.uncompressed_size = readInt(stream);
185 entry.filename_size = readShort(stream);
186 entry.extra_field_size = readShort(stream);
187 entry.file_comment_size = readShort(stream);
188 entry.disk_num = readShort(stream);
189 entry.internal_attr = readShort(stream);
190 entry.external_attr = readInt(stream);
191 entry.offset = readInt(stream);
192 entry.filename.assign(readString(stream, entry.filename_size));
193 entry.extra_field.assign(readString(stream, entry.extra_field_size));
194 entry.file_comment.assign(readString(stream, entry.file_comment_size));
196 catch (...)
198 return false;
200 return true;
203 bool readLocalFileHeader(StreamInterface *stream, LocalFileHeader &header)
207 unsigned signature = readInt(stream);
208 if (signature != LOC_FILE_HEADER_SIG)
209 return false;
211 header.min_version = readShort(stream);
212 header.general_flag = readShort(stream);
213 header.compression = readShort(stream);
214 header.lastmod_time = readShort(stream);
215 header.lastmod_date = readShort(stream);
216 header.crc32 = readInt(stream);
217 header.compressed_size = readInt(stream);
218 header.uncompressed_size = readInt(stream);
219 header.filename_size = readShort(stream);
220 header.extra_field_size = readShort(stream);
221 header.filename.assign(readString(stream, header.filename_size));
222 header.extra_field.assign(readString(stream, header.extra_field_size));
224 catch (...)
226 return false;
228 return true;
231 bool areHeadersConsistent(const LocalFileHeader &header, const CentralDirectoryEntry &entry)
233 if (header.min_version != entry.min_version)
234 return false;
235 if (header.general_flag != entry.general_flag)
236 return false;
237 if (header.compression != entry.compression)
238 return false;
239 if (!(header.general_flag & 0x08))
241 if (header.crc32 != entry.crc32)
242 return false;
243 if (header.compressed_size != entry.compressed_size)
244 return false;
245 if (header.uncompressed_size != entry.uncompressed_size)
246 return false;
248 return true;
251 #define BLOCK_SIZE 0x800
253 bool findSignatureAtOffset(StreamInterface *stream, unsigned long nOffset)
255 // read in reasonably sized chunk, and read more, to get overlapping sigs
256 unsigned char aBuffer[ BLOCK_SIZE + 4 ];
258 stream->sseek(nOffset, SEEK_SET);
260 unsigned long nBytesRead = stream->sread(aBuffer, sizeof(aBuffer));
262 for (long n = nBytesRead - 4; n >= 0; n--)
264 if (aBuffer[n ] == 0x50 && aBuffer[n+1] == 0x4b &&
265 aBuffer[n+2] == 0x05 && aBuffer[n+3] == 0x06)
266 { // a palpable hit ...
267 stream->sseek(nOffset + n, SEEK_SET);
268 return true;
272 return false;
275 bool findCentralDirectoryEnd(StreamInterface *stream)
277 if (!stream)
278 return false;
280 stream->sseek(0,SEEK_END);
282 long nLength = stream->stell();
283 if (nLength == -1)
284 return false;
288 for (long nOffset = nLength - BLOCK_SIZE - 4;
289 nOffset > 0; nOffset -= BLOCK_SIZE)
291 if (findSignatureAtOffset(stream, nOffset))
292 return true;
294 return findSignatureAtOffset(stream, 0);
296 catch (...)
298 return false;
302 bool isZipStream(StreamInterface *stream)
304 if (!findCentralDirectoryEnd(stream))
305 return false;
306 CentralDirectoryEnd end;
307 if (!readCentralDirectoryEnd(stream, end))
308 return false;
309 stream->sseek(end.cdir_offset, SEEK_SET);
310 CentralDirectoryEntry entry;
311 if (!readCentralDirectoryEntry(stream, entry))
312 return false;
313 stream->sseek(entry.offset, SEEK_SET);
314 LocalFileHeader header;
315 if (!readLocalFileHeader(stream, header))
316 return false;
317 if (!areHeadersConsistent(header, entry))
318 return false;
319 return true;
322 } // anonymous namespace
324 namespace internal
326 /* for case in-sensitive string comparison */
327 struct stricmp
329 explicit stricmp(const std::string &str) : str_(str)
332 bool operator() (const std::string &other)
334 return (0 == _stricmp(str_.c_str(), other.c_str()));
337 std::string str_;
339 } // namespace internal
341 /** Checks whether a file is a zip file or not
343 @precond The given parameter must be a string with length > 0
344 The file must exist
345 The file must be readable for the current user
347 @returns true if the file is a zip file
348 false if the file is not a zip file
350 @throws ParameterException if the given file name is empty
351 IOException if the specified file doesn't exist
352 AccessViolationException if read access to the file is denied
354 bool ZipFile::IsZipFile(const Filepath_t& /*FileName*/)
356 return true;
359 bool ZipFile::IsZipFile(void* /*stream*/)
361 return true;
365 /** Returns whether the version of the specified zip file may be uncompressed with the
366 currently used zlib version or not
368 @precond The given parameter must be a string with length > 0
369 The file must exist
370 The file must be readable for the current user
371 The file must be a valid zip file
373 @returns true if the file may be uncompressed with the currently used zlib
374 false if the file may not be uncompressed with the currently used zlib
376 @throws ParameterException if the given file name is empty
377 IOException if the specified file doesn't exist or is no zip file
378 AccessViolationException if read access to the file is denied
380 bool ZipFile::IsValidZipFileVersionNumber(const Filepath_t& /*FileName*/)
382 return true;
385 bool ZipFile::IsValidZipFileVersionNumber(void* /* stream*/)
387 return true;
391 /** Constructs a zip file from a zip file
393 @precond The given parameter must be a string with length > 0
394 The file must exist
395 The file must be readable for the current user
397 @throws ParameterException if the given file name is empty
398 IOException if the specified file doesn't exist or is no valid zip file
399 AccessViolationException if read access to the file is denied
400 WrongZipVersionException if the zip file cannot be uncompressed
401 with the used zlib version
403 ZipFile::ZipFile(const Filepath_t &FileName) :
404 m_pStream(nullptr),
405 m_bShouldFree(true)
407 m_pStream = new FileStream(FileName.c_str());
408 if (!isZipStream(m_pStream))
410 delete m_pStream;
411 m_pStream = nullptr;
415 ZipFile::ZipFile(StreamInterface *stream) :
416 m_pStream(stream),
417 m_bShouldFree(false)
419 if (!isZipStream(stream))
420 m_pStream = nullptr;
424 /** Destroys a zip file
426 ZipFile::~ZipFile()
428 if (m_pStream && m_bShouldFree)
429 delete m_pStream;
432 /** Provides an interface to read the uncompressed data of a content of the zip file
434 @precond The specified content must exist in this file
435 ppstm must not be NULL
437 void ZipFile::GetUncompressedContent(
438 const std::string &ContentName, /*inout*/ ZipContentBuffer_t &ContentBuffer)
440 if (!findCentralDirectoryEnd(m_pStream))
441 return;
442 CentralDirectoryEnd end;
443 if (!readCentralDirectoryEnd(m_pStream, end))
444 return;
445 m_pStream->sseek(end.cdir_offset, SEEK_SET);
446 CentralDirectoryEntry entry;
447 while (m_pStream->stell() != -1 && static_cast<unsigned long>(m_pStream->stell()) < end.cdir_offset + end.cdir_size)
449 if (!readCentralDirectoryEntry(m_pStream, entry))
450 return;
451 if (ContentName.length() == entry.filename_size && !_stricmp(entry.filename.c_str(), ContentName.c_str()))
452 break;
454 if (ContentName.length() != entry.filename_size)
455 return;
456 if (_stricmp(entry.filename.c_str(), ContentName.c_str()))
457 return;
458 m_pStream->sseek(entry.offset, SEEK_SET);
459 LocalFileHeader header;
460 if (!readLocalFileHeader(m_pStream, header))
461 return;
462 if (!areHeadersConsistent(header, entry))
463 return;
464 ContentBuffer.clear();
465 ContentBuffer = ZipContentBuffer_t(entry.uncompressed_size);
466 if (!entry.compression)
467 m_pStream->sread(reinterpret_cast<unsigned char *>(ContentBuffer.data()), entry.uncompressed_size);
468 else
470 int ret;
471 z_stream strm;
473 /* allocate inflate state */
474 strm.zalloc = Z_NULL;
475 strm.zfree = Z_NULL;
476 strm.opaque = Z_NULL;
477 strm.avail_in = 0;
478 strm.next_in = Z_NULL;
479 ret = inflateInit2(&strm,-MAX_WBITS);
480 if (ret != Z_OK)
481 return;
483 std::vector<unsigned char> tmpBuffer(entry.compressed_size);
484 if (entry.compressed_size != m_pStream->sread(tmpBuffer.data(), entry.compressed_size))
485 return;
487 strm.avail_in = entry.compressed_size;
488 strm.next_in = reinterpret_cast<Bytef *>(tmpBuffer.data());
490 strm.avail_out = entry.uncompressed_size;
491 strm.next_out = reinterpret_cast<Bytef *>(ContentBuffer.data());
492 ret = inflate(&strm, Z_FINISH);
493 switch (ret)
495 case Z_NEED_DICT:
496 case Z_DATA_ERROR:
497 case Z_MEM_ERROR:
498 (void)inflateEnd(&strm);
499 ContentBuffer.clear();
500 return;
502 (void)inflateEnd(&strm);
506 /** Returns a list with the content names contained within this file
509 ZipFile::DirectoryPtr_t ZipFile::GetDirectory() const
511 DirectoryPtr_t dir(new Directory_t());
512 if (!findCentralDirectoryEnd(m_pStream))
513 return dir;
514 CentralDirectoryEnd end;
515 if (!readCentralDirectoryEnd(m_pStream, end))
516 return dir;
517 m_pStream->sseek(end.cdir_offset, SEEK_SET);
518 CentralDirectoryEntry entry;
519 while (m_pStream->stell() != -1 && static_cast<unsigned long>(m_pStream->stell()) < end.cdir_offset + end.cdir_size)
521 if (!readCentralDirectoryEntry(m_pStream, entry))
522 return dir;
523 if (entry.filename_size)
524 dir->push_back(entry.filename);
526 return dir;
529 /** Convenience query function may even realized with
530 iterating over a ZipFileDirectory returned by
531 GetDirectory */
532 bool ZipFile::HasContent(const std::string &ContentName) const
534 //#i34314# we need to compare package content names
535 //case in-sensitive as it is not defined that such
536 //names must be handled case sensitive
537 DirectoryPtr_t dir = GetDirectory();
539 return std::any_of(dir->begin(), dir->end(), internal::stricmp(ContentName));
543 /** Returns the length of the longest file name
544 in the current zip file
546 long ZipFile::GetFileLongestFileNameLength() const
548 long lmax = 0;
549 if (!findCentralDirectoryEnd(m_pStream))
550 return lmax;
551 CentralDirectoryEnd end;
552 if (!readCentralDirectoryEnd(m_pStream, end))
553 return lmax;
554 m_pStream->sseek(end.cdir_offset, SEEK_SET);
555 CentralDirectoryEntry entry;
556 while (m_pStream->stell() != -1 && static_cast<unsigned long>(m_pStream->stell()) < end.cdir_offset + end.cdir_size)
558 if (!readCentralDirectoryEntry(m_pStream, entry))
559 return lmax;
560 if (entry.filename_size > lmax)
561 lmax = entry.filename_size;
563 return lmax;
566 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */