Merge branch 'tizen_4.0' into tizen_5.0
[platform/core/api/webapi-plugins.git] / src / archive / un_zip_extract_request.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_extract_request.h"
18
19 #include <errno.h>
20 #include <string.h>
21 #include <sys/stat.h>
22 #include <utime.h>
23 #include <cstdio>
24 #include <iostream>
25 #include <memory>
26 #include <string>
27
28 #include "common/logger.h"
29 #include "common/tools.h"
30
31 #include "archive_file.h"
32 #include "archive_utils.h"
33 #include "filesystem_file.h"
34 #include "un_zip.h"
35
36 namespace extension {
37 namespace archive {
38
39 using namespace common;
40 using common::tools::GetErrorString;
41
42 FilePathStatus getPathStatus(const std::string& path) {
43   ScopeLogger();
44   if (path.empty()) {
45     return FPS_NOT_EXIST;
46   }
47
48   std::string npath = removeTrailingDirectorySlashFromPath(path);
49
50   struct stat sb;
51   if (stat(npath.c_str(), &sb) == -1) {
52     return FPS_NOT_EXIST;
53   }
54   if (sb.st_mode & S_IFDIR) {
55     return FPS_DIRECTORY;
56   } else {
57     return FPS_FILE;
58   }
59 }
60
61 void divideToPathAndName(const std::string& filepath, std::string& out_path,
62                          std::string& out_name) {
63   ScopeLogger();
64   size_t pos_last_dir = filepath.find_last_of("/\\");
65   if (pos_last_dir == std::string::npos) {
66     out_path = "";
67     out_name = filepath;
68   } else {
69     out_path = filepath.substr(0, pos_last_dir + 1);
70     out_name = filepath.substr(pos_last_dir + 1);
71   }
72 }
73
74 void createMissingDirectories(const std::string& path, bool check_first = true) {
75   ScopeLogger();
76   if (check_first) {
77     const FilePathStatus path_status = getPathStatus(path);
78     // LoggerD("[%s] status: %d", path.c_str(), path_status);
79     if (FPS_DIRECTORY == path_status) {
80       return;
81     }
82   }
83
84   const size_t extract_path_len = path.length();
85   for (size_t i = 0; i < extract_path_len; ++i) {
86     const char& cur = path[i];
87     if ((('\\' == cur || '/' == cur) && i > 0) ||  // handle left side from current /
88         (extract_path_len - 1 == i)) {             // handle last subdirectory path
89       const std::string left_part = path.substr(0, i + 1);
90       const FilePathStatus status = getPathStatus(left_part);
91       // LoggerD("left_part: [%s] status:%d", left_part.c_str(), status);
92
93       if (FPS_DIRECTORY != status) {
94         // permissions would be changed after extract process would be finished,
95         // for now using default 0775
96         if (mkdir(left_part.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1) {
97           int errno_copy = errno;
98           LoggerE("Couldn't create new directory: %s errno: %d (%s)", left_part.c_str(), errno,
99                   GetErrorString(errno_copy).c_str());
100         }
101       }
102     }
103   }
104 }
105
106 void changeFileAccessAndModifyDate(const std::string& filepath, tm_unz tmu_date) {
107   ScopeLogger();
108   struct utimbuf ut;
109   struct tm newdate;
110   newdate.tm_sec = tmu_date.tm_sec;
111   newdate.tm_min = tmu_date.tm_min;
112   newdate.tm_hour = tmu_date.tm_hour;
113   newdate.tm_mday = tmu_date.tm_mday;
114   newdate.tm_mon = tmu_date.tm_mon;
115
116   if (tmu_date.tm_year > 1900) {
117     newdate.tm_year = tmu_date.tm_year - 1900;
118   } else {
119     newdate.tm_year = tmu_date.tm_year;
120   }
121   newdate.tm_isdst = -1;
122
123   ut.actime = ut.modtime = mktime(&newdate);
124   if (utime(filepath.c_str(), &ut) == -1) {
125     LoggerE("Couldn't set time for: [%s] errno: %s", filepath.c_str(),
126             GetErrorString(errno).c_str());
127   }
128 }
129
130 PlatformResult UnZipExtractRequest::execute(UnZip& owner, const std::string& extract_path,
131                                             const std::string& base_strip_path,
132                                             BaseProgressCallback* callback) {
133   ScopeLogger();
134   UnZipExtractRequest req(owner, extract_path, base_strip_path, callback);
135   if (!req.m_callback) {
136     return LogAndCreateResult(ErrorCode::UNKNOWN_ERR, "Problem with callback functionality",
137                               ("Callback is null"));
138   }
139   return req.run();
140 }
141
142 UnZipExtractRequest::UnZipExtractRequest(UnZip& owner, const std::string& extract_path,
143                                          const std::string& base_strip_path,
144                                          BaseProgressCallback* callback)
145     :
146
147       m_owner(owner),
148       m_extract_path(extract_path),
149       m_base_strip_path(base_strip_path),
150       m_callback(callback),
151
152       m_output_file(NULL),
153       m_buffer(NULL),
154       m_delete_output_file(false),
155       m_close_unz_current_file(false),
156       m_file_info(),
157       m_new_dir_status(FPS_NOT_EXIST),
158
159       m_is_directory_entry(false) {
160   ScopeLogger();
161   m_filename_inzip[0] = '\0';
162 }
163
164 PlatformResult UnZipExtractRequest::run() {
165   ScopeLogger();
166
167   PlatformResult result = getCurrentFileInfo();
168   if (result.error_code() != ErrorCode::NO_ERROR) {
169     LoggerE("Error: %s", result.message().c_str());
170     return result;
171   }
172
173   if (m_is_directory_entry) {
174     result = handleDirectoryEntry();
175   } else {
176     result = handleFileEntry();
177   }
178
179   return result;
180 }
181
182 UnZipExtractRequest::~UnZipExtractRequest() {
183   ScopeLogger();
184
185   if (m_output_file) {
186     fclose(m_output_file);
187     m_output_file = NULL;
188   }
189
190   if (m_delete_output_file && !m_is_directory_entry) {
191     if (std::remove(m_output_filepath.c_str()) != 0) {
192       LoggerE(
193           "Couldn't remove partial file! "
194           "std::remove(\"%s\") failed with errno: %s",
195           m_output_filepath.c_str(), GetErrorString(errno).c_str());
196     }
197   }
198
199   delete[] m_buffer;
200   m_buffer = NULL;
201
202   if (m_close_unz_current_file) {
203     int err = unzCloseCurrentFile(m_owner.m_unzip);
204     if (UNZ_OK != err) {
205       LoggerW("%s", getArchiveLogMessage(err, "unzCloseCurrentFile()").c_str());
206     }
207   }
208 }
209
210 PlatformResult UnZipExtractRequest::getCurrentFileInfo() {
211   ScopeLogger();
212   int err = unzGetCurrentFileInfo(m_owner.m_unzip, &m_file_info, m_filename_inzip,
213                                   sizeof(m_filename_inzip), NULL, 0, NULL, 0);
214   if (err != UNZ_OK) {
215     return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
216                               getArchiveLogMessage(err, "unzGetCurrentFileInfo()"),
217                               ("ret: %d", err));
218   }
219
220   LoggerD("Input from ZIP: m_filename_inzip: [%s]", m_filename_inzip);
221   LoggerD("m_base_strip_path: [%s]", m_base_strip_path.c_str());
222
223   std::string file_path = m_filename_inzip;
224   if (!m_base_strip_path.empty()) {
225     if (file_path.find(m_base_strip_path) != 0) {
226       LoggerW("m_base_strip_path: [%s] is not begin of m_filename_inzip: [%s]!",
227               m_base_strip_path.c_str(), m_filename_inzip);
228     } else {
229       file_path = file_path.substr(m_base_strip_path.length());
230       LoggerD("Stripped file name: [%s]", file_path.c_str());
231     }
232   } else {
233     LoggerD("Not stripped file name: [%s]", file_path.c_str());
234   }
235
236   m_output_filepath = removeDuplicatedSlashesFromPath(m_extract_path + "/" + file_path);
237
238   LoggerD("Packed: [%s], uncompressed_size: %lu, will extract to: [%s]", m_filename_inzip,
239           m_file_info.uncompressed_size, m_output_filepath.c_str());
240
241   std::string path, name;
242   divideToPathAndName(file_path, path, name);
243   m_new_dir_path = removeDuplicatedSlashesFromPath(m_extract_path + "/" + path);
244   m_new_dir_status = getPathStatus(m_new_dir_path);
245   m_is_directory_entry = name.empty();
246
247   LoggerD("New output dir: [%s] status: %d m_is_directory_entry: %d", m_new_dir_path.c_str(),
248           m_new_dir_status, m_is_directory_entry);
249
250   if (FPS_DIRECTORY != m_new_dir_status) {
251     if (m_is_directory_entry) {
252       std::string base_directories;
253       const size_t len = m_new_dir_path.length();
254
255       for (int i = static_cast<int>(len) - 2; i >= 0; i--) {  // skip last \, /
256         const char& cur = m_new_dir_path[i];
257         if ('\\' == cur || '/' == cur) {
258           base_directories = m_new_dir_path.substr(0, static_cast<size_t>(i));
259           break;
260         }
261       }
262
263       LoggerD("Type: DIRECTORY checking base output directories: [%s]", base_directories.c_str());
264       createMissingDirectories(base_directories, false);
265     } else {
266       LoggerD("Type: FILE checking output dir: [%s]", m_new_dir_path.c_str());
267       createMissingDirectories(m_new_dir_path, false);
268     }
269   }
270   return PlatformResult(ErrorCode::NO_ERROR);
271 }
272
273 PlatformResult UnZipExtractRequest::handleDirectoryEntry() {
274   ScopeLogger();
275   if (FPS_DIRECTORY != m_new_dir_status) {
276     if (FPS_FILE == m_new_dir_status) {
277       if (m_callback->getOverwrite()) {  // Is a file & overwrite is set:
278         std::string fn = removeTrailingDirectorySlashFromPath(m_new_dir_path);
279         if (std::remove(fn.c_str()) != 0) {
280           SLoggerE("std::remove(\"%s\") failed with errno: %s", m_new_dir_path.c_str(),
281                    GetErrorString(errno).c_str());
282           return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
283                                     "Could not overwrite file in output directory");
284         }
285       } else {  // Is a file & overwrite is not set:
286         return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
287                                   "Failed to extract directory, "
288                                   "file with the same name exists in output directory");
289       }
290     }
291
292     // Try to create new directory in output directory
293     if (mkdir(m_new_dir_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1) {
294       SLoggerE("Couldn't create new directory: %s errno: %s", m_new_dir_path.c_str(),
295                GetErrorString(errno).c_str());
296       return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
297                                 "Could not create new directory in extract output directory");
298     }
299   }
300
301   LoggerD("Set dir: [%s] access and modify to: %4u-%2u-%2u %2u:%2u:%2u", m_new_dir_path.c_str(),
302           m_file_info.tmu_date.tm_year, m_file_info.tmu_date.tm_mon, m_file_info.tmu_date.tm_mday,
303           m_file_info.tmu_date.tm_hour, m_file_info.tmu_date.tm_min, m_file_info.tmu_date.tm_sec);
304
305   // change modify date and store permission for later update
306   storePermissions();
307   // Directory already exists we only need to update time
308   changeFileAccessAndModifyDate(m_new_dir_path, m_file_info.tmu_date);
309
310   LoggerD("Extracted directory entry: [%s]", m_new_dir_path.c_str());
311
312   return PlatformResult(ErrorCode::NO_ERROR);
313 }
314
315 PlatformResult UnZipExtractRequest::prepareOutputSubdirectory() {
316   ScopeLogger();
317   // This zip entry points to file - verify that parent directory in output dir exists
318   if (FPS_DIRECTORY != m_new_dir_status) {
319     if (FPS_FILE == m_new_dir_status) {
320       SLoggerE("Path: %s is pointing to file not directory!", m_new_dir_path.c_str());
321       return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
322                                 "Failed to extract file from zip archive, "
323                                 "output path is invalid");
324     }
325
326     // permissions would be changed after extract process would be finished,
327     // for now using default 0775
328     if (mkdir(m_new_dir_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1) {
329       LoggerW("couldn't create new directory: %s errno: %s", m_new_dir_path.c_str(),
330               GetErrorString(errno).c_str());
331     }
332   }
333
334   if (m_callback->isCanceled()) {
335     return LogAndCreateResult(ErrorCode::OPERATION_CANCELED_ERR, "Operation canceled");
336   }
337
338   const FilePathStatus output_fstatus = getPathStatus(m_output_filepath);
339   if (FPS_NOT_EXIST != output_fstatus) {
340     if (!m_callback->getOverwrite()) {
341       SLoggerE("%s exists at output path: [%s], overwrite is set to FALSE",
342                (FPS_DIRECTORY == output_fstatus ? "Directory" : "File"), m_output_filepath.c_str());
343       return LogAndCreateResult(ErrorCode::INVALID_MODIFICATION_ERR, "File already exists.");
344     } else {
345       if (FPS_DIRECTORY == output_fstatus) {
346         filesystem::PathPtr path = filesystem::Path::create(m_output_filepath);
347         filesystem::NodePtr node;
348         PlatformResult result = filesystem::Node::resolve(path, &node);
349         if (result.error_code() != ErrorCode::NO_ERROR) {
350           LoggerE("Error: %s", result.message().c_str());
351           return result;
352         }
353         result = node->remove(filesystem::OPT_RECURSIVE);
354         if (result.error_code() != ErrorCode::NO_ERROR) {
355           LoggerE("Error: %s", result.message().c_str());
356           return result;
357         }
358       }
359     }
360   }
361
362   return PlatformResult(ErrorCode::NO_ERROR);
363 }
364
365 PlatformResult UnZipExtractRequest::handleFileEntry() {
366   ScopeLogger();
367
368   PlatformResult result = prepareOutputSubdirectory();
369   if (result.error_code() != ErrorCode::NO_ERROR) {
370     LoggerE("File exists but overwrite is false");
371     return result;
372   }
373
374   int err =
375       unzOpenCurrentFilePassword(m_owner.m_unzip,
376                                  NULL);  // password is not supported yet therefore passing NULL
377   if (UNZ_OK != err) {
378     return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
379                               getArchiveLogMessage(err, "unzOpenCurrentFilePassword()"),
380                               ("ret: %d", err));
381   }
382
383   // We have successfully opened curent file, therefore we should close it later
384   m_close_unz_current_file = true;
385
386   const size_t buffer_size = m_owner.m_default_buffer_size;
387   m_buffer = new (std::nothrow) char[buffer_size];
388   if (!m_buffer) {
389     return LogAndCreateResult(
390         ErrorCode::UNKNOWN_ERR, "Memory allocation failed",
391         ("Couldn't allocate buffer with size: %s", bytesToReadableString(buffer_size).c_str()));
392   }
393
394   m_output_file = fopen(m_output_filepath.c_str(), "wb");
395   if (!m_output_file) {
396     SLoggerE("Couldn't open output file: %s", m_output_filepath.c_str());
397     return LogAndCreateResult(ErrorCode::UNKNOWN_ERR, "Could not create extracted file");
398   }
399   m_delete_output_file = true;
400
401   bool marked_as_finished = false;
402
403   LoggerD("Started extracting: [%s] uncompressed size: %lu - %s", m_filename_inzip,
404           m_file_info.uncompressed_size,
405           bytesToReadableString(m_file_info.uncompressed_size).c_str());
406
407   ExtractAllProgressCallback* extract_callback = NULL;
408   if (m_callback->getCallbackType() == EXTRACT_ALL_PROGRESS_CALLBACK ||
409       m_callback->getCallbackType() == EXTRACT_ENTRY_PROGRESS_CALLBACK) {
410     extract_callback = dynamic_cast<ExtractAllProgressCallback*>(m_callback);
411     if (NULL == extract_callback) {
412       SLoggerE("extract_callback is null");
413       return LogAndCreateResult(ErrorCode::UNKNOWN_ERR, "Could not create extracted file");
414     }
415     extract_callback->startedExtractingFile(m_file_info.uncompressed_size);
416   }
417
418   ArchiveFileEntryPtrMapPtr entries = m_callback->getArchiveFile()->getEntryMap();
419   auto it = entries->find(m_filename_inzip);
420   if (it == entries->end()) {
421     return LogAndCreateResult(ErrorCode::NOT_FOUND_ERR, "Entry not found");
422   }
423
424   while (true) {
425     if (m_callback->isCanceled()) {
426       return LogAndCreateResult(ErrorCode::OPERATION_CANCELED_ERR, "Operation canceled");
427     }
428
429     int read_size = unzReadCurrentFile(m_owner.m_unzip, m_buffer, buffer_size);
430     if (read_size < 0) {
431       SLoggerE("unzReadCurrentFile failed with error code: %d for file: %s", read_size,
432                m_filename_inzip);
433       return LogAndCreateResult(ErrorCode::UNKNOWN_ERR, "Failed to extract file from zip archive");
434     } else if (0 == read_size) {
435       if (extract_callback) {
436         if (!marked_as_finished) {
437           LoggerD("NOT marked_as_finished -> increment extracted files counter");
438           extract_callback->finishedExtractingFile();
439
440           // Call progress callback only if we expected empty file
441           if (m_file_info.uncompressed_size == 0) {
442             LoggerD("Calling progress callback(%f, %s)", extract_callback->getOverallProgress(),
443                     m_filename_inzip);
444             extract_callback->callProgressCallbackOnMainThread(
445                 extract_callback->getOverallProgress(), it->second);
446           }
447         }
448       }
449       // Finished writing file, we should not delete extracted file
450       m_delete_output_file = false;
451       break;
452     }
453
454     if (fwrite(m_buffer, read_size, 1, m_output_file) != 1) {
455       return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
456                                 "Could not write extract file into output file");
457     }
458
459     if (extract_callback) {
460       extract_callback->extractedPartOfFile(read_size);
461       LoggerD("File: [%s] extracted: %s - %f%%; total progress %f%%", m_filename_inzip,
462               bytesToReadableString(read_size).c_str(),
463               100.0f * extract_callback->getCurrentFileProgress(),
464               100.0f * extract_callback->getOverallProgress());
465
466       // It is better to update number of extracted entries so we will have
467       // overal progres: 1.0 if all files are extracted
468       //
469       if (extract_callback->getCurrentFileProgress() >= 1.0) {
470         LoggerD(
471             "Current file: [%s] progress: %f >= 1.0 -> "
472             "marked_as_finished = true and increment extracted files counter",
473             m_filename_inzip, extract_callback->getCurrentFileProgress());
474         marked_as_finished = true;
475         extract_callback->finishedExtractingFile();
476       }
477
478       LoggerD("Calling progress callback(%f, %s)", extract_callback->getOverallProgress(),
479               m_filename_inzip);
480       extract_callback->callProgressCallbackOnMainThread(extract_callback->getOverallProgress(),
481                                                          it->second);
482     }
483   }
484
485   if (m_output_file) {
486     fclose(m_output_file);
487     m_output_file = NULL;
488   }
489
490   // change modify date and store permission for later update
491   storePermissions();
492   changeFileAccessAndModifyDate(m_output_filepath, m_file_info.tmu_date);
493
494   return PlatformResult(ErrorCode::NO_ERROR);
495 }
496
497 void UnZipExtractRequest::storePermissions() {
498   // hold access information for later set
499   // The high 16 bits of the external file attributes seem to be used for OS-specific permissions
500   __mode_t mode = m_file_info.external_fa >> 16;
501   // check if proper permission mode is provided, if not use default 0775
502   if (mode == 0) {
503     mode = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH;
504   }
505   m_owner.path_access_map[m_output_filepath.c_str()] = mode;
506 }
507
508 }  // namespace archive
509 }  // namespace extension