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