зеркало из https://github.com/mozilla/gecko-dev.git
564 строки
15 KiB
C++
564 строки
15 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public License
|
|
* Version 1.0 (the "NPL"); you may not use this file except in
|
|
* compliance with the NPL. You may obtain a copy of the NPL at
|
|
* http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the NPL is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
|
|
* for the specific language governing rights and limitations under the
|
|
* NPL.
|
|
*
|
|
* The Initial Developer of this code under the NPL is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
|
|
* Reserved.
|
|
*/
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "plstr.h"
|
|
#include "prprf.h"
|
|
|
|
#include "ZipArchive.h"
|
|
#include "CUtils.h"
|
|
#include "StringUtils.h"
|
|
#include "LogModule.h"
|
|
#include "MemoryAccess.h"
|
|
|
|
#include "zlib.h"
|
|
|
|
|
|
UT_DEFINE_LOG_MODULE(ZipArchive);
|
|
|
|
/* External layout of various zip file records. */
|
|
|
|
struct External_LocalFileHeader
|
|
{
|
|
char signature[4]; // 'PK\003\004'
|
|
char version_needed_to_extract[2];
|
|
char general_purpose_bit_flag[2];
|
|
char compression_method[2];
|
|
char last_mod_file_time[2];
|
|
char last_mod_file_date[2];
|
|
char crc32[4];
|
|
char compressed_size[4];
|
|
char uncompressed_size[4];
|
|
char filename_length[2];
|
|
char extra_field_length[2];
|
|
};
|
|
|
|
struct External_CentralDirectoryHeader
|
|
{
|
|
char signature[4]; // 'PK\001\002'
|
|
char version_made_by[2];
|
|
char version_needed_to_extract[2];
|
|
char general_purpose_bit_flag[2];
|
|
char compression_method[2];
|
|
char last_mod_file_time[2];
|
|
char last_mod_file_date[2];
|
|
char crc32[4];
|
|
char compressed_size[4];
|
|
char uncompressed_size[4];
|
|
char filename_length[2];
|
|
char extra_field_length[2];
|
|
char file_comment_length[2];
|
|
char disk_number_start[2];
|
|
char internal_file_attributes[2];
|
|
char external_file_attributes[4];
|
|
char relative_offset_local_header[4];
|
|
};
|
|
|
|
struct External_EndCentralDirectoryHeader
|
|
{
|
|
char signature[4]; // 'PK\005\006'
|
|
char number_this_disk[2];
|
|
char num_disk_with_start_central_dir[2];
|
|
char num_entries_central_dir_this_disk[2];
|
|
char total_entries_central_dir[2];
|
|
char size_central_directory[4];
|
|
char offset_start_central_directory[4];
|
|
char zipfile_comment_length[2];
|
|
};
|
|
|
|
struct Internal_LocalFileHeader
|
|
{
|
|
uint8 signature[4];
|
|
uint16 version_needed_to_extract;
|
|
uint16 general_purpose_bit_flag;
|
|
uint16 compression_method;
|
|
uint16 last_mod_file_time;
|
|
uint16 last_mod_file_date;
|
|
uint32 crc32;
|
|
uint32 compressed_size;
|
|
uint32 uncompressed_size;
|
|
uint16 filename_length;
|
|
uint16 extra_field_length;
|
|
};
|
|
|
|
struct Internal_CentralDirectoryHeader
|
|
{
|
|
uint8 signature[4];
|
|
uint16 version_made_by;
|
|
uint16 version_needed_to_extract;
|
|
uint16 general_purpose_bit_flag;
|
|
uint16 compression_method;
|
|
uint16 last_mod_file_time;
|
|
uint16 last_mod_file_date;
|
|
uint32 crc32;
|
|
uint32 compressed_size;
|
|
uint32 uncompressed_size;
|
|
uint16 filename_length;
|
|
uint16 extra_field_length;
|
|
uint16 file_comment_length;
|
|
uint16 disk_number_start;
|
|
uint16 internal_file_attributes;
|
|
uint32 external_file_attributes;
|
|
uint32 relative_offset_local_header;
|
|
};
|
|
|
|
struct Internal_EndCentralDirectoryHeader
|
|
{
|
|
uint8 signature[4];
|
|
uint16 number_this_disk;
|
|
uint16 num_disk_with_start_central_dir;
|
|
uint16 num_entries_central_dir_this_disk;
|
|
uint16 total_entries_central_dir;
|
|
uint32 size_central_directory;
|
|
uint32 offset_start_central_directory;
|
|
uint16 zipfile_comment_length;
|
|
};
|
|
|
|
static void import(Internal_LocalFileHeader *,
|
|
const External_LocalFileHeader &);
|
|
static void import(Internal_CentralDirectoryHeader *,
|
|
const External_CentralDirectoryHeader &);
|
|
static void import(Internal_EndCentralDirectoryHeader *,
|
|
const External_EndCentralDirectoryHeader &);
|
|
|
|
#define STORED 0 /* compression methods */
|
|
#define SHRUNK 1
|
|
#define REDUCED1 2
|
|
#define REDUCED2 3
|
|
#define REDUCED3 4
|
|
#define REDUCED4 5
|
|
#define IMPLODED 6
|
|
#define TOKENIZED 7
|
|
#define DEFLATED 8
|
|
|
|
|
|
/* Public functions */
|
|
ZipArchive::ZipArchive(const char *archiveName, Pool &pool, bool &status)
|
|
: pool(pool)
|
|
{
|
|
status = true;
|
|
|
|
fp = PR_Open(archiveName, PR_RDONLY, 0);
|
|
if (!fp || !initReader())
|
|
status = false;
|
|
}
|
|
|
|
|
|
ZipArchive::~ZipArchive()
|
|
{
|
|
if (fp)
|
|
PR_Close(fp);
|
|
}
|
|
|
|
bool ZipArchive::get(const DirectoryEntry *dp, char *&buf, Int32 &len)
|
|
{
|
|
char *xbuf;
|
|
int32 offset;
|
|
External_LocalFileHeader ext_fhdr;
|
|
Internal_LocalFileHeader int_fhdr;
|
|
|
|
// Seek to the start of the local file header
|
|
if (PR_Seek (fp, dp->off, PR_SEEK_SET) != dp->off)
|
|
return false;
|
|
|
|
// Read the local file header into memory
|
|
if (!readFully(&ext_fhdr, sizeof(External_LocalFileHeader)))
|
|
return false;
|
|
|
|
// Swap endianness, if needed
|
|
import(&int_fhdr, ext_fhdr);
|
|
|
|
// Verify local file header signature, in case of corruption or confusion
|
|
if (int_fhdr.signature[0] != 0x50 || int_fhdr.signature[1] != 0x4b ||
|
|
int_fhdr.signature[2] != 0x03 || int_fhdr.signature[3] != 0x04)
|
|
return false;
|
|
|
|
// Compute offset within archive to start of file data
|
|
offset = dp->off + sizeof(External_LocalFileHeader) +
|
|
int_fhdr.filename_length + int_fhdr.extra_field_length;
|
|
|
|
// Seek to start of file data
|
|
if (PR_Seek (fp, offset, PR_SEEK_SET) != offset)
|
|
return false;
|
|
|
|
len = dp->len;
|
|
buf = xbuf = new(pool) char[dp->len];
|
|
|
|
if (dp->method == STORED)
|
|
return readFully(xbuf, dp->len);
|
|
|
|
if (dp->method == DEFLATED)
|
|
return inflateFully(dp->size, xbuf, dp->len);
|
|
|
|
return false;
|
|
}
|
|
|
|
Uint32 ZipArchive::getNumElements(const char *fn_suffix)
|
|
{
|
|
int suf_len = strlen(fn_suffix);
|
|
Uint32 nelems = 0;
|
|
DirectoryEntry *entry;
|
|
|
|
entry = dir.firstNode();
|
|
while (entry) {
|
|
const char *fn = entry->fn;
|
|
int fn_len = strlen(fn);
|
|
if (suf_len >= fn_len
|
|
&& PL_strncasecmp(&fn[fn_len - suf_len], fn_suffix, suf_len) == 0) {
|
|
nelems++;
|
|
}
|
|
entry = dir.next(entry);
|
|
}
|
|
|
|
return nelems;
|
|
}
|
|
|
|
Uint32 ZipArchive::listElements(const char *fn_suffix, char **&rbuf)
|
|
{
|
|
int suf_len = strlen(fn_suffix);
|
|
Uint32 nelems;
|
|
Uint32 i;
|
|
char **buf = new (pool) char *[nel];
|
|
DirectoryEntry *entry;
|
|
|
|
rbuf = buf;
|
|
entry = dir.firstNode();
|
|
|
|
for (i=0, nelems=0; i < nel; i++) {
|
|
const char *fn = entry->fn;
|
|
int fn_len = strlen(fn);
|
|
if (fn_len >= suf_len
|
|
&& PL_strncasecmp(&fn[fn_len - suf_len], fn_suffix, suf_len) == 0) {
|
|
buf[nelems] = dupString(fn, pool);
|
|
nelems++;
|
|
}
|
|
entry = dir.next(entry);
|
|
}
|
|
|
|
return nelems;
|
|
}
|
|
|
|
/* Private Functions */
|
|
/*
|
|
* Initialize zip file reader, read in central directory and construct the
|
|
* lookup table for locating zip file members.
|
|
*/
|
|
bool ZipArchive::initReader()
|
|
{
|
|
External_EndCentralDirectoryHeader ext_ehdr;
|
|
Internal_EndCentralDirectoryHeader int_ehdr;
|
|
char *central_directory, *cd_ptr;
|
|
DirectoryEntry *edir;
|
|
PRInt32 pos;
|
|
|
|
if (! findEnd())
|
|
return false;
|
|
if (! readFully(&ext_ehdr, sizeof(ext_ehdr)))
|
|
return false;
|
|
|
|
// Byte-swap the header as necessary.
|
|
import(&int_ehdr, ext_ehdr);
|
|
|
|
cenoff = int_ehdr.offset_start_central_directory;
|
|
|
|
// Block read the entire central directory. Add one byte to make sure
|
|
// we can null-terminate the last filename in the directory.
|
|
|
|
central_directory = new (pool) char[int_ehdr.size_central_directory + 1];
|
|
if (!central_directory)
|
|
return false;
|
|
pos = PR_Seek(fp, int_ehdr.offset_start_central_directory, PR_SEEK_SET);
|
|
if (pos == -1)
|
|
return false;
|
|
if (! readFully(central_directory, int_ehdr.size_central_directory))
|
|
return false;
|
|
|
|
// Import all of the individual directory entries.
|
|
|
|
nel = int_ehdr.total_entries_central_dir;
|
|
edir = new (pool) DirectoryEntry[nel];
|
|
cd_ptr = central_directory;
|
|
|
|
for (int i = 0; i < nel; ++i) {
|
|
Internal_CentralDirectoryHeader int_chdr;
|
|
import(&int_chdr, *(External_CentralDirectoryHeader*)cd_ptr);
|
|
|
|
edir[i].len = int_chdr.uncompressed_size;
|
|
edir[i].size = int_chdr.compressed_size;
|
|
edir[i].method = int_chdr.compression_method;
|
|
edir[i].mod = 0; // FIXME
|
|
edir[i].off = int_chdr.relative_offset_local_header;
|
|
|
|
cd_ptr += sizeof(External_CentralDirectoryHeader);
|
|
edir[i].fn = cd_ptr;
|
|
cd_ptr += int_chdr.filename_length;
|
|
|
|
// Null terminate the filename at the expense of either the
|
|
// extra_field_length padding, or the signature on the next
|
|
// entry, for which we care not at all.
|
|
//
|
|
// FIXME -- do we care about transforming filenames as infozip
|
|
// would have us do in the general case?
|
|
|
|
*cd_ptr = '\0';
|
|
|
|
cd_ptr += int_chdr.extra_field_length;
|
|
|
|
// Sanity check the entry.
|
|
if (cd_ptr - central_directory > int_ehdr.size_central_directory)
|
|
return false;
|
|
|
|
// Insert the entry into the tree.
|
|
dir.insert(edir[i]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Find end of central directory (END) header in zip file and set the file
|
|
* pointer to start of header. Return FALSE if the END header could not be
|
|
* found, otherwise return TRUE.
|
|
*/
|
|
bool ZipArchive::findEnd()
|
|
{
|
|
char buf[BUFSIZ+4];
|
|
PRInt32 pos, dest, n;
|
|
|
|
pos = PR_Seek(fp, 0, PR_SEEK_END);
|
|
if (pos == -1)
|
|
return false;
|
|
if (pos < sizeof(External_EndCentralDirectoryHeader))
|
|
return false;
|
|
|
|
// Loop through the blocks of the file, starting at the end and going
|
|
// toward the beginning.
|
|
|
|
// Align our block reading.
|
|
n = pos % BUFSIZ;
|
|
if (!n)
|
|
n = BUFSIZ;
|
|
dest = pos - n;
|
|
|
|
// The signature cannot be closer to the end than the size of the struct.
|
|
n -= sizeof(External_EndCentralDirectoryHeader)-4;
|
|
memset(buf+n, 0, 4);
|
|
|
|
do {
|
|
pos = PR_Seek(fp, dest, PR_SEEK_SET);
|
|
if (!readFully(buf, n))
|
|
return false;
|
|
|
|
// Ah for a memrchr...
|
|
while (--n >= 0)
|
|
if (buf[n] == 'P' && buf[n+1] == 'K' && buf[n+2] == 5 && buf[n+3] == 6) {
|
|
// Found it!
|
|
endoff = pos + n;
|
|
return PR_Seek(fp, endoff, PR_SEEK_SET) == endoff;
|
|
}
|
|
|
|
// The signature might span multiple blocks. Arrange for the first
|
|
// few bits of the current block to be adjacent to the last few bits
|
|
// of the previous block.
|
|
memcpy(buf+BUFSIZ, buf, 4);
|
|
|
|
// Back up for the next block.
|
|
dest = pos - BUFSIZ;
|
|
n = BUFSIZ;
|
|
} while (pos > 0);
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* Read exactly 'len' bytes of data into 'buf'. Return true if all bytes
|
|
* could be read, otherwise return false.
|
|
*/
|
|
bool ZipArchive::readFully(void *buf, Int32 len)
|
|
{
|
|
char *bp = (char *) buf;
|
|
|
|
while (len > 0) {
|
|
Int32 n = PR_Read(fp, bp, len);
|
|
|
|
if (n <= 0)
|
|
return false;
|
|
|
|
bp += n;
|
|
len -= n;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Fully inflate the zip archive into buffer buf, whose length is len.
|
|
* Return true on success, false on failure.
|
|
*/
|
|
bool ZipArchive::inflateFully(Uint32 size, void *outbuf, Uint32 len)
|
|
{
|
|
char inbuf[BUFSIZ];
|
|
z_stream zs;
|
|
bool success;
|
|
Uint32 n;
|
|
|
|
// Read the first data block for zlib.
|
|
n = size < BUFSIZ ? size : BUFSIZ;
|
|
if (!readFully(inbuf, n))
|
|
return false;
|
|
|
|
// Set up the z_stream.
|
|
zs.next_in = (Bytef *) inbuf;
|
|
zs.avail_in = n;
|
|
zs.next_out = (Bytef *) outbuf;
|
|
zs.avail_out = len;
|
|
zs.zalloc = 0;
|
|
zs.zfree = 0;
|
|
zs.opaque = 0;
|
|
|
|
// windowBits is passed < 0 to tell that there is no zlib header.
|
|
// Note that in this case inflate *requires* an extra "dummy" byte
|
|
// after the compressed stream in order to complete decompression and
|
|
// return Z_STREAM_END.
|
|
|
|
if (inflateInit2(&zs, -MAX_WBITS) != Z_OK)
|
|
return false;
|
|
|
|
success = true;
|
|
while (1) {
|
|
size -= n;
|
|
|
|
// Process the block. If size == 0, we know we're on the last hunk.
|
|
if (size != 0) {
|
|
int ret = inflate(&zs, 0);
|
|
if (ret != Z_OK) {
|
|
success = false;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
// Add the extra dummy byte mentioned above. Just steal it from
|
|
// the surrounding stack frame.
|
|
zs.avail_in += 1;
|
|
|
|
int ret = inflate(&zs, Z_FINISH);
|
|
if (ret != Z_STREAM_END)
|
|
success = false;
|
|
break;
|
|
}
|
|
|
|
n = size < BUFSIZ ? size : BUFSIZ;
|
|
if (!readFully(inbuf, n)) {
|
|
success = false;
|
|
break;
|
|
}
|
|
|
|
zs.next_in = (Bytef *) inbuf;
|
|
zs.avail_in = n;
|
|
}
|
|
|
|
inflateEnd(&zs);
|
|
return success;
|
|
}
|
|
|
|
static void
|
|
import(Internal_LocalFileHeader *in,
|
|
const External_LocalFileHeader &ex)
|
|
{
|
|
memcpy(in->signature, ex.signature, 4);
|
|
in->version_needed_to_extract
|
|
= readLittleUHalfwordUnaligned(ex.version_needed_to_extract);
|
|
in->general_purpose_bit_flag
|
|
= readLittleUHalfwordUnaligned(ex.general_purpose_bit_flag);
|
|
in->last_mod_file_time
|
|
= readLittleUHalfwordUnaligned(ex.last_mod_file_time);
|
|
in->last_mod_file_date
|
|
= readLittleUHalfwordUnaligned(ex.last_mod_file_date);
|
|
in->crc32
|
|
= readLittleUWordUnaligned(ex.crc32);
|
|
in->compressed_size
|
|
= readLittleUWordUnaligned(ex.compressed_size);
|
|
in->uncompressed_size
|
|
= readLittleUWordUnaligned(ex.uncompressed_size);
|
|
in->filename_length
|
|
= readLittleUHalfwordUnaligned(ex.filename_length);
|
|
in->extra_field_length
|
|
= readLittleUHalfwordUnaligned(ex.extra_field_length);
|
|
}
|
|
|
|
static void
|
|
import(Internal_CentralDirectoryHeader *in,
|
|
const External_CentralDirectoryHeader &ex)
|
|
{
|
|
memcpy(in->signature, ex.signature, 4);
|
|
in->version_made_by
|
|
= readLittleUHalfwordUnaligned(ex.version_made_by);
|
|
in->version_needed_to_extract
|
|
= readLittleUHalfwordUnaligned(ex.version_needed_to_extract);
|
|
in->general_purpose_bit_flag
|
|
= readLittleUHalfwordUnaligned(ex.general_purpose_bit_flag);
|
|
in->compression_method
|
|
= readLittleUHalfwordUnaligned(ex.compression_method);
|
|
in->last_mod_file_time
|
|
= readLittleUHalfwordUnaligned(ex.last_mod_file_time);
|
|
in->last_mod_file_date
|
|
= readLittleUHalfwordUnaligned(ex.last_mod_file_date);
|
|
in->crc32
|
|
= readLittleUWordUnaligned(ex.crc32);
|
|
in->compressed_size
|
|
= readLittleUWordUnaligned(ex.compressed_size);
|
|
in->uncompressed_size
|
|
= readLittleUWordUnaligned(ex.uncompressed_size);
|
|
in->filename_length
|
|
= readLittleUHalfwordUnaligned(ex.filename_length);
|
|
in->extra_field_length
|
|
= readLittleUHalfwordUnaligned(ex.extra_field_length);
|
|
in->file_comment_length
|
|
= readLittleUHalfwordUnaligned(ex.file_comment_length);
|
|
in->disk_number_start
|
|
= readLittleUHalfwordUnaligned(ex.disk_number_start);
|
|
in->internal_file_attributes
|
|
= readLittleUHalfwordUnaligned(ex.internal_file_attributes);
|
|
in->external_file_attributes
|
|
= readLittleUHalfwordUnaligned(ex.external_file_attributes);
|
|
in->relative_offset_local_header
|
|
= readLittleUWordUnaligned(ex.relative_offset_local_header);
|
|
}
|
|
|
|
static void
|
|
import(Internal_EndCentralDirectoryHeader *in,
|
|
const External_EndCentralDirectoryHeader &ex)
|
|
{
|
|
memcpy(in->signature, ex.signature, 4);
|
|
in->number_this_disk
|
|
= readLittleUHalfwordUnaligned(ex.number_this_disk);
|
|
in->num_disk_with_start_central_dir
|
|
= readLittleUHalfwordUnaligned(ex.num_disk_with_start_central_dir);
|
|
in->num_entries_central_dir_this_disk
|
|
= readLittleUHalfwordUnaligned(ex.num_entries_central_dir_this_disk);
|
|
in->total_entries_central_dir
|
|
= readLittleUHalfwordUnaligned(ex.total_entries_central_dir);
|
|
in->size_central_directory
|
|
= readLittleUWordUnaligned(ex.size_central_directory);
|
|
in->offset_start_central_directory
|
|
= readLittleUWordUnaligned(ex.offset_start_central_directory);
|
|
in->zipfile_comment_length
|
|
= readLittleUHalfwordUnaligned(ex.zipfile_comment_length);
|
|
}
|