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 short readShort(StreamInterface
*stream
)
110 if (!stream
|| stream
->stell() == -1)
111 throw IOException(-1);
112 unsigned short tmpBuf
;
113 unsigned long numBytesRead
= stream
->sread(
114 reinterpret_cast<unsigned char *>( &tmpBuf
), 2);
115 if (numBytesRead
!= 2)
116 throw IOException(-1);
120 static unsigned readInt(StreamInterface
*stream
)
122 if (!stream
|| stream
->stell() == -1)
123 throw IOException(-1);
125 unsigned long numBytesRead
= stream
->sread(
126 reinterpret_cast<unsigned char *>( &tmpBuf
), 4);
127 if (numBytesRead
!= 4)
128 throw IOException(-1);
132 static std::string
readString(StreamInterface
*stream
, unsigned long size
)
134 if (!stream
|| stream
->stell() == -1)
135 throw IOException(-1);
136 unsigned char *tmp
= new unsigned char[size
];
137 unsigned long numBytesRead
= stream
->sread(tmp
, size
);
138 if (numBytesRead
!= size
)
141 throw IOException(-1);
144 std::string
aStr((char *)tmp
, size
);
149 static bool readCentralDirectoryEnd(StreamInterface
*stream
, CentralDirectoryEnd
&end
)
153 unsigned signature
= readInt(stream
);
154 if (signature
!= CDIR_END_SIG
)
157 end
.disk_num
= readShort(stream
);
158 end
.cdir_disk
= readShort(stream
);
159 end
.disk_entries
= readShort(stream
);
160 end
.cdir_entries
= readShort(stream
);
161 end
.cdir_size
= readInt(stream
);
162 end
.cdir_offset
= readInt(stream
);
163 end
.comment_size
= readShort(stream
);
164 end
.comment
.assign(readString(stream
, end
.comment_size
));
173 static bool readCentralDirectoryEntry(StreamInterface
*stream
, CentralDirectoryEntry
&entry
)
177 unsigned signature
= readInt(stream
);
178 if (signature
!= CDIR_ENTRY_SIG
)
181 entry
.creator_version
= readShort(stream
);
182 entry
.min_version
= readShort(stream
);
183 entry
.general_flag
= readShort(stream
);
184 entry
.compression
= readShort(stream
);
185 entry
.lastmod_time
= readShort(stream
);
186 entry
.lastmod_date
= readShort(stream
);
187 entry
.crc32
= readInt(stream
);
188 entry
.compressed_size
= readInt(stream
);
189 entry
.uncompressed_size
= readInt(stream
);
190 entry
.filename_size
= readShort(stream
);
191 entry
.extra_field_size
= readShort(stream
);
192 entry
.file_comment_size
= readShort(stream
);
193 entry
.disk_num
= readShort(stream
);
194 entry
.internal_attr
= readShort(stream
);
195 entry
.external_attr
= readInt(stream
);
196 entry
.offset
= readInt(stream
);
197 entry
.filename
.assign(readString(stream
, entry
.filename_size
));
198 entry
.extra_field
.assign(readString(stream
, entry
.extra_field_size
));
199 entry
.file_comment
.assign(readString(stream
, entry
.file_comment_size
));
208 static bool readLocalFileHeader(StreamInterface
*stream
, LocalFileHeader
&header
)
212 unsigned signature
= readInt(stream
);
213 if (signature
!= LOC_FILE_HEADER_SIG
)
216 header
.min_version
= readShort(stream
);
217 header
.general_flag
= readShort(stream
);
218 header
.compression
= readShort(stream
);
219 header
.lastmod_time
= readShort(stream
);
220 header
.lastmod_date
= readShort(stream
);
221 header
.crc32
= readInt(stream
);
222 header
.compressed_size
= readInt(stream
);
223 header
.uncompressed_size
= readInt(stream
);
224 header
.filename_size
= readShort(stream
);
225 header
.extra_field_size
= readShort(stream
);
226 header
.filename
.assign(readString(stream
, header
.filename_size
));
227 header
.extra_field
.assign(readString(stream
, header
.extra_field_size
));
236 static bool areHeadersConsistent(const LocalFileHeader
&header
, const CentralDirectoryEntry
&entry
)
238 if (header
.min_version
!= entry
.min_version
)
240 if (header
.general_flag
!= entry
.general_flag
)
242 if (header
.compression
!= entry
.compression
)
244 if (!(header
.general_flag
& 0x08))
246 if (header
.crc32
!= entry
.crc32
)
248 if (header
.compressed_size
!= entry
.compressed_size
)
250 if (header
.uncompressed_size
!= entry
.uncompressed_size
)
256 #define BLOCK_SIZE 0x800
258 static bool findSignatureAtOffset(StreamInterface
*stream
, unsigned long nOffset
)
260 // read in reasonably sized chunk, and read more, to get overlapping sigs
261 unsigned char aBuffer
[ BLOCK_SIZE
+ 4 ];
263 stream
->sseek(nOffset
, SEEK_SET
);
265 unsigned long nBytesRead
= stream
->sread(aBuffer
, sizeof(aBuffer
));
267 for (long n
= nBytesRead
- 4; n
>= 0; n
--)
269 if (aBuffer
[n
] == 0x50 && aBuffer
[n
+1] == 0x4b &&
270 aBuffer
[n
+2] == 0x05 && aBuffer
[n
+3] == 0x06)
271 { // a palpable hit ...
272 stream
->sseek(nOffset
+ n
, SEEK_SET
);
280 static bool findCentralDirectoryEnd(StreamInterface
*stream
)
285 stream
->sseek(0,SEEK_END
);
287 long nLength
= stream
->stell();
293 for (long nOffset
= nLength
- BLOCK_SIZE
- 4;
294 nOffset
> 0; nOffset
-= BLOCK_SIZE
)
296 if (findSignatureAtOffset(stream
, nOffset
))
299 return findSignatureAtOffset(stream
, 0);
307 static bool isZipStream(StreamInterface
*stream
)
309 if (!findCentralDirectoryEnd(stream
))
311 CentralDirectoryEnd end
;
312 if (!readCentralDirectoryEnd(stream
, end
))
314 stream
->sseek(end
.cdir_offset
, SEEK_SET
);
315 CentralDirectoryEntry entry
;
316 if (!readCentralDirectoryEntry(stream
, entry
))
318 stream
->sseek(entry
.offset
, SEEK_SET
);
319 LocalFileHeader header
;
320 if (!readLocalFileHeader(stream
, header
))
322 if (!areHeadersConsistent(header
, entry
))
327 } // anonymous namespace
331 /* for case in-sensitive string comparison */
332 struct stricmp
: public std::unary_function
<std::string
, bool>
334 stricmp(const std::string
&str
) : str_(str
)
337 bool operator() (const std::string
&other
)
339 return (0 == _stricmp(str_
.c_str(), other
.c_str()));
344 } // namespace internal
346 /** Checks whether a file is a zip file or not
348 @precond The given parameter must be a string with length > 0
350 The file must be readable for the current user
352 @returns true if the file is a zip file
353 false if the file is not a zip file
355 @throws ParameterException if the given file name is empty
356 IOException if the specified file doesn't exist
357 AccessViolationException if read access to the file is denied
359 bool ZipFile::IsZipFile(const std::string
& /*FileName*/)
364 bool ZipFile::IsZipFile(void* /*stream*/)
370 /** Returns whether the version of the specified zip file may be uncompressed with the
371 currently used zlib version or not
373 @precond The given parameter must be a string with length > 0
375 The file must be readable for the current user
376 The file must be a valid zip file
378 @returns true if the file may be uncompressed with the currently used zlib
379 false if the file may not be uncompressed with the currently used zlib
381 @throws ParameterException if the given file name is empty
382 IOException if the specified file doesn't exist or is no zip file
383 AccessViolationException if read access to the file is denied
385 bool ZipFile::IsValidZipFileVersionNumber(const std::string
& /*FileName*/)
390 bool ZipFile::IsValidZipFileVersionNumber(void* /* stream*/)
396 /** Constructs a zip file from a zip file
398 @precond The given parameter must be a string with length > 0
400 The file must be readable for the current user
402 @throws ParameterException if the given file name is empty
403 IOException if the specified file doesn't exist or is no valid zip file
404 AccessViolationException if read access to the file is denied
405 WrongZipVersionException if the zip file cannot be uncompressed
406 with the used zlib version
408 ZipFile::ZipFile(const std::string
&FileName
) :
412 m_pStream
= new FileStream(FileName
.c_str());
413 if (m_pStream
&& !isZipStream(m_pStream
))
420 ZipFile::ZipFile(StreamInterface
*stream
) :
424 if (!isZipStream(stream
))
429 /** Destroys a zip file
433 if (m_pStream
&& m_bShouldFree
)
437 /** Provides an interface to read the uncompressed data of a content of the zip file
439 @precond The specified content must exist in this file
440 ppstm must not be NULL
442 void ZipFile::GetUncompressedContent(
443 const std::string
&ContentName
, /*inout*/ ZipContentBuffer_t
&ContentBuffer
)
445 if (!findCentralDirectoryEnd(m_pStream
))
447 CentralDirectoryEnd end
;
448 if (!readCentralDirectoryEnd(m_pStream
, end
))
450 m_pStream
->sseek(end
.cdir_offset
, SEEK_SET
);
451 CentralDirectoryEntry entry
;
452 while (m_pStream
->stell() != -1 && (unsigned long)m_pStream
->stell() < end
.cdir_offset
+ end
.cdir_size
)
454 if (!readCentralDirectoryEntry(m_pStream
, entry
))
456 if (ContentName
.length() == entry
.filename_size
&& !_stricmp(entry
.filename
.c_str(), ContentName
.c_str()))
459 if (ContentName
.length() != entry
.filename_size
)
461 if (_stricmp(entry
.filename
.c_str(), ContentName
.c_str()))
463 m_pStream
->sseek(entry
.offset
, SEEK_SET
);
464 LocalFileHeader header
;
465 if (!readLocalFileHeader(m_pStream
, header
))
467 if (!areHeadersConsistent(header
, entry
))
469 ContentBuffer
.clear();
470 ContentBuffer
= ZipContentBuffer_t(entry
.uncompressed_size
);
471 if (!entry
.compression
)
472 m_pStream
->sread((unsigned char *)&ContentBuffer
[0], entry
.uncompressed_size
);
478 /* allocate inflate state */
479 strm
.zalloc
= Z_NULL
;
481 strm
.opaque
= Z_NULL
;
483 strm
.next_in
= Z_NULL
;
484 ret
= inflateInit2(&strm
,-MAX_WBITS
);
488 std::vector
<unsigned char> tmpBuffer(entry
.compressed_size
);
489 if (entry
.compressed_size
!= m_pStream
->sread(&tmpBuffer
[0], entry
.compressed_size
))
492 strm
.avail_in
= entry
.compressed_size
;
493 strm
.next_in
= reinterpret_cast<Bytef
*>(&tmpBuffer
[0]);
495 strm
.avail_out
= entry
.uncompressed_size
;
496 strm
.next_out
= reinterpret_cast<Bytef
*>(&ContentBuffer
[0]);
497 ret
= inflate(&strm
, Z_FINISH
);
503 (void)inflateEnd(&strm
);
504 ContentBuffer
.clear();
507 (void)inflateEnd(&strm
);
511 /** Returns a list with the content names contained within this file
514 ZipFile::DirectoryPtr_t
ZipFile::GetDirectory() const
516 DirectoryPtr_t
dir(new Directory_t());
517 if (!findCentralDirectoryEnd(m_pStream
))
519 CentralDirectoryEnd end
;
520 if (!readCentralDirectoryEnd(m_pStream
, end
))
522 m_pStream
->sseek(end
.cdir_offset
, SEEK_SET
);
523 CentralDirectoryEntry entry
;
524 while (m_pStream
->stell() != -1 && (unsigned long)m_pStream
->stell() < end
.cdir_offset
+ end
.cdir_size
)
526 if (!readCentralDirectoryEntry(m_pStream
, entry
))
528 if (entry
.filename_size
)
529 dir
->push_back(entry
.filename
);
534 /** Convinience query function may even realized with
535 iterating over a ZipFileDirectory returned by
537 bool ZipFile::HasContent(const std::string
&ContentName
) const
539 //#i34314# we need to compare package content names
540 //case in-sensitive as it is not defined that such
541 //names must be handled case sensitive
542 DirectoryPtr_t dir
= GetDirectory();
543 Directory_t::iterator iter
=
544 std::find_if(dir
->begin(), dir
->end(), internal::stricmp(ContentName
));
546 return (iter
!= dir
->end());
550 /** Returns the length of the longest file name
551 in the current zip file
553 long ZipFile::GetFileLongestFileNameLength() const
556 if (!findCentralDirectoryEnd(m_pStream
))
558 CentralDirectoryEnd end
;
559 if (!readCentralDirectoryEnd(m_pStream
, end
))
561 m_pStream
->sseek(end
.cdir_offset
, SEEK_SET
);
562 CentralDirectoryEntry entry
;
563 while (m_pStream
->stell() != -1 && (unsigned long)m_pStream
->stell() < end
.cdir_offset
+ end
.cdir_size
)
565 if (!readCentralDirectoryEntry(m_pStream
, entry
))
567 if (entry
.filename_size
> lmax
)
568 lmax
= entry
.filename_size
;
573 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */