xref: /core/shell/source/win32/zipfile/zipfile.cxx (revision 70b3bdd4)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
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 .
18  */
19 
20 #include "zipexcptn.hxx"
21 #include <zipfile.hxx>
22 #include <global.hxx>
23 #include <types.hxx>
24 #include <stream_helper.hxx>
25 
26 #include <malloc.h>
27 #include <algorithm>
28 #include <memory>
29 
30 #include <string.h>
31 
32 #include <o3tl/safeint.hxx>
33 
34 #include <zlib.h>
35 
36 namespace
37 {
38 
39 struct LocalFileHeader
40 {
41     unsigned short min_version;
42     unsigned short general_flag;
43     unsigned short compression;
44     unsigned short lastmod_time;
45     unsigned short lastmod_date;
46     unsigned crc32;
47     unsigned compressed_size;
48     unsigned uncompressed_size;
49     unsigned short filename_size;
50     unsigned short extra_field_size;
51     std::string filename;
52     std::string extra_field;
LocalFileHeader__anon1644b0540111::LocalFileHeader53     LocalFileHeader()
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() {}
57 };
58 
59 struct CentralDirectoryEntry
60 {
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;
67     unsigned crc32;
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;
76     unsigned offset;
77     std::string filename;
78     std::string extra_field;
79     std::string file_comment;
CentralDirectoryEntry__anon1644b0540111::CentralDirectoryEntry80     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() {}
85 };
86 
87 struct CentralDirectoryEnd
88 {
89     unsigned short disk_num;
90     unsigned short cdir_disk;
91     unsigned short disk_entries;
92     unsigned short cdir_entries;
93     unsigned cdir_size;
94     unsigned cdir_offset;
95     unsigned short comment_size;
96     std::string comment;
CentralDirectoryEnd__anon1644b0540111::CentralDirectoryEnd97     CentralDirectoryEnd()
98         : disk_num(0), cdir_disk(0), disk_entries(0), cdir_entries(0),
99           cdir_size(0), cdir_offset(0), comment_size(0), comment() {}
100 };
101 
102 #define CDIR_ENTRY_SIG 0x02014b50
103 #define LOC_FILE_HEADER_SIG 0x04034b50
104 #define CDIR_END_SIG 0x06054b50
105 
106 // This little lot performs in a truly appalling way without
107 // buffering eg. on an IStream.
108 
readShort(StreamInterface * stream)109 unsigned short readShort(StreamInterface *stream)
110 {
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);
118     return tmpBuf;
119 }
120 
readInt(StreamInterface * stream)121 unsigned readInt(StreamInterface *stream)
122 {
123     if (!stream || stream->stell() == -1)
124         throw IOException(-1);
125     unsigned tmpBuf;
126     unsigned long numBytesRead = stream->sread(
127         reinterpret_cast<unsigned char *>( &tmpBuf ), 4);
128     if (numBytesRead != 4)
129         throw IOException(-1);
130     return tmpBuf;
131 }
132 
readString(StreamInterface * stream,unsigned long size)133 std::string readString(StreamInterface *stream, unsigned long size)
134 {
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)
140     {
141         throw IOException(-1);
142     }
143 
144     std::string aStr(reinterpret_cast<char *>(tmp.get()), size);
145     return aStr;
146 }
147 
readCentralDirectoryEnd(StreamInterface * stream,CentralDirectoryEnd & end)148 bool readCentralDirectoryEnd(StreamInterface *stream, CentralDirectoryEnd &end)
149 {
150     try
151     {
152         unsigned signature = readInt(stream);
153         if (signature != CDIR_END_SIG)
154             return false;
155 
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));
164     }
165     catch (...)
166     {
167         return false;
168     }
169     return true;
170 }
171 
readCentralDirectoryEntry(StreamInterface * stream,CentralDirectoryEntry & entry)172 bool readCentralDirectoryEntry(StreamInterface *stream, CentralDirectoryEntry &entry)
173 {
174     try
175     {
176         unsigned signature = readInt(stream);
177         if (signature != CDIR_ENTRY_SIG)
178             return false;
179 
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));
199     }
200     catch (...)
201     {
202         return false;
203     }
204     return true;
205 }
206 
readLocalFileHeader(StreamInterface * stream,LocalFileHeader & header)207 bool readLocalFileHeader(StreamInterface *stream, LocalFileHeader &header)
208 {
209     try
210     {
211         unsigned signature = readInt(stream);
212         if (signature != LOC_FILE_HEADER_SIG)
213             return false;
214 
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));
227     }
228     catch (...)
229     {
230         return false;
231     }
232     return true;
233 }
234 
areHeadersConsistent(const LocalFileHeader & header,const CentralDirectoryEntry & entry)235 bool areHeadersConsistent(const LocalFileHeader &header, const CentralDirectoryEntry &entry)
236 {
237     if (header.min_version != entry.min_version)
238         return false;
239     if (header.general_flag != entry.general_flag)
240         return false;
241     if (header.compression != entry.compression)
242         return false;
243     if (!(header.general_flag & 0x08))
244     {
245         if (header.crc32 != entry.crc32)
246             return false;
247         if (header.compressed_size != entry.compressed_size)
248             return false;
249         if (header.uncompressed_size != entry.uncompressed_size)
250             return false;
251     }
252     return true;
253 }
254 
255 #define BLOCK_SIZE 0x800
256 
findSignatureAtOffset(StreamInterface * stream,unsigned long nOffset)257 bool findSignatureAtOffset(StreamInterface *stream, unsigned long nOffset)
258 {
259     // read in reasonably sized chunk, and read more, to get overlapping sigs
260     unsigned char aBuffer[ BLOCK_SIZE + 4 ];
261 
262     stream->sseek(nOffset, SEEK_SET);
263 
264     unsigned long nBytesRead = stream->sread(aBuffer, sizeof(aBuffer));
265 
266     for (long n = nBytesRead - 4; n >= 0; n--)
267     {
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);
272             return true;
273         }
274     }
275 
276     return false;
277 }
278 
findCentralDirectoryEnd(StreamInterface * stream)279 bool findCentralDirectoryEnd(StreamInterface *stream)
280 {
281     if (!stream)
282         return false;
283 
284     stream->sseek(0,SEEK_END);
285 
286     long nLength = stream->stell();
287     if (nLength == -1)
288         return false;
289 
290     try
291     {
292         for (long nOffset = nLength - BLOCK_SIZE - 4;
293              nOffset > 0; nOffset -= BLOCK_SIZE)
294         {
295             if (findSignatureAtOffset(stream, nOffset))
296                 return true;
297         }
298         return findSignatureAtOffset(stream, 0);
299     }
300     catch (...)
301     {
302         return false;
303     }
304 }
305 
isZipStream(StreamInterface * stream)306 bool isZipStream(StreamInterface *stream)
307 {
308     if (!findCentralDirectoryEnd(stream))
309         return false;
310     CentralDirectoryEnd end;
311     if (!readCentralDirectoryEnd(stream, end))
312         return false;
313     stream->sseek(end.cdir_offset, SEEK_SET);
314     CentralDirectoryEntry entry;
315     if (!readCentralDirectoryEntry(stream, entry))
316         return false;
317     stream->sseek(entry.offset, SEEK_SET);
318     LocalFileHeader header;
319     if (!readLocalFileHeader(stream, header))
320         return false;
321     if (!areHeadersConsistent(header, entry))
322         return false;
323     return true;
324 }
325 
326 } // anonymous namespace
327 
328 namespace internal
329 {
330 
331 namespace {
332 
333 /* for case in-sensitive string comparison */
334 struct stricmp
335 {
stricmpinternal::__anon1644b0540211::stricmp336     explicit stricmp(const std::string &str) : str_(str)
337     {}
338 
operator ()internal::__anon1644b0540211::stricmp339     bool operator() (const std::string &other)
340     {
341         return (0 == _stricmp(str_.c_str(), other.c_str()));
342     }
343 
344     std::string str_;
345 };
346 
347 }
348 
349 } // namespace internal
350 
351 /** Checks whether a file is a zip file or not
352 
353     @precond    The given parameter must be a string with length > 0
354             The file must exist
355             The file must be readable for the current user
356 
357     @returns    true if the file is a zip file
358             false if the file is not a zip file
359 
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
363 */
IsZipFile(const Filepath_t &)364 bool ZipFile::IsZipFile(const Filepath_t& /*FileName*/)
365 {
366     return true;
367 }
368 
IsZipFile(void *)369 bool ZipFile::IsZipFile(void* /*stream*/)
370 {
371     return true;
372 }
373 
374 
375 /** Returns whether the version of the specified zip file may be uncompressed with the
376           currently used zlib version or not
377 
378     @precond    The given parameter must be a string with length > 0
379             The file must exist
380             The file must be readable for the current user
381             The file must be a valid zip file
382 
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
385 
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
389 */
IsValidZipFileVersionNumber(const Filepath_t &)390 bool ZipFile::IsValidZipFileVersionNumber(const Filepath_t& /*FileName*/)
391 {
392     return true;
393 }
394 
IsValidZipFileVersionNumber(void *)395 bool ZipFile::IsValidZipFileVersionNumber(void* /* stream*/)
396 {
397     return true;
398 }
399 
400 
401 /** Constructs a zip file from a zip file
402 
403     @precond    The given parameter must be a string with length > 0
404             The file must exist
405             The file must be readable for the current user
406 
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
412 */
ZipFile(const Filepath_t & FileName)413 ZipFile::ZipFile(const Filepath_t &FileName) :
414     m_pStream(nullptr),
415     m_bShouldFree(true)
416 {
417     m_pStream = new FileStream(FileName.c_str());
418     if (!isZipStream(m_pStream))
419     {
420         delete m_pStream;
421         m_pStream = nullptr;
422     }
423 }
424 
ZipFile(StreamInterface * stream)425 ZipFile::ZipFile(StreamInterface *stream) :
426     m_pStream(stream),
427     m_bShouldFree(false)
428 {
429     if (!isZipStream(stream))
430         m_pStream = nullptr;
431 }
432 
433 
434 /** Destroys a zip file
435 */
~ZipFile()436 ZipFile::~ZipFile()
437 {
438     if (m_pStream && m_bShouldFree)
439         delete m_pStream;
440 }
441 
442 /** Provides an interface to read the uncompressed data of a content of the zip file
443 
444     @precond    The specified content must exist in this file
445             ppstm must not be NULL
446 */
GetUncompressedContent(const std::string & ContentName,ZipContentBuffer_t & ContentBuffer)447 void ZipFile::GetUncompressedContent(
448     const std::string &ContentName, /*inout*/ ZipContentBuffer_t &ContentBuffer)
449 {
450     if (!findCentralDirectoryEnd(m_pStream))
451         return;
452     CentralDirectoryEnd end;
453     if (!readCentralDirectoryEnd(m_pStream, end))
454         return;
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)
458     {
459         if (!readCentralDirectoryEntry(m_pStream, entry))
460             return;
461         if (ContentName.length() == entry.filename_size && !_stricmp(entry.filename.c_str(), ContentName.c_str()))
462             break;
463     }
464     if (ContentName.length() != entry.filename_size)
465         return;
466     if (_stricmp(entry.filename.c_str(), ContentName.c_str()))
467         return;
468     m_pStream->sseek(entry.offset, SEEK_SET);
469     LocalFileHeader header;
470     if (!readLocalFileHeader(m_pStream, header))
471         return;
472     if (!areHeadersConsistent(header, entry))
473         return;
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);
478     else
479     {
480         int ret;
481         z_stream strm;
482 
483         /* allocate inflate state */
484         strm.zalloc = Z_NULL;
485         strm.zfree = Z_NULL;
486         strm.opaque = Z_NULL;
487         strm.avail_in = 0;
488         strm.next_in = Z_NULL;
489         ret = inflateInit2(&strm,-MAX_WBITS);
490         if (ret != Z_OK)
491             return;
492 
493         std::vector<unsigned char> tmpBuffer(entry.compressed_size);
494         if (entry.compressed_size != m_pStream->sread(tmpBuffer.data(), entry.compressed_size))
495             return;
496 
497         strm.avail_in = entry.compressed_size;
498         strm.next_in = reinterpret_cast<Bytef *>(tmpBuffer.data());
499 
500         strm.avail_out = entry.uncompressed_size;
501         strm.next_out = reinterpret_cast<Bytef *>(ContentBuffer.data());
502         ret = inflate(&strm, Z_FINISH);
503         switch (ret)
504         {
505         case Z_NEED_DICT:
506         case Z_DATA_ERROR:
507         case Z_MEM_ERROR:
508             (void)inflateEnd(&strm);
509             ContentBuffer.clear();
510             return;
511         }
512         (void)inflateEnd(&strm);
513     }
514 }
515 
516 /** Returns a list with the content names contained within this file
517 
518 */
GetDirectory() const519 ZipFile::DirectoryPtr_t ZipFile::GetDirectory() const
520 {
521     DirectoryPtr_t dir(new Directory_t());
522     if (!findCentralDirectoryEnd(m_pStream))
523         return dir;
524     CentralDirectoryEnd end;
525     if (!readCentralDirectoryEnd(m_pStream, end))
526         return dir;
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)
530     {
531         if (!readCentralDirectoryEntry(m_pStream, entry))
532             return dir;
533         if (entry.filename_size)
534             dir->push_back(entry.filename);
535     }
536     return dir;
537 }
538 
539 /** Convenience query function may even realized with
540     iterating over a ZipFileDirectory returned by
541     GetDirectory */
HasContent(const std::string & ContentName) const542 bool ZipFile::HasContent(const std::string &ContentName) const
543 {
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();
548 
549     return std::any_of(dir->begin(), dir->end(), internal::stricmp(ContentName));
550 }
551 
552 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
553