1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
24 #include <stream_helper.hxx>
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
;
43 unsigned compressed_size
;
44 unsigned uncompressed_size
;
45 unsigned short filename_size
;
46 unsigned short extra_field_size
;
48 std::string extra_field
;
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
;
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
;
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
;
91 unsigned short comment_size
;
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);
117 unsigned readInt(StreamInterface
*stream
)
119 if (!stream
|| stream
->stell() == -1)
120 throw IOException(-1);
122 unsigned long numBytesRead
= stream
->sread(
123 reinterpret_cast<unsigned char *>( &tmpBuf
), 4);
124 if (numBytesRead
!= 4)
125 throw IOException(-1);
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
);
144 bool readCentralDirectoryEnd(StreamInterface
*stream
, CentralDirectoryEnd
&end
)
148 unsigned signature
= readInt(stream
);
149 if (signature
!= CDIR_END_SIG
)
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
));
168 bool readCentralDirectoryEntry(StreamInterface
*stream
, CentralDirectoryEntry
&entry
)
172 unsigned signature
= readInt(stream
);
173 if (signature
!= CDIR_ENTRY_SIG
)
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
));
203 bool readLocalFileHeader(StreamInterface
*stream
, LocalFileHeader
&header
)
207 unsigned signature
= readInt(stream
);
208 if (signature
!= LOC_FILE_HEADER_SIG
)
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
));
231 bool areHeadersConsistent(const LocalFileHeader
&header
, const CentralDirectoryEntry
&entry
)
233 if (header
.min_version
!= entry
.min_version
)
235 if (header
.general_flag
!= entry
.general_flag
)
237 if (header
.compression
!= entry
.compression
)
239 if (!(header
.general_flag
& 0x08))
241 if (header
.crc32
!= entry
.crc32
)
243 if (header
.compressed_size
!= entry
.compressed_size
)
245 if (header
.uncompressed_size
!= entry
.uncompressed_size
)
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
);
275 bool findCentralDirectoryEnd(StreamInterface
*stream
)
280 stream
->sseek(0,SEEK_END
);
282 long nLength
= stream
->stell();
288 for (long nOffset
= nLength
- BLOCK_SIZE
- 4;
289 nOffset
> 0; nOffset
-= BLOCK_SIZE
)
291 if (findSignatureAtOffset(stream
, nOffset
))
294 return findSignatureAtOffset(stream
, 0);
302 bool isZipStream(StreamInterface
*stream
)
304 if (!findCentralDirectoryEnd(stream
))
306 CentralDirectoryEnd end
;
307 if (!readCentralDirectoryEnd(stream
, end
))
309 stream
->sseek(end
.cdir_offset
, SEEK_SET
);
310 CentralDirectoryEntry entry
;
311 if (!readCentralDirectoryEntry(stream
, entry
))
313 stream
->sseek(entry
.offset
, SEEK_SET
);
314 LocalFileHeader header
;
315 if (!readLocalFileHeader(stream
, header
))
317 if (!areHeadersConsistent(header
, entry
))
322 } // anonymous namespace
326 /* for case in-sensitive string comparison */
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()));
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
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*/)
359 bool ZipFile::IsZipFile(void* /*stream*/)
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
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*/)
385 bool ZipFile::IsValidZipFileVersionNumber(void* /* stream*/)
391 /** Constructs a zip file from a zip file
393 @precond The given parameter must be a string with length > 0
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
) :
407 m_pStream
= new FileStream(FileName
.c_str());
408 if (!isZipStream(m_pStream
))
415 ZipFile::ZipFile(StreamInterface
*stream
) :
419 if (!isZipStream(stream
))
424 /** Destroys a zip file
428 if (m_pStream
&& m_bShouldFree
)
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
))
442 CentralDirectoryEnd end
;
443 if (!readCentralDirectoryEnd(m_pStream
, end
))
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
))
451 if (ContentName
.length() == entry
.filename_size
&& !_stricmp(entry
.filename
.c_str(), ContentName
.c_str()))
454 if (ContentName
.length() != entry
.filename_size
)
456 if (_stricmp(entry
.filename
.c_str(), ContentName
.c_str()))
458 m_pStream
->sseek(entry
.offset
, SEEK_SET
);
459 LocalFileHeader header
;
460 if (!readLocalFileHeader(m_pStream
, header
))
462 if (!areHeadersConsistent(header
, entry
))
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
);
473 /* allocate inflate state */
474 strm
.zalloc
= Z_NULL
;
476 strm
.opaque
= Z_NULL
;
478 strm
.next_in
= Z_NULL
;
479 ret
= inflateInit2(&strm
,-MAX_WBITS
);
483 std::vector
<unsigned char> tmpBuffer(entry
.compressed_size
);
484 if (entry
.compressed_size
!= m_pStream
->sread(tmpBuffer
.data(), entry
.compressed_size
))
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
);
498 (void)inflateEnd(&strm
);
499 ContentBuffer
.clear();
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
))
514 CentralDirectoryEnd end
;
515 if (!readCentralDirectoryEnd(m_pStream
, end
))
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
))
523 if (entry
.filename_size
)
524 dir
->push_back(entry
.filename
);
529 /** Convenience query function may even realized with
530 iterating over a ZipFileDirectory returned by
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
549 if (!findCentralDirectoryEnd(m_pStream
))
551 CentralDirectoryEnd end
;
552 if (!readCentralDirectoryEnd(m_pStream
, end
))
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
))
560 if (entry
.filename_size
> lmax
)
561 lmax
= entry
.filename_size
;
566 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */