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