tizen 2.3.1 release
[framework/web/wearable/wrt-plugins-tizen.git] / src / Archive / UnZipExtractRequest.cpp
1 //
2 // Tizen Web Device API
3 // Copyright (c) 2014 Samsung Electronics Co., Ltd.
4 //
5 // Licensed under the Apache License, Version 2.0 (the License);
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 //
17
18 #include "UnZipExtractRequest.h"
19
20 #include <cstdio>
21 #include <errno.h>
22 #include <iostream>
23 #include <memory>
24 #include <string>
25 #include <string.h>
26 #include <sys/stat.h>
27 #include <utime.h>
28
29 #include <Logger.h>
30 #include <Node.h>
31 #include <Path.h>
32 #include <PlatformException.h>
33
34 #include "ArchiveFile.h"
35 #include "ArchiveUtils.h"
36 #include "UnZip.h"
37
38 namespace DeviceAPI {
39
40 using namespace DeviceAPI::Common;
41
42 namespace Archive {
43
44 FilePathStatus getPathStatus(const std::string& path)
45 {
46     if(path.empty()) {
47         return FPS_NOT_EXIST;
48     }
49
50     std::string npath = removeTrailingDirectorySlashFromPath(path);
51
52     struct stat sb;
53     if (stat(npath.c_str(), &sb) == -1) {
54         return FPS_NOT_EXIST;
55     }
56     if(sb.st_mode & S_IFDIR) {
57         return FPS_DIRECTORY;
58     } else {
59         return FPS_FILE;
60     }
61 }
62
63 void divideToPathAndName(const std::string& filepath, std::string& out_path,
64         std::string& out_name)
65 {
66
67     size_t pos_last_dir = filepath.find_last_of("/\\");
68     if(pos_last_dir == std::string::npos) {
69         out_path = "";
70         out_name = filepath;
71     } else {
72         out_path = filepath.substr(0, pos_last_dir+1);
73         out_name = filepath.substr(pos_last_dir+1);
74     }
75 }
76
77 void createMissingDirectories(const std::string& path, bool check_first = true)
78 {
79     if(check_first) {
80         const FilePathStatus path_status = getPathStatus(path);
81         //LOGD("[%s] status: %d", path.c_str(), path_status);
82         if(FPS_DIRECTORY == path_status) {
83             return;
84         }
85     }
86
87     const size_t extract_path_len = path.length();
88     for(size_t i = 0; i < extract_path_len; ++i) {
89         const char& cur = path[i];
90         if( (('\\' == cur || '/' == cur) && i > 0) ||   //handle left side from current /
91                 (extract_path_len-1 == i) ) {           //handle last subdirectory path
92
93             const std::string left_part = path.substr(0,i+1);
94             const FilePathStatus status = getPathStatus(left_part);
95             //LOGD("left_part: [%s] status:%d", left_part.c_str(), status);
96
97             if(FPS_DIRECTORY != status) {
98                 //TODO investigate 0775 (mode) - Filesystem assumed that file should have parent mode
99                 if(mkdir(left_part.c_str(), 0775) == -1) {
100                     LOGE("Couldn't create new directory: %s errno:%s",
101                             left_part.c_str(), strerror(errno));
102                //TODO check why mkdir return -1 but directory is successfully created
103                //     throw UnknownException(
104                //             "Could not create new directory");
105                 }
106             }
107         }
108     }
109 }
110
111 void changeFileAccessAndModifyDate(const std::string& filepath, tm_unz tmu_date)
112 {
113   struct utimbuf ut;
114   struct tm newdate;
115   newdate.tm_sec = tmu_date.tm_sec;
116   newdate.tm_min = tmu_date.tm_min;
117   newdate.tm_hour = tmu_date.tm_hour;
118   newdate.tm_mday = tmu_date.tm_mday;
119   newdate.tm_mon = tmu_date.tm_mon;
120
121   if (tmu_date.tm_year > 1900) {
122       newdate.tm_year = tmu_date.tm_year - 1900;
123   } else {
124       newdate.tm_year = tmu_date.tm_year ;
125   }
126   newdate.tm_isdst = -1;
127
128   ut.actime = ut.modtime = mktime(&newdate);
129   if(utime(filepath.c_str(), &ut) == -1) {
130       LOGE("Couldn't set time for: [%s] errno:%s", filepath.c_str(), strerror(errno));
131   }
132 }
133
134 void UnZipExtractRequest::execute(UnZip& owner, const std::string& extract_path,
135         const std::string& base_strip_path,
136         BaseProgressCallback* callback)
137 {
138     UnZipExtractRequest req(owner, extract_path, base_strip_path, callback);
139     req.run();
140 }
141
142 UnZipExtractRequest::UnZipExtractRequest(UnZip& owner,
143         const std::string& extract_path,
144         const std::string& base_strip_path,
145         BaseProgressCallback* callback) :
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_new_dir_status(FPS_NOT_EXIST),
157
158         m_is_directory_entry(false)
159 {
160     if(!m_callback){
161         LOGE("Callback is null");
162         throw UnknownException("Problem with callback functionality");
163     }
164 }
165
166 void UnZipExtractRequest::run()
167 {
168     LOGD("Entered");
169
170     getCurrentFileInfo();
171
172     if(m_is_directory_entry) {
173         handleDirectoryEntry();
174     } else {
175         handleFileEntry();
176     }
177 }
178
179 UnZipExtractRequest::~UnZipExtractRequest()
180 {
181     if(m_output_file) {
182         fclose(m_output_file);
183         m_output_file = NULL;
184     }
185
186     if(m_delete_output_file && !m_is_directory_entry) {
187         if(std::remove(m_output_filepath.c_str()) != 0) {
188             LOGE("Couldn't remove partial file! "
189                     "std::remove(\"%s\") failed with errno:%s",
190                     m_output_filepath.c_str(), strerror(errno));
191         }
192     }
193
194     delete [] m_buffer;
195     m_buffer = NULL;
196
197     if(m_close_unz_current_file) {
198         int err = unzCloseCurrentFile (m_owner.m_unzip);
199         if(UNZ_OK != err) {
200             LOGW("%s",getArchiveLogMessage(err, "unzCloseCurrentFile()").c_str());
201         }
202     }
203 }
204
205 void UnZipExtractRequest::getCurrentFileInfo()
206 {
207     LOGD("Entered");
208     int err = unzGetCurrentFileInfo(m_owner.m_unzip, &m_file_info,
209             m_filename_inzip, sizeof(m_filename_inzip), NULL, 0, NULL, 0);
210     if (err != UNZ_OK) {
211         LOGE("ret: %d", err);
212         throwArchiveException(err, "unzGetCurrentFileInfo()");
213     }
214
215     LOGD("Input from ZIP: m_filename_inzip: [%s]", m_filename_inzip);
216     LOGD("m_base_strip_path: [%s]", m_base_strip_path.c_str());
217
218     std::string file_path = m_filename_inzip;
219     if(!m_base_strip_path.empty()) {
220         if(file_path.find(m_base_strip_path) != 0) {
221             LOGW("m_base_strip_path: [%s] is not begin of m_filename_inzip: [%s]!",
222                     m_base_strip_path.c_str(),
223                     m_filename_inzip);
224         }
225         else {
226             file_path = file_path.substr(m_base_strip_path.length());
227             LOGD("Stripped file name: [%s]", file_path.c_str());
228         }
229     }
230     else {
231         LOGD("Not stripped file name: [%s]", file_path.c_str());
232     }
233
234     m_output_filepath = removeDuplicatedSlashesFromPath(m_extract_path + "/" + file_path);
235
236     LOGD("Packed: [%s], uncompressed_size: %d, will extract to: [%s]",
237             m_filename_inzip, m_file_info.uncompressed_size,
238             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     LOGD("New output dir: [%s] status: %d m_is_directory_entry: %d",
247             m_new_dir_path.c_str(), m_new_dir_status, m_is_directory_entry);
248
249     if(FPS_DIRECTORY != m_new_dir_status) {
250
251         if(m_is_directory_entry) {
252
253             std::string base_directories;
254             const size_t len = m_new_dir_path.length();
255
256             for(int i = static_cast<int>(len) - 2; i >= 0; i--) {  //skip last \, /
257                 const char& cur = m_new_dir_path[i];
258                 if('\\' == cur || '/' == cur) {
259                     base_directories = m_new_dir_path.substr(0, static_cast<size_t>(i));
260                     break;
261                 }
262             }
263
264             LOGD("Type: DIRECTORY checking base output directories: [%s]",
265                     base_directories.c_str());
266             createMissingDirectories(base_directories, false);
267         } else {
268             LOGD("Type: FILE checking output dir: [%s]", m_new_dir_path.c_str());
269             createMissingDirectories(m_new_dir_path, false);
270         }
271     }
272 }
273
274 void UnZipExtractRequest::handleDirectoryEntry()
275 {
276     LOGD("Entered");
277     if(FPS_DIRECTORY != m_new_dir_status) {
278
279         if(FPS_FILE == m_new_dir_status) {
280             if(m_callback->getOverwrite()) {    //Is a file & overwrite is set:
281                 std::string fn = removeTrailingDirectorySlashFromPath(m_new_dir_path);
282                 if(std::remove(fn.c_str()) != 0) {
283                     LOGE("std::remove(\"%s\") failed with errno:%s",
284                             m_new_dir_path.c_str(), strerror(errno));
285                     throw UnknownException(
286                             "Could not overwrite file in output directory");
287                 }
288             } else {                            //Is a file & overwrite is not set:
289                 LOGE("Failed to extract directory, "
290                         "file with the same name exists in output directory");
291                 throw UnknownException("Failed to extract directory, "
292                         "file with the same name exists in output directory");
293             }
294         }
295
296         //Try to create new directory in output directory
297         if(mkdir(m_new_dir_path.c_str(), 0775) == -1) {
298             LOGE("Couldn't create new directory: %s errno:%s",
299                     m_new_dir_path.c_str(), strerror(errno));
300             throw UnknownException(
301                     "Could not create new directory in extract output directory");
302         }
303     }
304
305     LOGD("Set dir: [%s] access and modify to: %4d-%2d-%2d %2d:%2d:%2d", m_new_dir_path.c_str(),
306             m_file_info.tmu_date.tm_year,
307             m_file_info.tmu_date.tm_mon,
308             m_file_info.tmu_date.tm_mday,
309             m_file_info.tmu_date.tm_hour,
310             m_file_info.tmu_date.tm_min,
311             m_file_info.tmu_date.tm_sec);
312
313     // Directory already exists we only need to update time
314     changeFileAccessAndModifyDate(m_new_dir_path, m_file_info.tmu_date);
315
316     LOGD("Extracted directory entry: [%s]", m_new_dir_path.c_str());
317 }
318
319 bool UnZipExtractRequest::prepareOutputSubdirectory()
320 {
321     LOGD("Entered");
322     //This zip entry points to file - verify that parent directory in output dir exists
323     if(FPS_DIRECTORY != m_new_dir_status) {
324         if(FPS_FILE == m_new_dir_status) {
325             LOGE("Path: %s is pointing to file not directory!",
326                     m_new_dir_path.c_str());
327             throw UnknownException("Failed to extract file from zip archive, "
328                     "output path is invalid");
329         }
330
331         //Try to create new directory in output directory
332         //TODO investigate 0775 (mode) - Filesystem assumed that file should have parent mode
333         if(mkdir(m_new_dir_path.c_str(), 0775) == -1) {
334             LOGW("couldn't create new directory: %s errno:%s",
335                     m_new_dir_path.c_str(), strerror(errno));
336             //TODO check why mkdir return -1 but directory is successfully created
337             //     throw UnknownException(
338             //     "Could not create new directory in extract output directory");
339         }
340     }
341
342     if(m_callback->isCanceled()) {
343         LOGD("Operation cancelled");
344         throw OperationCanceledException();
345     }
346
347     const FilePathStatus output_fstatus = getPathStatus(m_output_filepath);
348     if(FPS_NOT_EXIST != output_fstatus) {
349         if(!m_callback->getOverwrite()) {
350             LOGW("%s exists at output path: [%s], overwrite is set to FALSE",
351                     (FPS_DIRECTORY == output_fstatus ? "Directory" : "File"),
352                     m_output_filepath.c_str());
353
354             //Just skip this file - TODO: this should be documented in WIDL
355             return false;
356         } else {
357             if(FPS_DIRECTORY == output_fstatus) {
358                 try {
359                     Filesystem::PathPtr path = Filesystem::Path::create(m_output_filepath);
360                     Filesystem::NodePtr node = Filesystem::Node::resolve(path);
361                     node->remove(Filesystem::OPT_RECURSIVE);
362                     LOGD("Removed directory: [%s]", m_output_filepath.c_str());
363                 } catch(BasePlatformException& ex) {
364                     LOGE("Remove dir: [%s] failed with exception: %s:%s",
365                             m_output_filepath.c_str(),
366                             ex.getName().c_str(), ex.getMessage().c_str());
367                     throw UnknownException("Could not overwrite existing directory");
368                 } catch (...) {
369                     LOGE("Remove dir: [%s] failed", m_output_filepath.c_str());
370                     throw UnknownException("Could not overwrite existing directory");
371                 }
372             } //else {
373                 //We will overwrite it with fopen
374             //}
375         }
376     }
377
378     return true;
379 }
380
381 void UnZipExtractRequest::handleFileEntry()
382 {
383     LOGD("Entered");
384     if(!prepareOutputSubdirectory()) {
385         LOGE("File exists but overwrite is false");
386         throw InvalidModificationException("file already exists.");
387     }
388
389     int err = unzOpenCurrentFilePassword(m_owner.m_unzip,
390         NULL); //password is not supported yet therefore passing NULL
391     if (UNZ_OK != err) {
392         LOGE("ret: %d", err);
393         throwArchiveException(err, "unzOpenCurrentFilePassword()");
394     }
395
396     //We have successfully opened curent file, therefore we should close it later
397     m_close_unz_current_file = true;
398
399     const size_t buffer_size = m_owner.m_default_buffer_size;
400     m_buffer = new(std::nothrow) char[buffer_size];
401     if(!m_buffer) {
402         LOGE("Couldn't allocate buffer with size: %s",
403                 bytesToReadableString(buffer_size).c_str());
404         throw UnknownException("Memory allocation failed");
405     }
406
407     m_output_file = fopen(m_output_filepath.c_str(), "wb");
408     if(!m_output_file) {
409         LOGE("Couldn't open output file: %s", m_output_filepath.c_str());
410         throw UnknownException("Could not create extracted file");
411     }
412     m_delete_output_file = true;
413
414     int read_size = 0;
415     bool marked_as_finished = false;
416
417     LOGD("Started extracting: [%s] uncompressed size: %d - %s", m_filename_inzip,
418             m_file_info.uncompressed_size,
419             bytesToReadableString(m_file_info.uncompressed_size).c_str());
420
421     ExtractAllProgressCallback* extract_callback = NULL;
422     if(m_callback->getCallbackType() == EXTRACT_ALL_PROGRESS_CALLBACK ||
423             m_callback->getCallbackType() == EXTRACT_ENTRY_PROGRESS_CALLBACK) {
424         extract_callback = static_cast<ExtractAllProgressCallback*>(m_callback);
425         extract_callback->startedExtractingFile(m_file_info.uncompressed_size);
426     }
427
428     ArchiveFileEntryPtrMapPtr entries = m_callback->getArchiveFile()->getEntryMap();
429     auto it = entries->find(m_filename_inzip);
430     if (it == entries->end()) {
431         LOGE("Entry not found");
432         throw Common::NotFoundException("Entry not found");
433     }
434
435     while(true) {
436         if(m_callback->isCanceled()) {
437             LOGD("Operation cancelled");
438             throw OperationCanceledException();
439         }
440
441         read_size = unzReadCurrentFile(m_owner.m_unzip, m_buffer, buffer_size);
442         if (read_size < 0) {
443             LOGE("unzReadCurrentFile failed with error code:%d for file:%s", read_size,
444                     m_filename_inzip);
445             throw UnknownException("Failed to extract file from zip archive");
446         }
447         else if(0 == read_size) {
448
449             if(extract_callback) {
450                 if(!marked_as_finished) {
451                     LOGD("NOT marked_as_finished -> increment extracted files counter");
452                     extract_callback->finishedExtractingFile();
453
454                     //Call progress callback only if we expected empty file
455                     if(m_file_info.uncompressed_size == 0) {
456                         LOGD("Calling progress callback(%f, %s)",
457                                 extract_callback->getOverallProgress(), m_filename_inzip);
458                         extract_callback->callProgressCallbackOnMainThread(
459                                 extract_callback->getOverallProgress(), it->second);
460                     }
461                 }
462             }
463             //Finished writing file, we should not delete extracted file
464             m_delete_output_file = false;
465             break;
466         }
467
468         if (fwrite(m_buffer, read_size, 1, m_output_file) != 1) {
469             LOGE("Couldn't write extracted data to output file:%s",
470                     m_output_filepath.c_str());
471             throw UnknownException("Could not write extract file into output file");
472         }
473
474         if(extract_callback) {
475             extract_callback->extractedPartOfFile(read_size);
476             LOGD("File: [%s] extracted: %s - %f%%; total progress %f%%", m_filename_inzip,
477                     bytesToReadableString(read_size).c_str(),
478                     100.0f * extract_callback->getCurrentFileProgress(),
479                     100.0f * extract_callback->getOverallProgress());
480
481             // It is better to update number of extracted entries so we will have
482             // overal progres: 1.0 if all files are extracted
483             //
484             if(extract_callback->getCurrentFileProgress() >= 1.0) {
485                 LOGD("Current file: [%s] progress: %f >= 1.0 -> "
486                         "marked_as_finished = true and increment extracted files counter",
487                         m_filename_inzip, extract_callback->getCurrentFileProgress());
488                 marked_as_finished = true;
489                 extract_callback->finishedExtractingFile();
490             }
491
492             LOGD("Calling progress callback(%f, %s)",
493                     extract_callback->getOverallProgress(), m_filename_inzip);
494             extract_callback->callProgressCallbackOnMainThread(
495                     extract_callback->getOverallProgress(), it->second);
496         }
497     }
498
499     if(m_output_file) {
500         fclose(m_output_file);
501         m_output_file = NULL;
502     }
503
504     changeFileAccessAndModifyDate(m_output_filepath, m_file_info.tmu_date);
505 }
506
507 } //namespace Archive
508 } //namespace DeviceAPI