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