1 /*
2 * Copyright (c) 2021 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_file.h"
17
18 #include <cassert>
19 #include <cstring>
20 #include <ostream>
21
22 #include "securec.h"
23 #include "zlib.h"
24 #include "app_log_wrapper.h"
25
26 namespace OHOS {
27 namespace AppExecFwk {
28 namespace {
29
30 constexpr uint32_t MAX_FILE_NAME = 256;
31 constexpr uint32_t UNZIP_BUFFER_SIZE = 1024;
32 constexpr uint32_t UNZIP_BUF_IN_LEN = 160 * UNZIP_BUFFER_SIZE; // in buffer length: 160KB
33 constexpr uint32_t UNZIP_BUF_OUT_LEN = 320 * UNZIP_BUFFER_SIZE; // out buffer length: 320KB
34 constexpr uint32_t LOCAL_HEADER_SIGNATURE = 0x04034b50;
35 constexpr uint32_t CENTRAL_SIGNATURE = 0x02014b50;
36 constexpr uint32_t EOCD_SIGNATURE = 0x06054b50;
37 constexpr uint32_t DATA_DESC_SIGNATURE = 0x08074b50;
38 constexpr uint32_t FLAG_DATA_DESC = 0x8;
39 constexpr size_t FILE_READ_COUNT = 1;
40
41 } // namespace
42
ZipEntry(const CentralDirEntry & centralEntry)43 ZipEntry::ZipEntry(const CentralDirEntry ¢ralEntry)
44 {
45 compressionMethod = centralEntry.compressionMethod;
46 uncompressedSize = centralEntry.uncompressedSize;
47 compressedSize = centralEntry.compressedSize;
48 localHeaderOffset = centralEntry.localHeaderOffset;
49 crc = centralEntry.crc;
50 flags = centralEntry.flags;
51 }
52
ZipFile(const std::string & pathName)53 ZipFile::ZipFile(const std::string &pathName) : pathName_(pathName)
54 {
55 APP_LOGI("create instance from %{private}s", pathName_.c_str());
56 }
57
~ZipFile()58 ZipFile::~ZipFile()
59 {
60 Close();
61 }
62
SetContentLocation(const ZipPos start,const size_t length)63 void ZipFile::SetContentLocation(const ZipPos start, const size_t length)
64 {
65 APP_LOGD("set content location start position(%{public}llu), length(%{public}zu)", start, length);
66 fileStartPos_ = start;
67 fileLength_ = length;
68 }
69
CheckEndDir(const EndDir & endDir) const70 bool ZipFile::CheckEndDir(const EndDir &endDir) const
71 {
72 size_t lenEndDir = sizeof(EndDir);
73 if ((endDir.numDisk != 0) || (endDir.signature != EOCD_SIGNATURE) || (endDir.startDiskOfCentralDir != 0) ||
74 (endDir.offset >= fileLength_) || (endDir.totalEntriesInThisDisk != endDir.totalEntries) ||
75 (endDir.commentLen != 0) ||
76 // central dir can't overlap end of central dir
77 ((endDir.offset + endDir.sizeOfCentralDir + lenEndDir) > fileLength_)) {
78 APP_LOGE("end dir format error");
79 return false;
80 }
81 return true;
82 }
83
ParseEndDirectory()84 bool ZipFile::ParseEndDirectory()
85 {
86 size_t endDirLen = sizeof(EndDir);
87 size_t endFilePos = fileStartPos_ + fileLength_;
88
89 if (fileLength_ <= endDirLen) {
90 APP_LOGE("parse EOCD file length(%{public}llu) <= end dir length(%{public}llu)", fileStartPos_, fileLength_);
91 return false;
92 }
93
94 size_t eocdPos = endFilePos - endDirLen;
95 if (fseek(file_, eocdPos, SEEK_SET) != 0) {
96 APP_LOGE("locate EOCD seek failed, error: %{public}s", strerror(errno));
97 return false;
98 }
99
100 if (fread(&endDir_, sizeof(EndDir), FILE_READ_COUNT, file_) != FILE_READ_COUNT) {
101 APP_LOGE("read EOCD struct failed, error: %{public}s", strerror(errno));
102 return false;
103 }
104
105 centralDirPos_ = endDir_.offset + fileStartPos_;
106 APP_LOGD("parse EOCD offset(0x%{public}08x) file start position(0x%{public}08llx)", endDir_.offset, fileStartPos_);
107
108 return CheckEndDir(endDir_);
109 }
110
ParseAllEntries()111 bool ZipFile::ParseAllEntries()
112 {
113 bool ret = true;
114 ZipPos currentPos = centralDirPos_;
115 CentralDirEntry directoryEntry = {0};
116 size_t fileLength = 0;
117
118 for (int32_t i = 0; i < endDir_.totalEntries; i++) {
119 std::string fileName;
120 fileName.reserve(MAX_FILE_NAME);
121 fileName.resize(MAX_FILE_NAME - 1);
122
123 if (fseek(file_, currentPos, SEEK_SET) != 0) {
124 APP_LOGE("parse entry(%{public}d) seek zipEntry failed, error: %{public}s", i, strerror(errno));
125 ret = false;
126 break;
127 }
128
129 if (fread(&directoryEntry, sizeof(CentralDirEntry), FILE_READ_COUNT, file_) != FILE_READ_COUNT) {
130 APP_LOGE("parse entry(%{public}d) read ZipEntry failed, error: %{public}s", i, strerror(errno));
131 ret = false;
132 break;
133 }
134
135 if (directoryEntry.signature != CENTRAL_SIGNATURE) {
136 APP_LOGE("parse entry(%{public}d) check signature(0x%08x) at pos(0x%08llx) failed",
137 i,
138 directoryEntry.signature,
139 currentPos);
140 ret = false;
141 break;
142 }
143
144 fileLength = (directoryEntry.nameSize >= MAX_FILE_NAME) ? (MAX_FILE_NAME - 1) : directoryEntry.nameSize;
145 if (fread(&(fileName[0]), fileLength, FILE_READ_COUNT, file_) != FILE_READ_COUNT) {
146 APP_LOGE("parse entry(%{public}d) read file name failed, error: %{public}s", i, strerror(errno));
147 ret = false;
148 break;
149 }
150 fileName.resize(fileLength);
151
152 ZipEntry currentEntry(directoryEntry);
153 currentEntry.fileName = fileName;
154 entriesMap_[fileName] = currentEntry;
155
156 currentPos += sizeof(directoryEntry);
157 currentPos += directoryEntry.nameSize + directoryEntry.extraSize + directoryEntry.commentSize;
158 }
159
160 APP_LOGD("parse %{public}d central entries from %{private}s", endDir_.totalEntries, pathName_.c_str());
161 return ret;
162 }
163
Open()164 bool ZipFile::Open()
165 {
166 APP_LOGD("open: %{private}s", pathName_.c_str());
167
168 if (isOpen_) {
169 APP_LOGE("has already opened");
170 return true;
171 }
172
173 if (pathName_.length() > PATH_MAX) {
174 APP_LOGE("path length(%{public}u) longer than max path length(%{public}d)",
175 static_cast<unsigned int>(pathName_.length()),
176 PATH_MAX);
177 return false;
178 }
179 std::string realPath;
180 realPath.reserve(PATH_MAX);
181 realPath.resize(PATH_MAX - 1);
182 if (realpath(pathName_.c_str(), &(realPath[0])) == nullptr) {
183 APP_LOGE("transform real path error: %{public}s", strerror(errno));
184 return false;
185 }
186
187 FILE *tmpFile = fopen(&(realPath[0]), "rb");
188 if (tmpFile == nullptr) {
189 APP_LOGE("open file(%{private}s) failed, error: %{public}s", pathName_.c_str(), strerror(errno));
190 return false;
191 }
192
193 if (fileLength_ == 0) {
194 if (fseek(tmpFile, 0, SEEK_END) != 0) {
195 APP_LOGE("file seek failed, error: %{public}s", strerror(errno));
196 fclose(tmpFile);
197 return false;
198 }
199
200 fileLength_ = ftell(tmpFile);
201 if (fileStartPos_ >= fileLength_) {
202 APP_LOGE("open start pos > length failed");
203 fclose(tmpFile);
204 return false;
205 }
206
207 fileLength_ -= fileStartPos_;
208 }
209
210 file_ = tmpFile;
211 bool result = ParseEndDirectory();
212 if (result) {
213 result = ParseAllEntries();
214 }
215 // it means open file success.
216 isOpen_ = true;
217 return result;
218 }
219
Close()220 void ZipFile::Close()
221 {
222 APP_LOGD("close: %{private}s", pathName_.c_str());
223
224 if (!isOpen_ || file_ == nullptr) {
225 APP_LOGW("file is not opened");
226 return;
227 }
228
229 entriesMap_.clear();
230 pathName_ = "";
231 isOpen_ = false;
232
233 if (fclose(file_) != 0) {
234 APP_LOGW("close failed, error: %{public}s", strerror(errno));
235 }
236 file_ = nullptr;
237 }
238
239 // Get all file zipEntry in this file
GetAllEntries() const240 const ZipEntryMap &ZipFile::GetAllEntries() const
241 {
242 return entriesMap_;
243 }
244
GetEntry(const std::string & entryName,ZipEntry & resultEntry) const245 bool ZipFile::GetEntry(const std::string &entryName, ZipEntry &resultEntry) const
246 {
247 APP_LOGD("get entry by name: %{public}s", entryName.c_str());
248 auto iter = entriesMap_.find(entryName);
249 if (iter != entriesMap_.end()) {
250 resultEntry = iter->second;
251 APP_LOGD("get entry succeed");
252 return true;
253 }
254 APP_LOGE("get entry failed");
255 return false;
256 }
257
GetLocalHeaderSize(const uint16_t nameSize,const uint16_t extraSize) const258 size_t ZipFile::GetLocalHeaderSize(const uint16_t nameSize, const uint16_t extraSize) const
259 {
260 return sizeof(LocalHeader) + nameSize + extraSize;
261 }
262
CheckDataDesc(const ZipEntry & zipEntry,const LocalHeader & localHeader) const263 bool ZipFile::CheckDataDesc(const ZipEntry &zipEntry, const LocalHeader &localHeader) const
264 {
265 uint32_t crcLocal = 0;
266 uint32_t compressedLocal = 0;
267 uint32_t uncompressedLocal = 0;
268
269 if (localHeader.flags & FLAG_DATA_DESC) { // use data desc
270 DataDesc dataDesc;
271 ZipPos descPos = zipEntry.localHeaderOffset + GetLocalHeaderSize(localHeader.nameSize, localHeader.extraSize);
272 descPos += fileStartPos_ + zipEntry.compressedSize;
273
274 if (fseek(file_, descPos, SEEK_SET) != 0) {
275 APP_LOGE("check local header seek datadesc failed, error: %{public}s", strerror(errno));
276 return false;
277 }
278
279 if (fread(&dataDesc, sizeof(DataDesc), FILE_READ_COUNT, file_) != FILE_READ_COUNT) {
280 APP_LOGE("check local header read datadesc failed, error: %{public}s", strerror(errno));
281 return false;
282 }
283
284 if (dataDesc.signature != DATA_DESC_SIGNATURE) {
285 APP_LOGE("check local header check datadesc signature failed");
286 return false;
287 }
288
289 crcLocal = dataDesc.crc;
290 compressedLocal = dataDesc.compressedSize;
291 uncompressedLocal = dataDesc.uncompressedSize;
292 } else {
293 crcLocal = localHeader.crc;
294 compressedLocal = localHeader.compressedSize;
295 uncompressedLocal = localHeader.uncompressedSize;
296 }
297
298 if ((zipEntry.crc != crcLocal) || (zipEntry.compressedSize != compressedLocal) ||
299 (zipEntry.uncompressedSize != uncompressedLocal)) {
300 APP_LOGE("check local header compressed size corrupted");
301 return false;
302 }
303
304 return true;
305 }
306
CheckCoherencyLocalHeader(const ZipEntry & zipEntry,uint16_t & extraSize) const307 bool ZipFile::CheckCoherencyLocalHeader(const ZipEntry &zipEntry, uint16_t &extraSize) const
308 {
309 LocalHeader localHeader = {0};
310
311 if (zipEntry.localHeaderOffset >= fileLength_) {
312 APP_LOGE("check local file header offset is overflow %{public}d", zipEntry.localHeaderOffset);
313 return false;
314 }
315
316 if (fseek(file_, fileStartPos_ + zipEntry.localHeaderOffset, SEEK_SET) != 0) {
317 APP_LOGE("check local header seek failed, error: %{public}s", strerror(errno));
318 return false;
319 }
320
321 if (fread(&localHeader, sizeof(LocalHeader), FILE_READ_COUNT, file_) != FILE_READ_COUNT) {
322 APP_LOGE("check local header read localheader failed, error: %{public}s", strerror(errno));
323 return false;
324 }
325
326 if ((localHeader.signature != LOCAL_HEADER_SIGNATURE) ||
327 (zipEntry.compressionMethod != localHeader.compressionMethod)) {
328 APP_LOGE("check local header signature or compressionMethod failed");
329 return false;
330 }
331
332 // current only support store and Z_DEFLATED method
333 if ((zipEntry.compressionMethod != Z_DEFLATED) && (zipEntry.compressionMethod != 0)) {
334 APP_LOGE("check local header compressionMethod(%{public}d) not support", zipEntry.compressionMethod);
335 return false;
336 }
337
338 std::string fileName;
339 fileName.reserve(MAX_FILE_NAME);
340 fileName.resize(MAX_FILE_NAME - 1);
341 size_t fileLength = (localHeader.nameSize >= MAX_FILE_NAME) ? (MAX_FILE_NAME - 1) : localHeader.nameSize;
342 if (fileLength != zipEntry.fileName.length()) {
343 APP_LOGE("check local header file name size failed");
344 return false;
345 }
346 if (fread(&(fileName[0]), fileLength, FILE_READ_COUNT, file_) != FILE_READ_COUNT) {
347 APP_LOGE("check local header read file name failed, error: %{public}s", strerror(errno));
348 return false;
349 }
350 fileName.resize(fileLength);
351 if (zipEntry.fileName != fileName) {
352 APP_LOGE("check local header file name corrupted");
353 return false;
354 }
355
356 if (!CheckDataDesc(zipEntry, localHeader)) {
357 APP_LOGE("check data desc failed");
358 return false;
359 }
360
361 extraSize = localHeader.extraSize;
362 return true;
363 }
364
SeekToEntryStart(const ZipEntry & zipEntry,const uint16_t extraSize) const365 bool ZipFile::SeekToEntryStart(const ZipEntry &zipEntry, const uint16_t extraSize) const
366 {
367 ZipPos startOffset = zipEntry.localHeaderOffset;
368 // get data offset, add signature+localheader+namesize+extrasize
369 startOffset += GetLocalHeaderSize(zipEntry.fileName.length(), extraSize);
370 if (startOffset + zipEntry.compressedSize > fileLength_) {
371 APP_LOGE("startOffset(%{public}lld)+entryCompressedSize(%{public}ud) > fileLength(%{public}llu)",
372 startOffset,
373 zipEntry.compressedSize,
374 fileLength_);
375 return false;
376 }
377 startOffset += fileStartPos_; // add file start relative to file stream
378
379 APP_LOGD("seek to entry start 0x%{public}08llx", startOffset);
380 if (fseek(file_, startOffset, SEEK_SET) != 0) {
381 APP_LOGE("seek failed, error: %{public}s", strerror(errno));
382 return false;
383 }
384 return true;
385 }
386
UnzipWithStore(const ZipEntry & zipEntry,const uint16_t extraSize,std::ostream & dest) const387 bool ZipFile::UnzipWithStore(const ZipEntry &zipEntry, const uint16_t extraSize, std::ostream &dest) const
388 {
389 APP_LOGD("unzip with store");
390
391 if (!SeekToEntryStart(zipEntry, extraSize)) {
392 APP_LOGE("seek to entry start failed");
393 return false;
394 }
395
396 uint32_t remainSize = zipEntry.compressedSize;
397 std::string readBuffer;
398 readBuffer.reserve(UNZIP_BUF_OUT_LEN);
399 readBuffer.resize(UNZIP_BUF_OUT_LEN - 1);
400 while (remainSize > 0) {
401 size_t readBytes;
402 size_t readLen = (remainSize > UNZIP_BUF_OUT_LEN) ? UNZIP_BUF_OUT_LEN : remainSize;
403 readBytes = fread(&(readBuffer[0]), sizeof(Byte), readLen, file_);
404 if (readBytes == 0) {
405 APP_LOGE("unzip store read failed, error: %{public}s", strerror(errno));
406 return false;
407 }
408 remainSize -= readBytes;
409 dest.write(&(readBuffer[0]), readBytes);
410 }
411
412 return true;
413 }
414
InitZStream(z_stream & zstream) const415 bool ZipFile::InitZStream(z_stream &zstream) const
416 {
417 // init zlib stream
418 if (memset_s(&zstream, sizeof(z_stream), 0, sizeof(z_stream))) {
419 APP_LOGE("unzip stream buffer init failed");
420 return false;
421 }
422 int32_t zlibErr = inflateInit2(&zstream, -MAX_WBITS);
423 if (zlibErr != Z_OK) {
424 APP_LOGE("unzip inflated init failed");
425 return false;
426 }
427
428 BytePtr bufOut = new (std::nothrow) Byte[UNZIP_BUF_OUT_LEN];
429 if (bufOut == nullptr) {
430 APP_LOGE("unzip inflated new out buffer failed");
431 return false;
432 }
433
434 BytePtr bufIn = new (std::nothrow) Byte[UNZIP_BUF_IN_LEN];
435 if (bufIn == nullptr) {
436 APP_LOGE("unzip inflated new in buffer failed");
437 delete[] bufOut;
438 return false;
439 }
440 zstream.next_out = bufOut;
441 zstream.next_in = bufIn;
442 zstream.avail_out = UNZIP_BUF_OUT_LEN;
443 return true;
444 }
445
ReadZStream(const BytePtr & buffer,z_stream & zstream,uint32_t & remainCompressedSize) const446 bool ZipFile::ReadZStream(const BytePtr &buffer, z_stream &zstream, uint32_t &remainCompressedSize) const
447 {
448 if (zstream.avail_in == 0) {
449 size_t readBytes;
450 size_t remainBytes = (remainCompressedSize > UNZIP_BUF_IN_LEN) ? UNZIP_BUF_IN_LEN : remainCompressedSize;
451 readBytes = fread(buffer, sizeof(Byte), remainBytes, file_);
452 if (readBytes == 0) {
453 APP_LOGE("unzip inflated read failed, error: %{public}s", strerror(errno));
454 return false;
455 }
456
457 remainCompressedSize -= readBytes;
458 zstream.avail_in = readBytes;
459 zstream.next_in = buffer;
460 }
461 return true;
462 }
463
UnzipWithInflated(const ZipEntry & zipEntry,const uint16_t extraSize,std::ostream & dest) const464 bool ZipFile::UnzipWithInflated(const ZipEntry &zipEntry, const uint16_t extraSize, std::ostream &dest) const
465 {
466 APP_LOGD("unzip with inflated");
467
468 z_stream zstream;
469 if (!SeekToEntryStart(zipEntry, extraSize)) {
470 APP_LOGE("seek to entry start failed");
471 return false;
472 }
473 if (!InitZStream(zstream)) {
474 APP_LOGE("init zstream failed");
475 return false;
476 }
477 BytePtr bufIn = zstream.next_in;
478 BytePtr bufOut = zstream.next_out;
479
480 bool ret = true;
481 int32_t zlibErr = Z_OK;
482 uint32_t remainCompressedSize = zipEntry.compressedSize;
483 size_t inflateLen = 0;
484 uint32_t crc = 0;
485 while ((remainCompressedSize > 0) || (zstream.avail_in > 0)) {
486 if (!ReadZStream(bufIn, zstream, remainCompressedSize)) {
487 ret = false;
488 break;
489 }
490
491 zlibErr = inflate(&zstream, Z_SYNC_FLUSH);
492 if ((zlibErr >= Z_OK) && (zstream.msg != nullptr)) {
493 APP_LOGE("unzip inflated inflate, error: %{public}d, err msg: %{public}s", zlibErr, zstream.msg);
494 ret = false;
495 break;
496 }
497
498 inflateLen = UNZIP_BUF_OUT_LEN - zstream.avail_out;
499 if (inflateLen > 0) {
500 dest.write((const char *)bufOut, inflateLen);
501 crc = crc32(crc, bufOut, inflateLen);
502 zstream.next_out = bufOut;
503 zstream.avail_out = UNZIP_BUF_OUT_LEN;
504 }
505 }
506
507 if (crc != zipEntry.crc) {
508 APP_LOGE("unzip inflate crc check");
509 ret = false;
510 }
511
512 // free all dynamically allocated data structures except the next_in and next_out for this stream.
513 zlibErr = inflateEnd(&zstream);
514 if (zlibErr != Z_OK) {
515 APP_LOGE("unzip inflateEnd error, error: %{public}d", zlibErr);
516 ret = false;
517 }
518
519 delete[] bufOut;
520 delete[] bufIn;
521 return ret;
522 }
523
GetEntryDataOffset(const ZipEntry & zipEntry,const uint16_t extraSize) const524 ZipPos ZipFile::GetEntryDataOffset(const ZipEntry &zipEntry, const uint16_t extraSize) const
525 {
526 // get entry data offset relative file
527 ZipPos offset = zipEntry.localHeaderOffset;
528
529 offset += GetLocalHeaderSize(zipEntry.fileName.length(), extraSize);
530 offset += fileStartPos_;
531
532 return offset;
533 }
534
GetDataOffsetRelative(const std::string & file,ZipPos & offset,uint32_t & length) const535 bool ZipFile::GetDataOffsetRelative(const std::string &file, ZipPos &offset, uint32_t &length) const
536 {
537 APP_LOGD("get data relative offset for file %{private}s", file.c_str());
538
539 ZipEntry zipEntry;
540 if (!GetEntry(file, zipEntry)) {
541 APP_LOGE("extract file: not find file");
542 return false;
543 }
544
545 uint16_t extraSize = 0;
546 if (!CheckCoherencyLocalHeader(zipEntry, extraSize)) {
547 APP_LOGE("check coherency local header failed");
548 return false;
549 }
550
551 offset = GetEntryDataOffset(zipEntry, extraSize);
552 length = zipEntry.compressedSize;
553 return true;
554 }
555
ExtractFile(const std::string & file,std::ostream & dest) const556 bool ZipFile::ExtractFile(const std::string &file, std::ostream &dest) const
557 {
558 APP_LOGD("extract file %{private}s", file.c_str());
559
560 ZipEntry zipEntry;
561 if (!GetEntry(file, zipEntry)) {
562 APP_LOGE("extract file: not find file");
563 return false;
564 }
565
566 uint16_t extraSize = 0;
567 if (!CheckCoherencyLocalHeader(zipEntry, extraSize)) {
568 APP_LOGE("check coherency local header failed");
569 return false;
570 }
571
572 bool ret = true;
573 if (zipEntry.compressionMethod == 0) {
574 ret = UnzipWithStore(zipEntry, extraSize, dest);
575 } else {
576 ret = UnzipWithInflated(zipEntry, extraSize, dest);
577 }
578
579 return ret;
580 }
581
582 } // namespace AppExecFwk
583 } // namespace OHOS
584