Update git submodules
[LibreOffice.git] / shell / source / win32 / zipfile / zipfile.cxx
blob19203e631ed5efd13ca5fcf4e20ae8fdb25cca0f
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 #include <o3tl/safeint.hxx>
34 #include <zlib.h>
36 namespace
39 struct LocalFileHeader
41 unsigned short min_version;
42 unsigned short general_flag;
43 unsigned short compression;
44 unsigned short lastmod_time;
45 unsigned short lastmod_date;
46 unsigned crc32;
47 unsigned compressed_size;
48 unsigned uncompressed_size;
49 unsigned short filename_size;
50 unsigned short extra_field_size;
51 std::string filename;
52 std::string extra_field;
53 LocalFileHeader()
54 : min_version(0), general_flag(0), compression(0), lastmod_time(0), lastmod_date(0),
55 crc32(0), compressed_size(0), uncompressed_size(0), filename_size(0), extra_field_size(0),
56 filename(), extra_field() {}
59 struct CentralDirectoryEntry
61 unsigned short creator_version;
62 unsigned short min_version;
63 unsigned short general_flag;
64 unsigned short compression;
65 unsigned short lastmod_time;
66 unsigned short lastmod_date;
67 unsigned crc32;
68 unsigned compressed_size;
69 unsigned uncompressed_size;
70 unsigned short filename_size;
71 unsigned short extra_field_size;
72 unsigned short file_comment_size;
73 unsigned short disk_num;
74 unsigned short internal_attr;
75 unsigned external_attr;
76 unsigned offset;
77 std::string filename;
78 std::string extra_field;
79 std::string file_comment;
80 CentralDirectoryEntry()
81 : creator_version(0), min_version(0), general_flag(0), compression(0), lastmod_time(0),
82 lastmod_date(0), crc32(0), compressed_size(0), uncompressed_size(0), filename_size(0),
83 extra_field_size(0), file_comment_size(0), disk_num(0), internal_attr(0),
84 external_attr(0), offset(0), filename(), extra_field(), file_comment() {}
87 struct CentralDirectoryEnd
89 unsigned short disk_num;
90 unsigned short cdir_disk;
91 unsigned short disk_entries;
92 unsigned short cdir_entries;
93 unsigned cdir_size;
94 unsigned cdir_offset;
95 unsigned short comment_size;
96 std::string comment;
97 CentralDirectoryEnd()
98 : disk_num(0), cdir_disk(0), disk_entries(0), cdir_entries(0),
99 cdir_size(0), cdir_offset(0), comment_size(0), comment() {}
102 #define CDIR_ENTRY_SIG 0x02014b50
103 #define LOC_FILE_HEADER_SIG 0x04034b50
104 #define CDIR_END_SIG 0x06054b50
106 // This little lot performs in a truly appalling way without
107 // buffering eg. on an IStream.
109 unsigned short readShort(StreamInterface *stream)
111 if (!stream || stream->stell() == -1)
112 throw IOException(-1);
113 unsigned short tmpBuf;
114 unsigned long numBytesRead = stream->sread(
115 reinterpret_cast<unsigned char *>( &tmpBuf ), 2);
116 if (numBytesRead != 2)
117 throw IOException(-1);
118 return tmpBuf;
121 unsigned readInt(StreamInterface *stream)
123 if (!stream || stream->stell() == -1)
124 throw IOException(-1);
125 unsigned tmpBuf;
126 unsigned long numBytesRead = stream->sread(
127 reinterpret_cast<unsigned char *>( &tmpBuf ), 4);
128 if (numBytesRead != 4)
129 throw IOException(-1);
130 return tmpBuf;
133 std::string readString(StreamInterface *stream, unsigned long size)
135 if (!stream || stream->stell() == -1)
136 throw IOException(-1);
137 auto tmp = std::make_unique<unsigned char[]>(size);
138 unsigned long numBytesRead = stream->sread(tmp.get(), size);
139 if (numBytesRead != size)
141 throw IOException(-1);
144 std::string aStr(reinterpret_cast<char *>(tmp.get()), size);
145 return aStr;
148 bool readCentralDirectoryEnd(StreamInterface *stream, CentralDirectoryEnd &end)
152 unsigned signature = readInt(stream);
153 if (signature != CDIR_END_SIG)
154 return false;
156 end.disk_num = readShort(stream);
157 end.cdir_disk = readShort(stream);
158 end.disk_entries = readShort(stream);
159 end.cdir_entries = readShort(stream);
160 end.cdir_size = readInt(stream);
161 end.cdir_offset = readInt(stream);
162 end.comment_size = readShort(stream);
163 end.comment.assign(readString(stream, end.comment_size));
165 catch (...)
167 return false;
169 return true;
172 bool readCentralDirectoryEntry(StreamInterface *stream, CentralDirectoryEntry &entry)
176 unsigned signature = readInt(stream);
177 if (signature != CDIR_ENTRY_SIG)
178 return false;
180 entry.creator_version = readShort(stream);
181 entry.min_version = readShort(stream);
182 entry.general_flag = readShort(stream);
183 entry.compression = readShort(stream);
184 entry.lastmod_time = readShort(stream);
185 entry.lastmod_date = readShort(stream);
186 entry.crc32 = readInt(stream);
187 entry.compressed_size = readInt(stream);
188 entry.uncompressed_size = readInt(stream);
189 entry.filename_size = readShort(stream);
190 entry.extra_field_size = readShort(stream);
191 entry.file_comment_size = readShort(stream);
192 entry.disk_num = readShort(stream);
193 entry.internal_attr = readShort(stream);
194 entry.external_attr = readInt(stream);
195 entry.offset = readInt(stream);
196 entry.filename.assign(readString(stream, entry.filename_size));
197 entry.extra_field.assign(readString(stream, entry.extra_field_size));
198 entry.file_comment.assign(readString(stream, entry.file_comment_size));
200 catch (...)
202 return false;
204 return true;
207 bool readLocalFileHeader(StreamInterface *stream, LocalFileHeader &header)
211 unsigned signature = readInt(stream);
212 if (signature != LOC_FILE_HEADER_SIG)
213 return false;
215 header.min_version = readShort(stream);
216 header.general_flag = readShort(stream);
217 header.compression = readShort(stream);
218 header.lastmod_time = readShort(stream);
219 header.lastmod_date = readShort(stream);
220 header.crc32 = readInt(stream);
221 header.compressed_size = readInt(stream);
222 header.uncompressed_size = readInt(stream);
223 header.filename_size = readShort(stream);
224 header.extra_field_size = readShort(stream);
225 header.filename.assign(readString(stream, header.filename_size));
226 header.extra_field.assign(readString(stream, header.extra_field_size));
228 catch (...)
230 return false;
232 return true;
235 bool areHeadersConsistent(const LocalFileHeader &header, const CentralDirectoryEntry &entry)
237 if (header.min_version != entry.min_version)
238 return false;
239 if (header.general_flag != entry.general_flag)
240 return false;
241 if (header.compression != entry.compression)
242 return false;
243 if (!(header.general_flag & 0x08))
245 if (header.crc32 != entry.crc32)
246 return false;
247 if (header.compressed_size != entry.compressed_size)
248 return false;
249 if (header.uncompressed_size != entry.uncompressed_size)
250 return false;
252 return true;
255 #define BLOCK_SIZE 0x800
257 bool findSignatureAtOffset(StreamInterface *stream, unsigned long nOffset)
259 // read in reasonably sized chunk, and read more, to get overlapping sigs
260 unsigned char aBuffer[ BLOCK_SIZE + 4 ];
262 stream->sseek(nOffset, SEEK_SET);
264 unsigned long nBytesRead = stream->sread(aBuffer, sizeof(aBuffer));
266 for (long n = nBytesRead - 4; n >= 0; n--)
268 if (aBuffer[n ] == 0x50 && aBuffer[n+1] == 0x4b &&
269 aBuffer[n+2] == 0x05 && aBuffer[n+3] == 0x06)
270 { // a palpable hit ...
271 stream->sseek(nOffset + n, SEEK_SET);
272 return true;
276 return false;
279 bool findCentralDirectoryEnd(StreamInterface *stream)
281 if (!stream)
282 return false;
284 stream->sseek(0,SEEK_END);
286 long nLength = stream->stell();
287 if (nLength == -1)
288 return false;
292 for (long nOffset = nLength - BLOCK_SIZE - 4;
293 nOffset > 0; nOffset -= BLOCK_SIZE)
295 if (findSignatureAtOffset(stream, nOffset))
296 return true;
298 return findSignatureAtOffset(stream, 0);
300 catch (...)
302 return false;
306 bool isZipStream(StreamInterface *stream)
308 if (!findCentralDirectoryEnd(stream))
309 return false;
310 CentralDirectoryEnd end;
311 if (!readCentralDirectoryEnd(stream, end))
312 return false;
313 stream->sseek(end.cdir_offset, SEEK_SET);
314 CentralDirectoryEntry entry;
315 if (!readCentralDirectoryEntry(stream, entry))
316 return false;
317 stream->sseek(entry.offset, SEEK_SET);
318 LocalFileHeader header;
319 if (!readLocalFileHeader(stream, header))
320 return false;
321 if (!areHeadersConsistent(header, entry))
322 return false;
323 return true;
326 } // anonymous namespace
328 namespace internal
331 namespace {
333 /* for case in-sensitive string comparison */
334 struct stricmp
336 explicit stricmp(const std::string &str) : str_(str)
339 bool operator() (const std::string &other)
341 return (0 == _stricmp(str_.c_str(), other.c_str()));
344 std::string str_;
349 } // namespace internal
351 /** Checks whether a file is a zip file or not
353 @precond The given parameter must be a string with length > 0
354 The file must exist
355 The file must be readable for the current user
357 @returns true if the file is a zip file
358 false if the file is not a zip file
360 @throws ParameterException if the given file name is empty
361 IOException if the specified file doesn't exist
362 AccessViolationException if read access to the file is denied
364 bool ZipFile::IsZipFile(const Filepath_t& /*FileName*/)
366 return true;
369 bool ZipFile::IsZipFile(void* /*stream*/)
371 return true;
375 /** Returns whether the version of the specified zip file may be uncompressed with the
376 currently used zlib version or not
378 @precond The given parameter must be a string with length > 0
379 The file must exist
380 The file must be readable for the current user
381 The file must be a valid zip file
383 @returns true if the file may be uncompressed with the currently used zlib
384 false if the file may not be uncompressed with the currently used zlib
386 @throws ParameterException if the given file name is empty
387 IOException if the specified file doesn't exist or is no zip file
388 AccessViolationException if read access to the file is denied
390 bool ZipFile::IsValidZipFileVersionNumber(const Filepath_t& /*FileName*/)
392 return true;
395 bool ZipFile::IsValidZipFileVersionNumber(void* /* stream*/)
397 return true;
401 /** Constructs a zip file from a zip file
403 @precond The given parameter must be a string with length > 0
404 The file must exist
405 The file must be readable for the current user
407 @throws ParameterException if the given file name is empty
408 IOException if the specified file doesn't exist or is no valid zip file
409 AccessViolationException if read access to the file is denied
410 WrongZipVersionException if the zip file cannot be uncompressed
411 with the used zlib version
413 ZipFile::ZipFile(const Filepath_t &FileName) :
414 m_pStream(nullptr),
415 m_bShouldFree(true)
417 m_pStream = new FileStream(FileName.c_str());
418 if (!isZipStream(m_pStream))
420 delete m_pStream;
421 m_pStream = nullptr;
425 ZipFile::ZipFile(StreamInterface *stream) :
426 m_pStream(stream),
427 m_bShouldFree(false)
429 if (!isZipStream(stream))
430 m_pStream = nullptr;
434 /** Destroys a zip file
436 ZipFile::~ZipFile()
438 if (m_pStream && m_bShouldFree)
439 delete m_pStream;
442 /** Provides an interface to read the uncompressed data of a content of the zip file
444 @precond The specified content must exist in this file
445 ppstm must not be NULL
447 void ZipFile::GetUncompressedContent(
448 const std::string &ContentName, /*inout*/ ZipContentBuffer_t &ContentBuffer)
450 if (!findCentralDirectoryEnd(m_pStream))
451 return;
452 CentralDirectoryEnd end;
453 if (!readCentralDirectoryEnd(m_pStream, end))
454 return;
455 m_pStream->sseek(end.cdir_offset, SEEK_SET);
456 CentralDirectoryEntry entry;
457 while (m_pStream->stell() != -1 && o3tl::make_unsigned(m_pStream->stell()) < end.cdir_offset + end.cdir_size)
459 if (!readCentralDirectoryEntry(m_pStream, entry))
460 return;
461 if (ContentName.length() == entry.filename_size && !_stricmp(entry.filename.c_str(), ContentName.c_str()))
462 break;
464 if (ContentName.length() != entry.filename_size)
465 return;
466 if (_stricmp(entry.filename.c_str(), ContentName.c_str()))
467 return;
468 m_pStream->sseek(entry.offset, SEEK_SET);
469 LocalFileHeader header;
470 if (!readLocalFileHeader(m_pStream, header))
471 return;
472 if (!areHeadersConsistent(header, entry))
473 return;
474 ContentBuffer.clear();
475 ContentBuffer = ZipContentBuffer_t(entry.uncompressed_size);
476 if (!entry.compression)
477 m_pStream->sread(reinterpret_cast<unsigned char *>(ContentBuffer.data()), entry.uncompressed_size);
478 else
480 int ret;
481 z_stream strm;
483 /* allocate inflate state */
484 strm.zalloc = Z_NULL;
485 strm.zfree = Z_NULL;
486 strm.opaque = Z_NULL;
487 strm.avail_in = 0;
488 strm.next_in = Z_NULL;
489 ret = inflateInit2(&strm,-MAX_WBITS);
490 if (ret != Z_OK)
491 return;
493 std::vector<unsigned char> tmpBuffer(entry.compressed_size);
494 if (entry.compressed_size != m_pStream->sread(tmpBuffer.data(), entry.compressed_size))
495 return;
497 strm.avail_in = entry.compressed_size;
498 strm.next_in = reinterpret_cast<Bytef *>(tmpBuffer.data());
500 strm.avail_out = entry.uncompressed_size;
501 strm.next_out = reinterpret_cast<Bytef *>(ContentBuffer.data());
502 ret = inflate(&strm, Z_FINISH);
503 switch (ret)
505 case Z_NEED_DICT:
506 case Z_DATA_ERROR:
507 case Z_MEM_ERROR:
508 (void)inflateEnd(&strm);
509 ContentBuffer.clear();
510 return;
512 (void)inflateEnd(&strm);
516 /** Returns a list with the content names contained within this file
519 ZipFile::DirectoryPtr_t ZipFile::GetDirectory() const
521 DirectoryPtr_t dir(new Directory_t());
522 if (!findCentralDirectoryEnd(m_pStream))
523 return dir;
524 CentralDirectoryEnd end;
525 if (!readCentralDirectoryEnd(m_pStream, end))
526 return dir;
527 m_pStream->sseek(end.cdir_offset, SEEK_SET);
528 CentralDirectoryEntry entry;
529 while (m_pStream->stell() != -1 && o3tl::make_unsigned(m_pStream->stell()) < end.cdir_offset + end.cdir_size)
531 if (!readCentralDirectoryEntry(m_pStream, entry))
532 return dir;
533 if (entry.filename_size)
534 dir->push_back(entry.filename);
536 return dir;
539 /** Convenience query function may even realized with
540 iterating over a ZipFileDirectory returned by
541 GetDirectory */
542 bool ZipFile::HasContent(const std::string &ContentName) const
544 //#i34314# we need to compare package content names
545 //case in-sensitive as it is not defined that such
546 //names must be handled case sensitive
547 DirectoryPtr_t dir = GetDirectory();
549 return std::any_of(dir->begin(), dir->end(), internal::stricmp(ContentName));
552 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */