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
166 const int result = unzGetCurrentFileInfo(zipFile_,
167 &raw_file_info,
168 raw_file_name_in_zip,
169 sizeof(raw_file_name_in_zip) - 1,
170 NULL, // extraField.
171 0, // extraFieldBufferSize.
172 NULL, // szComment.
173 0); // commentBufferSize.
174
175 if (result != UNZ_OK) {
176 return false;
177 }
178 if (raw_file_name_in_zip[0] == '\0') {
179 return false;
180 }
181 currentEntryInfo_.reset(new (std::nothrow) EntryInfo(std::string(raw_file_name_in_zip), raw_file_info));
182 return true;
183 }
184
ExtractCurrentEntry(WriterDelegate * delegate,uint64_t numBytesToExtract) const185 bool ZipReader::ExtractCurrentEntry(WriterDelegate *delegate, uint64_t numBytesToExtract) const
186 {
187 if ((zipFile_ == nullptr) || (delegate == nullptr)) {
188 return false;
189 }
190 const int openResult = unzOpenCurrentFile(zipFile_);
191 if (openResult != UNZ_OK) {
192 return false;
193 }
194 if (!delegate->PrepareOutput()) {
195 return false;
196 }
197 auto buf = std::make_unique<char[]>(kZipBufSize);
198 uint64_t remainingCapacity = numBytesToExtract;
199 bool entirefileextracted = false;
200
201 while (remainingCapacity > 0) {
202 const int numBytesRead = unzReadCurrentFile(zipFile_, buf.get(), kZipBufSize);
203 if (numBytesRead == 0) {
204 entirefileextracted = true;
205 break;
206 } else if (numBytesRead < 0) {
207 // If numBytesRead < 0, then it's a specific UNZ_* error code.
208 break;
209 } else {
210 uint64_t numBytesToWrite = std::min<uint64_t>(remainingCapacity, checked_cast<uint64_t>(numBytesRead));
211 if (!delegate->WriteBytes(buf.get(), numBytesToWrite)) {
212 break;
213 }
214 if (remainingCapacity == checked_cast<uint64_t>(numBytesRead)) {
215 // Ensures function returns true if the entire file has been read.
216 entirefileextracted = (unzReadCurrentFile(zipFile_, buf.get(), 1) == 0);
217 }
218 if (remainingCapacity >= numBytesToWrite) {
219 remainingCapacity -= numBytesToWrite;
220 }
221 }
222 }
223
224 unzCloseCurrentFile(zipFile_);
225 // closeFile
226 delegate->SetTimeModified(GetCurrentSystemTime());
227
228 return entirefileextracted;
229 }
230
OpenInternal()231 bool ZipReader::OpenInternal()
232 {
233 if (zipFile_ == nullptr) {
234 return false;
235 }
236
237 unz_global_info zipInfo = {}; // Zero-clear.
238 if (unzGetGlobalInfo(zipFile_, &zipInfo) != UNZ_OK) {
239 return false;
240 }
241 numEntries_ = zipInfo.number_entry;
242 if (numEntries_ < 0) {
243 return false;
244 }
245
246 // We are already at the end if the zip file is empty.
247 reachedEnd_ = (numEntries_ == 0);
248 return true;
249 }
250
Reset()251 void ZipReader::Reset()
252 {
253 zipFile_ = nullptr;
254 numEntries_ = 0;
255 reachedEnd_ = false;
256 currentEntryInfo_.reset();
257 }
258
259 // FilePathWriterDelegate
FilePathWriterDelegate(const FilePath & outputFilePath)260 FilePathWriterDelegate::FilePathWriterDelegate(const FilePath &outputFilePath) : outputFilePath_(outputFilePath)
261 {}
262
~FilePathWriterDelegate()263 FilePathWriterDelegate::~FilePathWriterDelegate()
264 {}
265
PrepareOutput()266 bool FilePathWriterDelegate::PrepareOutput()
267 {
268 if (!FilePathCheckValid(outputFilePath_.Value())) {
269 APP_LOGE("%{public}s called, outputFilePath_ invalid !!!.", __func__);
270 return false;
271 }
272 // We can't rely on parent directory entries being specified in the
273 // zip, so we make sure they are created.
274 if (!FilePath::CreateDirectory(outputFilePath_.DirName())) {
275 return false;
276 }
277
278 file_ = fopen(outputFilePath_.Value().c_str(), "wb");
279 return FilePath::PathIsValid(outputFilePath_);
280 }
281
WriteBytes(const char * data,int numBytes)282 bool FilePathWriterDelegate::WriteBytes(const char *data, int numBytes)
283 {
284 if ((file_ == nullptr) || (numBytes <= 0) || (data == nullptr)) {
285 return false;
286 }
287 int writebytes = fwrite(data, 1, numBytes, file_);
288 return numBytes == writebytes;
289 }
290
SetTimeModified(const struct tm * time)291 void FilePathWriterDelegate::SetTimeModified(const struct tm *time)
292 {
293 fclose(file_);
294 file_ = nullptr;
295 }
296
297 } // namespace LIBZIP
298 } // namespace AppExecFwk
299 } // namespace OHOS