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 // Whether the file is encrypted is bit 0 of the flag.
51 isEncrypted_ = rawFileInfo.flag & 1;
52
53 // Construct the last modified time. The timezone info is not present in
54 // zip files, so we construct the time as local time.
55 if (GetCurrentSystemTime() != nullptr) {
56 lastModified_ = *GetCurrentSystemTime();
57 }
58 }
59
ZipReader()60 ZipReader::ZipReader()
61 {
62 Reset();
63 }
64
~ZipReader()65 ZipReader::~ZipReader()
66 {
67 Close();
68 }
69
Open(FilePath & zipFilePath)70 bool ZipReader::Open(FilePath &zipFilePath)
71 {
72 if (zipFile_ != nullptr) {
73 return false;
74 }
75
76 // Use of "Unsafe" function does not look good, but there is no way to do
77 // this safely on Linux. See file_util.h for details.
78 std::string zipfile = zipFilePath.Value();
79 zipFile_ = OpenForUnzipping(zipfile);
80 if (zipFile_ == nullptr) {
81 return false;
82 }
83
84 return OpenInternal();
85 }
86
OpenFromPlatformFile(PlatformFile zipFd)87 bool ZipReader::OpenFromPlatformFile(PlatformFile zipFd)
88 {
89 if (zipFile_ != nullptr) {
90 return false;
91 }
92 zipFile_ = OpenFdForUnzipping(zipFd);
93 if (!zipFile_) {
94 return false;
95 }
96
97 return OpenInternal();
98 }
99
OpenFromString(const std::string & data)100 bool ZipReader::OpenFromString(const std::string &data)
101 {
102 zipFile_ = PrepareMemoryForUnzipping(data);
103 if (!zipFile_) {
104 return false;
105 }
106
107 return OpenInternal();
108 }
109
Close()110 void ZipReader::Close()
111 {
112 if (zipFile_) {
113 unzClose(zipFile_);
114 }
115 Reset();
116 }
117
HasMore()118 bool ZipReader::HasMore()
119 {
120 return !reachedEnd_;
121 }
122
AdvanceToNextEntry()123 bool ZipReader::AdvanceToNextEntry()
124 {
125 if (zipFile_ == nullptr) {
126 return false;
127 }
128 // Should not go further if we already reached the end.
129 if (reachedEnd_) {
130 return false;
131 }
132 unz_file_pos position = {};
133 if (unzGetFilePos(zipFile_, &position) != UNZ_OK) {
134 return false;
135 }
136
137 const int currentEntryIndex = position.num_of_file;
138 // If we are currently at the last entry, then the next position is the
139 // end of the zip file, so mark that we reached the end.
140 if (currentEntryIndex + 1 == numEntries_) {
141 reachedEnd_ = true;
142 } else {
143 if (unzGoToNextFile(zipFile_) != UNZ_OK) {
144 return false;
145 }
146 }
147 currentEntryInfo_.reset();
148 return true;
149 }
150
GetCurrentEntryPos(unz_file_pos & filePos)151 bool ZipReader::GetCurrentEntryPos(unz_file_pos &filePos)
152 {
153 if (unzGetFilePos(zipFile_, &filePos) != UNZ_OK) {
154 return false;
155 }
156 return true;
157 }
158
OpenCurrentEntryInZip()159 bool ZipReader::OpenCurrentEntryInZip()
160 {
161 if (zipFile_ == nullptr) {
162 return false;
163 }
164
165 unz_file_info raw_file_info = {};
166 char raw_file_name_in_zip[kZipMaxPath] = {};
167 const int result = unzGetCurrentFileInfo(zipFile_,
168 &raw_file_info,
169 raw_file_name_in_zip,
170 sizeof(raw_file_name_in_zip) - 1,
171 NULL, // extraField.
172 0, // extraFieldBufferSize.
173 NULL, // szComment.
174 0); // commentBufferSize.
175 if (result != UNZ_OK) {
176 APP_LOGE("unzGetCurrentFileInfo %{public}d", result);
177 return false;
178 }
179 if (raw_file_name_in_zip[0] == '\0') {
180 return false;
181 }
182 EntryInfo *entryInfo = new (std::nothrow) EntryInfo(std::string(raw_file_name_in_zip), raw_file_info);
183 if (entryInfo == nullptr) {
184 return false;
185 }
186 currentEntryInfo_.reset(entryInfo);
187 return true;
188 }
189
ExtractCurrentEntry(WriterDelegate * delegate,uint64_t numBytesToExtract) const190 bool ZipReader::ExtractCurrentEntry(WriterDelegate *delegate, uint64_t numBytesToExtract) const
191 {
192 return ExtractEntry(delegate, zipFile_, numBytesToExtract);
193 }
194
ExtractEntry(WriterDelegate * delegate,const unzFile & zipFile,uint64_t numBytesToExtract) const195 bool ZipReader::ExtractEntry(WriterDelegate *delegate, const unzFile &zipFile, uint64_t numBytesToExtract) const
196 {
197 if ((zipFile == nullptr) || (delegate == nullptr)) {
198 return false;
199 }
200 const int openResult = unzOpenCurrentFile(zipFile);
201 if (openResult != UNZ_OK) {
202 APP_LOGW("unzOpen err %{public}d", openResult);
203 return false;
204 }
205 auto buf = std::make_unique<char[]>(kZipBufSize);
206 uint64_t remainingCapacity = numBytesToExtract;
207 bool entirefileextracted = false;
208
209 while (remainingCapacity > 0) {
210 const int numBytesRead = unzReadCurrentFile(zipFile, buf.get(), kZipBufSize);
211 if (numBytesRead == 0) {
212 entirefileextracted = true;
213 APP_LOGD("extract entry");
214 break;
215 } else if (numBytesRead < 0) {
216 // If numBytesRead < 0, then it's a specific UNZ_* error code.
217 APP_LOGE("unzReadCurrentFile < 0 %{public}d", numBytesRead);
218 break;
219 } else {
220 uint64_t numBytesToWrite = std::min<uint64_t>(remainingCapacity, checked_cast<uint64_t>(numBytesRead));
221 if (!delegate->WriteBytes(buf.get(), numBytesToWrite)) {
222 APP_LOGE("WriteBytes %{public}lu", (unsigned long) numBytesToWrite);
223 break;
224 }
225 if (remainingCapacity == checked_cast<uint64_t>(numBytesRead)) {
226 // Ensures function returns true if the entire file has been read.
227 entirefileextracted = (unzReadCurrentFile(zipFile, buf.get(), 1) == 0);
228 APP_LOGI("extract entry %{public}d", entirefileextracted);
229 }
230 if (remainingCapacity >= numBytesToWrite) {
231 remainingCapacity -= numBytesToWrite;
232 }
233 }
234 }
235
236 unzCloseCurrentFile(zipFile);
237 // closeFile
238 delegate->SetTimeModified(GetCurrentSystemTime());
239
240 return entirefileextracted;
241 }
242
OpenInternal()243 bool ZipReader::OpenInternal()
244 {
245 if (zipFile_ == nullptr) {
246 return false;
247 }
248
249 unz_global_info zipInfo = {}; // Zero-clear.
250 if (unzGetGlobalInfo(zipFile_, &zipInfo) != UNZ_OK) {
251 return false;
252 }
253 numEntries_ = zipInfo.number_entry;
254 if (numEntries_ < 0) {
255 return false;
256 }
257
258 // We are already at the end if the zip file is empty.
259 reachedEnd_ = (numEntries_ == 0);
260 return true;
261 }
262
Reset()263 void ZipReader::Reset()
264 {
265 zipFile_ = nullptr;
266 numEntries_ = 0;
267 reachedEnd_ = false;
268 currentEntryInfo_.reset();
269 }
270
271 // ZipParallelReader
~ZipParallelReader()272 ZipParallelReader::~ZipParallelReader()
273 {
274 Close();
275 }
276
Open(FilePath & zipFilePath)277 bool ZipParallelReader::Open(FilePath &zipFilePath)
278 {
279 if (!zipFiles_.empty()) {
280 return false;
281 }
282 if (!ZipReader::Open(zipFilePath)) {
283 return false;
284 }
285
286 unzFile zipFile;
287 std::string zipFilePathValue = zipFilePath.Value();
288 for (int32_t i = 0; i < concurrency_; i++) {
289 zipFile = OpenForUnzipping(zipFilePathValue);
290 if (!zipFile) {
291 return false;
292 }
293 zipFiles_.push_back(zipFile);
294 }
295 return true;
296 }
297
GotoEntry(unzFile & zipFile,unz_file_pos filePos)298 bool ZipParallelReader::GotoEntry(unzFile &zipFile, unz_file_pos filePos)
299 {
300 if (zipFile == nullptr) {
301 return false;
302 }
303 if (static_cast<int>(filePos.num_of_file) >= ZipReader::num_entries()) {
304 return false;
305 }
306
307 if (unzGoToFilePos(zipFile, &filePos) != UNZ_OK) {
308 return false;
309 }
310 return true;
311 }
312
GetZipHandler(int & resourceId)313 unzFile ZipParallelReader::GetZipHandler(int &resourceId)
314 {
315 resourceId %= concurrency_;
316 while (!mtxes_[resourceId].try_lock()) {
317 resourceId = (resourceId + 1) % concurrency_;
318 }
319 return zipFiles_[resourceId];
320 }
321
ReleaseZipHandler(const int & resourceId)322 void ZipParallelReader::ReleaseZipHandler(const int &resourceId)
323 {
324 mtxes_[resourceId].unlock();
325 }
326
Close()327 void ZipParallelReader::Close()
328 {
329 ZipReader::Close();
330 for (unzFile zipFile : zipFiles_) {
331 unzClose(zipFile);
332 }
333 }
334
335 // FilePathWriterDelegate
FilePathWriterDelegate(const FilePath & outputFilePath)336 FilePathWriterDelegate::FilePathWriterDelegate(const FilePath &outputFilePath) : outputFilePath_(outputFilePath)
337 {}
338
~FilePathWriterDelegate()339 FilePathWriterDelegate::~FilePathWriterDelegate()
340 {}
341
PrepareOutput()342 bool FilePathWriterDelegate::PrepareOutput()
343 {
344 if (!FilePathCheckValid(outputFilePath_.Value())) {
345 APP_LOGE("outputFilePath_ invalid");
346 return false;
347 }
348 // We can't rely on parent directory entries being specified in the
349 // zip, so we make sure they are created.
350 if (!FilePath::CreateDirectory(outputFilePath_.DirName())) {
351 return false;
352 }
353
354 file_ = fopen(outputFilePath_.Value().c_str(), "wb");
355 if (file_ == nullptr) {
356 APP_LOGE("fopen %{private}s err: %{public}d %{public}s",
357 outputFilePath_.Value().c_str(), errno, strerror(errno));
358 return false;
359 }
360 return FilePath::PathIsValid(outputFilePath_);
361 }
362
WriteBytes(const char * data,int numBytes)363 bool FilePathWriterDelegate::WriteBytes(const char *data, int numBytes)
364 {
365 if ((file_ == nullptr) || (numBytes <= 0) || (data == nullptr)) {
366 return false;
367 }
368 int writebytes = fwrite(data, 1, numBytes, file_);
369 bool ret = (numBytes == writebytes);
370 if (!ret) {
371 APP_LOGE("fwrite %{public}d %{public}d", numBytes, writebytes);
372 }
373 return ret;
374 }
375
SetTimeModified(const struct tm * time)376 void FilePathWriterDelegate::SetTimeModified(const struct tm *time)
377 {
378 if (file_ != nullptr) {
379 fclose(file_);
380 file_ = nullptr;
381 }
382 }
383
384 } // namespace LIBZIP
385 } // namespace AppExecFwk
386 } // namespace OHOS