bump product version to 4.1.6.2
[LibreOffice.git] / shell / source / win32 / zipfile / zipfile.cxx
blob270fbe1ec6bda508260ad60e83bee22bce29c9a1
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 "internal/zipfile.hxx"
22 #include "internal/global.hxx"
23 #include "internal/types.hxx"
24 #include "internal/stream_helper.hxx"
26 #include <malloc.h>
27 #include <algorithm>
28 #include <functional>
30 #include <string.h>
32 namespace
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;
42 unsigned crc32;
43 unsigned compressed_size;
44 unsigned uncompressed_size;
45 unsigned short filename_size;
46 unsigned short extra_field_size;
47 std::string filename;
48 std::string extra_field;
49 LocalFileHeader()
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() {}
53 ~LocalFileHeader() {}
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;
64 unsigned crc32;
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;
73 unsigned offset;
74 std::string filename;
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;
91 unsigned cdir_size;
92 unsigned cdir_offset;
93 unsigned short comment_size;
94 std::string comment;
95 CentralDirectoryEnd()
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);
116 return tmpBuf;
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);
128 return tmpBuf;
131 static unsigned readInt(StreamInterface *stream)
133 if (!stream || stream->stell() == -1)
134 throw IOException(-1);
135 unsigned tmpBuf;
136 unsigned long numBytesRead = stream->sread(
137 reinterpret_cast<unsigned char *>( &tmpBuf ), 4);
138 if (numBytesRead != 4)
139 throw IOException(-1);
140 return tmpBuf;
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];
148 if (!tmp)
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);
155 delete [] tmp;
156 return aStr;
159 static bool readCentralDirectoryEnd(StreamInterface *stream, CentralDirectoryEnd &end)
163 unsigned signature = readInt(stream);
164 if (signature != CDIR_END_SIG)
165 return false;
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));
176 catch (...)
178 return false;
180 return true;
183 static bool readCentralDirectoryEntry(StreamInterface *stream, CentralDirectoryEntry &entry)
187 unsigned signature = readInt(stream);
188 if (signature != CDIR_ENTRY_SIG)
189 return false;
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));
212 catch (...)
214 return false;
216 return true;
219 static bool readLocalFileHeader(StreamInterface *stream, LocalFileHeader &header)
223 unsigned signature = readInt(stream);
224 if (signature != LOC_FILE_HEADER_SIG)
225 return false;
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));
241 catch (...)
243 return false;
245 return true;
248 static bool areHeadersConsistent(const LocalFileHeader &header, const CentralDirectoryEntry &entry)
250 if (header.min_version != entry.min_version)
251 return false;
252 if (header.general_flag != entry.general_flag)
253 return false;
254 if (header.compression != entry.compression)
255 return false;
256 if (!(header.general_flag & 0x08))
258 if (header.crc32 != entry.crc32)
259 return false;
260 if (header.compressed_size != entry.compressed_size)
261 return false;
262 if (header.uncompressed_size != entry.uncompressed_size)
263 return false;
265 return true;
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));
278 if (nBytesRead < 0)
279 return false;
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);
287 return true;
291 return false;
294 static bool findCentralDirectoryEnd(StreamInterface *stream)
296 if (!stream)
297 return false;
299 stream->sseek(0,SEEK_END);
301 long nLength = stream->stell();
302 if (nLength == -1)
303 return false;
307 for (long nOffset = nLength - BLOCK_SIZE;
308 nOffset > 0; nOffset -= BLOCK_SIZE)
310 if (findSignatureAtOffset(stream, nOffset))
311 return true;
313 return findSignatureAtOffset(stream, 0);
315 catch (...)
317 return false;
321 static bool isZipStream(StreamInterface *stream)
323 if (!findCentralDirectoryEnd(stream))
324 return false;
325 CentralDirectoryEnd end;
326 if (!readCentralDirectoryEnd(stream, end))
327 return false;
328 stream->sseek(end.cdir_offset, SEEK_SET);
329 CentralDirectoryEntry entry;
330 if (!readCentralDirectoryEntry(stream, entry))
331 return false;
332 stream->sseek(entry.offset, SEEK_SET);
333 LocalFileHeader header;
334 if (!readLocalFileHeader(stream, header))
335 return false;
336 if (!areHeadersConsistent(header, entry))
337 return false;
338 return true;
341 } // anonymous namespace
343 namespace internal
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()));
356 std::string 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
363 The file must exist
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*/)
375 return true;
378 bool ZipFile::IsZipFile(void* /*stream*/)
380 return true;
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
388 The file must exist
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*/)
401 return true;
404 bool ZipFile::IsValidZipFileVersionNumber(void* /* stream*/)
406 return true;
410 /** Constructs a zip file from a zip file
412 @precond The given parameter must be a string with length > 0
413 The file must exist
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) :
423 m_pStream(0),
424 m_bShouldFree(true)
426 m_pStream = new FileStream(FileName.c_str());
427 if (m_pStream && !isZipStream(m_pStream))
429 delete m_pStream;
430 m_pStream = 0;
434 ZipFile::ZipFile(StreamInterface *stream) :
435 m_pStream(stream),
436 m_bShouldFree(false)
438 if (!isZipStream(stream))
439 m_pStream = 0;
443 /** Destroys a zip file
445 ZipFile::~ZipFile()
447 if (m_pStream && m_bShouldFree)
448 delete m_pStream;
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))
460 return;
461 CentralDirectoryEnd end;
462 if (!readCentralDirectoryEnd(m_pStream, end))
463 return;
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))
469 return;
470 if (ContentName.length() == entry.filename_size && !_stricmp(entry.filename.c_str(), ContentName.c_str()))
471 break;
473 if (ContentName.length() != entry.filename_size)
474 return;
475 if (_stricmp(entry.filename.c_str(), ContentName.c_str()))
476 return;
477 m_pStream->sseek(entry.offset, SEEK_SET);
478 LocalFileHeader header;
479 if (!readLocalFileHeader(m_pStream, header))
480 return;
481 if (!areHeadersConsistent(header, entry))
482 return;
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);
487 else
489 int ret;
490 z_stream strm;
492 /* allocate inflate state */
493 strm.zalloc = Z_NULL;
494 strm.zfree = Z_NULL;
495 strm.opaque = Z_NULL;
496 strm.avail_in = 0;
497 strm.next_in = Z_NULL;
498 ret = inflateInit2(&strm,-MAX_WBITS);
499 if (ret != Z_OK)
500 return;
502 std::vector<unsigned char> tmpBuffer(entry.compressed_size);
503 if (entry.compressed_size != m_pStream->sread(&tmpBuffer[0], entry.compressed_size))
504 return;
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);
512 switch (ret)
514 case Z_NEED_DICT:
515 case Z_DATA_ERROR:
516 case Z_MEM_ERROR:
517 (void)inflateEnd(&strm);
518 ContentBuffer.clear();
519 return;
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))
532 return dir;
533 CentralDirectoryEnd end;
534 if (!readCentralDirectoryEnd(m_pStream, end))
535 return dir;
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))
541 return dir;
542 if (entry.filename_size)
543 dir->push_back(entry.filename);
545 return dir;
548 /** Convinience query function may even realized with
549 iterating over a ZipFileDirectory returned by
550 GetDirectory */
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
569 long lmax = 0;
570 if (!findCentralDirectoryEnd(m_pStream))
571 return lmax;
572 CentralDirectoryEnd end;
573 if (!readCentralDirectoryEnd(m_pStream, end))
574 return lmax;
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))
580 return lmax;
581 if (entry.filename_size > lmax)
582 lmax = entry.filename_size;
584 return lmax;
587 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */