android_system_core/libzipfile/centraldir.c
Doug Zongker 287c71ca84 fix decompression bug in fastboot
fastboot passes the *uncompressed* length of the file as the length of
the input to the inflate() call, which happens to work unless the
compressed data is actually larger than the uncompressed data (which
it can be for very small files).  Fix this to pass the correct
compressed length down to the inflate call.
2009-06-16 17:36:04 -07:00

253 lines
7.4 KiB
C

#include "private.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
enum {
// finding the directory
CD_SIGNATURE = 0x06054b50,
EOCD_LEN = 22, // EndOfCentralDir len, excl. comment
MAX_COMMENT_LEN = 65535,
MAX_EOCD_SEARCH = MAX_COMMENT_LEN + EOCD_LEN,
// central directory entries
ENTRY_SIGNATURE = 0x02014b50,
ENTRY_LEN = 46, // CentralDirEnt len, excl. var fields
// local file header
LFH_SIZE = 30,
};
unsigned int
read_le_int(const unsigned char* buf)
{
return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
}
unsigned int
read_le_short(const unsigned char* buf)
{
return buf[0] | (buf[1] << 8);
}
static int
read_central_dir_values(Zipfile* file, const unsigned char* buf, int len)
{
if (len < EOCD_LEN) {
// looks like ZIP file got truncated
fprintf(stderr, " Zip EOCD: expected >= %d bytes, found %d\n",
EOCD_LEN, len);
return -1;
}
file->disknum = read_le_short(&buf[0x04]);
file->diskWithCentralDir = read_le_short(&buf[0x06]);
file->entryCount = read_le_short(&buf[0x08]);
file->totalEntryCount = read_le_short(&buf[0x0a]);
file->centralDirSize = read_le_int(&buf[0x0c]);
file->centralDirOffest = read_le_int(&buf[0x10]);
file->commentLen = read_le_short(&buf[0x14]);
if (file->commentLen > 0) {
if (EOCD_LEN + file->commentLen > len) {
fprintf(stderr, "EOCD(%d) + comment(%d) exceeds len (%d)\n",
EOCD_LEN, file->commentLen, len);
return -1;
}
file->comment = buf + EOCD_LEN;
}
return 0;
}
static int
read_central_directory_entry(Zipfile* file, Zipentry* entry,
const unsigned char** buf, ssize_t* len)
{
const unsigned char* p;
unsigned short versionMadeBy;
unsigned short versionToExtract;
unsigned short gpBitFlag;
unsigned short compressionMethod;
unsigned short lastModFileTime;
unsigned short lastModFileDate;
unsigned long crc32;
unsigned short extraFieldLength;
unsigned short fileCommentLength;
unsigned short diskNumberStart;
unsigned short internalAttrs;
unsigned long externalAttrs;
unsigned long localHeaderRelOffset;
const unsigned char* extraField;
const unsigned char* fileComment;
unsigned int dataOffset;
unsigned short lfhExtraFieldSize;
p = *buf;
if (*len < ENTRY_LEN) {
fprintf(stderr, "cde entry not large enough\n");
return -1;
}
if (read_le_int(&p[0x00]) != ENTRY_SIGNATURE) {
fprintf(stderr, "Whoops: didn't find expected signature\n");
return -1;
}
versionMadeBy = read_le_short(&p[0x04]);
versionToExtract = read_le_short(&p[0x06]);
gpBitFlag = read_le_short(&p[0x08]);
entry->compressionMethod = read_le_short(&p[0x0a]);
lastModFileTime = read_le_short(&p[0x0c]);
lastModFileDate = read_le_short(&p[0x0e]);
crc32 = read_le_int(&p[0x10]);
entry->compressedSize = read_le_int(&p[0x14]);
entry->uncompressedSize = read_le_int(&p[0x18]);
entry->fileNameLength = read_le_short(&p[0x1c]);
extraFieldLength = read_le_short(&p[0x1e]);
fileCommentLength = read_le_short(&p[0x20]);
diskNumberStart = read_le_short(&p[0x22]);
internalAttrs = read_le_short(&p[0x24]);
externalAttrs = read_le_int(&p[0x26]);
localHeaderRelOffset = read_le_int(&p[0x2a]);
p += ENTRY_LEN;
// filename
if (entry->fileNameLength != 0) {
entry->fileName = p;
} else {
entry->fileName = NULL;
}
p += entry->fileNameLength;
// extra field
if (extraFieldLength != 0) {
extraField = p;
} else {
extraField = NULL;
}
p += extraFieldLength;
// comment, if any
if (fileCommentLength != 0) {
fileComment = p;
} else {
fileComment = NULL;
}
p += fileCommentLength;
*buf = p;
// the size of the extraField in the central dir is how much data there is,
// but the one in the local file header also contains some padding.
p = file->buf + localHeaderRelOffset;
extraFieldLength = read_le_short(&p[0x1c]);
dataOffset = localHeaderRelOffset + LFH_SIZE
+ entry->fileNameLength + extraFieldLength;
entry->data = file->buf + dataOffset;
#if 0
printf("file->buf=%p entry->data=%p dataOffset=%x localHeaderRelOffset=%d "
"entry->fileNameLength=%d extraFieldLength=%d\n",
file->buf, entry->data, dataOffset, localHeaderRelOffset,
entry->fileNameLength, extraFieldLength);
#endif
return 0;
}
/*
* Find the central directory and read the contents.
*
* The fun thing about ZIP archives is that they may or may not be
* readable from start to end. In some cases, notably for archives
* that were written to stdout, the only length information is in the
* central directory at the end of the file.
*
* Of course, the central directory can be followed by a variable-length
* comment field, so we have to scan through it backwards. The comment
* is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff
* itself, plus apparently sometimes people throw random junk on the end
* just for the fun of it.
*
* This is all a little wobbly. If the wrong value ends up in the EOCD
* area, we're hosed. This appears to be the way that everbody handles
* it though, so we're in pretty good company if this fails.
*/
int
read_central_dir(Zipfile *file)
{
int err;
const unsigned char* buf = file->buf;
ssize_t bufsize = file->bufsize;
const unsigned char* eocd;
const unsigned char* p;
const unsigned char* start;
ssize_t len;
int i;
// too small to be a ZIP archive?
if (bufsize < EOCD_LEN) {
fprintf(stderr, "Length is %d -- too small\n", bufsize);
goto bail;
}
// find the end-of-central-dir magic
if (bufsize > MAX_EOCD_SEARCH) {
start = buf + bufsize - MAX_EOCD_SEARCH;
} else {
start = buf;
}
p = buf + bufsize - 4;
while (p >= start) {
if (*p == 0x50 && read_le_int(p) == CD_SIGNATURE) {
eocd = p;
break;
}
p--;
}
if (p < start) {
fprintf(stderr, "EOCD not found, not Zip\n");
goto bail;
}
// extract eocd values
err = read_central_dir_values(file, eocd, (buf+bufsize)-eocd);
if (err != 0) {
goto bail;
}
if (file->disknum != 0
|| file->diskWithCentralDir != 0
|| file->entryCount != file->totalEntryCount) {
fprintf(stderr, "Archive spanning not supported\n");
goto bail;
}
// Loop through and read the central dir entries.
p = buf + file->centralDirOffest;
len = (buf+bufsize)-p;
for (i=0; i < file->totalEntryCount; i++) {
Zipentry* entry = malloc(sizeof(Zipentry));
memset(entry, sizeof(Zipentry), 0);
err = read_central_directory_entry(file, entry, &p, &len);
if (err != 0) {
fprintf(stderr, "read_central_directory_entry failed\n");
free(entry);
goto bail;
}
// add it to our list
entry->next = file->entries;
file->entries = entry;
}
return 0;
bail:
return -1;
}