[Archive] Fixed permission handling for windows-zipped files
[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   LoggerD("Input from ZIP: m_filename_inzip: [%s]", m_filename_inzip);
220   LoggerD("m_base_strip_path: [%s]", m_base_strip_path.c_str());
221
222   std::string file_path = m_filename_inzip;
223   if (!m_base_strip_path.empty()) {
224     if (file_path.find(m_base_strip_path) != 0) {
225       LoggerW("m_base_strip_path: [%s] is not begin of m_filename_inzip: [%s]!",
226               m_base_strip_path.c_str(), m_filename_inzip);
227     } else {
228       file_path = file_path.substr(m_base_strip_path.length());
229       LoggerD("Stripped file name: [%s]", file_path.c_str());
230     }
231   } else {
232     LoggerD("Not stripped file name: [%s]", file_path.c_str());
233   }
234
235   m_output_filepath = removeDuplicatedSlashesFromPath(m_extract_path + "/" + file_path);
236
237   LoggerD("Packed: [%s], uncompressed_size: %lu, will extract to: [%s]", m_filename_inzip,
238           m_file_info.uncompressed_size, m_output_filepath.c_str());
239
240   std::string path, name;
241   divideToPathAndName(file_path, path, name);
242   m_new_dir_path = removeDuplicatedSlashesFromPath(m_extract_path + "/" + path);
243   m_new_dir_status = getPathStatus(m_new_dir_path);
244   m_is_directory_entry = name.empty();
245
246   LoggerD("New output dir: [%s] status: %d m_is_directory_entry: %d", m_new_dir_path.c_str(),
247           m_new_dir_status, m_is_directory_entry);
248
249   if (FPS_DIRECTORY != m_new_dir_status) {
250     if (m_is_directory_entry) {
251       std::string base_directories;
252       const size_t len = m_new_dir_path.length();
253
254       for (int i = static_cast<int>(len) - 2; i >= 0; i--) {  // skip last \, /
255         const char& cur = m_new_dir_path[i];
256         if ('\\' == cur || '/' == cur) {
257           base_directories = m_new_dir_path.substr(0, static_cast<size_t>(i));
258           break;
259         }
260       }
261
262       LoggerD("Type: DIRECTORY checking base output directories: [%s]", base_directories.c_str());
263       createMissingDirectories(base_directories, false);
264     } else {
265       LoggerD("Type: FILE checking output dir: [%s]", m_new_dir_path.c_str());
266       createMissingDirectories(m_new_dir_path, false);
267     }
268   }
269   return PlatformResult(ErrorCode::NO_ERROR);
270 }
271
272 PlatformResult UnZipExtractRequest::handleDirectoryEntry() {
273   ScopeLogger();
274   if (FPS_DIRECTORY != m_new_dir_status) {
275     if (FPS_FILE == m_new_dir_status) {
276       if (m_callback->getOverwrite()) {  // Is a file & overwrite is set:
277         std::string fn = removeTrailingDirectorySlashFromPath(m_new_dir_path);
278         if (std::remove(fn.c_str()) != 0) {
279           SLoggerE("std::remove(\"%s\") failed with errno: %s", m_new_dir_path.c_str(),
280                    GetErrorString(errno).c_str());
281           return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
282                                     "Could not overwrite file in output directory");
283         }
284       } else {  // Is a file & overwrite is not set:
285         return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
286                                   "Failed to extract directory, "
287                                   "file with the same name exists in output directory");
288       }
289     }
290
291     // Try to create new directory in output directory
292     if (mkdir(m_new_dir_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1) {
293       SLoggerE("Couldn't create new directory: %s errno: %s", m_new_dir_path.c_str(),
294                GetErrorString(errno).c_str());
295       return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
296                                 "Could not create new directory in extract output directory");
297     }
298   }
299
300   LoggerD("Set dir: [%s] access and modify to: %4d-%2d-%2d %2d:%2d:%2d", m_new_dir_path.c_str(),
301           m_file_info.tmu_date.tm_year, m_file_info.tmu_date.tm_mon, m_file_info.tmu_date.tm_mday,
302           m_file_info.tmu_date.tm_hour, m_file_info.tmu_date.tm_min, m_file_info.tmu_date.tm_sec);
303
304   // change modify date and store permission for later update
305   storePermissions();
306   // Directory already exists we only need to update time
307   changeFileAccessAndModifyDate(m_new_dir_path, m_file_info.tmu_date);
308
309   LoggerD("Extracted directory entry: [%s]", m_new_dir_path.c_str());
310
311   return PlatformResult(ErrorCode::NO_ERROR);
312 }
313
314 PlatformResult UnZipExtractRequest::prepareOutputSubdirectory() {
315   ScopeLogger();
316   // This zip entry points to file - verify that parent directory in output dir exists
317   if (FPS_DIRECTORY != m_new_dir_status) {
318     if (FPS_FILE == m_new_dir_status) {
319       SLoggerE("Path: %s is pointing to file not directory!", m_new_dir_path.c_str());
320       return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
321                                 "Failed to extract file from zip archive, "
322                                 "output path is invalid");
323     }
324
325     // permissions would be changed after extract process would be finished,
326     // for now using default 0775
327     if (mkdir(m_new_dir_path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == -1) {
328       LoggerW("couldn't create new directory: %s errno: %s", m_new_dir_path.c_str(),
329               GetErrorString(errno).c_str());
330     }
331   }
332
333   if (m_callback->isCanceled()) {
334     return LogAndCreateResult(ErrorCode::OPERATION_CANCELED_ERR, "Operation canceled");
335   }
336
337   const FilePathStatus output_fstatus = getPathStatus(m_output_filepath);
338   if (FPS_NOT_EXIST != output_fstatus) {
339     if (!m_callback->getOverwrite()) {
340       SLoggerE("%s exists at output path: [%s], overwrite is set to FALSE",
341                (FPS_DIRECTORY == output_fstatus ? "Directory" : "File"), m_output_filepath.c_str());
342       return LogAndCreateResult(ErrorCode::INVALID_MODIFICATION_ERR, "File already exists.");
343     } else {
344       if (FPS_DIRECTORY == output_fstatus) {
345         filesystem::PathPtr path = filesystem::Path::create(m_output_filepath);
346         filesystem::NodePtr node;
347         PlatformResult result = filesystem::Node::resolve(path, &node);
348         if (result.error_code() != ErrorCode::NO_ERROR) {
349           LoggerE("Error: %s", result.message().c_str());
350           return result;
351         }
352         result = node->remove(filesystem::OPT_RECURSIVE);
353         if (result.error_code() != ErrorCode::NO_ERROR) {
354           LoggerE("Error: %s", result.message().c_str());
355           return result;
356         }
357       }
358     }
359   }
360
361   return PlatformResult(ErrorCode::NO_ERROR);
362 }
363
364 PlatformResult UnZipExtractRequest::handleFileEntry() {
365   ScopeLogger();
366
367   PlatformResult result = prepareOutputSubdirectory();
368   if (result.error_code() != ErrorCode::NO_ERROR) {
369     LoggerE("File exists but overwrite is false");
370     return result;
371   }
372
373   int err =
374       unzOpenCurrentFilePassword(m_owner.m_unzip,
375                                  NULL);  // password is not supported yet therefore passing NULL
376   if (UNZ_OK != err) {
377     return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
378                               getArchiveLogMessage(err, "unzOpenCurrentFilePassword()"),
379                               ("ret: %d", err));
380   }
381
382   // We have successfully opened curent file, therefore we should close it later
383   m_close_unz_current_file = true;
384
385   const size_t buffer_size = m_owner.m_default_buffer_size;
386   m_buffer = new (std::nothrow) char[buffer_size];
387   if (!m_buffer) {
388     return LogAndCreateResult(
389         ErrorCode::UNKNOWN_ERR, "Memory allocation failed",
390         ("Couldn't allocate buffer with size: %s", bytesToReadableString(buffer_size).c_str()));
391   }
392
393   m_output_file = fopen(m_output_filepath.c_str(), "wb");
394   if (!m_output_file) {
395     SLoggerE("Couldn't open output file: %s", m_output_filepath.c_str());
396     return LogAndCreateResult(ErrorCode::UNKNOWN_ERR, "Could not create extracted file");
397   }
398   m_delete_output_file = true;
399
400   bool marked_as_finished = false;
401
402   LoggerD("Started extracting: [%s] uncompressed size: %lu - %s", m_filename_inzip,
403           m_file_info.uncompressed_size,
404           bytesToReadableString(m_file_info.uncompressed_size).c_str());
405
406   ExtractAllProgressCallback* extract_callback = NULL;
407   if (m_callback->getCallbackType() == EXTRACT_ALL_PROGRESS_CALLBACK ||
408       m_callback->getCallbackType() == EXTRACT_ENTRY_PROGRESS_CALLBACK) {
409     extract_callback = dynamic_cast<ExtractAllProgressCallback*>(m_callback);
410     if (NULL == extract_callback) {
411       SLoggerE("extract_callback is null");
412       return LogAndCreateResult(ErrorCode::UNKNOWN_ERR, "Could not create extracted file");
413     }
414     extract_callback->startedExtractingFile(m_file_info.uncompressed_size);
415   }
416
417   ArchiveFileEntryPtrMapPtr entries = m_callback->getArchiveFile()->getEntryMap();
418   auto it = entries->find(m_filename_inzip);
419   if (it == entries->end()) {
420     return LogAndCreateResult(ErrorCode::NOT_FOUND_ERR, "Entry not found");
421   }
422
423   while (true) {
424     if (m_callback->isCanceled()) {
425       return LogAndCreateResult(ErrorCode::OPERATION_CANCELED_ERR, "Operation canceled");
426     }
427
428     int read_size = unzReadCurrentFile(m_owner.m_unzip, m_buffer, buffer_size);
429     if (read_size < 0) {
430       SLoggerE("unzReadCurrentFile failed with error code: %d for file: %s", read_size,
431                m_filename_inzip);
432       return LogAndCreateResult(ErrorCode::UNKNOWN_ERR, "Failed to extract file from zip archive");
433     } else if (0 == read_size) {
434       if (extract_callback) {
435         if (!marked_as_finished) {
436           LoggerD("NOT marked_as_finished -> increment extracted files counter");
437           extract_callback->finishedExtractingFile();
438
439           // Call progress callback only if we expected empty file
440           if (m_file_info.uncompressed_size == 0) {
441             LoggerD("Calling progress callback(%f, %s)", extract_callback->getOverallProgress(),
442                     m_filename_inzip);
443             extract_callback->callProgressCallbackOnMainThread(
444                 extract_callback->getOverallProgress(), it->second);
445           }
446         }
447       }
448       // Finished writing file, we should not delete extracted file
449       m_delete_output_file = false;
450       break;
451     }
452
453     if (fwrite(m_buffer, read_size, 1, m_output_file) != 1) {
454       return LogAndCreateResult(ErrorCode::UNKNOWN_ERR,
455                                 "Could not write extract file into output file");
456     }
457
458     if (extract_callback) {
459       extract_callback->extractedPartOfFile(read_size);
460       LoggerD("File: [%s] extracted: %s - %f%%; total progress %f%%", m_filename_inzip,
461               bytesToReadableString(read_size).c_str(),
462               100.0f * extract_callback->getCurrentFileProgress(),
463               100.0f * extract_callback->getOverallProgress());
464
465       // It is better to update number of extracted entries so we will have
466       // overal progres: 1.0 if all files are extracted
467       //
468       if (extract_callback->getCurrentFileProgress() >= 1.0) {
469         LoggerD(
470             "Current file: [%s] progress: %f >= 1.0 -> "
471             "marked_as_finished = true and increment extracted files counter",
472             m_filename_inzip, extract_callback->getCurrentFileProgress());
473         marked_as_finished = true;
474         extract_callback->finishedExtractingFile();
475       }
476
477       LoggerD("Calling progress callback(%f, %s)", extract_callback->getOverallProgress(),
478               m_filename_inzip);
479       extract_callback->callProgressCallbackOnMainThread(extract_callback->getOverallProgress(),
480                                                          it->second);
481     }
482   }
483
484   if (m_output_file) {
485     fclose(m_output_file);
486     m_output_file = NULL;
487   }
488
489   // change modify date and store permission for later update
490   storePermissions();
491   changeFileAccessAndModifyDate(m_output_filepath, m_file_info.tmu_date);
492
493   return PlatformResult(ErrorCode::NO_ERROR);
494 }
495
496 void UnZipExtractRequest::storePermissions() {
497   // hold access information for later set
498   // The high 16 bits of the external file attributes seem to be used for
499   // OS-specific permissions https://unix.stackexchange.com/a/14727 this answer describes that
500   // structure is:
501   // * 4 bits - file type
502   // * 3 bits - setuid, setgid, sticky
503   // * 9 bits - permissions
504   __mode_t mode = m_file_info.external_fa >> 16;
505   unsigned int linux_file_type = mode >> 12;
506   __mode_t default_mode = (S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
507   // check if proper permission mode is provided, if not use default 0775
508   // validation is made for checking if both permissions and file type information is not zero
509   if (mode == 0 || linux_file_type == 0) {
510     mode = default_mode;
511     LoggerW(
512         "File %s has incomplete information for unix filesystem, using "
513         "default file permissions:  %o",
514         m_filename_inzip, mode);
515   }
516   LoggerD("Storing permissions for %s: %o", m_filename_inzip, mode);
517   m_owner.path_access_map[m_output_filepath.c_str()] = mode;
518 }
519
520 }  // namespace archive
521 }  // namespace extension