Bump version to 5.0-14
[LibreOffice.git] / shell / source / win32 / zipfile / zipfile.cxx
blob9cebf36ead13a4faa91f30d78d8f5bade85b4814
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 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);
117 return tmpBuf;
120 static unsigned readInt(StreamInterface *stream)
122 if (!stream || stream->stell() == -1)
123 throw IOException(-1);
124 unsigned tmpBuf;
125 unsigned long numBytesRead = stream->sread(
126 reinterpret_cast<unsigned char *>( &tmpBuf ), 4);
127 if (numBytesRead != 4)
128 throw IOException(-1);
129 return tmpBuf;
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)
140 delete [] tmp;
141 throw IOException(-1);
144 std::string aStr((char *)tmp, size);
145 delete [] tmp;
146 return aStr;
149 static bool readCentralDirectoryEnd(StreamInterface *stream, CentralDirectoryEnd &end)
153 unsigned signature = readInt(stream);
154 if (signature != CDIR_END_SIG)
155 return false;
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));
166 catch (...)
168 return false;
170 return true;
173 static bool readCentralDirectoryEntry(StreamInterface *stream, CentralDirectoryEntry &entry)
177 unsigned signature = readInt(stream);
178 if (signature != CDIR_ENTRY_SIG)
179 return false;
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));
201 catch (...)
203 return false;
205 return true;
208 static bool readLocalFileHeader(StreamInterface *stream, LocalFileHeader &header)
212 unsigned signature = readInt(stream);
213 if (signature != LOC_FILE_HEADER_SIG)
214 return false;
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));
229 catch (...)
231 return false;
233 return true;
236 static bool areHeadersConsistent(const LocalFileHeader &header, const CentralDirectoryEntry &entry)
238 if (header.min_version != entry.min_version)
239 return false;
240 if (header.general_flag != entry.general_flag)
241 return false;
242 if (header.compression != entry.compression)
243 return false;
244 if (!(header.general_flag & 0x08))
246 if (header.crc32 != entry.crc32)
247 return false;
248 if (header.compressed_size != entry.compressed_size)
249 return false;
250 if (header.uncompressed_size != entry.uncompressed_size)
251 return false;
253 return true;
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);
273 return true;
277 return false;
280 static bool findCentralDirectoryEnd(StreamInterface *stream)
282 if (!stream)
283 return false;
285 stream->sseek(0,SEEK_END);
287 long nLength = stream->stell();
288 if (nLength == -1)
289 return false;
293 for (long nOffset = nLength - BLOCK_SIZE - 4;
294 nOffset > 0; nOffset -= BLOCK_SIZE)
296 if (findSignatureAtOffset(stream, nOffset))
297 return true;
299 return findSignatureAtOffset(stream, 0);
301 catch (...)
303 return false;
307 static bool isZipStream(StreamInterface *stream)
309 if (!findCentralDirectoryEnd(stream))
310 return false;
311 CentralDirectoryEnd end;
312 if (!readCentralDirectoryEnd(stream, end))
313 return false;
314 stream->sseek(end.cdir_offset, SEEK_SET);
315 CentralDirectoryEntry entry;
316 if (!readCentralDirectoryEntry(stream, entry))
317 return false;
318 stream->sseek(entry.offset, SEEK_SET);
319 LocalFileHeader header;
320 if (!readLocalFileHeader(stream, header))
321 return false;
322 if (!areHeadersConsistent(header, entry))
323 return false;
324 return true;
327 } // anonymous namespace
329 namespace internal
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()));
342 std::string 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
349 The file must exist
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*/)
361 return true;
364 bool ZipFile::IsZipFile(void* /*stream*/)
366 return true;
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
374 The file must exist
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*/)
387 return true;
390 bool ZipFile::IsValidZipFileVersionNumber(void* /* stream*/)
392 return true;
396 /** Constructs a zip file from a zip file
398 @precond The given parameter must be a string with length > 0
399 The file must exist
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) :
409 m_pStream(0),
410 m_bShouldFree(true)
412 m_pStream = new FileStream(FileName.c_str());
413 if (m_pStream && !isZipStream(m_pStream))
415 delete m_pStream;
416 m_pStream = 0;
420 ZipFile::ZipFile(StreamInterface *stream) :
421 m_pStream(stream),
422 m_bShouldFree(false)
424 if (!isZipStream(stream))
425 m_pStream = 0;
429 /** Destroys a zip file
431 ZipFile::~ZipFile()
433 if (m_pStream && m_bShouldFree)
434 delete m_pStream;
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))
446 return;
447 CentralDirectoryEnd end;
448 if (!readCentralDirectoryEnd(m_pStream, end))
449 return;
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))
455 return;
456 if (ContentName.length() == entry.filename_size && !_stricmp(entry.filename.c_str(), ContentName.c_str()))
457 break;
459 if (ContentName.length() != entry.filename_size)
460 return;
461 if (_stricmp(entry.filename.c_str(), ContentName.c_str()))
462 return;
463 m_pStream->sseek(entry.offset, SEEK_SET);
464 LocalFileHeader header;
465 if (!readLocalFileHeader(m_pStream, header))
466 return;
467 if (!areHeadersConsistent(header, entry))
468 return;
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);
473 else
475 int ret;
476 z_stream strm;
478 /* allocate inflate state */
479 strm.zalloc = Z_NULL;
480 strm.zfree = Z_NULL;
481 strm.opaque = Z_NULL;
482 strm.avail_in = 0;
483 strm.next_in = Z_NULL;
484 ret = inflateInit2(&strm,-MAX_WBITS);
485 if (ret != Z_OK)
486 return;
488 std::vector<unsigned char> tmpBuffer(entry.compressed_size);
489 if (entry.compressed_size != m_pStream->sread(&tmpBuffer[0], entry.compressed_size))
490 return;
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);
498 switch (ret)
500 case Z_NEED_DICT:
501 case Z_DATA_ERROR:
502 case Z_MEM_ERROR:
503 (void)inflateEnd(&strm);
504 ContentBuffer.clear();
505 return;
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))
518 return dir;
519 CentralDirectoryEnd end;
520 if (!readCentralDirectoryEnd(m_pStream, end))
521 return dir;
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))
527 return dir;
528 if (entry.filename_size)
529 dir->push_back(entry.filename);
531 return dir;
534 /** Convinience query function may even realized with
535 iterating over a ZipFileDirectory returned by
536 GetDirectory */
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
555 long lmax = 0;
556 if (!findCentralDirectoryEnd(m_pStream))
557 return lmax;
558 CentralDirectoryEnd end;
559 if (!readCentralDirectoryEnd(m_pStream, end))
560 return lmax;
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))
566 return lmax;
567 if (entry.filename_size > lmax)
568 lmax = entry.filename_size;
570 return lmax;
573 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */