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>
32 #include <o3tl/safeint.hxx>
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
;
47 unsigned compressed_size
;
48 unsigned uncompressed_size
;
49 unsigned short filename_size
;
50 unsigned short extra_field_size
;
52 std::string extra_field
;
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
;
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
;
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
;
95 unsigned short comment_size
;
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);
121 unsigned readInt(StreamInterface
*stream
)
123 if (!stream
|| stream
->stell() == -1)
124 throw IOException(-1);
126 unsigned long numBytesRead
= stream
->sread(
127 reinterpret_cast<unsigned char *>( &tmpBuf
), 4);
128 if (numBytesRead
!= 4)
129 throw IOException(-1);
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
);
148 bool readCentralDirectoryEnd(StreamInterface
*stream
, CentralDirectoryEnd
&end
)
152 unsigned signature
= readInt(stream
);
153 if (signature
!= CDIR_END_SIG
)
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
));
172 bool readCentralDirectoryEntry(StreamInterface
*stream
, CentralDirectoryEntry
&entry
)
176 unsigned signature
= readInt(stream
);
177 if (signature
!= CDIR_ENTRY_SIG
)
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
));
207 bool readLocalFileHeader(StreamInterface
*stream
, LocalFileHeader
&header
)
211 unsigned signature
= readInt(stream
);
212 if (signature
!= LOC_FILE_HEADER_SIG
)
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
));
235 bool areHeadersConsistent(const LocalFileHeader
&header
, const CentralDirectoryEntry
&entry
)
237 if (header
.min_version
!= entry
.min_version
)
239 if (header
.general_flag
!= entry
.general_flag
)
241 if (header
.compression
!= entry
.compression
)
243 if (!(header
.general_flag
& 0x08))
245 if (header
.crc32
!= entry
.crc32
)
247 if (header
.compressed_size
!= entry
.compressed_size
)
249 if (header
.uncompressed_size
!= entry
.uncompressed_size
)
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
);
279 bool findCentralDirectoryEnd(StreamInterface
*stream
)
284 stream
->sseek(0,SEEK_END
);
286 long nLength
= stream
->stell();
292 for (long nOffset
= nLength
- BLOCK_SIZE
- 4;
293 nOffset
> 0; nOffset
-= BLOCK_SIZE
)
295 if (findSignatureAtOffset(stream
, nOffset
))
298 return findSignatureAtOffset(stream
, 0);
306 bool isZipStream(StreamInterface
*stream
)
308 if (!findCentralDirectoryEnd(stream
))
310 CentralDirectoryEnd end
;
311 if (!readCentralDirectoryEnd(stream
, end
))
313 stream
->sseek(end
.cdir_offset
, SEEK_SET
);
314 CentralDirectoryEntry entry
;
315 if (!readCentralDirectoryEntry(stream
, entry
))
317 stream
->sseek(entry
.offset
, SEEK_SET
);
318 LocalFileHeader header
;
319 if (!readLocalFileHeader(stream
, header
))
321 if (!areHeadersConsistent(header
, entry
))
326 } // anonymous namespace
333 /* for case in-sensitive string comparison */
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()));
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
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*/)
369 bool ZipFile::IsZipFile(void* /*stream*/)
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
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*/)
395 bool ZipFile::IsValidZipFileVersionNumber(void* /* stream*/)
401 /** Constructs a zip file from a zip file
403 @precond The given parameter must be a string with length > 0
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
) :
417 m_pStream
= new FileStream(FileName
.c_str());
418 if (!isZipStream(m_pStream
))
425 ZipFile::ZipFile(StreamInterface
*stream
) :
429 if (!isZipStream(stream
))
434 /** Destroys a zip file
438 if (m_pStream
&& m_bShouldFree
)
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
))
452 CentralDirectoryEnd end
;
453 if (!readCentralDirectoryEnd(m_pStream
, end
))
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
))
461 if (ContentName
.length() == entry
.filename_size
&& !_stricmp(entry
.filename
.c_str(), ContentName
.c_str()))
464 if (ContentName
.length() != entry
.filename_size
)
466 if (_stricmp(entry
.filename
.c_str(), ContentName
.c_str()))
468 m_pStream
->sseek(entry
.offset
, SEEK_SET
);
469 LocalFileHeader header
;
470 if (!readLocalFileHeader(m_pStream
, header
))
472 if (!areHeadersConsistent(header
, entry
))
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
);
483 /* allocate inflate state */
484 strm
.zalloc
= Z_NULL
;
486 strm
.opaque
= Z_NULL
;
488 strm
.next_in
= Z_NULL
;
489 ret
= inflateInit2(&strm
,-MAX_WBITS
);
493 std::vector
<unsigned char> tmpBuffer(entry
.compressed_size
);
494 if (entry
.compressed_size
!= m_pStream
->sread(tmpBuffer
.data(), entry
.compressed_size
))
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
);
508 (void)inflateEnd(&strm
);
509 ContentBuffer
.clear();
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
))
524 CentralDirectoryEnd end
;
525 if (!readCentralDirectoryEnd(m_pStream
, end
))
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
))
533 if (entry
.filename_size
)
534 dir
->push_back(entry
.filename
);
539 /** Convenience query function may even realized with
540 iterating over a ZipFileDirectory returned by
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: */