1 /*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 //
18 // Access to Zip archives.
19 //
20
21 #define LOG_TAG "zip"
22
23 #include <utils/Log.h>
24 #include <ziparchive/zip_archive.h>
25
26 #include "ZipFile.h"
27
28 #include <zlib.h>
29
30 #include "zopfli/deflate.h"
31
32 #include <memory.h>
33 #include <sys/stat.h>
34 #include <errno.h>
35 #include <assert.h>
36 #include <inttypes.h>
37
38 _Static_assert(sizeof(off_t) == 8, "off_t too small");
39
40 namespace android {
41
42 /*
43 * Some environments require the "b", some choke on it.
44 */
45 #define FILE_OPEN_RO "rb"
46 #define FILE_OPEN_RW "r+b"
47 #define FILE_OPEN_RW_CREATE "w+b"
48
49 /* should live somewhere else? */
errnoToStatus(int err)50 static status_t errnoToStatus(int err)
51 {
52 if (err == ENOENT)
53 return NAME_NOT_FOUND;
54 else if (err == EACCES)
55 return PERMISSION_DENIED;
56 else
57 return UNKNOWN_ERROR;
58 }
59
60 /*
61 * Open a file and parse its guts.
62 */
open(const char * zipFileName,int flags)63 status_t ZipFile::open(const char* zipFileName, int flags)
64 {
65 bool newArchive = false;
66
67 assert(mZipFp == NULL); // no reopen
68
69 if ((flags & kOpenTruncate))
70 flags |= kOpenCreate; // trunc implies create
71
72 if ((flags & kOpenReadOnly) && (flags & kOpenReadWrite))
73 return INVALID_OPERATION; // not both
74 if (!((flags & kOpenReadOnly) || (flags & kOpenReadWrite)))
75 return INVALID_OPERATION; // not neither
76 if ((flags & kOpenCreate) && !(flags & kOpenReadWrite))
77 return INVALID_OPERATION; // create requires write
78
79 if (flags & kOpenTruncate) {
80 newArchive = true;
81 } else {
82 newArchive = (access(zipFileName, F_OK) != 0);
83 if (!(flags & kOpenCreate) && newArchive) {
84 /* not creating, must already exist */
85 ALOGD("File %s does not exist", zipFileName);
86 return NAME_NOT_FOUND;
87 }
88 }
89
90 /* open the file */
91 const char* openflags;
92 if (flags & kOpenReadWrite) {
93 if (newArchive)
94 openflags = FILE_OPEN_RW_CREATE;
95 else
96 openflags = FILE_OPEN_RW;
97 } else {
98 openflags = FILE_OPEN_RO;
99 }
100 mZipFp = fopen(zipFileName, openflags);
101 if (mZipFp == NULL) {
102 int err = errno;
103 ALOGD("fopen failed: %d\n", err);
104 return errnoToStatus(err);
105 }
106
107 status_t result;
108 if (!newArchive) {
109 /*
110 * Load the central directory. If that fails, then this probably
111 * isn't a Zip archive.
112 */
113 result = readCentralDir();
114 } else {
115 /*
116 * Newly-created. The EndOfCentralDir constructor actually
117 * sets everything to be the way we want it (all zeroes). We
118 * set mNeedCDRewrite so that we create *something* if the
119 * caller doesn't add any files. (We could also just unlink
120 * the file if it's brand new and nothing was added, but that's
121 * probably doing more than we really should -- the user might
122 * have a need for empty zip files.)
123 */
124 mNeedCDRewrite = true;
125 result = OK;
126 }
127
128 if (flags & kOpenReadOnly)
129 mReadOnly = true;
130 else
131 assert(!mReadOnly);
132
133 return result;
134 }
135
136 /*
137 * Return the Nth entry in the archive.
138 */
getEntryByIndex(int idx) const139 ZipEntry* ZipFile::getEntryByIndex(int idx) const
140 {
141 if (idx < 0 || idx >= (int) mEntries.size())
142 return NULL;
143
144 return mEntries[idx];
145 }
146
147 /*
148 * Find an entry by name.
149 */
getEntryByName(const char * fileName) const150 ZipEntry* ZipFile::getEntryByName(const char* fileName) const
151 {
152 /*
153 * Do a stupid linear string-compare search.
154 *
155 * There are various ways to speed this up, especially since it's rare
156 * to intermingle changes to the archive with "get by name" calls. We
157 * don't want to sort the mEntries vector itself, however, because
158 * it's used to recreate the Central Directory.
159 *
160 * (Hash table works, parallel list of pointers in sorted order is good.)
161 */
162 int idx;
163
164 for (idx = mEntries.size()-1; idx >= 0; idx--) {
165 ZipEntry* pEntry = mEntries[idx];
166 if (!pEntry->getDeleted() &&
167 strcmp(fileName, pEntry->getFileName()) == 0)
168 {
169 return pEntry;
170 }
171 }
172
173 return NULL;
174 }
175
176 /*
177 * Empty the mEntries vector.
178 */
discardEntries(void)179 void ZipFile::discardEntries(void)
180 {
181 int count = mEntries.size();
182
183 while (--count >= 0)
184 delete mEntries[count];
185
186 mEntries.clear();
187 }
188
189
190 /*
191 * Find the central directory and read the contents.
192 *
193 * The fun thing about ZIP archives is that they may or may not be
194 * readable from start to end. In some cases, notably for archives
195 * that were written to stdout, the only length information is in the
196 * central directory at the end of the file.
197 *
198 * Of course, the central directory can be followed by a variable-length
199 * comment field, so we have to scan through it backwards. The comment
200 * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff
201 * itself, plus apparently sometimes people throw random junk on the end
202 * just for the fun of it.
203 *
204 * This is all a little wobbly. If the wrong value ends up in the EOCD
205 * area, we're hosed. This appears to be the way that everbody handles
206 * it though, so we're in pretty good company if this fails.
207 */
readCentralDir(void)208 status_t ZipFile::readCentralDir(void)
209 {
210 fseeko(mZipFp, 0, SEEK_END);
211 off_t fileLength = ftello(mZipFp);
212 rewind(mZipFp);
213
214 /* too small to be a ZIP archive? */
215 if (fileLength < EndOfCentralDir::kEOCDLen) {
216 ALOGD("Length is %lld -- too small\n", (long long) fileLength);
217 return INVALID_OPERATION;
218 }
219
220 off_t seekStart;
221 size_t readAmount;
222 if (fileLength > EndOfCentralDir::kMaxEOCDSearch) {
223 seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch;
224 readAmount = EndOfCentralDir::kMaxEOCDSearch;
225 } else {
226 seekStart = 0;
227 readAmount = fileLength;
228 }
229 if (fseeko(mZipFp, seekStart, SEEK_SET) != 0) {
230 ALOGD("Failure seeking to end of zip at %lld", (long long) seekStart);
231 return UNKNOWN_ERROR;
232 }
233
234 /* read the last part of the file into the buffer */
235 uint8_t buf[EndOfCentralDir::kMaxEOCDSearch];
236 if (fread(buf, 1, readAmount, mZipFp) != readAmount) {
237 if (feof(mZipFp)) {
238 ALOGW("fread %zu bytes failed, unexpected EOF", readAmount);
239 } else {
240 ALOGW("fread %zu bytes failed, %s", readAmount, strerror(errno));
241 }
242 return UNKNOWN_ERROR;
243 }
244
245 /* find the end-of-central-dir magic */
246 int i;
247 for (i = readAmount - 4; i >= 0; i--) {
248 if (buf[i] == 0x50 &&
249 ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature)
250 {
251 ALOGV("+++ Found EOCD at buf+%d\n", i);
252 break;
253 }
254 }
255 if (i < 0) {
256 ALOGD("EOCD not found, not Zip\n");
257 return INVALID_OPERATION;
258 }
259
260 /* extract eocd values */
261 status_t result = mEOCD.readBuf(buf + i, readAmount - i);
262 if (result != OK) {
263 ALOGD("Failure reading %zu bytes of EOCD values", readAmount - i);
264 return result;
265 }
266 //mEOCD.dump();
267
268 if (mEOCD.mDiskNumber != 0 || mEOCD.mDiskWithCentralDir != 0 ||
269 mEOCD.mNumEntries != mEOCD.mTotalNumEntries)
270 {
271 ALOGD("Archive spanning not supported\n");
272 return INVALID_OPERATION;
273 }
274
275 /*
276 * So far so good. "mCentralDirSize" is the size in bytes of the
277 * central directory, so we can just seek back that far to find it.
278 * We can also seek forward mCentralDirOffset bytes from the
279 * start of the file.
280 *
281 * We're not guaranteed to have the rest of the central dir in the
282 * buffer, nor are we guaranteed that the central dir will have any
283 * sort of convenient size. We need to skip to the start of it and
284 * read the header, then the other goodies.
285 *
286 * The only thing we really need right now is the file comment, which
287 * we're hoping to preserve.
288 */
289 if (fseeko(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
290 ALOGD("Failure seeking to central dir offset %" PRIu32 "\n",
291 mEOCD.mCentralDirOffset);
292 return UNKNOWN_ERROR;
293 }
294
295 /*
296 * Loop through and read the central dir entries.
297 */
298 ALOGV("Scanning %" PRIu16 " entries...\n", mEOCD.mTotalNumEntries);
299 int entry;
300 for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) {
301 ZipEntry* pEntry = new ZipEntry;
302
303 result = pEntry->initFromCDE(mZipFp);
304 if (result != OK) {
305 ALOGD("initFromCDE failed\n");
306 delete pEntry;
307 return result;
308 }
309
310 mEntries.add(pEntry);
311 }
312
313
314 /*
315 * If all went well, we should now be back at the EOCD.
316 */
317 {
318 uint8_t checkBuf[4];
319 if (fread(checkBuf, 1, 4, mZipFp) != 4) {
320 if (feof(mZipFp)) {
321 ALOGW("fread EOCD failed, unexpected EOF");
322 } else {
323 ALOGW("fread EOCD failed, %s", strerror(errno));
324 }
325 return INVALID_OPERATION;
326 }
327 if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) {
328 ALOGD("EOCD read check failed\n");
329 return UNKNOWN_ERROR;
330 }
331 ALOGV("+++ EOCD read check passed\n");
332 }
333
334 return OK;
335 }
336
337
338 /*
339 * Add a new file to the archive.
340 *
341 * This requires creating and populating a ZipEntry structure, and copying
342 * the data into the file at the appropriate position. The "appropriate
343 * position" is the current location of the central directory, which we
344 * casually overwrite (we can put it back later).
345 *
346 * If we were concerned about safety, we would want to make all changes
347 * in a temp file and then overwrite the original after everything was
348 * safely written. Not really a concern for us.
349 */
addCommon(const char * fileName,const void * data,size_t size,const char * storageName,int compressionMethod,ZipEntry ** ppEntry)350 status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size,
351 const char* storageName, int compressionMethod, ZipEntry** ppEntry)
352 {
353 ZipEntry* pEntry = NULL;
354 status_t result = OK;
355 off_t lfhPosn, startPosn, endPosn, uncompressedLen;
356 uint32_t crc = 0;
357 time_t modWhen;
358
359 if (mReadOnly)
360 return INVALID_OPERATION;
361
362 assert(compressionMethod == ZipEntry::kCompressDeflated ||
363 compressionMethod == ZipEntry::kCompressStored);
364
365 /* make sure we're in a reasonable state */
366 assert(mZipFp != NULL);
367 assert(mEntries.size() == mEOCD.mTotalNumEntries);
368
369 /* make sure it doesn't already exist */
370 if (getEntryByName(storageName) != NULL)
371 return ALREADY_EXISTS;
372
373 FILE* inputFp = NULL;
374 if (!data) {
375 inputFp = fopen(fileName, FILE_OPEN_RO);
376 if (inputFp == NULL)
377 return errnoToStatus(errno);
378 }
379
380 if (fseeko(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
381 result = UNKNOWN_ERROR;
382 goto bail;
383 }
384
385 pEntry = new ZipEntry;
386 pEntry->initNew(storageName, NULL);
387
388 /*
389 * From here on out, failures are more interesting.
390 */
391 mNeedCDRewrite = true;
392
393 /*
394 * Write the LFH, even though it's still mostly blank. We need it
395 * as a place-holder. In theory the LFH isn't necessary, but in
396 * practice some utilities demand it.
397 */
398 lfhPosn = ftello(mZipFp);
399 pEntry->mLFH.write(mZipFp);
400 startPosn = ftello(mZipFp);
401
402 /*
403 * Copy the data in, possibly compressing it as we go.
404 */
405 if (compressionMethod == ZipEntry::kCompressDeflated) {
406 bool failed = false;
407 result = compressFpToFp(mZipFp, inputFp, data, size, &crc);
408 if (result != OK) {
409 ALOGD("compression failed, storing\n");
410 failed = true;
411 } else {
412 /*
413 * Make sure it has compressed "enough". This probably ought
414 * to be set through an API call, but I don't expect our
415 * criteria to change over time.
416 */
417 off_t src = inputFp ? ftello(inputFp) : size;
418 off_t dst = ftello(mZipFp) - startPosn;
419 if (dst + (dst / 10) > src) {
420 ALOGD("insufficient compression (src=%lld dst=%lld), storing\n",
421 (long long) src, (long long) dst);
422 failed = true;
423 }
424 }
425
426 if (failed) {
427 compressionMethod = ZipEntry::kCompressStored;
428 if (inputFp) rewind(inputFp);
429 fseeko(mZipFp, startPosn, SEEK_SET);
430 /* fall through to kCompressStored case */
431 }
432 }
433 /* handle "no compression" request, or failed compression from above */
434 if (compressionMethod == ZipEntry::kCompressStored) {
435 if (inputFp) {
436 result = copyFpToFp(mZipFp, inputFp, &crc);
437 } else {
438 result = copyDataToFp(mZipFp, data, size, &crc);
439 }
440 if (result != OK) {
441 // don't need to truncate; happens in CDE rewrite
442 ALOGD("failed copying data in\n");
443 goto bail;
444 }
445 }
446
447 // currently seeked to end of file
448 uncompressedLen = inputFp ? ftello(inputFp) : size;
449
450 /*
451 * We could write the "Data Descriptor", but there doesn't seem to
452 * be any point since we're going to go back and write the LFH.
453 *
454 * Update file offsets.
455 */
456 endPosn = ftello(mZipFp); // seeked to end of compressed data
457
458 /*
459 * Success! Fill out new values.
460 */
461 pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc,
462 compressionMethod);
463 modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp));
464 pEntry->setModWhen(modWhen);
465 pEntry->setLFHOffset(lfhPosn);
466 mEOCD.mNumEntries++;
467 mEOCD.mTotalNumEntries++;
468 mEOCD.mCentralDirSize = 0; // mark invalid; set by flush()
469 mEOCD.mCentralDirOffset = endPosn;
470
471 /*
472 * Go back and write the LFH.
473 */
474 if (fseeko(mZipFp, lfhPosn, SEEK_SET) != 0) {
475 result = UNKNOWN_ERROR;
476 goto bail;
477 }
478 pEntry->mLFH.write(mZipFp);
479
480 /*
481 * Add pEntry to the list.
482 */
483 mEntries.add(pEntry);
484 if (ppEntry != NULL)
485 *ppEntry = pEntry;
486 pEntry = NULL;
487
488 bail:
489 if (inputFp != NULL)
490 fclose(inputFp);
491 delete pEntry;
492 return result;
493 }
494
495 /*
496 * Based on the current position in the output zip, assess where the entry
497 * payload will end up if written as-is. If alignment is not satisfactory,
498 * add some padding in the extra field.
499 *
500 */
alignEntry(android::ZipEntry * pEntry,uint32_t alignTo)501 status_t ZipFile::alignEntry(android::ZipEntry* pEntry, uint32_t alignTo){
502 if (alignTo == 0 || alignTo == 1)
503 return OK;
504
505 // Calculate where the entry payload offset will end up if we were to write
506 // it as-is.
507 uint64_t expectedPayloadOffset = ftello(mZipFp) +
508 android::ZipEntry::LocalFileHeader::kLFHLen +
509 pEntry->mLFH.mFileNameLength +
510 pEntry->mLFH.mExtraFieldLength;
511
512 // If the alignment is not what was requested, add some padding in the extra
513 // so the payload ends up where is requested.
514 uint64_t alignDiff = alignTo - (expectedPayloadOffset % alignTo);
515 if (alignDiff == alignTo)
516 return OK;
517
518 return pEntry->addPadding(alignDiff);
519 }
520
521 /*
522 * Add an entry by copying it from another zip file. If "padding" is
523 * nonzero, the specified number of bytes will be added to the "extra"
524 * field in the header.
525 *
526 * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
527 */
add(const ZipFile * pSourceZip,const ZipEntry * pSourceEntry,int alignTo,ZipEntry ** ppEntry)528 status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
529 int alignTo, ZipEntry** ppEntry)
530 {
531 ZipEntry* pEntry = NULL;
532 status_t result;
533 off_t lfhPosn, endPosn;
534
535 if (mReadOnly)
536 return INVALID_OPERATION;
537
538 /* make sure we're in a reasonable state */
539 assert(mZipFp != NULL);
540 assert(mEntries.size() == mEOCD.mTotalNumEntries);
541
542 if (fseeko(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
543 result = UNKNOWN_ERROR;
544 goto bail;
545 }
546
547 pEntry = new ZipEntry;
548 if (pEntry == NULL) {
549 result = NO_MEMORY;
550 goto bail;
551 }
552
553 result = pEntry->initFromExternal(pSourceEntry);
554 if (result != OK)
555 goto bail;
556
557 result = alignEntry(pEntry, alignTo);
558 if (result != OK)
559 goto bail;
560
561 /*
562 * From here on out, failures are more interesting.
563 */
564 mNeedCDRewrite = true;
565
566 /*
567 * Write the LFH. Since we're not recompressing the data, we already
568 * have all of the fields filled out.
569 */
570 lfhPosn = ftello(mZipFp);
571 pEntry->mLFH.write(mZipFp);
572
573 /*
574 * Copy the data over.
575 *
576 * If the "has data descriptor" flag is set, we want to copy the DD
577 * fields as well. This is a fixed-size area immediately following
578 * the data.
579 */
580 if (fseeko(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0) {
581 result = UNKNOWN_ERROR;
582 goto bail;
583 }
584
585 off_t copyLen;
586 copyLen = pSourceEntry->getCompressedLen();
587 if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0)
588 copyLen += ZipEntry::kDataDescriptorLen;
589
590 if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL)
591 != OK)
592 {
593 ALOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName);
594 result = UNKNOWN_ERROR;
595 goto bail;
596 }
597
598 /*
599 * Update file offsets.
600 */
601 endPosn = ftello(mZipFp);
602
603 /*
604 * Success! Fill out new values.
605 */
606 pEntry->setLFHOffset(lfhPosn); // sets mCDE.mLocalHeaderRelOffset
607 mEOCD.mNumEntries++;
608 mEOCD.mTotalNumEntries++;
609 mEOCD.mCentralDirSize = 0; // mark invalid; set by flush()
610 mEOCD.mCentralDirOffset = endPosn;
611
612 /*
613 * Add pEntry to the list.
614 */
615 mEntries.add(pEntry);
616 if (ppEntry != NULL)
617 *ppEntry = pEntry;
618 pEntry = NULL;
619
620 result = OK;
621
622 bail:
623 delete pEntry;
624 return result;
625 }
626
627 /*
628 * Add an entry by copying it from another zip file, recompressing with
629 * Zopfli if already compressed.
630 *
631 * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
632 */
addRecompress(const ZipFile * pSourceZip,const ZipEntry * pSourceEntry,ZipEntry ** ppEntry)633 status_t ZipFile::addRecompress(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
634 ZipEntry** ppEntry)
635 {
636 ZipEntry* pEntry = NULL;
637 status_t result;
638 off_t lfhPosn, uncompressedLen;
639
640 if (mReadOnly)
641 return INVALID_OPERATION;
642
643 /* make sure we're in a reasonable state */
644 assert(mZipFp != NULL);
645 assert(mEntries.size() == mEOCD.mTotalNumEntries);
646
647 if (fseeko(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
648 result = UNKNOWN_ERROR;
649 goto bail;
650 }
651
652 pEntry = new ZipEntry;
653 if (pEntry == NULL) {
654 result = NO_MEMORY;
655 goto bail;
656 }
657
658 result = pEntry->initFromExternal(pSourceEntry);
659 if (result != OK)
660 goto bail;
661
662 /*
663 * From here on out, failures are more interesting.
664 */
665 mNeedCDRewrite = true;
666
667 /*
668 * Write the LFH, even though it's still mostly blank. We need it
669 * as a place-holder. In theory the LFH isn't necessary, but in
670 * practice some utilities demand it.
671 */
672 lfhPosn = ftello(mZipFp);
673 pEntry->mLFH.write(mZipFp);
674
675 /*
676 * Copy the data over.
677 *
678 * If the "has data descriptor" flag is set, we want to copy the DD
679 * fields as well. This is a fixed-size area immediately following
680 * the data.
681 */
682 if (fseeko(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0) {
683 result = UNKNOWN_ERROR;
684 goto bail;
685 }
686
687 uncompressedLen = pSourceEntry->getUncompressedLen();
688
689 if (pSourceEntry->isCompressed()) {
690 void *buf = pSourceZip->uncompress(pSourceEntry);
691 if (buf == NULL) {
692 result = NO_MEMORY;
693 goto bail;
694 }
695 off_t startPosn = ftello(mZipFp);
696 uint32_t crc;
697 if (compressFpToFp(mZipFp, NULL, buf, uncompressedLen, &crc) != OK) {
698 ALOGW("recompress of '%s' failed\n", pEntry->mCDE.mFileName);
699 result = UNKNOWN_ERROR;
700 free(buf);
701 goto bail;
702 }
703 off_t endPosn = ftello(mZipFp);
704 pEntry->setDataInfo(uncompressedLen, endPosn - startPosn,
705 pSourceEntry->getCRC32(), ZipEntry::kCompressDeflated);
706 free(buf);
707 } else {
708 off_t copyLen = pSourceEntry->getCompressedLen();
709 if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0)
710 copyLen += ZipEntry::kDataDescriptorLen;
711
712 if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL)
713 != OK)
714 {
715 ALOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName);
716 result = UNKNOWN_ERROR;
717 goto bail;
718 }
719 }
720
721 /*
722 * Success! Fill out new values.
723 */
724 pEntry->setLFHOffset(lfhPosn);
725 mEOCD.mNumEntries++;
726 mEOCD.mTotalNumEntries++;
727 mEOCD.mCentralDirSize = 0; // mark invalid; set by flush()
728 mEOCD.mCentralDirOffset = ftello(mZipFp);
729
730 /*
731 * Go back and write the LFH.
732 */
733 if (fseeko(mZipFp, lfhPosn, SEEK_SET) != 0) {
734 result = UNKNOWN_ERROR;
735 goto bail;
736 }
737 pEntry->mLFH.write(mZipFp);
738
739 /*
740 * Add pEntry to the list.
741 */
742 mEntries.add(pEntry);
743 if (ppEntry != NULL)
744 *ppEntry = pEntry;
745 pEntry = NULL;
746
747 result = OK;
748
749 bail:
750 delete pEntry;
751 return result;
752 }
753
754 /*
755 * Copy all of the bytes in "src" to "dst".
756 *
757 * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
758 * will be seeked immediately past the data.
759 */
copyFpToFp(FILE * dstFp,FILE * srcFp,uint32_t * pCRC32)760 status_t ZipFile::copyFpToFp(FILE* dstFp, FILE* srcFp, uint32_t* pCRC32)
761 {
762 uint8_t tmpBuf[32768];
763 size_t count;
764
765 *pCRC32 = crc32(0L, Z_NULL, 0);
766
767 while (1) {
768 count = fread(tmpBuf, 1, sizeof(tmpBuf), srcFp);
769 if (ferror(srcFp) || ferror(dstFp)) {
770 status_t status = errnoToStatus(errno);
771 ALOGW("fread %zu bytes failed, %s", count, strerror(errno));
772 return status;
773 }
774 if (count == 0)
775 break;
776
777 *pCRC32 = crc32(*pCRC32, tmpBuf, count);
778
779 if (fwrite(tmpBuf, 1, count, dstFp) != count) {
780 ALOGW("fwrite %zu bytes failed, %s", count, strerror(errno));
781 return UNKNOWN_ERROR;
782 }
783 }
784
785 return OK;
786 }
787
788 /*
789 * Copy all of the bytes in "src" to "dst".
790 *
791 * On exit, "dstFp" will be seeked immediately past the data.
792 */
copyDataToFp(FILE * dstFp,const void * data,size_t size,uint32_t * pCRC32)793 status_t ZipFile::copyDataToFp(FILE* dstFp,
794 const void* data, size_t size, uint32_t* pCRC32)
795 {
796 *pCRC32 = crc32(0L, Z_NULL, 0);
797 if (size > 0) {
798 *pCRC32 = crc32(*pCRC32, (const unsigned char*)data, size);
799 if (fwrite(data, 1, size, dstFp) != size) {
800 ALOGW("fwrite %zu bytes failed, %s", size, strerror(errno));
801 return UNKNOWN_ERROR;
802 }
803 }
804
805 return OK;
806 }
807
808 /*
809 * Copy some of the bytes in "src" to "dst".
810 *
811 * If "pCRC32" is NULL, the CRC will not be computed.
812 *
813 * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
814 * will be seeked immediately past the data just written.
815 */
copyPartialFpToFp(FILE * dstFp,FILE * srcFp,size_t length,uint32_t * pCRC32)816 status_t ZipFile::copyPartialFpToFp(FILE* dstFp, FILE* srcFp, size_t length,
817 uint32_t* pCRC32)
818 {
819 uint8_t tmpBuf[32768];
820 size_t count;
821
822 if (pCRC32 != NULL)
823 *pCRC32 = crc32(0L, Z_NULL, 0);
824
825 while (length) {
826 size_t readSize;
827
828 readSize = sizeof(tmpBuf);
829 if (readSize > length)
830 readSize = length;
831
832 count = fread(tmpBuf, 1, readSize, srcFp);
833 if (count != readSize) { // error or unexpected EOF
834 if (feof(srcFp)) {
835 ALOGW("fread %zu bytes failed, unexpected EOF", readSize);
836 } else {
837 ALOGW("fread %zu bytes failed, %s", readSize, strerror(errno));
838 }
839 return UNKNOWN_ERROR;
840 }
841
842 if (pCRC32 != NULL)
843 *pCRC32 = crc32(*pCRC32, tmpBuf, count);
844
845 if (fwrite(tmpBuf, 1, count, dstFp) != count) {
846 ALOGW("fwrite %zu bytes failed, %s", count, strerror(errno));
847 return UNKNOWN_ERROR;
848 }
849
850 length -= readSize;
851 }
852
853 return OK;
854 }
855
856 /*
857 * Compress all of the data in "srcFp" and write it to "dstFp".
858 *
859 * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
860 * will be seeked immediately past the compressed data.
861 */
compressFpToFp(FILE * dstFp,FILE * srcFp,const void * data,size_t size,uint32_t * pCRC32)862 status_t ZipFile::compressFpToFp(FILE* dstFp, FILE* srcFp,
863 const void* data, size_t size, uint32_t* pCRC32)
864 {
865 status_t result = OK;
866 const size_t kBufSize = 1024 * 1024;
867 uint8_t* inBuf = NULL;
868 uint8_t* outBuf = NULL;
869 size_t outSize = 0;
870 bool atEof = false; // no feof() aviailable yet
871 uint32_t crc;
872 ZopfliOptions options;
873 unsigned char bp = 0;
874
875 ZopfliInitOptions(&options);
876
877 crc = crc32(0L, Z_NULL, 0);
878
879 if (data) {
880 crc = crc32(crc, (const unsigned char*)data, size);
881 ZopfliDeflate(&options, 2, true, (const unsigned char*)data, size, &bp,
882 &outBuf, &outSize);
883 } else {
884 /*
885 * Create an input buffer and an output buffer.
886 */
887 inBuf = new uint8_t[kBufSize];
888 if (inBuf == NULL) {
889 result = NO_MEMORY;
890 goto bail;
891 }
892
893 /*
894 * Loop while we have data.
895 */
896 do {
897 size_t getSize;
898 getSize = fread(inBuf, 1, kBufSize, srcFp);
899 if (ferror(srcFp)) {
900 ALOGD("deflate read failed (errno=%d)\n", errno);
901 result = UNKNOWN_ERROR;
902 delete[] inBuf;
903 goto bail;
904 }
905 if (getSize < kBufSize) {
906 ALOGV("+++ got %zu bytes, EOF reached\n", getSize);
907 atEof = true;
908 }
909
910 crc = crc32(crc, inBuf, getSize);
911 ZopfliDeflate(&options, 2, atEof, inBuf, getSize, &bp, &outBuf, &outSize);
912 } while (!atEof);
913 delete[] inBuf;
914 }
915
916 ALOGV("+++ writing %zu bytes\n", outSize);
917 if (fwrite(outBuf, 1, outSize, dstFp) != outSize) {
918 ALOGW("fwrite %zu bytes failed, %s", outSize, strerror(errno));
919 result = UNKNOWN_ERROR;
920 goto bail;
921 }
922
923 *pCRC32 = crc;
924
925 bail:
926 free(outBuf);
927
928 return result;
929 }
930
931 /*
932 * Mark an entry as deleted.
933 *
934 * We will eventually need to crunch the file down, but if several files
935 * are being removed (perhaps as part of an "update" process) we can make
936 * things considerably faster by deferring the removal to "flush" time.
937 */
remove(ZipEntry * pEntry)938 status_t ZipFile::remove(ZipEntry* pEntry)
939 {
940 /*
941 * Should verify that pEntry is actually part of this archive, and
942 * not some stray ZipEntry from a different file.
943 */
944
945 /* mark entry as deleted, and mark archive as dirty */
946 pEntry->setDeleted();
947 mNeedCDRewrite = true;
948 return OK;
949 }
950
951 /*
952 * Flush any pending writes.
953 *
954 * In particular, this will crunch out deleted entries, and write the
955 * Central Directory and EOCD if we have stomped on them.
956 */
flush(void)957 status_t ZipFile::flush(void)
958 {
959 status_t result = OK;
960 off_t eocdPosn;
961 int i, count;
962
963 if (mReadOnly)
964 return INVALID_OPERATION;
965 if (!mNeedCDRewrite)
966 return OK;
967
968 assert(mZipFp != NULL);
969
970 result = crunchArchive();
971 if (result != OK)
972 return result;
973
974 if (fseeko(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) return UNKNOWN_ERROR;
975
976 count = mEntries.size();
977 for (i = 0; i < count; i++) {
978 ZipEntry* pEntry = mEntries[i];
979 pEntry->mCDE.write(mZipFp);
980 }
981
982 eocdPosn = ftello(mZipFp);
983 mEOCD.mCentralDirSize = eocdPosn - mEOCD.mCentralDirOffset;
984
985 mEOCD.write(mZipFp);
986
987 /*
988 * If we had some stuff bloat up during compression and get replaced
989 * with plain files, or if we deleted some entries, there's a lot
990 * of wasted space at the end of the file. Remove it now.
991 */
992 if (ftruncate(fileno(mZipFp), ftello(mZipFp)) != 0) {
993 ALOGW("ftruncate failed %lld: %s\n", (long long) ftello(mZipFp), strerror(errno));
994 // not fatal
995 }
996
997 /* should we clear the "newly added" flag in all entries now? */
998
999 mNeedCDRewrite = false;
1000 return OK;
1001 }
1002
1003 /*
1004 * Crunch deleted files out of an archive by shifting the later files down.
1005 *
1006 * Because we're not using a temp file, we do the operation inside the
1007 * current file.
1008 */
crunchArchive(void)1009 status_t ZipFile::crunchArchive(void)
1010 {
1011 status_t result = OK;
1012 int i, count;
1013 long delCount, adjust;
1014
1015 #if 0
1016 printf("CONTENTS:\n");
1017 for (i = 0; i < (int) mEntries.size(); i++) {
1018 printf(" %d: lfhOff=%ld del=%d\n",
1019 i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted());
1020 }
1021 printf(" END is %ld\n", (long) mEOCD.mCentralDirOffset);
1022 #endif
1023
1024 /*
1025 * Roll through the set of files, shifting them as appropriate. We
1026 * could probably get a slight performance improvement by sliding
1027 * multiple files down at once (because we could use larger reads
1028 * when operating on batches of small files), but it's not that useful.
1029 */
1030 count = mEntries.size();
1031 delCount = adjust = 0;
1032 for (i = 0; i < count; i++) {
1033 ZipEntry* pEntry = mEntries[i];
1034 long span;
1035
1036 if (pEntry->getLFHOffset() != 0) {
1037 long nextOffset;
1038
1039 /* Get the length of this entry by finding the offset
1040 * of the next entry. Directory entries don't have
1041 * file offsets, so we need to find the next non-directory
1042 * entry.
1043 */
1044 nextOffset = 0;
1045 for (int ii = i+1; nextOffset == 0 && ii < count; ii++)
1046 nextOffset = mEntries[ii]->getLFHOffset();
1047 if (nextOffset == 0)
1048 nextOffset = mEOCD.mCentralDirOffset;
1049 span = nextOffset - pEntry->getLFHOffset();
1050
1051 assert(span >= ZipEntry::LocalFileHeader::kLFHLen);
1052 } else {
1053 /* This is a directory entry. It doesn't have
1054 * any actual file contents, so there's no need to
1055 * move anything.
1056 */
1057 span = 0;
1058 }
1059
1060 //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n",
1061 // i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count);
1062
1063 if (pEntry->getDeleted()) {
1064 adjust += span;
1065 delCount++;
1066
1067 delete pEntry;
1068 mEntries.removeAt(i);
1069
1070 /* adjust loop control */
1071 count--;
1072 i--;
1073 } else if (span != 0 && adjust > 0) {
1074 /* shuffle this entry back */
1075 //printf("+++ Shuffling '%s' back %ld\n",
1076 // pEntry->getFileName(), adjust);
1077 result = filemove(mZipFp, pEntry->getLFHOffset() - adjust,
1078 pEntry->getLFHOffset(), span);
1079 if (result != OK) {
1080 /* this is why you use a temp file */
1081 ALOGE("error during crunch - archive is toast\n");
1082 return result;
1083 }
1084
1085 pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust);
1086 }
1087 }
1088
1089 /*
1090 * Fix EOCD info. We have to wait until the end to do some of this
1091 * because we use mCentralDirOffset to determine "span" for the
1092 * last entry.
1093 */
1094 mEOCD.mCentralDirOffset -= adjust;
1095 mEOCD.mNumEntries -= delCount;
1096 mEOCD.mTotalNumEntries -= delCount;
1097 mEOCD.mCentralDirSize = 0; // mark invalid; set by flush()
1098
1099 assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries);
1100 assert(mEOCD.mNumEntries == count);
1101
1102 return result;
1103 }
1104
1105 /*
1106 * Works like memmove(), but on pieces of a file.
1107 */
filemove(FILE * fp,off_t dst,off_t src,size_t n)1108 status_t ZipFile::filemove(FILE* fp, off_t dst, off_t src, size_t n)
1109 {
1110 if (dst == src || n <= 0)
1111 return OK;
1112
1113 uint8_t readBuf[32768];
1114
1115 if (dst < src) {
1116 /* shift stuff toward start of file; must read from start */
1117 while (n != 0) {
1118 size_t getSize = sizeof(readBuf);
1119 if (getSize > n)
1120 getSize = n;
1121
1122 if (fseeko(fp, src, SEEK_SET) != 0) {
1123 ALOGW("filemove src seek %lld failed, %s",
1124 (long long) src, strerror(errno));
1125 return UNKNOWN_ERROR;
1126 }
1127
1128 if (fread(readBuf, 1, getSize, fp) != getSize) {
1129 if (feof(fp)) {
1130 ALOGW("fread %zu bytes off=%lld failed, unexpected EOF",
1131 getSize, (long long) src);
1132 } else {
1133 ALOGW("fread %zu bytes off=%lld failed, %s",
1134 getSize, (long long) src, strerror(errno));
1135 }
1136 return UNKNOWN_ERROR;
1137 }
1138
1139 if (fseeko(fp, dst, SEEK_SET) != 0) {
1140 ALOGW("filemove dst seek %lld failed, %s",
1141 (long long) dst, strerror(errno));
1142 return UNKNOWN_ERROR;
1143 }
1144
1145 if (fwrite(readBuf, 1, getSize, fp) != getSize) {
1146 ALOGW("filemove write %zu off=%lld failed, %s",
1147 getSize, (long long) dst, strerror(errno));
1148 return UNKNOWN_ERROR;
1149 }
1150
1151 src += getSize;
1152 dst += getSize;
1153 n -= getSize;
1154 }
1155 } else {
1156 /* shift stuff toward end of file; must read from end */
1157 assert(false); // write this someday, maybe
1158 return UNKNOWN_ERROR;
1159 }
1160
1161 return OK;
1162 }
1163
1164
1165 /*
1166 * Get the modification time from a file descriptor.
1167 */
getModTime(int fd)1168 time_t ZipFile::getModTime(int fd)
1169 {
1170 struct stat sb;
1171
1172 if (fstat(fd, &sb) < 0) {
1173 ALOGD("HEY: fstat on fd %d failed\n", fd);
1174 return (time_t) -1;
1175 }
1176
1177 return sb.st_mtime;
1178 }
1179
1180
1181 #if 0 /* this is a bad idea */
1182 /*
1183 * Get a copy of the Zip file descriptor.
1184 *
1185 * We don't allow this if the file was opened read-write because we tend
1186 * to leave the file contents in an uncertain state between calls to
1187 * flush(). The duplicated file descriptor should only be valid for reads.
1188 */
1189 int ZipFile::getZipFd(void) const
1190 {
1191 if (!mReadOnly)
1192 return INVALID_OPERATION;
1193 assert(mZipFp != NULL);
1194
1195 int fd;
1196 fd = dup(fileno(mZipFp));
1197 if (fd < 0) {
1198 ALOGD("didn't work, errno=%d\n", errno);
1199 }
1200
1201 return fd;
1202 }
1203 #endif
1204
1205
1206 #if 0
1207 /*
1208 * Expand data.
1209 */
1210 bool ZipFile::uncompress(const ZipEntry* pEntry, void* buf) const
1211 {
1212 return false;
1213 }
1214 #endif
1215
1216 class BufferWriter : public zip_archive::Writer {
1217 public:
BufferWriter(void * buf,size_t size)1218 BufferWriter(void* buf, size_t size) : Writer(),
1219 buf_(reinterpret_cast<uint8_t*>(buf)), size_(size), bytes_written_(0) {}
1220
Append(uint8_t * buf,size_t buf_size)1221 bool Append(uint8_t* buf, size_t buf_size) override {
1222 if (bytes_written_ + buf_size > size_) {
1223 return false;
1224 }
1225
1226 memcpy(buf_ + bytes_written_, buf, buf_size);
1227 bytes_written_ += buf_size;
1228 return true;
1229 }
1230
1231 private:
1232 uint8_t* const buf_;
1233 const size_t size_;
1234 size_t bytes_written_;
1235 };
1236
1237 class FileReader : public zip_archive::Reader {
1238 public:
FileReader(FILE * fp)1239 FileReader(FILE* fp) : Reader(), fp_(fp), current_offset_(0) {
1240 }
1241
ReadAtOffset(uint8_t * buf,size_t len,off64_t offset) const1242 bool ReadAtOffset(uint8_t* buf, size_t len, off64_t offset) const {
1243 // Data is usually requested sequentially, so this helps avoid pointless
1244 // seeks every time we perform a read. There's an impedence mismatch
1245 // here because the original API was designed around pread and pwrite.
1246 if (offset != current_offset_) {
1247 if (fseeko(fp_, offset, SEEK_SET) != 0) {
1248 return false;
1249 }
1250
1251 current_offset_ = offset;
1252 }
1253
1254 size_t read = fread(buf, 1, len, fp_);
1255 if (read != len) {
1256 return false;
1257 }
1258
1259 current_offset_ += read;
1260 return true;
1261 }
1262
1263 private:
1264 FILE* fp_;
1265 mutable off64_t current_offset_;
1266 };
1267
1268 // free the memory when you're done
uncompress(const ZipEntry * entry) const1269 void* ZipFile::uncompress(const ZipEntry* entry) const
1270 {
1271 size_t unlen = entry->getUncompressedLen();
1272 size_t clen = entry->getCompressedLen();
1273
1274 void* buf = malloc(unlen);
1275 if (buf == NULL) {
1276 return NULL;
1277 }
1278
1279 fseeko(mZipFp, 0, SEEK_SET);
1280
1281 off_t offset = entry->getFileOffset();
1282 if (fseeko(mZipFp, offset, SEEK_SET) != 0) {
1283 goto bail;
1284 }
1285
1286 switch (entry->getCompressionMethod())
1287 {
1288 case ZipEntry::kCompressStored: {
1289 ssize_t amt = fread(buf, 1, unlen, mZipFp);
1290 if (amt != (ssize_t)unlen) {
1291 goto bail;
1292 }
1293 #if 0
1294 printf("data...\n");
1295 const unsigned char* p = (unsigned char*)buf;
1296 const unsigned char* end = p+unlen;
1297 for (int i=0; i<32 && p < end; i++) {
1298 printf("0x%08x ", (int)(offset+(i*0x10)));
1299 for (int j=0; j<0x10 && p < end; j++) {
1300 printf(" %02x", *p);
1301 p++;
1302 }
1303 printf("\n");
1304 }
1305 #endif
1306
1307 }
1308 break;
1309 case ZipEntry::kCompressDeflated: {
1310 const FileReader reader(mZipFp);
1311 BufferWriter writer(buf, unlen);
1312 if (zip_archive::Inflate(reader, clen, unlen, &writer, nullptr) != 0) {
1313 goto bail;
1314 }
1315 break;
1316 }
1317 default:
1318 goto bail;
1319 }
1320 return buf;
1321
1322 bail:
1323 free(buf);
1324 return NULL;
1325 }
1326
1327
1328 /*
1329 * ===========================================================================
1330 * ZipFile::EndOfCentralDir
1331 * ===========================================================================
1332 */
1333
1334 /*
1335 * Read the end-of-central-dir fields.
1336 *
1337 * "buf" should be positioned at the EOCD signature, and should contain
1338 * the entire EOCD area including the comment.
1339 */
readBuf(const uint8_t * buf,int len)1340 status_t ZipFile::EndOfCentralDir::readBuf(const uint8_t* buf, int len)
1341 {
1342 /* don't allow re-use */
1343 assert(mComment == NULL);
1344
1345 if (len < kEOCDLen) {
1346 /* looks like ZIP file got truncated */
1347 ALOGD(" Zip EOCD: expected >= %d bytes, found %d\n",
1348 kEOCDLen, len);
1349 return INVALID_OPERATION;
1350 }
1351
1352 /* this should probably be an assert() */
1353 if (ZipEntry::getLongLE(&buf[0x00]) != kSignature)
1354 return UNKNOWN_ERROR;
1355
1356 mDiskNumber = ZipEntry::getShortLE(&buf[0x04]);
1357 mDiskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]);
1358 mNumEntries = ZipEntry::getShortLE(&buf[0x08]);
1359 mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]);
1360 mCentralDirSize = ZipEntry::getLongLE(&buf[0x0c]);
1361 mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]);
1362 mCommentLen = ZipEntry::getShortLE(&buf[0x14]);
1363
1364 // TODO: validate mCentralDirOffset
1365
1366 if (mCommentLen > 0) {
1367 if (kEOCDLen + mCommentLen > len) {
1368 ALOGD("EOCD(%d) + comment(%" PRIu16 ") exceeds len (%d)\n",
1369 kEOCDLen, mCommentLen, len);
1370 return UNKNOWN_ERROR;
1371 }
1372 mComment = new uint8_t[mCommentLen];
1373 memcpy(mComment, buf + kEOCDLen, mCommentLen);
1374 }
1375
1376 return OK;
1377 }
1378
1379 /*
1380 * Write an end-of-central-directory section.
1381 */
write(FILE * fp)1382 status_t ZipFile::EndOfCentralDir::write(FILE* fp)
1383 {
1384 uint8_t buf[kEOCDLen];
1385
1386 ZipEntry::putLongLE(&buf[0x00], kSignature);
1387 ZipEntry::putShortLE(&buf[0x04], mDiskNumber);
1388 ZipEntry::putShortLE(&buf[0x06], mDiskWithCentralDir);
1389 ZipEntry::putShortLE(&buf[0x08], mNumEntries);
1390 ZipEntry::putShortLE(&buf[0x0a], mTotalNumEntries);
1391 ZipEntry::putLongLE(&buf[0x0c], mCentralDirSize);
1392 ZipEntry::putLongLE(&buf[0x10], mCentralDirOffset);
1393 ZipEntry::putShortLE(&buf[0x14], mCommentLen);
1394
1395 if (fwrite(buf, 1, kEOCDLen, fp) != kEOCDLen) {
1396 ALOGW("fwrite EOCD failed, %s", strerror(errno));
1397 return UNKNOWN_ERROR;
1398 }
1399 if (mCommentLen > 0) {
1400 assert(mComment != NULL);
1401 if (fwrite(mComment, mCommentLen, 1, fp) != mCommentLen) {
1402 ALOGW("fwrite %d bytes failed, %s",
1403 (int) mCommentLen, strerror(errno));
1404 return UNKNOWN_ERROR;
1405 }
1406 }
1407
1408 return OK;
1409 }
1410
1411 /*
1412 * Dump the contents of an EndOfCentralDir object.
1413 */
dump(void) const1414 void ZipFile::EndOfCentralDir::dump(void) const
1415 {
1416 ALOGD(" EndOfCentralDir contents:\n");
1417 ALOGD(" diskNum=%" PRIu16 " diskWCD=%" PRIu16 " numEnt=%" PRIu16 " totalNumEnt=%" PRIu16 "\n",
1418 mDiskNumber, mDiskWithCentralDir, mNumEntries, mTotalNumEntries);
1419 ALOGD(" centDirSize=%" PRIu32 " centDirOff=%" PRIu32 " commentLen=%" PRIu32 "\n",
1420 mCentralDirSize, mCentralDirOffset, mCommentLen);
1421 }
1422
1423 } // namespace android
1424