• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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