1 /*
2  * Copyright (c) 2022 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "zip_reader.h"
17 
18 #include <stdio.h>
19 #include <time.h>
20 #include <unistd.h>
21 #include <utility>
22 
23 #include "app_log_wrapper.h"
24 #include "checked_cast.h"
25 #include "contrib/minizip/unzip.h"
26 #include "string_ex.h"
27 #include "zip_internal.h"
28 #include "zip_utils.h"
29 
30 using namespace OHOS::AppExecFwk;
31 
32 namespace OHOS {
33 namespace AppExecFwk {
34 namespace LIBZIP {
35 
36 // The implementation assumes that file names in zip files
37 // are encoded in UTF-8. This is true for zip files created by Zip()
38 // function in zip.h, but not true for user-supplied random zip files.
EntryInfo(const std::string & fileNameInZip,const unz_file_info & rawFileInfo)39 ZipReader::EntryInfo::EntryInfo(const std::string &fileNameInZip, const unz_file_info &rawFileInfo)
40     : filePath_(FilePath::FromUTF8Unsafe(fileNameInZip)), isDirectory_(false), isUnsafe_(false), isEncrypted_(false)
41 {
42     originalSize_ = rawFileInfo.uncompressed_size;
43 
44     // Directory entries in zip files end with "/".
45     isDirectory_ = EndsWith(fileNameInZip, "/");
46 
47     // Check the file name here for directory traversal issues.
48     isUnsafe_ = filePath_.ReferencesParent();
49 
50     // We also consider that the file name is unsafe, if it's absolute.
51     // On Windows, IsAbsolute() returns false for paths starting with "/".
52     if (filePath_.IsAbsolute() || StartsWith(fileNameInZip, "/")) {
53         isUnsafe_ = false;
54     }
55 
56     // Whether the file is encrypted is bit 0 of the flag.
57     isEncrypted_ = rawFileInfo.flag & 1;
58 
59     // Construct the last modified time. The timezone info is not present in
60     // zip files, so we construct the time as local time.
61     if (GetCurrentSystemTime() != nullptr) {
62         lastModified_ = *GetCurrentSystemTime();
63     }
64 }
65 
ZipReader()66 ZipReader::ZipReader()
67 {
68     Reset();
69 }
70 
~ZipReader()71 ZipReader::~ZipReader()
72 {
73     Close();
74 }
75 
Open(FilePath & zipFilePath)76 bool ZipReader::Open(FilePath &zipFilePath)
77 {
78     if (zipFile_ != nullptr) {
79         return false;
80     }
81 
82     // Use of "Unsafe" function does not look good, but there is no way to do
83     // this safely on Linux. See file_util.h for details.
84     std::string zipfile = zipFilePath.Value();
85     zipFile_ = OpenForUnzipping(zipfile);
86     if (zipFile_ == nullptr) {
87         return false;
88     }
89 
90     return OpenInternal();
91 }
92 
OpenFromPlatformFile(PlatformFile zipFd)93 bool ZipReader::OpenFromPlatformFile(PlatformFile zipFd)
94 {
95     if (zipFile_ != nullptr) {
96         return false;
97     }
98     zipFile_ = OpenFdForUnzipping(zipFd);
99     if (!zipFile_) {
100         return false;
101     }
102 
103     return OpenInternal();
104 }
105 
OpenFromString(const std::string & data)106 bool ZipReader::OpenFromString(const std::string &data)
107 {
108     zipFile_ = PrepareMemoryForUnzipping(data);
109     if (!zipFile_) {
110         return false;
111     }
112 
113     return OpenInternal();
114 }
115 
Close()116 void ZipReader::Close()
117 {
118     if (zipFile_) {
119         unzClose(zipFile_);
120     }
121     Reset();
122 }
123 
HasMore()124 bool ZipReader::HasMore()
125 {
126     return !reachedEnd_;
127 }
128 
AdvanceToNextEntry()129 bool ZipReader::AdvanceToNextEntry()
130 {
131     if (zipFile_ == nullptr) {
132         return false;
133     }
134     // Should not go further if we already reached the end.
135     if (reachedEnd_) {
136         return false;
137     }
138     unz_file_pos position = {};
139     if (unzGetFilePos(zipFile_, &position) != UNZ_OK) {
140         return false;
141     }
142 
143     const int currentEntryIndex = position.num_of_file;
144     // If we are currently at the last entry, then the next position is the
145     // end of the zip file, so mark that we reached the end.
146     if (currentEntryIndex + 1 == numEntries_) {
147         reachedEnd_ = true;
148     } else {
149         if (unzGoToNextFile(zipFile_) != UNZ_OK) {
150             return false;
151         }
152     }
153     currentEntryInfo_.reset();
154     return true;
155 }
156 
OpenCurrentEntryInZip()157 bool ZipReader::OpenCurrentEntryInZip()
158 {
159     if (zipFile_ == nullptr) {
160         return false;
161     }
162 
163     unz_file_info raw_file_info = {};
164     char raw_file_name_in_zip[kZipMaxPath] = {};
165     const int result = unzGetCurrentFileInfo(zipFile_,
166         &raw_file_info,
167         raw_file_name_in_zip,
168         sizeof(raw_file_name_in_zip) - 1,
169         NULL,  // extraField.
170         0,     // extraFieldBufferSize.
171         NULL,  // szComment.
172         0);    // commentBufferSize.
173     if (result != UNZ_OK) {
174         return false;
175     }
176     if (raw_file_name_in_zip[0] == '\0') {
177         return false;
178     }
179     EntryInfo *entryInfo = new (std::nothrow) EntryInfo(std::string(raw_file_name_in_zip), raw_file_info);
180     if (entryInfo == nullptr) {
181         return false;
182     }
183     currentEntryInfo_.reset(entryInfo);
184     return true;
185 }
186 
ExtractCurrentEntry(WriterDelegate * delegate,uint64_t numBytesToExtract) const187 bool ZipReader::ExtractCurrentEntry(WriterDelegate *delegate, uint64_t numBytesToExtract) const
188 {
189     if ((zipFile_ == nullptr) || (delegate == nullptr)) {
190         return false;
191     }
192     const int openResult = unzOpenCurrentFile(zipFile_);
193     if (openResult != UNZ_OK) {
194         return false;
195     }
196     if (!delegate->PrepareOutput()) {
197         return false;
198     }
199     auto buf = std::make_unique<char[]>(kZipBufSize);
200     uint64_t remainingCapacity = numBytesToExtract;
201     bool entirefileextracted = false;
202 
203     while (remainingCapacity > 0) {
204         const int numBytesRead = unzReadCurrentFile(zipFile_, buf.get(), kZipBufSize);
205         if (numBytesRead == 0) {
206             entirefileextracted = true;
207             break;
208         } else if (numBytesRead < 0) {
209             // If numBytesRead < 0, then it's a specific UNZ_* error code.
210             break;
211         } else {
212             uint64_t numBytesToWrite = std::min<uint64_t>(remainingCapacity, checked_cast<uint64_t>(numBytesRead));
213             if (!delegate->WriteBytes(buf.get(), numBytesToWrite)) {
214                 break;
215             }
216             if (remainingCapacity == checked_cast<uint64_t>(numBytesRead)) {
217                 // Ensures function returns true if the entire file has been read.
218                 entirefileextracted = (unzReadCurrentFile(zipFile_, buf.get(), 1) == 0);
219             }
220             if (remainingCapacity >= numBytesToWrite) {
221                 remainingCapacity -= numBytesToWrite;
222             }
223         }
224     }
225 
226     unzCloseCurrentFile(zipFile_);
227     // closeFile
228     delegate->SetTimeModified(GetCurrentSystemTime());
229 
230     return entirefileextracted;
231 }
232 
OpenInternal()233 bool ZipReader::OpenInternal()
234 {
235     if (zipFile_ == nullptr) {
236         return false;
237     }
238 
239     unz_global_info zipInfo = {};  // Zero-clear.
240     if (unzGetGlobalInfo(zipFile_, &zipInfo) != UNZ_OK) {
241         return false;
242     }
243     numEntries_ = zipInfo.number_entry;
244     if (numEntries_ < 0) {
245         return false;
246     }
247 
248     // We are already at the end if the zip file is empty.
249     reachedEnd_ = (numEntries_ == 0);
250     return true;
251 }
252 
Reset()253 void ZipReader::Reset()
254 {
255     zipFile_ = nullptr;
256     numEntries_ = 0;
257     reachedEnd_ = false;
258     currentEntryInfo_.reset();
259 }
260 
261 // FilePathWriterDelegate
FilePathWriterDelegate(const FilePath & outputFilePath)262 FilePathWriterDelegate::FilePathWriterDelegate(const FilePath &outputFilePath) : outputFilePath_(outputFilePath)
263 {}
264 
~FilePathWriterDelegate()265 FilePathWriterDelegate::~FilePathWriterDelegate()
266 {}
267 
PrepareOutput()268 bool FilePathWriterDelegate::PrepareOutput()
269 {
270     if (!FilePathCheckValid(outputFilePath_.Value())) {
271         APP_LOGE("outputFilePath_ invalid");
272         return false;
273     }
274     // We can't rely on parent directory entries being specified in the
275     // zip, so we make sure they are created.
276     if (!FilePath::CreateDirectory(outputFilePath_.DirName())) {
277         return false;
278     }
279 
280     file_ = fopen(outputFilePath_.Value().c_str(), "wb");
281     if (file_ == nullptr) {
282         APP_LOGE("fopen %{private}s err: %{public}d %{public}s",
283             outputFilePath_.Value().c_str(), errno, strerror(errno));
284         return false;
285     }
286     return FilePath::PathIsValid(outputFilePath_);
287 }
288 
WriteBytes(const char * data,int numBytes)289 bool FilePathWriterDelegate::WriteBytes(const char *data, int numBytes)
290 {
291     if ((file_ == nullptr) || (numBytes <= 0) || (data == nullptr)) {
292         return false;
293     }
294     int writebytes = fwrite(data, 1, numBytes, file_);
295     return numBytes == writebytes;
296 }
297 
SetTimeModified(const struct tm * time)298 void FilePathWriterDelegate::SetTimeModified(const struct tm *time)
299 {
300     if (file_ != nullptr) {
301         fclose(file_);
302         file_ = nullptr;
303     }
304 }
305 
306 }  // namespace LIBZIP
307 }  // namespace AppExecFwk
308 }  // namespace OHOS