[Archive] Fixed permission handling for windows-zipped files
[platform/core/api/webapi-plugins.git] / src / archive / un_zip.cc
1 /*
2  * Copyright (c) 2015 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 #include "un_zip.h"
18
19 #include <errno.h>
20 #include <string.h>
21 #include <sys/stat.h>
22 #include <cstdio>
23 #include <iostream>
24 #include <memory>
25 #include <string>
26
27 #include "common/logger.h"
28 #include "common/platform_exception.h"
29 #include "common/tools.h"
30 #include "filesystem_file.h"
31
32 #include "archive_file.h"
33 #include "archive_utils.h"
34 #include "un_zip_extract_request.h"
35
36 namespace extension {
37 namespace archive {
38
39 using namespace common;
40 using common::tools::GetErrorString;
41
42 UnZip::UnZip(const std::string& filename)
43     : m_zipfile_name(filename),
44       m_unzip(NULL),
45       m_default_buffer_size(1024 * 1024),
46       m_is_open(false) {
47   ScopeLogger();
48   m_unzip = unzOpen(filename.c_str());
49 }
50
51 UnZip::~UnZip() {
52   ScopeLogger();
53   for (auto& x : path_access_map) {
54     LoggerD("Setting permission for path: %s  [%o] ", x.first.c_str(),
55             x.second);
56     if (chmod(x.first.c_str(), x.second) == -1) {
57       LoggerE("Couldn't set permissions for: [%s] errno: %s", x.first.c_str(),
58               GetErrorString(errno).c_str());
59     }
60   }
61   close();
62 }
63
64 PlatformResult UnZip::close() {
65   ScopeLogger();
66   if (!m_is_open) {
67     LoggerD("Unzip already closed - exiting");
68     return PlatformResult(ErrorCode::NO_ERROR);
69   }
70
71   int errclose = unzClose(m_unzip);
72   m_unzip = NULL;
73
74   if (errclose != UNZ_OK) {
75     return LogAndCreateResult(ErrorCode::UNKNOWN_ERR, getArchiveLogMessage(errclose, "unzClose()"),
76                               ("ret: %d", errclose));
77   }
78   m_is_open = false;
79   return PlatformResult(ErrorCode::NO_ERROR);
80 }
81
82 PlatformResult UnZip::open(const std::string& filename, UnZipPtr* out_unzip) {
83   ScopeLogger();
84   UnZipPtr unzip = UnZipPtr(new UnZip(filename));
85
86   if (!unzip->m_unzip) {
87     return LogAndCreateResult(ErrorCode::INVALID_VALUES_ERR, "Failed to open zip file",
88                               ("unzOpen returned NULL : It means the file is invalid."));
89   }
90   unzip->m_is_open = true;
91   *out_unzip = unzip;
92   return PlatformResult(ErrorCode::NO_ERROR);
93 }
94
95 PlatformResult UnZip::listEntries(unsigned long* decompressedSize,
96                                   ArchiveFileEntryPtrMapPtr* out_map) {
97   ScopeLogger();
98   if (!m_is_open) {
99     return LogAndCreateResult(ErrorCode::UNKNOWN_ERR, "Failed to get list of files in zip archive");
100   }
101   unz_global_info gi;
102   int err = unzGetGlobalInfo(m_unzip, &gi);
103   if (UNZ_OK != err) {
104     return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
105                               getArchiveLogMessage(err, "unzGetGlobalInfo()"), ("ret: %d", err));
106   }
107
108   char filename_inzip[512];
109   unz_file_info file_info;
110
111   ArchiveFileEntryPtrMapPtr map = ArchiveFileEntryPtrMapPtr(new ArchiveFileEntryPtrMap());
112
113   unsigned long totalDecompressed = 0;
114
115   err = unzGoToFirstFile(m_unzip);
116   if (err != UNZ_OK) {
117     LoggerW("%s", getArchiveLogMessage(err, "unzGoToFirstFile()").c_str());
118   }
119
120   for (uLong i = 0; i < gi.number_entry; i++) {
121     err = unzGetCurrentFileInfo(m_unzip, &file_info, filename_inzip, sizeof(filename_inzip), NULL,
122                                 0, NULL, 0);
123     if (err != UNZ_OK) {
124       return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
125                                 getArchiveLogMessage(err, "unzGetCurrentFileInfo()"),
126                                 ("ret: %d", err));
127     }
128
129     LoggerD("file: %s | unc size: %lu | comp size: %lu", filename_inzip,
130             file_info.uncompressed_size, file_info.compressed_size);
131
132     ArchiveFileEntryPtr entry = ArchiveFileEntryPtr(new ArchiveFileEntry());
133     entry->setName(filename_inzip);
134     entry->setSize(file_info.uncompressed_size);
135     entry->setCompressedSize(file_info.compressed_size);
136
137     totalDecompressed += file_info.uncompressed_size;
138
139     tm date;  // = file_info.tmu_date;
140     date.tm_sec = file_info.tmu_date.tm_sec;
141     date.tm_min = file_info.tmu_date.tm_min;
142     date.tm_hour = file_info.tmu_date.tm_hour;
143     date.tm_mday = file_info.tmu_date.tm_mday;
144     date.tm_mon = file_info.tmu_date.tm_mon;
145     date.tm_year = file_info.tmu_date.tm_year - 1900;
146     date.tm_wday = 0;
147     date.tm_yday = 0;
148     date.tm_isdst = 0;
149     LoggerD("%d, %d, %d, %d, %d, %d", date.tm_hour, date.tm_min, date.tm_sec, date.tm_mday,
150             date.tm_mon, date.tm_year);
151     entry->setModified(mktime(&date));
152
153     map->insert(std::make_pair(filename_inzip, entry));
154
155     if ((i + 1) < gi.number_entry) {
156       err = unzGoToNextFile(m_unzip);
157       if (UNZ_OK != err) {
158         return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
159                                   getArchiveLogMessage(err, "unzGoToNextFile()"), ("ret: %d", err));
160       }
161     }
162   }
163
164   (*decompressedSize) = totalDecompressed;
165
166   *out_map = map;
167
168   return PlatformResult(ErrorCode::NO_ERROR);
169 }
170
171 PlatformResult UnZip::extractAllFilesTo(const std::string& extract_path,
172                                         ExtractAllProgressCallback* callback) {
173   ScopeLogger();
174   if (!m_is_open) {
175     return LogAndCreateResult(ErrorCode::UNKNOWN_ERR, "Failed to extract zip archive");
176   }
177
178   //
179   // Calculate number of entries to extract and total number of bytes
180   //
181   unz_global_info gi;
182   PlatformResult result = updateCallbackWithArchiveStatistics(callback, gi);
183   if (result.error_code() != ErrorCode::NO_ERROR) {
184     LoggerE("Error: %s", result.message().c_str());
185     return result;
186   }
187
188   //
189   // Begin extracting entries
190   //
191   int err = unzGoToFirstFile(m_unzip);
192   if (err != UNZ_OK) {
193     LoggerE("%s", getArchiveLogMessage(err, "unzGoToFirstFile()").c_str());
194   }
195
196   for (uLong i = 0; i < gi.number_entry; i++) {
197     if (callback->isCanceled()) {
198       return LogAndCreateResult(ErrorCode::OPERATION_CANCELED_ERR, "Operation canceled");
199     }
200
201     result = extractCurrentFile(extract_path, std::string(), callback);
202     if (result.error_code() != ErrorCode::NO_ERROR) {
203       LoggerE("Fail: extractCurrentFile()");
204       return result;
205     }
206
207     if ((i + 1) < gi.number_entry) {
208       err = unzGoToNextFile(m_unzip);
209       if (UNZ_OK != err) {
210         return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
211                                   getArchiveLogMessage(err, "unzGoToNextFile()"), ("ret: %d", err));
212       }
213     }
214   }
215
216   callback->callSuccessCallbackOnMainThread();
217
218   return PlatformResult(ErrorCode::NO_ERROR);
219 }
220
221 struct ExtractDataHolder {
222   UnZip* unzip;
223   ExtractEntryProgressCallback* callback;
224   std::string root_output_path;
225 };
226
227 PlatformResult UnZip::extractTo(ExtractEntryProgressCallback* callback) {
228   ScopeLogger();
229   if (!m_is_open) {
230     return LogAndCreateResult(ErrorCode::UNKNOWN_ERR, "Extract archive file entry failed");
231   }
232
233   if (!callback) {
234     return LogAndCreateResult(ErrorCode::UNKNOWN_ERR, "Extract archive file entry failed",
235                               ("callback is NULL"));
236   }
237
238   if (!callback->getArchiveFileEntry()) {
239     return LogAndCreateResult(ErrorCode::UNKNOWN_ERR, "Extract archive file entry failed",
240                               ("callback->getArchiveFileEntry() is NULL"));
241   }
242
243   filesystem::FilePtr out_dir = callback->getDirectory();
244   if (!out_dir) {
245     return LogAndCreateResult(ErrorCode::INVALID_VALUES_ERR, "Output directory is not correct");
246   }
247
248   NodePtr out_node = out_dir->getNode();
249   if (!out_node) {
250     return LogAndCreateResult(ErrorCode::INVALID_VALUES_ERR, "Output directory is not correct");
251   }
252
253   PathPtr out_path = out_node->getPath();
254   if (!out_path) {
255     return LogAndCreateResult(ErrorCode::INVALID_VALUES_ERR, "Output directory is not correct");
256   }
257
258   auto entry_name_in_zip = callback->getArchiveFileEntry()->getName();
259   auto root_output_path = out_dir->getNode()->getPath()->getFullPath();
260   LoggerD("Extract: [%s] to root output directory: [%s] (stripBasePath: [%s])",
261           entry_name_in_zip.c_str(), root_output_path.c_str(),
262           callback->getStripBasePath().c_str());
263
264   //
265   // Calculate number of entries to extract and total number of bytes
266   //
267   unz_global_info gi;
268   PlatformResult result = updateCallbackWithArchiveStatistics(callback, gi, entry_name_in_zip);
269   if (result.error_code() != ErrorCode::NO_ERROR) {
270     LoggerE("Fail: updateCallbackWithArchiveStatistics()");
271     return result;
272   }
273
274   //
275   // Begin extracting entries
276   //
277
278   ExtractDataHolder h;
279   h.unzip = this;
280   h.callback = callback;
281   h.root_output_path = root_output_path;
282
283   // this loop call internally progress callbacks
284   unsigned int matched;
285   result = IterateFilesInZip(gi, entry_name_in_zip, callback, extractItFunction, matched, &h);
286   if (result.error_code() != ErrorCode::NO_ERROR) {
287     LoggerE("Fail: IterateFilesInZip()");
288     return result;
289   }
290
291   // after finish extracting success callback will be called
292   callback->callSuccessCallbackOnMainThread();
293
294   return PlatformResult(ErrorCode::NO_ERROR);
295 }
296
297 PlatformResult UnZip::extractItFunction(const std::string& file_name, unz_file_info& file_info,
298                                         void* user_data) {
299   ScopeLogger();
300   ExtractDataHolder* h = static_cast<ExtractDataHolder*>(user_data);
301   if (!h) {
302     return LogAndCreateResult(ErrorCode::UNKNOWN_ERR, "Could not list content of zip archive",
303                               ("ExtractDataHolder is NULL!"));
304   }
305
306   PlatformResult result = h->unzip->extractCurrentFile(
307       h->root_output_path, h->callback->getStripBasePath(), h->callback);
308   if (result.error_code() != ErrorCode::NO_ERROR) {
309     LoggerE("Error: %s", result.message().c_str());
310     return result;
311   }
312   return PlatformResult(ErrorCode::NO_ERROR);
313 }
314
315 PlatformResult UnZip::IterateFilesInZip(unz_global_info& gi, const std::string& entry_name_in_zip,
316                                         OperationCallbackData* callback,
317                                         UnZip::IterateFunction itfunc,
318                                         unsigned int& num_file_or_folder_matched, void* user_data) {
319   ScopeLogger();
320   int err = unzGoToFirstFile(m_unzip);
321   if (UNZ_OK != err) {
322     LoggerW("%s", getArchiveLogMessage(err, "unzGoToFirstFile()").c_str());
323   }
324
325   num_file_or_folder_matched = 0;
326   const bool is_directory = isDirectoryPath(entry_name_in_zip);
327
328   unz_file_info cur_file_info;
329   char tmp_fname[512];
330
331   for (uLong i = 0; i < gi.number_entry; i++) {
332     if (callback->isCanceled()) {
333       return LogAndCreateResult(ErrorCode::OPERATION_CANCELED_ERR, "Operation canceled");
334     }
335
336     err = unzGetCurrentFileInfo(m_unzip, &cur_file_info, tmp_fname, sizeof(tmp_fname), NULL, 0,
337                                 NULL, 0);
338     if (UNZ_OK != err) {
339       return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
340                                 getArchiveLogMessage(err, "unzGetCurrentFileInfo()"),
341                                 ("ret: %d", err));
342     }
343
344     const std::string cur_filename_in_zip(tmp_fname);
345     bool match = true;
346
347     if (!entry_name_in_zip.empty()) {
348       if (is_directory) {
349         // If entry_name_in_zip is pointing at directory we need to check each entry in
350         // zip if its path starts with entry_name_in_zip
351         match = (0 == cur_filename_in_zip.find(entry_name_in_zip));
352       } else {
353         // If entry name points to file we only extract entry with matching name
354         match = (cur_filename_in_zip == entry_name_in_zip);
355       }
356     }
357
358     if (match) {
359       PlatformResult result = itfunc(cur_filename_in_zip, cur_file_info, user_data);
360       if (result.error_code() != ErrorCode::NO_ERROR) {
361         LoggerE("Error: %s", result.message().c_str());
362         return result;
363       }
364       ++num_file_or_folder_matched;
365     }
366
367     if ((i + 1) < gi.number_entry) {
368       err = unzGoToNextFile(m_unzip);
369       if (UNZ_OK != err) {
370         return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
371                                   getArchiveLogMessage(err, "unzGoToNextFile()"), ("ret: %d", err));
372       }
373     }
374   }
375
376   return PlatformResult(ErrorCode::NO_ERROR);
377 }
378
379 PlatformResult UnZip::extractCurrentFile(const std::string& extract_path,
380                                          const std::string& base_strip_path,
381                                          BaseProgressCallback* callback) {
382   ScopeLogger();
383
384   if (callback->isCanceled()) {
385     return LogAndCreateResult(ErrorCode::OPERATION_CANCELED_ERR, "Operation canceled");
386   }
387
388   LoggerD("extract_path: [%s] base_strip_path: [%s] ", extract_path.c_str(),
389           base_strip_path.c_str());
390   return UnZipExtractRequest::execute(*this, extract_path, base_strip_path, callback);
391 }
392
393 struct ArchiveStatistics {
394   ArchiveStatistics() : uncompressed_size(0), number_of_files(0), number_of_folders(0) {
395   }
396
397   unsigned long uncompressed_size;
398   unsigned long number_of_files;
399   unsigned long number_of_folders;
400 };
401
402 PlatformResult generateArchiveStatistics(const std::string& file_name, unz_file_info& file_info,
403                                          void* user_data) {
404   ScopeLogger();
405   if (user_data) {
406     ArchiveStatistics* astats = static_cast<ArchiveStatistics*>(user_data);
407     astats->uncompressed_size += file_info.uncompressed_size;
408
409     if (isDirectoryPath(file_name)) {
410       astats->number_of_folders += 1;
411     } else {
412       astats->number_of_files += 1;
413     }
414   }
415   return PlatformResult(ErrorCode::NO_ERROR);
416 }
417
418 PlatformResult UnZip::updateCallbackWithArchiveStatistics(ExtractAllProgressCallback* callback,
419                                                           unz_global_info& out_global_info,
420                                                           const std::string& optional_filter) {
421   ScopeLogger();
422   int err = unzGetGlobalInfo(m_unzip, &out_global_info);
423   if (UNZ_OK != err) {
424     return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
425                               getArchiveLogMessage(err, "unzGetGlobalInfo()"), ("ret: %d", err));
426   }
427
428   ArchiveStatistics astats;
429   unsigned int num_matched;
430
431   PlatformResult result = IterateFilesInZip(out_global_info, optional_filter, callback,
432                                             generateArchiveStatistics, num_matched, &astats);
433   if (result.error_code() != ErrorCode::NO_ERROR) {
434     LoggerE("Error: %s", result.message().c_str());
435     return result;
436   }
437   if (0 == num_matched) {
438     SLoggerE("No matching file/directory: [%s] has been found in zip archive",
439              optional_filter.c_str());
440     return LogAndCreateResult(ErrorCode::NOT_FOUND_ERR, "Could not extract file from archive");
441   }
442
443   callback->setExpectedDecompressedSize(astats.uncompressed_size);
444   LoggerD("Expected uncompressed size: %s",
445           bytesToReadableString(astats.uncompressed_size).c_str());
446
447   callback->setNumberOfFilesToExtract(astats.number_of_files);
448   LoggerD("Number entries to extract: files: %lu folders: %lu", astats.number_of_files,
449           astats.number_of_folders);
450
451   return PlatformResult(ErrorCode::NO_ERROR);
452 }
453
454 }  // namespace archive
455 }  // namespace extension