d862c018bcd1d7eac56820acd96e3c8c2d89c2f7
[framework/web/wrt-commons.git] / modules / core / src / zip_input.cpp
1 /*
2  * Copyright (c) 2011 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *    Licensed under the Apache License, Version 2.0 (the "License");
5  *    you may not use this file except in compliance with the License.
6  *    You may obtain a copy of the License at
7  *
8  *        http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *    Unless required by applicable law or agreed to in writing, software
11  *    distributed under the License is distributed on an "AS IS" BASIS,
12  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *    See the License for the specific language governing permissions and
14  *    limitations under the License.
15  */
16 /*
17  * @file        zip_input.cpp
18  * @author      Przemyslaw Dobrowolski (p.dobrowolsk@samsung.com)
19  * @version     1.0
20  * @brief       This file is the implementation file of zip input
21  */
22 #include <dpl/zip_input.h>
23 #include <dpl/file_input_mapping.h>
24 #include <dpl/binary_queue.h>
25 #include <dpl/scoped_free.h>
26 #include <dpl/scoped_ptr.h>
27 #include <dpl/scoped_array.h>
28 #include <dpl/foreach.h>
29 #include <dpl/log/log.h>
30 #include <minizip/framework_minizip.h>
31 #include <new>
32
33 namespace DPL
34 {
35 namespace // anonymous
36 {
37 const size_t EXTRACT_BUFFER_SIZE = 4096;
38
39 class ScopedUnzClose
40 {
41 private:
42     unzFile m_file;
43
44 public:
45     ScopedUnzClose(unzFile file)
46         : m_file(file)
47     {
48     }
49
50     ~ScopedUnzClose()
51     {
52         if (!m_file)
53             return;
54
55         if (unzClose(m_file) != UNZ_OK)
56             LogPedantic("Failed to close zip input file");
57     }
58
59     unzFile Release()
60     {
61         unzFile file = m_file;
62
63         m_file = NULL;
64
65         return file;
66     }
67 };
68 } // namespace anonymous
69
70 /*
71  * Seekable multiplexing device
72  *
73  * Explanation:
74  * Minizip library lacks serious support for multithreaded
75  * access to zip files. Thus, they cannot be easily extracted
76  * simulateously. Here is introduced seekable device which does
77  * have a context with seek index for each file. File is mapped to
78  * memory and because of that no real synchronization is needed.
79  * Memory addresses can be indexed.
80  *
81  * About generalization:
82  * To achieve the same results on abstract input device, there must be
83  * provided a mechanism to read data from random address without synchronization.
84  * In other words: stateless. As described above, stateless property can be
85  * achieved via memory mapping.
86  */
87 class Device
88 {
89 private:
90     DPL::ScopedPtr<FileInputMapping> m_fileMapping;
91
92     struct File
93     {
94         off64_t offset;
95         Device *device;
96
97         File(Device *d)
98             : offset(0),
99               device(d)
100         {
101         }
102     };
103
104 public:
105     Device(const std::string &fileName)
106     {
107         Try
108         {
109             LogPedantic("Creating file mapping");
110             m_fileMapping.Reset(new FileInputMapping(fileName));
111         }
112         Catch (FileInputMapping::Exception::Base)
113         {
114             LogPedantic("Failed to create file mapping");
115
116             ReThrowMsg(ZipInput::Exception::OpenFailed,
117                        "Failed to open zip file mapping");
118         }
119
120         LogPedantic("File mapping created");
121     }
122
123     // zlib_filefunc64_def interface: files
124     static voidpf ZCALLBACK open64_file(voidpf opaque,
125                                         const void* filename,
126                                         int mode)
127     {
128         (void)filename;
129         (void)mode;
130
131         Device *device = static_cast<Device *>(opaque);
132
133         // Open file for master device
134         return new File(device);
135     }
136
137     static uLong ZCALLBACK read_file(voidpf opaque,
138                                      voidpf pstream,
139                                      void* buf,
140                                      uLong size)
141     {
142         Device *device = static_cast<Device *>(opaque);
143         File *deviceFile = static_cast<File *>(pstream);
144
145         // Check if offset is out of bounds
146         if (deviceFile->offset >= device->m_fileMapping->GetSize())
147         {
148             LogPedantic("Device: read offset out of bounds");
149             return -1;
150         }
151
152         off64_t bytesLeft = device->m_fileMapping->GetSize() -
153                             deviceFile->offset;
154
155         off64_t bytesToRead;
156
157         // Calculate bytes to read
158         if (static_cast<off64_t>(size) > bytesLeft)
159             bytesToRead = bytesLeft;
160         else
161             bytesToRead = static_cast<off64_t>(size);
162
163         // Do copy
164         memcpy(buf,
165                device->m_fileMapping->GetAddress() + deviceFile->offset,
166                static_cast<size_t>(bytesToRead));
167
168         // Increment file offset
169         deviceFile->offset += bytesToRead;
170
171         // Return bytes that were actually read
172         return static_cast<uLong>(bytesToRead);
173     }
174
175     static uLong ZCALLBACK write_file(voidpf opaque,
176                                       voidpf stream,
177                                       const void* buf,
178                                       uLong size)
179     {
180         (void)opaque;
181         (void)stream;
182         (void)buf;
183         (void)size;
184
185         // Not supported by device
186         LogPedantic("Unsupported function called!");
187         return -1;
188     }
189
190     static int ZCALLBACK close_file(voidpf opaque,
191                                     voidpf stream)
192     {
193         (void)opaque;
194         File *deviceFile = static_cast<File *>(stream);
195
196         // Delete file
197         delete deviceFile;
198
199         // Always OK
200         return 0;
201     }
202
203     static int ZCALLBACK testerror_file(voidpf opaque,
204                                         voidpf stream)
205     {
206         (void)opaque;
207         (void)stream;
208
209         // No errors
210         return 0;
211     }
212
213     static ZPOS64_T ZCALLBACK tell64_file(voidpf opaque,
214                                           voidpf stream)
215     {
216         (void)opaque;
217         File *deviceFile = static_cast<File *>(stream);
218
219         return static_cast<ZPOS64_T>(deviceFile->offset);
220     }
221
222     static long ZCALLBACK seek64_file(voidpf opaque,
223                                       voidpf stream,
224                                       ZPOS64_T offset,
225                                       int origin)
226     {
227         Device *device = static_cast<Device *>(opaque);
228         File *deviceFile = static_cast<File *>(stream);
229
230         switch (origin)
231         {
232             case ZLIB_FILEFUNC_SEEK_SET:
233                 deviceFile->offset = static_cast<off64_t>(offset);
234
235                 break;
236
237             case ZLIB_FILEFUNC_SEEK_CUR:
238                 deviceFile->offset += static_cast<off64_t>(offset);
239
240                 break;
241
242             case ZLIB_FILEFUNC_SEEK_END:
243                 deviceFile->offset =
244                     device->m_fileMapping->GetSize() -
245                     static_cast<off64_t>(offset);
246
247                 break;
248
249             default:
250                 return -1;
251         }
252
253         return 0;
254     }
255 };
256
257 ZipInput::ZipInput(const std::string &fileName)
258     : m_device(NULL),
259       m_numberOfFiles(0),
260       m_globalComment(),
261       m_fileInfos()
262 {
263     LogPedantic("Zip input file: " << fileName);
264
265     // Create master device
266     LogPedantic("Creating master device");
267     ScopedPtr<Device> device(new Device(fileName));
268
269     // Open master file
270     zlib_filefunc64_def interface;
271     interface.zopen64_file = &Device::open64_file;
272     interface.zread_file = &Device::read_file;
273     interface.zwrite_file = &Device::write_file;
274     interface.ztell64_file = &Device::tell64_file;
275     interface.zseek64_file = &Device::seek64_file;
276     interface.zclose_file = &Device::close_file;
277     interface.zerror_file = &Device::testerror_file;
278     interface.opaque = device.Get();
279
280     LogPedantic("Opening zip file");
281     unzFile file = unzOpen2_64(NULL, &interface);
282
283     if (file == NULL)
284     {
285         LogPedantic("Failed to open zip file");
286
287          // Some errror occured
288         ThrowMsg(Exception::OpenFailed,
289                  "Failed to open zip file: " << fileName);
290     }
291
292     // Begin scope
293     ScopedUnzClose scopedUnzClose(file);
294
295     // Read contents
296     ReadGlobalInfo(file);
297     ReadGlobalComment(file);
298     ReadInfos(file);
299
300     // Release scoped unz close
301     m_masterFile = scopedUnzClose.Release();
302     m_device = device.Release();
303
304     LogPedantic("Zip file opened");
305 }
306
307 ZipInput::~ZipInput()
308 {
309     // Close zip
310     if (unzClose(static_cast<unzFile>(m_masterFile)) != UNZ_OK)
311         LogPedantic("Failed to close zip input file");
312
313     // Close device
314     delete m_device;
315 }
316
317 void ZipInput::ReadGlobalInfo(void *masterFile)
318 {
319     // Read number of entries and global comment
320     unz_global_info globalInfo;
321
322     if (unzGetGlobalInfo(static_cast<unzFile>(masterFile),
323                          &globalInfo) != UNZ_OK)
324     {
325         LogPedantic("Failed to read zip global info");
326
327         ThrowMsg(Exception::ReadGlobalInfoFailed,
328                  "Failed to read global info");
329     }
330
331     m_numberOfFiles = static_cast<size_t>(globalInfo.number_entry);
332     m_globalCommentSize = static_cast<size_t>(globalInfo.size_comment);
333
334     LogPedantic("Number of files: " << m_numberOfFiles);
335     LogPedantic("Global comment size: " << m_globalCommentSize);
336 }
337
338 void ZipInput::ReadGlobalComment(void *masterFile)
339 {
340     ScopedArray<char> comment(new char[m_globalCommentSize + 1]);
341
342     if (unzGetGlobalComment(static_cast<unzFile>(masterFile),
343                             comment.Get(),
344                             m_globalCommentSize + 1) != UNZ_OK)
345     {
346         LogPedantic("Failed to read zip global comment");
347
348         ThrowMsg(Exception::ReadGlobalCommentFailed,
349                  "Failed to read global comment");
350     }
351
352     m_globalComment = comment.Get();
353     LogPedantic("Global comment: " << m_globalComment);
354 }
355
356 void ZipInput::ReadInfos(void *masterFile)
357 {
358     // Read infos
359     m_fileInfos.reserve(m_numberOfFiles);
360
361     if (unzGoToFirstFile(static_cast<unzFile>(masterFile)) != UNZ_OK)
362     {
363         LogPedantic("Failed to go to first file");
364         ThrowMsg(Exception::SeekFileFailed, "Failed to seek first file");
365     }
366
367     for (size_t i = 0; i < m_numberOfFiles; ++i)
368     {
369         unz_file_pos_s filePos;
370
371         if (unzGetFilePos(static_cast<unzFile>(masterFile),
372                           &filePos) != UNZ_OK)
373         {
374             LogPedantic("Failed to get file pos");
375             ThrowMsg(Exception::FileInfoFailed, "Failed to get zip file info");
376         }
377
378         unz_file_info64 fileInfo;
379
380         if (unzGetCurrentFileInfo64(static_cast<unzFile>(masterFile),
381                                     &fileInfo,
382                                     NULL,
383                                     0,
384                                     NULL,
385                                     0,
386                                     NULL,
387                                     0) != UNZ_OK)
388         {
389             LogPedantic("Failed to get file pos");
390             ThrowMsg(Exception::FileInfoFailed, "Failed to get zip file info");
391         }
392
393         ScopedArray<char> fileName(new char[fileInfo.size_filename + 1]);
394         ScopedArray<char> fileComment(new char[fileInfo.size_file_comment + 1]);
395
396         if (unzGetCurrentFileInfo64(static_cast<unzFile>(masterFile),
397                                     &fileInfo,
398                                     fileName.Get(),
399                                     fileInfo.size_filename + 1,
400                                     NULL,
401                                     0,
402                                     fileComment.Get(),
403                                     fileInfo.size_file_comment + 1) != UNZ_OK)
404         {
405             LogPedantic("Failed to get file pos");
406             ThrowMsg(Exception::FileInfoFailed, "Failed to get zip file info");
407         }
408
409         m_fileInfos.push_back(
410             FileInfo(
411                 FileHandle(
412                     static_cast<size_t>(filePos.pos_in_zip_directory),
413                     static_cast<size_t>(filePos.num_of_file)
414                 ),
415                 std::string(fileName.Get()),
416                 std::string(fileComment.Get()),
417                 static_cast<unsigned long>(fileInfo.version),
418                 static_cast<unsigned long>(fileInfo.version_needed),
419                 static_cast<unsigned long>(fileInfo.flag),
420                 static_cast<unsigned long>(fileInfo.compression_method),
421                 static_cast<unsigned long>(fileInfo.dosDate),
422                 static_cast<unsigned long>(fileInfo.crc),
423                 static_cast<off64_t>(fileInfo.compressed_size),
424                 static_cast<off64_t>(fileInfo.uncompressed_size),
425                 static_cast<unsigned long>(fileInfo.disk_num_start),
426                 static_cast<unsigned long>(fileInfo.internal_fa),
427                 static_cast<unsigned long>(fileInfo.external_fa),
428                 FileDateTime(
429                     fileInfo.tmu_date.tm_sec,
430                     fileInfo.tmu_date.tm_min,
431                     fileInfo.tmu_date.tm_hour,
432                     fileInfo.tmu_date.tm_mday,
433                     fileInfo.tmu_date.tm_mon,
434                     fileInfo.tmu_date.tm_year
435                 )
436             )
437         );
438
439         // If this is not the last file, go to next one
440         if (i != m_numberOfFiles - 1)
441         {
442             if (unzGoToNextFile(
443                     static_cast<unzFile>(masterFile))!= UNZ_OK)
444             {
445                 LogPedantic("Failed to go to next file");
446
447                 ThrowMsg(Exception::FileInfoFailed,
448                          "Failed to go to next file");
449             }
450         }
451     }
452 }
453
454 ZipInput::const_iterator ZipInput::begin() const
455 {
456     return m_fileInfos.begin();
457 }
458
459 ZipInput::const_iterator ZipInput::end() const
460 {
461     return m_fileInfos.end();
462 }
463
464 ZipInput::const_reverse_iterator ZipInput::rbegin() const
465 {
466     return m_fileInfos.rbegin();
467 }
468
469 ZipInput::const_reverse_iterator ZipInput::rend() const
470 {
471     return m_fileInfos.rend();
472 }
473
474 ZipInput::size_type ZipInput::size() const
475 {
476     return m_fileInfos.size();
477 }
478
479 ZipInput::File *ZipInput::OpenFile(FileHandle handle)
480 {
481     return new File(m_device, handle);
482 }
483
484 ZipInput::File *ZipInput::OpenFile(const std::string &fileName)
485 {
486     FOREACH(iterator, m_fileInfos)
487     {
488         if (iterator->name == fileName)
489         {
490             return new File(m_device, iterator->handle);
491         }
492     }
493
494     ThrowMsg(Exception::OpenFileFailed,
495              "Failed to open zip file: " << fileName);
496 }
497
498 ZipInput::File::File(class Device *device, FileHandle handle)
499 {
500     // Open file file
501     zlib_filefunc64_def interface;
502     interface.zopen64_file = &Device::open64_file;
503     interface.zread_file = &Device::read_file;
504     interface.zwrite_file = &Device::write_file;
505     interface.ztell64_file = &Device::tell64_file;
506     interface.zseek64_file = &Device::seek64_file;
507     interface.zclose_file = &Device::close_file;
508     interface.zerror_file = &Device::testerror_file;
509     interface.opaque = device;
510
511     LogPedantic("Opening zip file");
512     unzFile file = unzOpen2_64(NULL, &interface);
513
514     if (file == NULL)
515     {
516         LogPedantic("Failed to open zip file");
517
518          // Some errror occured
519         ThrowMsg(ZipInput::Exception::OpenFileFailed,
520                  "Failed to open zip file");
521     }
522
523     // Begin scope
524     ScopedUnzClose scopedUnzClose(file);
525
526     // Look up file handle
527     unz64_file_pos filePos =
528     {
529         static_cast<ZPOS64_T>(handle.first),
530         static_cast<ZPOS64_T>(handle.second)
531     };
532
533     if (unzGoToFilePos64(file, &filePos) != UNZ_OK)
534     {
535         LogPedantic("Failed to seek to zip file");
536
537          // Some errror occured
538         ThrowMsg(ZipInput::Exception::OpenFileFailed,
539                  "Failed to open zip file");
540     }
541
542     // Open current file for reading
543     if (unzOpenCurrentFile(file) != UNZ_OK)
544     {
545         LogPedantic("Failed to open current zip file");
546
547          // Some errror occured
548         ThrowMsg(ZipInput::Exception::OpenFileFailed,
549                  "Failed to open current zip file");
550     }
551
552     // Release scoped unz close
553     m_file = scopedUnzClose.Release();
554
555     LogPedantic("Zip file opened");
556 }
557
558 ZipInput::File::~File()
559 {
560     // Close current file for reading
561     if (unzCloseCurrentFile(static_cast<unzFile>(m_file)) != UNZ_OK)
562         LogPedantic("Failed to close current zip input file");
563
564     // Close zip file
565     if (unzClose(static_cast<unzFile>(m_file)) != UNZ_OK)
566         LogPedantic("Failed to close zip input file");
567 }
568
569 DPL::BinaryQueueAutoPtr ZipInput::File::Read(size_t size)
570 {
571     // Do not even try to unzip if requested zero bytes
572     if (size == 0)
573         return DPL::BinaryQueueAutoPtr(new DPL::BinaryQueue());
574
575     // Calc data to read
576     size_t sizeToRead = size > EXTRACT_BUFFER_SIZE ?
577                         EXTRACT_BUFFER_SIZE :
578                         size;
579
580     // Extract zip file data (one-copy)
581     ScopedFree<void> rawBuffer(malloc(sizeToRead));
582
583     if (!rawBuffer)
584         throw std::bad_alloc();
585
586     // Do unpack
587     int bytes = unzReadCurrentFile(static_cast<unzFile>(m_file),
588                                    rawBuffer.Get(),
589                                    sizeToRead);
590
591     // Internal unzipper error
592     if (bytes < 0)
593     {
594         LogPedantic("Extract failed. Error: " << bytes);
595
596         ThrowMsg(ZipInput::Exception::ReadFileFailed,
597                  "Failed to extract file with error: " << bytes);
598     }
599
600     // Data was read (may be zero bytes)
601     DPL::BinaryQueueAutoPtr buffer(new DPL::BinaryQueue());
602
603     buffer->AppendUnmanaged(rawBuffer.Get(),
604                             static_cast<size_t>(bytes),
605                             &DPL::BinaryQueue::BufferDeleterFree,
606                             NULL);
607
608     rawBuffer.Release();
609
610     return buffer;
611 }
612
613 const std::string &ZipInput::GetGlobalComment() const
614 {
615     return m_globalComment;
616 }
617
618 bool ZipInput::empty() const
619 {
620     return m_fileInfos.empty();
621 }
622 } // namespace DPL