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
GetCurrentEntryPos(unz_file_pos & filePos)157 bool ZipReader::GetCurrentEntryPos(unz_file_pos &filePos)
158 {
159 if (unzGetFilePos(zipFile_, &filePos) != UNZ_OK) {
160 return false;
161 }
162 return true;
163 }
164
OpenCurrentEntryInZip()165 bool ZipReader::OpenCurrentEntryInZip()
166 {
167 if (zipFile_ == nullptr) {
168 return false;
169 }
170
171 unz_file_info raw_file_info = {};
172 char raw_file_name_in_zip[kZipMaxPath] = {};
173 const int result = unzGetCurrentFileInfo(zipFile_,
174 &raw_file_info,
175 raw_file_name_in_zip,
176 sizeof(raw_file_name_in_zip) - 1,
177 NULL, // extraField.
178 0, // extraFieldBufferSize.
179 NULL, // szComment.
180 0); // commentBufferSize.
181 if (result != UNZ_OK) {
182 APP_LOGE("unzGetCurrentFileInfo %{public}d", result);
183 return false;
184 }
185 if (raw_file_name_in_zip[0] == '\0') {
186 return false;
187 }
188 EntryInfo *entryInfo = new (std::nothrow) EntryInfo(std::string(raw_file_name_in_zip), raw_file_info);
189 if (entryInfo == nullptr) {
190 return false;
191 }
192 currentEntryInfo_.reset(entryInfo);
193 return true;
194 }
195
ExtractCurrentEntry(WriterDelegate * delegate,uint64_t numBytesToExtract) const196 bool ZipReader::ExtractCurrentEntry(WriterDelegate *delegate, uint64_t numBytesToExtract) const
197 {
198 return ExtractEntry(delegate, zipFile_, numBytesToExtract);
199 }
200
ExtractEntry(WriterDelegate * delegate,const unzFile & zipFile,uint64_t numBytesToExtract) const201 bool ZipReader::ExtractEntry(WriterDelegate *delegate, const unzFile &zipFile, uint64_t numBytesToExtract) const
202 {
203 if ((zipFile == nullptr) || (delegate == nullptr)) {
204 return false;
205 }
206 const int openResult = unzOpenCurrentFile(zipFile);
207 if (openResult != UNZ_OK) {
208 APP_LOGE("unzOpen err %{public}d", openResult);
209 return false;
210 }
211 auto buf = std::make_unique<char[]>(kZipBufSize);
212 uint64_t remainingCapacity = numBytesToExtract;
213 bool entirefileextracted = false;
214
215 while (remainingCapacity > 0) {
216 const int numBytesRead = unzReadCurrentFile(zipFile, buf.get(), kZipBufSize);
217 if (numBytesRead == 0) {
218 entirefileextracted = true;
219 APP_LOGD("extract entry");
220 break;
221 } else if (numBytesRead < 0) {
222 // If numBytesRead < 0, then it's a specific UNZ_* error code.
223 APP_LOGE("unzReadCurrentFile < 0 %{public}d", numBytesRead);
224 break;
225 } else {
226 uint64_t numBytesToWrite = std::min<uint64_t>(remainingCapacity, checked_cast<uint64_t>(numBytesRead));
227 if (!delegate->WriteBytes(buf.get(), numBytesToWrite)) {
228 APP_LOGE("WriteBytes %{public}lu", (unsigned long) numBytesToWrite);
229 break;
230 }
231 if (remainingCapacity == checked_cast<uint64_t>(numBytesRead)) {
232 // Ensures function returns true if the entire file has been read.
233 entirefileextracted = (unzReadCurrentFile(zipFile, buf.get(), 1) == 0);
234 APP_LOGI("extract entry %{public}d", entirefileextracted);
235 }
236 if (remainingCapacity >= numBytesToWrite) {
237 remainingCapacity -= numBytesToWrite;
238 }
239 }
240 }
241
242 unzCloseCurrentFile(zipFile);
243 // closeFile
244 delegate->SetTimeModified(GetCurrentSystemTime());
245
246 return entirefileextracted;
247 }
248
OpenInternal()249 bool ZipReader::OpenInternal()
250 {
251 if (zipFile_ == nullptr) {
252 return false;
253 }
254
255 unz_global_info zipInfo = {}; // Zero-clear.
256 if (unzGetGlobalInfo(zipFile_, &zipInfo) != UNZ_OK) {
257 return false;
258 }
259 numEntries_ = zipInfo.number_entry;
260 if (numEntries_ < 0) {
261 return false;
262 }
263
264 // We are already at the end if the zip file is empty.
265 reachedEnd_ = (numEntries_ == 0);
266 return true;
267 }
268
Reset()269 void ZipReader::Reset()
270 {
271 zipFile_ = nullptr;
272 numEntries_ = 0;
273 reachedEnd_ = false;
274 currentEntryInfo_.reset();
275 }
276
277 // ZipParallelReader
~ZipParallelReader()278 ZipParallelReader::~ZipParallelReader()
279 {
280 Close();
281 }
282
Open(FilePath & zipFilePath)283 bool ZipParallelReader::Open(FilePath &zipFilePath)
284 {
285 if (!zipFiles_.empty()) {
286 return false;
287 }
288 if (!ZipReader::Open(zipFilePath)) {
289 return false;
290 }
291
292 unzFile zipFile;
293 std::string zipFilePathValue = zipFilePath.Value();
294 for (int32_t i = 0; i < concurrency_; i++) {
295 zipFile = OpenForUnzipping(zipFilePathValue);
296 if (!zipFile) {
297 return false;
298 }
299 zipFiles_.push_back(zipFile);
300 }
301 return true;
302 }
303
GotoEntry(unzFile & zipFile,unz_file_pos filePos)304 bool ZipParallelReader::GotoEntry(unzFile &zipFile, unz_file_pos filePos)
305 {
306 if (zipFile == nullptr) {
307 return false;
308 }
309 if (static_cast<int>(filePos.num_of_file) >= ZipReader::num_entries()) {
310 return false;
311 }
312
313 if (unzGoToFilePos(zipFile, &filePos) != UNZ_OK) {
314 return false;
315 }
316 return true;
317 }
318
GetZipHandler(int & resourceId)319 unzFile ZipParallelReader::GetZipHandler(int &resourceId)
320 {
321 resourceId %= concurrency_;
322 while (!mtxes_[resourceId].try_lock()) {
323 resourceId = (resourceId + 1) % concurrency_;
324 }
325 return zipFiles_[resourceId];
326 }
327
ReleaseZipHandler(const int & resourceId)328 void ZipParallelReader::ReleaseZipHandler(const int &resourceId)
329 {
330 mtxes_[resourceId].unlock();
331 }
332
Close()333 void ZipParallelReader::Close()
334 {
335 ZipReader::Close();
336 for (unzFile zipFile : zipFiles_) {
337 unzClose(zipFile);
338 }
339 }
340
341 // FilePathWriterDelegate
FilePathWriterDelegate(const FilePath & outputFilePath)342 FilePathWriterDelegate::FilePathWriterDelegate(const FilePath &outputFilePath) : outputFilePath_(outputFilePath)
343 {}
344
~FilePathWriterDelegate()345 FilePathWriterDelegate::~FilePathWriterDelegate()
346 {}
347
PrepareOutput()348 bool FilePathWriterDelegate::PrepareOutput()
349 {
350 if (!FilePathCheckValid(outputFilePath_.Value())) {
351 APP_LOGE("outputFilePath_ invalid");
352 return false;
353 }
354 // We can't rely on parent directory entries being specified in the
355 // zip, so we make sure they are created.
356 if (!FilePath::CreateDirectory(outputFilePath_.DirName())) {
357 return false;
358 }
359
360 file_ = fopen(outputFilePath_.Value().c_str(), "wb");
361 if (file_ == nullptr) {
362 APP_LOGE("fopen %{private}s err: %{public}d %{public}s",
363 outputFilePath_.Value().c_str(), errno, strerror(errno));
364 return false;
365 }
366 return FilePath::PathIsValid(outputFilePath_);
367 }
368
WriteBytes(const char * data,int numBytes)369 bool FilePathWriterDelegate::WriteBytes(const char *data, int numBytes)
370 {
371 if ((file_ == nullptr) || (numBytes <= 0) || (data == nullptr)) {
372 return false;
373 }
374 int writebytes = fwrite(data, 1, numBytes, file_);
375 bool ret = (numBytes == writebytes);
376 if (!ret) {
377 APP_LOGE("fwrite %{public}d %{public}d", numBytes, writebytes);
378 }
379 return ret;
380 }
381
SetTimeModified(const struct tm * time)382 void FilePathWriterDelegate::SetTimeModified(const struct tm *time)
383 {
384 if (file_ != nullptr) {
385 fclose(file_);
386 file_ = nullptr;
387 }
388 }
389
390 } // namespace LIBZIP
391 } // namespace AppExecFwk
392 } // namespace OHOS