am b7ccd83f: am 39477373: Merge "Implement ZipWriter for quickly writing ZipFiles."
* commit 'b7ccd83fe3e4dda1a5b3fa1061803d6c16b4e6e3': Implement ZipWriter for quickly writing ZipFiles.
This commit is contained in:
commit
bc9dec80b8
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef LIBZIPARCHIVE_ZIPWRITER_H_
|
||||
#define LIBZIPARCHIVE_ZIPWRITER_H_
|
||||
|
||||
#include "base/macros.h"
|
||||
#include <utils/Compat.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <ctime>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* Writes a Zip file via a stateful interface.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* FILE* file = fopen("path/to/zip.zip", "wb");
|
||||
*
|
||||
* ZipWriter writer(file);
|
||||
*
|
||||
* writer.StartEntry("test.txt", ZipWriter::kCompress | ZipWriter::kAlign);
|
||||
* writer.WriteBytes(buffer, bufferLen);
|
||||
* writer.WriteBytes(buffer2, bufferLen2);
|
||||
* writer.FinishEntry();
|
||||
*
|
||||
* writer.StartEntry("empty.txt", 0);
|
||||
* writer.FinishEntry();
|
||||
*
|
||||
* writer.Finish();
|
||||
*
|
||||
* fclose(file);
|
||||
*/
|
||||
class ZipWriter {
|
||||
public:
|
||||
enum {
|
||||
/**
|
||||
* Flag to compress the zip entry using deflate.
|
||||
*/
|
||||
kCompress = 0x01,
|
||||
|
||||
/**
|
||||
* Flag to align the zip entry data on a 32bit boundary. Useful for
|
||||
* mmapping the data at runtime.
|
||||
*/
|
||||
kAlign32 = 0x02,
|
||||
};
|
||||
|
||||
static const char* ErrorCodeString(int32_t error_code);
|
||||
|
||||
/**
|
||||
* Create a ZipWriter that will write into a FILE stream. The file should be opened with
|
||||
* open mode of "wb" or "w+b". ZipWriter does not take ownership of the file stream. The
|
||||
* caller is responsible for closing the file.
|
||||
*/
|
||||
explicit ZipWriter(FILE* f);
|
||||
|
||||
// Move constructor.
|
||||
ZipWriter(ZipWriter&& zipWriter);
|
||||
|
||||
// Move assignment.
|
||||
ZipWriter& operator=(ZipWriter&& zipWriter);
|
||||
|
||||
/**
|
||||
* Starts a new zip entry with the given path and flags.
|
||||
* Flags can be a bitwise OR of ZipWriter::kCompress and ZipWriter::kAlign.
|
||||
* Subsequent calls to WriteBytes(const void*, size_t) will add data to this entry.
|
||||
* Returns 0 on success, and an error value < 0 on failure.
|
||||
*/
|
||||
int32_t StartEntry(const char* path, size_t flags);
|
||||
|
||||
/**
|
||||
* Same as StartEntry(const char*, size_t), but sets a last modified time for the entry.
|
||||
*/
|
||||
int32_t StartEntryWithTime(const char* path, size_t flags, time_t time);
|
||||
|
||||
/**
|
||||
* Writes bytes to the zip file for the previously started zip entry.
|
||||
* Returns 0 on success, and an error value < 0 on failure.
|
||||
*/
|
||||
int32_t WriteBytes(const void* data, size_t len);
|
||||
|
||||
/**
|
||||
* Finish a zip entry started with StartEntry(const char*, size_t) or
|
||||
* StartEntryWithTime(const char*, size_t, time_t). This must be called before
|
||||
* any new zip entries are started, or before Finish() is called.
|
||||
* Returns 0 on success, and an error value < 0 on failure.
|
||||
*/
|
||||
int32_t FinishEntry();
|
||||
|
||||
/**
|
||||
* Writes the Central Directory Headers and flushes the zip file stream.
|
||||
* Returns 0 on success, and an error value < 0 on failure.
|
||||
*/
|
||||
int32_t Finish();
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ZipWriter);
|
||||
|
||||
int32_t HandleError(int32_t error_code);
|
||||
|
||||
struct FileInfo {
|
||||
std::string path;
|
||||
uint16_t compression_method;
|
||||
uint32_t crc32;
|
||||
uint32_t compressed_size;
|
||||
uint32_t uncompressed_size;
|
||||
uint16_t last_mod_time;
|
||||
uint16_t last_mod_date;
|
||||
uint32_t local_file_header_offset;
|
||||
};
|
||||
|
||||
enum class State {
|
||||
kWritingZip,
|
||||
kWritingEntry,
|
||||
kDone,
|
||||
kError,
|
||||
};
|
||||
|
||||
FILE* file_;
|
||||
off64_t current_offset_;
|
||||
State state_;
|
||||
std::vector<FileInfo> files_;
|
||||
};
|
||||
|
||||
#endif /* LIBZIPARCHIVE_ZIPWRITER_H_ */
|
|
@ -15,7 +15,12 @@
|
|||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
source_files := zip_archive.cc
|
||||
source_files := zip_archive.cc zip_writer.cc
|
||||
test_files := zip_archive_test.cc zip_writer_test.cc entry_name_utils_test.cc
|
||||
|
||||
# Incorrectly warns when C++11 empty brace {} initializer is used.
|
||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61489
|
||||
common_cpp_flags := -Wno-missing-field-initializers
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_CPP_EXTENSION := .cc
|
||||
|
@ -24,7 +29,7 @@ LOCAL_STATIC_LIBRARIES := libz
|
|||
LOCAL_SHARED_LIBRARIES := libutils libbase
|
||||
LOCAL_MODULE:= libziparchive
|
||||
LOCAL_CFLAGS := -Werror -Wall
|
||||
LOCAL_CPPFLAGS := -Wold-style-cast
|
||||
LOCAL_CPPFLAGS := -Wold-style-cast $(common_cpp_flags)
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
@ -34,6 +39,8 @@ LOCAL_STATIC_LIBRARIES := libz libutils libbase
|
|||
LOCAL_MODULE:= libziparchive-host
|
||||
LOCAL_CFLAGS := -Werror
|
||||
LOCAL_CFLAGS_windows := -mno-ms-bitfields
|
||||
LOCAL_CPPFLAGS := $(common_cpp_flags)
|
||||
|
||||
LOCAL_MULTILIB := both
|
||||
LOCAL_MODULE_HOST_OS := darwin linux windows
|
||||
include $(BUILD_HOST_STATIC_LIBRARY)
|
||||
|
@ -45,6 +52,7 @@ LOCAL_STATIC_LIBRARIES := libutils
|
|||
LOCAL_SHARED_LIBRARIES := libz-host liblog libbase
|
||||
LOCAL_MODULE:= libziparchive-host
|
||||
LOCAL_CFLAGS := -Werror
|
||||
LOCAL_CPPFLAGS := $(common_cpp_flags)
|
||||
LOCAL_MULTILIB := both
|
||||
include $(BUILD_HOST_SHARED_LIBRARY)
|
||||
|
||||
|
@ -53,7 +61,8 @@ include $(CLEAR_VARS)
|
|||
LOCAL_MODULE := ziparchive-tests
|
||||
LOCAL_CPP_EXTENSION := .cc
|
||||
LOCAL_CFLAGS := -Werror
|
||||
LOCAL_SRC_FILES := zip_archive_test.cc entry_name_utils_test.cc
|
||||
LOCAL_CPPFLAGS := $(common_cpp_flags)
|
||||
LOCAL_SRC_FILES := $(test_files)
|
||||
LOCAL_SHARED_LIBRARIES := liblog libbase
|
||||
LOCAL_STATIC_LIBRARIES := libziparchive libz libutils
|
||||
include $(BUILD_NATIVE_TEST)
|
||||
|
@ -61,10 +70,9 @@ include $(BUILD_NATIVE_TEST)
|
|||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := ziparchive-tests-host
|
||||
LOCAL_CPP_EXTENSION := .cc
|
||||
LOCAL_CFLAGS += \
|
||||
-Werror \
|
||||
-Wno-unnamed-type-template-args
|
||||
LOCAL_SRC_FILES := zip_archive_test.cc entry_name_utils_test.cc
|
||||
LOCAL_CFLAGS := -Werror
|
||||
LOCAL_CPPFLAGS := -Wno-unnamed-type-template-args $(common_cpp_flags)
|
||||
LOCAL_SRC_FILES := $(test_files)
|
||||
LOCAL_SHARED_LIBRARIES := libziparchive-host liblog libbase
|
||||
LOCAL_STATIC_LIBRARIES := \
|
||||
libz \
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#include "zlib.h"
|
||||
|
||||
#include "entry_name_utils-inl.h"
|
||||
#include "zip_archive_common.h"
|
||||
#include "ziparchive/zip_archive.h"
|
||||
|
||||
using android::base::get_unaligned;
|
||||
|
@ -49,161 +50,6 @@ using android::base::get_unaligned;
|
|||
#define O_BINARY 0
|
||||
#endif
|
||||
|
||||
// The "end of central directory" (EOCD) record. Each archive
|
||||
// contains exactly once such record which appears at the end of
|
||||
// the archive. It contains archive wide information like the
|
||||
// number of entries in the archive and the offset to the central
|
||||
// directory of the offset.
|
||||
struct EocdRecord {
|
||||
static const uint32_t kSignature = 0x06054b50;
|
||||
|
||||
// End of central directory signature, should always be
|
||||
// |kSignature|.
|
||||
uint32_t eocd_signature;
|
||||
// The number of the current "disk", i.e, the "disk" that this
|
||||
// central directory is on.
|
||||
//
|
||||
// This implementation assumes that each archive spans a single
|
||||
// disk only. i.e, that disk_num == 1.
|
||||
uint16_t disk_num;
|
||||
// The disk where the central directory starts.
|
||||
//
|
||||
// This implementation assumes that each archive spans a single
|
||||
// disk only. i.e, that cd_start_disk == 1.
|
||||
uint16_t cd_start_disk;
|
||||
// The number of central directory records on this disk.
|
||||
//
|
||||
// This implementation assumes that each archive spans a single
|
||||
// disk only. i.e, that num_records_on_disk == num_records.
|
||||
uint16_t num_records_on_disk;
|
||||
// The total number of central directory records.
|
||||
uint16_t num_records;
|
||||
// The size of the central directory (in bytes).
|
||||
uint32_t cd_size;
|
||||
// The offset of the start of the central directory, relative
|
||||
// to the start of the file.
|
||||
uint32_t cd_start_offset;
|
||||
// Length of the central directory comment.
|
||||
uint16_t comment_length;
|
||||
private:
|
||||
EocdRecord() = default;
|
||||
DISALLOW_COPY_AND_ASSIGN(EocdRecord);
|
||||
} __attribute__((packed));
|
||||
|
||||
// A structure representing the fixed length fields for a single
|
||||
// record in the central directory of the archive. In addition to
|
||||
// the fixed length fields listed here, each central directory
|
||||
// record contains a variable length "file_name" and "extra_field"
|
||||
// whose lengths are given by |file_name_length| and |extra_field_length|
|
||||
// respectively.
|
||||
struct CentralDirectoryRecord {
|
||||
static const uint32_t kSignature = 0x02014b50;
|
||||
|
||||
// The start of record signature. Must be |kSignature|.
|
||||
uint32_t record_signature;
|
||||
// Tool version. Ignored by this implementation.
|
||||
uint16_t version_made_by;
|
||||
// Tool version. Ignored by this implementation.
|
||||
uint16_t version_needed;
|
||||
// The "general purpose bit flags" for this entry. The only
|
||||
// flag value that we currently check for is the "data descriptor"
|
||||
// flag.
|
||||
uint16_t gpb_flags;
|
||||
// The compression method for this entry, one of |kCompressStored|
|
||||
// and |kCompressDeflated|.
|
||||
uint16_t compression_method;
|
||||
// The file modification time and date for this entry.
|
||||
uint16_t last_mod_time;
|
||||
uint16_t last_mod_date;
|
||||
// The CRC-32 checksum for this entry.
|
||||
uint32_t crc32;
|
||||
// The compressed size (in bytes) of this entry.
|
||||
uint32_t compressed_size;
|
||||
// The uncompressed size (in bytes) of this entry.
|
||||
uint32_t uncompressed_size;
|
||||
// The length of the entry file name in bytes. The file name
|
||||
// will appear immediately after this record.
|
||||
uint16_t file_name_length;
|
||||
// The length of the extra field info (in bytes). This data
|
||||
// will appear immediately after the entry file name.
|
||||
uint16_t extra_field_length;
|
||||
// The length of the entry comment (in bytes). This data will
|
||||
// appear immediately after the extra field.
|
||||
uint16_t comment_length;
|
||||
// The start disk for this entry. Ignored by this implementation).
|
||||
uint16_t file_start_disk;
|
||||
// File attributes. Ignored by this implementation.
|
||||
uint16_t internal_file_attributes;
|
||||
// File attributes. Ignored by this implementation.
|
||||
uint32_t external_file_attributes;
|
||||
// The offset to the local file header for this entry, from the
|
||||
// beginning of this archive.
|
||||
uint32_t local_file_header_offset;
|
||||
private:
|
||||
CentralDirectoryRecord() = default;
|
||||
DISALLOW_COPY_AND_ASSIGN(CentralDirectoryRecord);
|
||||
} __attribute__((packed));
|
||||
|
||||
// The local file header for a given entry. This duplicates information
|
||||
// present in the central directory of the archive. It is an error for
|
||||
// the information here to be different from the central directory
|
||||
// information for a given entry.
|
||||
struct LocalFileHeader {
|
||||
static const uint32_t kSignature = 0x04034b50;
|
||||
|
||||
// The local file header signature, must be |kSignature|.
|
||||
uint32_t lfh_signature;
|
||||
// Tool version. Ignored by this implementation.
|
||||
uint16_t version_needed;
|
||||
// The "general purpose bit flags" for this entry. The only
|
||||
// flag value that we currently check for is the "data descriptor"
|
||||
// flag.
|
||||
uint16_t gpb_flags;
|
||||
// The compression method for this entry, one of |kCompressStored|
|
||||
// and |kCompressDeflated|.
|
||||
uint16_t compression_method;
|
||||
// The file modification time and date for this entry.
|
||||
uint16_t last_mod_time;
|
||||
uint16_t last_mod_date;
|
||||
// The CRC-32 checksum for this entry.
|
||||
uint32_t crc32;
|
||||
// The compressed size (in bytes) of this entry.
|
||||
uint32_t compressed_size;
|
||||
// The uncompressed size (in bytes) of this entry.
|
||||
uint32_t uncompressed_size;
|
||||
// The length of the entry file name in bytes. The file name
|
||||
// will appear immediately after this record.
|
||||
uint16_t file_name_length;
|
||||
// The length of the extra field info (in bytes). This data
|
||||
// will appear immediately after the entry file name.
|
||||
uint16_t extra_field_length;
|
||||
private:
|
||||
LocalFileHeader() = default;
|
||||
DISALLOW_COPY_AND_ASSIGN(LocalFileHeader);
|
||||
} __attribute__((packed));
|
||||
|
||||
struct DataDescriptor {
|
||||
// The *optional* data descriptor start signature.
|
||||
static const uint32_t kOptSignature = 0x08074b50;
|
||||
|
||||
// CRC-32 checksum of the entry.
|
||||
uint32_t crc32;
|
||||
// Compressed size of the entry.
|
||||
uint32_t compressed_size;
|
||||
// Uncompressed size of the entry.
|
||||
uint32_t uncompressed_size;
|
||||
private:
|
||||
DataDescriptor() = default;
|
||||
DISALLOW_COPY_AND_ASSIGN(DataDescriptor);
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
static const uint32_t kGPBDDFlagMask = 0x0008; // mask value that signifies that the entry has a DD
|
||||
|
||||
// The maximum size of a central directory or a file
|
||||
// comment in bytes.
|
||||
static const uint32_t kMaxCommentLen = 65535;
|
||||
|
||||
// The maximum number of bytes to scan backwards for the EOCD start.
|
||||
static const uint32_t kMaxEOCDSearch = kMaxCommentLen + sizeof(EocdRecord);
|
||||
|
||||
|
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef LIBZIPARCHIVE_ZIPARCHIVECOMMON_H_
|
||||
#define LIBZIPARCHIVE_ZIPARCHIVECOMMON_H_
|
||||
|
||||
#include "base/macros.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
// The "end of central directory" (EOCD) record. Each archive
|
||||
// contains exactly once such record which appears at the end of
|
||||
// the archive. It contains archive wide information like the
|
||||
// number of entries in the archive and the offset to the central
|
||||
// directory of the offset.
|
||||
struct EocdRecord {
|
||||
static const uint32_t kSignature = 0x06054b50;
|
||||
|
||||
// End of central directory signature, should always be
|
||||
// |kSignature|.
|
||||
uint32_t eocd_signature;
|
||||
// The number of the current "disk", i.e, the "disk" that this
|
||||
// central directory is on.
|
||||
//
|
||||
// This implementation assumes that each archive spans a single
|
||||
// disk only. i.e, that disk_num == 1.
|
||||
uint16_t disk_num;
|
||||
// The disk where the central directory starts.
|
||||
//
|
||||
// This implementation assumes that each archive spans a single
|
||||
// disk only. i.e, that cd_start_disk == 1.
|
||||
uint16_t cd_start_disk;
|
||||
// The number of central directory records on this disk.
|
||||
//
|
||||
// This implementation assumes that each archive spans a single
|
||||
// disk only. i.e, that num_records_on_disk == num_records.
|
||||
uint16_t num_records_on_disk;
|
||||
// The total number of central directory records.
|
||||
uint16_t num_records;
|
||||
// The size of the central directory (in bytes).
|
||||
uint32_t cd_size;
|
||||
// The offset of the start of the central directory, relative
|
||||
// to the start of the file.
|
||||
uint32_t cd_start_offset;
|
||||
// Length of the central directory comment.
|
||||
uint16_t comment_length;
|
||||
private:
|
||||
EocdRecord() = default;
|
||||
DISALLOW_COPY_AND_ASSIGN(EocdRecord);
|
||||
} __attribute__((packed));
|
||||
|
||||
// A structure representing the fixed length fields for a single
|
||||
// record in the central directory of the archive. In addition to
|
||||
// the fixed length fields listed here, each central directory
|
||||
// record contains a variable length "file_name" and "extra_field"
|
||||
// whose lengths are given by |file_name_length| and |extra_field_length|
|
||||
// respectively.
|
||||
struct CentralDirectoryRecord {
|
||||
static const uint32_t kSignature = 0x02014b50;
|
||||
|
||||
// The start of record signature. Must be |kSignature|.
|
||||
uint32_t record_signature;
|
||||
// Tool version. Ignored by this implementation.
|
||||
uint16_t version_made_by;
|
||||
// Tool version. Ignored by this implementation.
|
||||
uint16_t version_needed;
|
||||
// The "general purpose bit flags" for this entry. The only
|
||||
// flag value that we currently check for is the "data descriptor"
|
||||
// flag.
|
||||
uint16_t gpb_flags;
|
||||
// The compression method for this entry, one of |kCompressStored|
|
||||
// and |kCompressDeflated|.
|
||||
uint16_t compression_method;
|
||||
// The file modification time and date for this entry.
|
||||
uint16_t last_mod_time;
|
||||
uint16_t last_mod_date;
|
||||
// The CRC-32 checksum for this entry.
|
||||
uint32_t crc32;
|
||||
// The compressed size (in bytes) of this entry.
|
||||
uint32_t compressed_size;
|
||||
// The uncompressed size (in bytes) of this entry.
|
||||
uint32_t uncompressed_size;
|
||||
// The length of the entry file name in bytes. The file name
|
||||
// will appear immediately after this record.
|
||||
uint16_t file_name_length;
|
||||
// The length of the extra field info (in bytes). This data
|
||||
// will appear immediately after the entry file name.
|
||||
uint16_t extra_field_length;
|
||||
// The length of the entry comment (in bytes). This data will
|
||||
// appear immediately after the extra field.
|
||||
uint16_t comment_length;
|
||||
// The start disk for this entry. Ignored by this implementation).
|
||||
uint16_t file_start_disk;
|
||||
// File attributes. Ignored by this implementation.
|
||||
uint16_t internal_file_attributes;
|
||||
// File attributes. Ignored by this implementation.
|
||||
uint32_t external_file_attributes;
|
||||
// The offset to the local file header for this entry, from the
|
||||
// beginning of this archive.
|
||||
uint32_t local_file_header_offset;
|
||||
private:
|
||||
CentralDirectoryRecord() = default;
|
||||
DISALLOW_COPY_AND_ASSIGN(CentralDirectoryRecord);
|
||||
} __attribute__((packed));
|
||||
|
||||
// The local file header for a given entry. This duplicates information
|
||||
// present in the central directory of the archive. It is an error for
|
||||
// the information here to be different from the central directory
|
||||
// information for a given entry.
|
||||
struct LocalFileHeader {
|
||||
static const uint32_t kSignature = 0x04034b50;
|
||||
|
||||
// The local file header signature, must be |kSignature|.
|
||||
uint32_t lfh_signature;
|
||||
// Tool version. Ignored by this implementation.
|
||||
uint16_t version_needed;
|
||||
// The "general purpose bit flags" for this entry. The only
|
||||
// flag value that we currently check for is the "data descriptor"
|
||||
// flag.
|
||||
uint16_t gpb_flags;
|
||||
// The compression method for this entry, one of |kCompressStored|
|
||||
// and |kCompressDeflated|.
|
||||
uint16_t compression_method;
|
||||
// The file modification time and date for this entry.
|
||||
uint16_t last_mod_time;
|
||||
uint16_t last_mod_date;
|
||||
// The CRC-32 checksum for this entry.
|
||||
uint32_t crc32;
|
||||
// The compressed size (in bytes) of this entry.
|
||||
uint32_t compressed_size;
|
||||
// The uncompressed size (in bytes) of this entry.
|
||||
uint32_t uncompressed_size;
|
||||
// The length of the entry file name in bytes. The file name
|
||||
// will appear immediately after this record.
|
||||
uint16_t file_name_length;
|
||||
// The length of the extra field info (in bytes). This data
|
||||
// will appear immediately after the entry file name.
|
||||
uint16_t extra_field_length;
|
||||
private:
|
||||
LocalFileHeader() = default;
|
||||
DISALLOW_COPY_AND_ASSIGN(LocalFileHeader);
|
||||
} __attribute__((packed));
|
||||
|
||||
struct DataDescriptor {
|
||||
// The *optional* data descriptor start signature.
|
||||
static const uint32_t kOptSignature = 0x08074b50;
|
||||
|
||||
// CRC-32 checksum of the entry.
|
||||
uint32_t crc32;
|
||||
// Compressed size of the entry.
|
||||
uint32_t compressed_size;
|
||||
// Uncompressed size of the entry.
|
||||
uint32_t uncompressed_size;
|
||||
private:
|
||||
DataDescriptor() = default;
|
||||
DISALLOW_COPY_AND_ASSIGN(DataDescriptor);
|
||||
} __attribute__((packed));
|
||||
|
||||
// mask value that signifies that the entry has a DD
|
||||
static const uint32_t kGPBDDFlagMask = 0x0008;
|
||||
|
||||
// The maximum size of a central directory or a file
|
||||
// comment in bytes.
|
||||
static const uint32_t kMaxCommentLen = 65535;
|
||||
|
||||
#endif /* LIBZIPARCHIVE_ZIPARCHIVECOMMON_H_ */
|
|
@ -0,0 +1,262 @@
|
|||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "entry_name_utils-inl.h"
|
||||
#include "zip_archive_common.h"
|
||||
#include "ziparchive/zip_writer.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <zlib.h>
|
||||
|
||||
/* Zip compression methods we support */
|
||||
enum {
|
||||
kCompressStored = 0, // no compression
|
||||
kCompressDeflated = 8, // standard deflate
|
||||
};
|
||||
|
||||
// No error, operation completed successfully.
|
||||
static const int32_t kNoError = 0;
|
||||
|
||||
// The ZipWriter is in a bad state.
|
||||
static const int32_t kInvalidState = -1;
|
||||
|
||||
// There was an IO error while writing to disk.
|
||||
static const int32_t kIoError = -2;
|
||||
|
||||
// The zip entry name was invalid.
|
||||
static const int32_t kInvalidEntryName = -3;
|
||||
|
||||
static const char* sErrorCodes[] = {
|
||||
"Invalid state",
|
||||
"IO error",
|
||||
"Invalid entry name",
|
||||
};
|
||||
|
||||
const char* ZipWriter::ErrorCodeString(int32_t error_code) {
|
||||
if (error_code < 0 && (-error_code) < static_cast<int32_t>(arraysize(sErrorCodes))) {
|
||||
return sErrorCodes[-error_code];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ZipWriter::ZipWriter(FILE* f) : file_(f), current_offset_(0), state_(State::kWritingZip) {
|
||||
}
|
||||
|
||||
ZipWriter::ZipWriter(ZipWriter&& writer) : file_(writer.file_),
|
||||
current_offset_(writer.current_offset_),
|
||||
state_(writer.state_),
|
||||
files_(std::move(writer.files_)) {
|
||||
writer.file_ = nullptr;
|
||||
writer.state_ = State::kError;
|
||||
}
|
||||
|
||||
ZipWriter& ZipWriter::operator=(ZipWriter&& writer) {
|
||||
file_ = writer.file_;
|
||||
current_offset_ = writer.current_offset_;
|
||||
state_ = writer.state_;
|
||||
files_ = std::move(writer.files_);
|
||||
writer.file_ = nullptr;
|
||||
writer.state_ = State::kError;
|
||||
return *this;
|
||||
}
|
||||
|
||||
int32_t ZipWriter::HandleError(int32_t error_code) {
|
||||
state_ = State::kError;
|
||||
return error_code;
|
||||
}
|
||||
|
||||
int32_t ZipWriter::StartEntry(const char* path, size_t flags) {
|
||||
return StartEntryWithTime(path, flags, time_t());
|
||||
}
|
||||
|
||||
static void ExtractTimeAndDate(time_t when, uint16_t* out_time, uint16_t* out_date) {
|
||||
/* round up to an even number of seconds */
|
||||
when = static_cast<time_t>((static_cast<unsigned long>(when) + 1) & (~1));
|
||||
|
||||
struct tm* ptm;
|
||||
#if !defined(_WIN32)
|
||||
struct tm tm_result;
|
||||
ptm = localtime_r(&when, &tm_result);
|
||||
#else
|
||||
ptm = localtime(&when);
|
||||
#endif
|
||||
|
||||
int year = ptm->tm_year;
|
||||
if (year < 80) {
|
||||
year = 80;
|
||||
}
|
||||
|
||||
*out_date = (year - 80) << 9 | (ptm->tm_mon + 1) << 5 | ptm->tm_mday;
|
||||
*out_time = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
|
||||
}
|
||||
|
||||
int32_t ZipWriter::StartEntryWithTime(const char* path, size_t flags, time_t time) {
|
||||
if (state_ != State::kWritingZip) {
|
||||
return kInvalidState;
|
||||
}
|
||||
|
||||
FileInfo fileInfo = {};
|
||||
fileInfo.path = std::string(path);
|
||||
fileInfo.local_file_header_offset = current_offset_;
|
||||
|
||||
if (!IsValidEntryName(reinterpret_cast<const uint8_t*>(fileInfo.path.data()),
|
||||
fileInfo.path.size())) {
|
||||
return kInvalidEntryName;
|
||||
}
|
||||
|
||||
LocalFileHeader header = {};
|
||||
header.lfh_signature = LocalFileHeader::kSignature;
|
||||
|
||||
// Set this flag to denote that a DataDescriptor struct will appear after the data,
|
||||
// containing the crc and size fields.
|
||||
header.gpb_flags |= kGPBDDFlagMask;
|
||||
|
||||
// For now, ignore the ZipWriter::kCompress flag.
|
||||
fileInfo.compression_method = kCompressStored;
|
||||
header.compression_method = fileInfo.compression_method;
|
||||
|
||||
ExtractTimeAndDate(time, &fileInfo.last_mod_time, &fileInfo.last_mod_date);
|
||||
header.last_mod_time = fileInfo.last_mod_time;
|
||||
header.last_mod_date = fileInfo.last_mod_date;
|
||||
|
||||
header.file_name_length = fileInfo.path.size();
|
||||
|
||||
off64_t offset = current_offset_ + sizeof(header) + fileInfo.path.size();
|
||||
if ((flags & ZipWriter::kAlign32) && (offset & 0x03)) {
|
||||
// Pad the extra field so the data will be aligned.
|
||||
uint16_t padding = 4 - (offset % 4);
|
||||
header.extra_field_length = padding;
|
||||
offset += padding;
|
||||
}
|
||||
|
||||
if (fwrite(&header, sizeof(header), 1, file_) != 1) {
|
||||
return HandleError(kIoError);
|
||||
}
|
||||
|
||||
if (fwrite(path, sizeof(*path), fileInfo.path.size(), file_) != fileInfo.path.size()) {
|
||||
return HandleError(kIoError);
|
||||
}
|
||||
|
||||
if (fwrite("\0\0\0", 1, header.extra_field_length, file_) != header.extra_field_length) {
|
||||
return HandleError(kIoError);
|
||||
}
|
||||
|
||||
files_.emplace_back(std::move(fileInfo));
|
||||
|
||||
current_offset_ = offset;
|
||||
state_ = State::kWritingEntry;
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
int32_t ZipWriter::WriteBytes(const void* data, size_t len) {
|
||||
if (state_ != State::kWritingEntry) {
|
||||
return HandleError(kInvalidState);
|
||||
}
|
||||
|
||||
FileInfo& currentFile = files_.back();
|
||||
if (currentFile.compression_method & kCompressDeflated) {
|
||||
// TODO(adamlesinski): Implement compression using zlib deflate.
|
||||
assert(false);
|
||||
} else {
|
||||
if (fwrite(data, 1, len, file_) != len) {
|
||||
return HandleError(kIoError);
|
||||
}
|
||||
currentFile.crc32 = crc32(currentFile.crc32, reinterpret_cast<const Bytef*>(data), len);
|
||||
currentFile.compressed_size += len;
|
||||
current_offset_ += len;
|
||||
}
|
||||
|
||||
currentFile.uncompressed_size += len;
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
int32_t ZipWriter::FinishEntry() {
|
||||
if (state_ != State::kWritingEntry) {
|
||||
return kInvalidState;
|
||||
}
|
||||
|
||||
const uint32_t sig = DataDescriptor::kOptSignature;
|
||||
if (fwrite(&sig, sizeof(sig), 1, file_) != 1) {
|
||||
state_ = State::kError;
|
||||
return kIoError;
|
||||
}
|
||||
|
||||
FileInfo& currentFile = files_.back();
|
||||
DataDescriptor dd = {};
|
||||
dd.crc32 = currentFile.crc32;
|
||||
dd.compressed_size = currentFile.compressed_size;
|
||||
dd.uncompressed_size = currentFile.uncompressed_size;
|
||||
if (fwrite(&dd, sizeof(dd), 1, file_) != 1) {
|
||||
return HandleError(kIoError);
|
||||
}
|
||||
|
||||
current_offset_ += sizeof(DataDescriptor::kOptSignature) + sizeof(dd);
|
||||
state_ = State::kWritingZip;
|
||||
return kNoError;
|
||||
}
|
||||
|
||||
int32_t ZipWriter::Finish() {
|
||||
if (state_ != State::kWritingZip) {
|
||||
return kInvalidState;
|
||||
}
|
||||
|
||||
off64_t startOfCdr = current_offset_;
|
||||
for (FileInfo& file : files_) {
|
||||
CentralDirectoryRecord cdr = {};
|
||||
cdr.record_signature = CentralDirectoryRecord::kSignature;
|
||||
cdr.gpb_flags |= kGPBDDFlagMask;
|
||||
cdr.compression_method = file.compression_method;
|
||||
cdr.last_mod_time = file.last_mod_time;
|
||||
cdr.last_mod_date = file.last_mod_date;
|
||||
cdr.crc32 = file.crc32;
|
||||
cdr.compressed_size = file.compressed_size;
|
||||
cdr.uncompressed_size = file.uncompressed_size;
|
||||
cdr.file_name_length = file.path.size();
|
||||
cdr.local_file_header_offset = file.local_file_header_offset;
|
||||
if (fwrite(&cdr, sizeof(cdr), 1, file_) != 1) {
|
||||
return HandleError(kIoError);
|
||||
}
|
||||
|
||||
if (fwrite(file.path.data(), 1, file.path.size(), file_) != file.path.size()) {
|
||||
return HandleError(kIoError);
|
||||
}
|
||||
|
||||
current_offset_ += sizeof(cdr) + file.path.size();
|
||||
}
|
||||
|
||||
EocdRecord er = {};
|
||||
er.eocd_signature = EocdRecord::kSignature;
|
||||
er.disk_num = 1;
|
||||
er.cd_start_disk = 1;
|
||||
er.num_records_on_disk = files_.size();
|
||||
er.num_records = files_.size();
|
||||
er.cd_size = current_offset_ - startOfCdr;
|
||||
er.cd_start_offset = startOfCdr;
|
||||
|
||||
if (fwrite(&er, sizeof(er), 1, file_) != 1) {
|
||||
return HandleError(kIoError);
|
||||
}
|
||||
|
||||
if (fflush(file_) != 0) {
|
||||
return HandleError(kIoError);
|
||||
}
|
||||
|
||||
current_offset_ += sizeof(er);
|
||||
state_ = State::kDone;
|
||||
return kNoError;
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "ziparchive/zip_archive.h"
|
||||
#include "ziparchive/zip_writer.h"
|
||||
|
||||
#include <base/test_utils.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
|
||||
struct zipwriter : public ::testing::Test {
|
||||
TemporaryFile* temp_file_;
|
||||
int fd_;
|
||||
FILE* file_;
|
||||
|
||||
void SetUp() override {
|
||||
temp_file_ = new TemporaryFile();
|
||||
fd_ = temp_file_->fd;
|
||||
file_ = fdopen(fd_, "w");
|
||||
ASSERT_NE(file_, nullptr);
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
fclose(file_);
|
||||
delete temp_file_;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(zipwriter, WriteUncompressedZipWithOneFile) {
|
||||
ZipWriter writer(file_);
|
||||
|
||||
const char* expected = "hello";
|
||||
|
||||
ASSERT_EQ(writer.StartEntry("file.txt", 0), 0);
|
||||
ASSERT_EQ(writer.WriteBytes("he", 2), 0);
|
||||
ASSERT_EQ(writer.WriteBytes("llo", 3), 0);
|
||||
ASSERT_EQ(writer.FinishEntry(), 0);
|
||||
ASSERT_EQ(writer.Finish(), 0);
|
||||
|
||||
ASSERT_GE(lseek(fd_, 0, SEEK_SET), 0);
|
||||
|
||||
ZipArchiveHandle handle;
|
||||
ASSERT_EQ(OpenArchiveFd(fd_, "temp", &handle, false), 0);
|
||||
|
||||
ZipEntry data;
|
||||
ASSERT_EQ(FindEntry(handle, ZipString("file.txt"), &data), 0);
|
||||
EXPECT_EQ(data.compressed_length, strlen(expected));
|
||||
EXPECT_EQ(data.uncompressed_length, strlen(expected));
|
||||
EXPECT_EQ(data.method, kCompressStored);
|
||||
|
||||
char buffer[6];
|
||||
EXPECT_EQ(ExtractToMemory(handle, &data, reinterpret_cast<uint8_t*>(&buffer), sizeof(buffer)),
|
||||
0);
|
||||
buffer[5] = 0;
|
||||
|
||||
EXPECT_STREQ(expected, buffer);
|
||||
|
||||
CloseArchive(handle);
|
||||
}
|
||||
|
||||
TEST_F(zipwriter, WriteUncompressedZipWithMultipleFiles) {
|
||||
ZipWriter writer(file_);
|
||||
|
||||
ASSERT_EQ(writer.StartEntry("file.txt", 0), 0);
|
||||
ASSERT_EQ(writer.WriteBytes("he", 2), 0);
|
||||
ASSERT_EQ(writer.FinishEntry(), 0);
|
||||
|
||||
ASSERT_EQ(writer.StartEntry("file/file.txt", 0), 0);
|
||||
ASSERT_EQ(writer.WriteBytes("llo", 3), 0);
|
||||
ASSERT_EQ(writer.FinishEntry(), 0);
|
||||
|
||||
ASSERT_EQ(writer.StartEntry("file/file2.txt", 0), 0);
|
||||
ASSERT_EQ(writer.FinishEntry(), 0);
|
||||
|
||||
ASSERT_EQ(writer.Finish(), 0);
|
||||
|
||||
ASSERT_GE(lseek(fd_, 0, SEEK_SET), 0);
|
||||
|
||||
ZipArchiveHandle handle;
|
||||
ASSERT_EQ(OpenArchiveFd(fd_, "temp", &handle, false), 0);
|
||||
|
||||
char buffer[4];
|
||||
ZipEntry data;
|
||||
|
||||
ASSERT_EQ(FindEntry(handle, ZipString("file.txt"), &data), 0);
|
||||
EXPECT_EQ(data.method, kCompressStored);
|
||||
EXPECT_EQ(data.compressed_length, 2u);
|
||||
EXPECT_EQ(data.uncompressed_length, 2u);
|
||||
ASSERT_EQ(ExtractToMemory(handle, &data, reinterpret_cast<uint8_t*>(buffer), arraysize(buffer)),
|
||||
0);
|
||||
buffer[2] = 0;
|
||||
EXPECT_STREQ("he", buffer);
|
||||
|
||||
ASSERT_EQ(FindEntry(handle, ZipString("file/file.txt"), &data), 0);
|
||||
EXPECT_EQ(data.method, kCompressStored);
|
||||
EXPECT_EQ(data.compressed_length, 3u);
|
||||
EXPECT_EQ(data.uncompressed_length, 3u);
|
||||
ASSERT_EQ(ExtractToMemory(handle, &data, reinterpret_cast<uint8_t*>(buffer), arraysize(buffer)),
|
||||
0);
|
||||
buffer[3] = 0;
|
||||
EXPECT_STREQ("llo", buffer);
|
||||
|
||||
ASSERT_EQ(FindEntry(handle, ZipString("file/file2.txt"), &data), 0);
|
||||
EXPECT_EQ(data.method, kCompressStored);
|
||||
EXPECT_EQ(data.compressed_length, 0u);
|
||||
EXPECT_EQ(data.uncompressed_length, 0u);
|
||||
|
||||
CloseArchive(handle);
|
||||
}
|
||||
|
||||
TEST_F(zipwriter, WriteUncompressedZipWithAlignedFile) {
|
||||
ZipWriter writer(file_);
|
||||
|
||||
ASSERT_EQ(writer.StartEntry("align.txt", ZipWriter::kAlign32), 0);
|
||||
ASSERT_EQ(writer.WriteBytes("he", 2), 0);
|
||||
ASSERT_EQ(writer.FinishEntry(), 0);
|
||||
ASSERT_EQ(writer.Finish(), 0);
|
||||
|
||||
ASSERT_GE(lseek(fd_, 0, SEEK_SET), 0);
|
||||
|
||||
ZipArchiveHandle handle;
|
||||
ASSERT_EQ(OpenArchiveFd(fd_, "temp", &handle, false), 0);
|
||||
|
||||
ZipEntry data;
|
||||
ASSERT_EQ(FindEntry(handle, ZipString("align.txt"), &data), 0);
|
||||
EXPECT_EQ(data.offset & 0x03, 0);
|
||||
}
|
Loading…
Reference in New Issue