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 "internal/zipfile.hxx"
22 #include "internal/global.hxx"
23 #include "internal/types.hxx"
24 #include "internal/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() {}
56 struct CentralDirectoryEntry
58 unsigned short creator_version
;
59 unsigned short min_version
;
60 unsigned short general_flag
;
61 unsigned short compression
;
62 unsigned short lastmod_time
;
63 unsigned short lastmod_date
;
65 unsigned compressed_size
;
66 unsigned uncompressed_size
;
67 unsigned short filename_size
;
68 unsigned short extra_field_size
;
69 unsigned short file_comment_size
;
70 unsigned short disk_num
;
71 unsigned short internal_attr
;
72 unsigned external_attr
;
75 std::string extra_field
;
76 std::string file_comment
;
77 CentralDirectoryEntry()
78 : creator_version(0), min_version(0), general_flag(0), compression(0), lastmod_time(0),
79 lastmod_date(0), crc32(0), compressed_size(0), uncompressed_size(0), filename_size(0),
80 extra_field_size(0), file_comment_size(0), disk_num(0), internal_attr(0),
81 external_attr(0), offset(0), filename(), extra_field(), file_comment() {}
82 ~CentralDirectoryEntry() {}
85 struct CentralDirectoryEnd
87 unsigned short disk_num
;
88 unsigned short cdir_disk
;
89 unsigned short disk_entries
;
90 unsigned short cdir_entries
;
93 unsigned short comment_size
;
96 : disk_num(0), cdir_disk(0), disk_entries(0), cdir_entries(0),
97 cdir_size(0), cdir_offset(0), comment_size(0), comment() {}
98 ~CentralDirectoryEnd() {}
101 #define CDIR_ENTRY_SIG 0x02014b50
102 #define LOC_FILE_HEADER_SIG 0x04034b50
103 #define CDIR_END_SIG 0x06054b50
105 // This little lot performs in a truly appalling way without
106 // buffering eg. on an IStream.
108 static unsigned char readByte(StreamInterface
*stream
)
110 if (!stream
|| stream
->stell() == -1)
111 throw IOException(-1);
112 unsigned char tmpBuf
;
113 unsigned long numBytesRead
= stream
->sread(&tmpBuf
, 1);
114 if (numBytesRead
!= 1)
115 throw IOException(-1);
119 static unsigned short readShort(StreamInterface
*stream
)
121 if (!stream
|| stream
->stell() == -1)
122 throw IOException(-1);
123 unsigned short tmpBuf
;
124 unsigned long numBytesRead
= stream
->sread(
125 reinterpret_cast<unsigned char *>( &tmpBuf
), 2);
126 if (numBytesRead
!= 2)
127 throw IOException(-1);
131 static unsigned readInt(StreamInterface
*stream
)
133 if (!stream
|| stream
->stell() == -1)
134 throw IOException(-1);
136 unsigned long numBytesRead
= stream
->sread(
137 reinterpret_cast<unsigned char *>( &tmpBuf
), 4);
138 if (numBytesRead
!= 4)
139 throw IOException(-1);
143 static std::string
readString(StreamInterface
*stream
, unsigned long size
)
145 if (!stream
|| stream
->stell() == -1)
146 throw IOException(-1);
147 unsigned char *tmp
= new unsigned char[size
];
149 throw IOException(-1);
150 unsigned long numBytesRead
= stream
->sread(tmp
, size
);
151 if (numBytesRead
!= size
)
152 throw IOException(-1);
154 std::string
aStr((char *)tmp
, size
);
159 static bool readCentralDirectoryEnd(StreamInterface
*stream
, CentralDirectoryEnd
&end
)
163 unsigned signature
= readInt(stream
);
164 if (signature
!= CDIR_END_SIG
)
167 end
.disk_num
= readShort(stream
);
168 end
.cdir_disk
= readShort(stream
);
169 end
.disk_entries
= readShort(stream
);
170 end
.cdir_entries
= readShort(stream
);
171 end
.cdir_size
= readInt(stream
);
172 end
.cdir_offset
= readInt(stream
);
173 end
.comment_size
= readShort(stream
);
174 end
.comment
.assign(readString(stream
, end
.comment_size
));
183 static bool readCentralDirectoryEntry(StreamInterface
*stream
, CentralDirectoryEntry
&entry
)
187 unsigned signature
= readInt(stream
);
188 if (signature
!= CDIR_ENTRY_SIG
)
191 entry
.creator_version
= readShort(stream
);
192 entry
.min_version
= readShort(stream
);
193 entry
.general_flag
= readShort(stream
);
194 entry
.compression
= readShort(stream
);
195 entry
.lastmod_time
= readShort(stream
);
196 entry
.lastmod_date
= readShort(stream
);
197 entry
.crc32
= readInt(stream
);
198 entry
.compressed_size
= readInt(stream
);
199 entry
.uncompressed_size
= readInt(stream
);
200 entry
.filename_size
= readShort(stream
);
201 entry
.extra_field_size
= readShort(stream
);
202 entry
.file_comment_size
= readShort(stream
);
203 entry
.disk_num
= readShort(stream
);
204 entry
.internal_attr
= readShort(stream
);
205 entry
.external_attr
= readInt(stream
);
206 entry
.offset
= readInt(stream
);
207 unsigned short i
= 0;
208 entry
.filename
.assign(readString(stream
, entry
.filename_size
));
209 entry
.extra_field
.assign(readString(stream
, entry
.extra_field_size
));
210 entry
.file_comment
.assign(readString(stream
, entry
.file_comment_size
));
219 static bool readLocalFileHeader(StreamInterface
*stream
, LocalFileHeader
&header
)
223 unsigned signature
= readInt(stream
);
224 if (signature
!= LOC_FILE_HEADER_SIG
)
227 header
.min_version
= readShort(stream
);
228 header
.general_flag
= readShort(stream
);
229 header
.compression
= readShort(stream
);
230 header
.lastmod_time
= readShort(stream
);
231 header
.lastmod_date
= readShort(stream
);
232 header
.crc32
= readInt(stream
);
233 header
.compressed_size
= readInt(stream
);
234 header
.uncompressed_size
= readInt(stream
);
235 header
.filename_size
= readShort(stream
);
236 header
.extra_field_size
= readShort(stream
);
237 unsigned short i
= 0;
238 header
.filename
.assign(readString(stream
, header
.filename_size
));
239 header
.extra_field
.assign(readString(stream
, header
.extra_field_size
));
248 static bool areHeadersConsistent(const LocalFileHeader
&header
, const CentralDirectoryEntry
&entry
)
250 if (header
.min_version
!= entry
.min_version
)
252 if (header
.general_flag
!= entry
.general_flag
)
254 if (header
.compression
!= entry
.compression
)
256 if (!(header
.general_flag
& 0x08))
258 if (header
.crc32
!= entry
.crc32
)
260 if (header
.compressed_size
!= entry
.compressed_size
)
262 if (header
.uncompressed_size
!= entry
.uncompressed_size
)
268 #define BLOCK_SIZE 0x800
270 static bool findSignatureAtOffset(StreamInterface
*stream
, unsigned long nOffset
)
272 // read in reasonably sized chunk, and read more, to get overlapping sigs
273 unsigned char aBuffer
[ BLOCK_SIZE
+ 4 ];
275 stream
->sseek(nOffset
, SEEK_SET
);
277 unsigned long nBytesRead
= stream
->sread(aBuffer
, sizeof(aBuffer
));
281 for (long n
= nBytesRead
- 4; n
>= 0; n
--)
283 if (aBuffer
[n
] == 0x50 && aBuffer
[n
+1] == 0x4b &&
284 aBuffer
[n
+2] == 0x05 && aBuffer
[n
+3] == 0x06)
285 { // a palpable hit ...
286 stream
->sseek(nOffset
+ n
, SEEK_SET
);
294 static bool findCentralDirectoryEnd(StreamInterface
*stream
)
299 stream
->sseek(0,SEEK_END
);
301 long nLength
= stream
->stell();
307 for (long nOffset
= nLength
- BLOCK_SIZE
;
308 nOffset
> 0; nOffset
-= BLOCK_SIZE
)
310 if (findSignatureAtOffset(stream
, nOffset
))
313 return findSignatureAtOffset(stream
, 0);
321 static bool isZipStream(StreamInterface
*stream
)
323 if (!findCentralDirectoryEnd(stream
))
325 CentralDirectoryEnd end
;
326 if (!readCentralDirectoryEnd(stream
, end
))
328 stream
->sseek(end
.cdir_offset
, SEEK_SET
);
329 CentralDirectoryEntry entry
;
330 if (!readCentralDirectoryEntry(stream
, entry
))
332 stream
->sseek(entry
.offset
, SEEK_SET
);
333 LocalFileHeader header
;
334 if (!readLocalFileHeader(stream
, header
))
336 if (!areHeadersConsistent(header
, entry
))
341 } // anonymous namespace
345 /* for case in-sensitive string comparison */
346 struct stricmp
: public std::unary_function
<std::string
, bool>
348 stricmp(const std::string
&str
) : str_(str
)
351 bool operator() (const std::string
&other
)
353 return (0 == _stricmp(str_
.c_str(), other
.c_str()));
358 } // namespace internal
360 /** Checks whether a file is a zip file or not
362 @precond The given parameter must be a string with length > 0
364 The file must be readable for the current user
366 @returns true if the file is a zip file
367 false if the file is not a zip file
369 @throws ParameterException if the given file name is empty
370 IOException if the specified file doesn't exist
371 AccessViolationException if read access to the file is denied
373 bool ZipFile::IsZipFile(const std::string
& /*FileName*/)
378 bool ZipFile::IsZipFile(void* /*stream*/)
384 /** Returns wheter the version of the specified zip file may be uncompressed with the
385 currently used zlib version or not
387 @precond The given parameter must be a string with length > 0
389 The file must be readable for the current user
390 The file must be a valid zip file
392 @returns true if the file may be uncompressed with the currently used zlib
393 false if the file may not be uncompressed with the currently used zlib
395 @throws ParameterException if the given file name is empty
396 IOException if the specified file doesn't exist or is no zip file
397 AccessViolationException if read access to the file is denied
399 bool ZipFile::IsValidZipFileVersionNumber(const std::string
& /*FileName*/)
404 bool ZipFile::IsValidZipFileVersionNumber(void* /* stream*/)
410 /** Constructs a zip file from a zip file
412 @precond The given parameter must be a string with length > 0
414 The file must be readable for the current user
416 @throws ParameterException if the given file name is empty
417 IOException if the specified file doesn't exist or is no valid zip file
418 AccessViolationException if read access to the file is denied
419 WrongZipVersionException if the zip file cannot be uncompressed
420 with the used zlib version
422 ZipFile::ZipFile(const std::string
&FileName
) :
426 m_pStream
= new FileStream(FileName
.c_str());
427 if (m_pStream
&& !isZipStream(m_pStream
))
434 ZipFile::ZipFile(StreamInterface
*stream
) :
438 if (!isZipStream(stream
))
443 /** Destroys a zip file
447 if (m_pStream
&& m_bShouldFree
)
451 /** Provides an interface to read the uncompressed data of a content of the zip file
453 @precond The specified content must exist in this file
454 ppstm must not be NULL
456 void ZipFile::GetUncompressedContent(
457 const std::string
&ContentName
, /*inout*/ ZipContentBuffer_t
&ContentBuffer
)
459 if (!findCentralDirectoryEnd(m_pStream
))
461 CentralDirectoryEnd end
;
462 if (!readCentralDirectoryEnd(m_pStream
, end
))
464 m_pStream
->sseek(end
.cdir_offset
, SEEK_SET
);
465 CentralDirectoryEntry entry
;
466 while (m_pStream
->stell() != -1 && (unsigned long)m_pStream
->stell() < end
.cdir_offset
+ end
.cdir_size
)
468 if (!readCentralDirectoryEntry(m_pStream
, entry
))
470 if (ContentName
.length() == entry
.filename_size
&& !_stricmp(entry
.filename
.c_str(), ContentName
.c_str()))
473 if (ContentName
.length() != entry
.filename_size
)
475 if (_stricmp(entry
.filename
.c_str(), ContentName
.c_str()))
477 m_pStream
->sseek(entry
.offset
, SEEK_SET
);
478 LocalFileHeader header
;
479 if (!readLocalFileHeader(m_pStream
, header
))
481 if (!areHeadersConsistent(header
, entry
))
483 ContentBuffer
.clear();
484 ContentBuffer
= ZipContentBuffer_t(entry
.uncompressed_size
);
485 if (!entry
.compression
)
486 m_pStream
->sread((unsigned char *)&ContentBuffer
[0], entry
.uncompressed_size
);
492 /* allocate inflate state */
493 strm
.zalloc
= Z_NULL
;
495 strm
.opaque
= Z_NULL
;
497 strm
.next_in
= Z_NULL
;
498 ret
= inflateInit2(&strm
,-MAX_WBITS
);
502 std::vector
<unsigned char> tmpBuffer(entry
.compressed_size
);
503 if (entry
.compressed_size
!= m_pStream
->sread(&tmpBuffer
[0], entry
.compressed_size
))
506 strm
.avail_in
= entry
.compressed_size
;
507 strm
.next_in
= reinterpret_cast<Bytef
*>(&tmpBuffer
[0]);
509 strm
.avail_out
= entry
.uncompressed_size
;
510 strm
.next_out
= reinterpret_cast<Bytef
*>(&ContentBuffer
[0]);
511 ret
= inflate(&strm
, Z_FINISH
);
517 (void)inflateEnd(&strm
);
518 ContentBuffer
.clear();
521 (void)inflateEnd(&strm
);
525 /** Returns a list with the content names contained within this file
528 ZipFile::DirectoryPtr_t
ZipFile::GetDirectory() const
530 DirectoryPtr_t
dir(new Directory_t());
531 if (!findCentralDirectoryEnd(m_pStream
))
533 CentralDirectoryEnd end
;
534 if (!readCentralDirectoryEnd(m_pStream
, end
))
536 m_pStream
->sseek(end
.cdir_offset
, SEEK_SET
);
537 CentralDirectoryEntry entry
;
538 while (m_pStream
->stell() != -1 && (unsigned long)m_pStream
->stell() < end
.cdir_offset
+ end
.cdir_size
)
540 if (!readCentralDirectoryEntry(m_pStream
, entry
))
542 if (entry
.filename_size
)
543 dir
->push_back(entry
.filename
);
548 /** Convinience query function may even realized with
549 iterating over a ZipFileDirectory returned by
551 bool ZipFile::HasContent(const std::string
&ContentName
) const
553 //#i34314# we need to compare package content names
554 //case in-sensitive as it is not defined that such
555 //names must be handled case sensitive
556 DirectoryPtr_t dir
= GetDirectory();
557 Directory_t::iterator iter
=
558 std::find_if(dir
->begin(), dir
->end(), internal::stricmp(ContentName
));
560 return (iter
!= dir
->end());
564 /** Returns the length of the longest file name
565 in the current zip file
567 long ZipFile::GetFileLongestFileNameLength() const
570 if (!findCentralDirectoryEnd(m_pStream
))
572 CentralDirectoryEnd end
;
573 if (!readCentralDirectoryEnd(m_pStream
, end
))
575 m_pStream
->sseek(end
.cdir_offset
, SEEK_SET
);
576 CentralDirectoryEntry entry
;
577 while (m_pStream
->stell() != -1 && (unsigned long)m_pStream
->stell() < end
.cdir_offset
+ end
.cdir_size
)
579 if (!readCentralDirectoryEntry(m_pStream
, entry
))
581 if (entry
.filename_size
> lmax
)
582 lmax
= entry
.filename_size
;
587 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */