mirror of
https://github.com/lxsang/silk.git
synced 2024-09-08 03:56:39 +02:00
472 lines
14 KiB
C
Executable File
472 lines
14 KiB
C
Executable File
/*
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
|
|
#include "miniz.c"
|
|
#include "zip.h"
|
|
|
|
#if defined _WIN32 || defined __WIN32__ || defined __EMX__ || defined __DJGPP__
|
|
/* Win32, OS/2, DOS */
|
|
#define HAS_DEVICE(P) ((((P)[0] >= 'A' && (P)[0] <= 'Z') || ((P)[0] >= 'a' && (P)[0] <= 'z')) && (P)[1] == ':')
|
|
#define FILESYSTEM_PREFIX_LEN(P) (HAS_DEVICE (P) ? 2 : 0)
|
|
#define ISSLASH(C) ((C) == '/' || (C) == '\\')
|
|
#endif
|
|
|
|
#ifndef FILESYSTEM_PREFIX_LEN
|
|
#define FILESYSTEM_PREFIX_LEN(P) 0
|
|
#endif
|
|
|
|
#ifndef ISSLASH
|
|
#define ISSLASH(C) ((C) == '/')
|
|
#endif
|
|
|
|
#define cleanup(ptr) do { if (ptr) { free((void *)ptr); ptr = NULL; } } while (0)
|
|
#define strclone(ptr) ((ptr) ? strdup(ptr) : NULL)
|
|
|
|
char *basename(const char *name) {
|
|
char const *p;
|
|
char const *base = name += FILESYSTEM_PREFIX_LEN (name);
|
|
int all_slashes = 1;
|
|
|
|
for (p = name; *p; p++) {
|
|
if (ISSLASH(*p))
|
|
base = p + 1;
|
|
else
|
|
all_slashes = 0;
|
|
}
|
|
|
|
/* If NAME is all slashes, arrange to return `/'. */
|
|
if (*base == '\0' && ISSLASH(*name) && all_slashes)
|
|
--base;
|
|
|
|
return (char *)base;
|
|
}
|
|
|
|
static int mkpath(const char *path) {
|
|
const int mode = 0755;
|
|
char const *p;
|
|
char npath[MAX_PATH + 1] = { 0 };
|
|
int len = 0;
|
|
|
|
for (p = path; *p && len < MAX_PATH; p++) {
|
|
if (ISSLASH(*p) && len > 0) {
|
|
if (mkdir(npath, mode) == -1)
|
|
if (errno != EEXIST) return -1;
|
|
}
|
|
npath[len++] = *p;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct zip_entry_t {
|
|
const char *name;
|
|
mz_uint64 uncomp_size;
|
|
mz_uint64 comp_size;
|
|
mz_uint32 uncomp_crc32;
|
|
mz_uint64 offset;
|
|
mz_uint8 header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE];
|
|
mz_uint64 header_offset;
|
|
mz_uint16 method;
|
|
mz_zip_writer_add_state state;
|
|
tdefl_compressor comp;
|
|
};
|
|
|
|
struct zip_t {
|
|
mz_zip_archive archive;
|
|
mz_uint level;
|
|
struct zip_entry_t entry;
|
|
};
|
|
|
|
struct zip_t *zip_open(const char *zipname, int level, int append) {
|
|
struct zip_t *zip = NULL;
|
|
struct MZ_FILE_STAT_STRUCT fstat;
|
|
|
|
if (!zipname || strlen(zipname) < 1) {
|
|
// zip_t archive name is empty or NULL
|
|
return NULL;
|
|
}
|
|
|
|
if (level < 0) level = MZ_DEFAULT_LEVEL;
|
|
if ((level & 0xF) > MZ_UBER_COMPRESSION) {
|
|
// Wrong compression level
|
|
return NULL;
|
|
}
|
|
|
|
zip = (struct zip_t *)calloc((size_t)1, sizeof(struct zip_t));
|
|
if (zip) {
|
|
zip->level = level;
|
|
|
|
if (append && MZ_FILE_STAT(zipname, &fstat) == 0) {
|
|
// Append to an existing archive.
|
|
if (!mz_zip_reader_init_file(&(zip->archive), zipname, level | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) {
|
|
cleanup(zip);
|
|
return NULL;
|
|
}
|
|
|
|
if (!mz_zip_writer_init_from_reader(&(zip->archive), zipname)) {
|
|
mz_zip_reader_end(&(zip->archive));
|
|
cleanup(zip);
|
|
return NULL;
|
|
}
|
|
} else {
|
|
// Create a new archive.
|
|
if (!mz_zip_writer_init_file(&(zip->archive), zipname, 0)) {
|
|
// Cannot initialize zip_archive writer
|
|
cleanup(zip);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
return zip;
|
|
}
|
|
|
|
void zip_close(struct zip_t *zip) {
|
|
if (zip) {
|
|
// Always finalize, even if adding failed for some reason, so we have a valid central directory.
|
|
mz_zip_writer_finalize_archive(&(zip->archive));
|
|
mz_zip_writer_end(&(zip->archive));
|
|
|
|
cleanup(zip);
|
|
}
|
|
}
|
|
|
|
int zip_entry_open(struct zip_t *zip, const char *entryname) {
|
|
size_t entrylen = 0;
|
|
mz_zip_archive *pzip = NULL;
|
|
mz_uint num_alignment_padding_bytes, level;
|
|
|
|
if (!zip || !entryname) {
|
|
return -1;
|
|
}
|
|
|
|
entrylen = strlen(entryname);
|
|
if (entrylen < 1) {
|
|
return -1;
|
|
}
|
|
|
|
zip->entry.name = strclone(entryname);
|
|
if (!zip->entry.name) {
|
|
// Cannot parse zip entry name
|
|
return -1;
|
|
}
|
|
|
|
zip->entry.comp_size = 0;
|
|
zip->entry.uncomp_size = 0;
|
|
zip->entry.uncomp_crc32 = MZ_CRC32_INIT;
|
|
zip->entry.offset = zip->archive.m_archive_size;
|
|
zip->entry.header_offset = zip->archive.m_archive_size;
|
|
memset(zip->entry.header, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE * sizeof(mz_uint8));
|
|
zip->entry.method = 0;
|
|
|
|
pzip = &(zip->archive);
|
|
num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pzip);
|
|
|
|
if (!pzip->m_pState || (pzip->m_zip_mode != MZ_ZIP_MODE_WRITING)) {
|
|
// Wrong zip mode
|
|
return -1;
|
|
}
|
|
if (zip->level & MZ_ZIP_FLAG_COMPRESSED_DATA) {
|
|
// Wrong zip compression level
|
|
return -1;
|
|
}
|
|
// no zip64 support yet
|
|
if ((pzip->m_total_files == 0xFFFF) || ((pzip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + entrylen) > 0xFFFFFFFF)) {
|
|
// No zip64 support yet
|
|
return -1;
|
|
}
|
|
if (!mz_zip_writer_write_zeros(pzip, zip->entry.offset, num_alignment_padding_bytes + sizeof(zip->entry.header))) {
|
|
// Cannot memset zip entry header
|
|
return -1;
|
|
}
|
|
|
|
zip->entry.header_offset += num_alignment_padding_bytes;
|
|
if (pzip->m_file_offset_alignment) { MZ_ASSERT((zip->entry.header_offset & (pzip->m_file_offset_alignment - 1)) == 0); }
|
|
zip->entry.offset += num_alignment_padding_bytes + sizeof(zip->entry.header);
|
|
|
|
if (pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.offset, zip->entry.name, entrylen) != entrylen) {
|
|
// Cannot write data to zip entry
|
|
return -1;
|
|
}
|
|
|
|
zip->entry.offset += entrylen;
|
|
level = zip->level & 0xF;
|
|
if (level) {
|
|
zip->entry.state.m_pZip = pzip;
|
|
zip->entry.state.m_cur_archive_file_ofs = zip->entry.offset;
|
|
zip->entry.state.m_comp_size = 0;
|
|
|
|
if (tdefl_init(&(zip->entry.comp), mz_zip_writer_add_put_buf_callback, &(zip->entry.state), tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) {
|
|
// Cannot initialize the zip compressor
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int zip_entry_close(struct zip_t *zip) {
|
|
mz_zip_archive *pzip = NULL;
|
|
mz_uint level;
|
|
tdefl_status done;
|
|
mz_uint16 entrylen;
|
|
time_t t;
|
|
struct tm *tm;
|
|
mz_uint16 dos_time, dos_date;
|
|
|
|
if (!zip) {
|
|
// zip_t handler is not initialized
|
|
return -1;
|
|
}
|
|
|
|
pzip = &(zip->archive);
|
|
level = zip->level & 0xF;
|
|
if (level) {
|
|
done = tdefl_compress_buffer(&(zip->entry.comp), "", 0, TDEFL_FINISH);
|
|
if (done != TDEFL_STATUS_DONE && done != TDEFL_STATUS_OKAY) {
|
|
// Cannot flush compressed buffer
|
|
cleanup(zip->entry.name);
|
|
return -1;
|
|
}
|
|
zip->entry.comp_size = zip->entry.state.m_comp_size;
|
|
zip->entry.offset = zip->entry.state.m_cur_archive_file_ofs;
|
|
zip->entry.method = MZ_DEFLATED;
|
|
}
|
|
|
|
entrylen = (mz_uint16)strlen(zip->entry.name);
|
|
t = time(NULL);
|
|
tm = localtime(&t);
|
|
dos_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1));
|
|
dos_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday);
|
|
|
|
// no zip64 support yet
|
|
if ((zip->entry.comp_size > 0xFFFFFFFF) || (zip->entry.offset > 0xFFFFFFFF)) {
|
|
// No zip64 support, yet
|
|
cleanup(zip->entry.name);
|
|
return -1;
|
|
}
|
|
|
|
if (!mz_zip_writer_create_local_dir_header(pzip, zip->entry.header, entrylen, 0, zip->entry.uncomp_size, zip->entry.comp_size, zip->entry.uncomp_crc32, zip->entry.method, 0, dos_time, dos_date)) {
|
|
// Cannot create zip entry header
|
|
cleanup(zip->entry.name);
|
|
return -1;
|
|
}
|
|
|
|
if (pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.header_offset, zip->entry.header, sizeof(zip->entry.header)) != sizeof(zip->entry.header)) {
|
|
// Cannot write zip entry header
|
|
cleanup(zip->entry.name);
|
|
return -1;
|
|
}
|
|
|
|
if (!mz_zip_writer_add_to_central_dir(pzip, zip->entry.name, entrylen, NULL, 0, "", 0, zip->entry.uncomp_size, zip->entry.comp_size, zip->entry.uncomp_crc32, zip->entry.method, 0, dos_time, dos_date, zip->entry.header_offset, 0)) {
|
|
// Cannot write to zip central dir
|
|
cleanup(zip->entry.name);
|
|
return -1;
|
|
}
|
|
|
|
pzip->m_total_files++;
|
|
pzip->m_archive_size = zip->entry.offset;
|
|
|
|
cleanup(zip->entry.name);
|
|
return 0;
|
|
}
|
|
|
|
int zip_entry_write(struct zip_t *zip, const void *buf, size_t bufsize) {
|
|
mz_uint level;
|
|
mz_zip_archive *pzip = NULL;
|
|
tdefl_status status;
|
|
|
|
if (!zip) {
|
|
// zip_t handler is not initialized
|
|
return -1;
|
|
}
|
|
|
|
pzip = &(zip->archive);
|
|
if (buf && bufsize > 0) {
|
|
zip->entry.uncomp_size += bufsize;
|
|
zip->entry.uncomp_crc32 = (mz_uint32)mz_crc32(zip->entry.uncomp_crc32, (const mz_uint8 *)buf, bufsize);
|
|
|
|
level = zip->level & 0xF;
|
|
if (!level) {
|
|
if ((pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.offset, buf, bufsize) != bufsize)) {
|
|
// Cannot write buffer
|
|
return -1;
|
|
}
|
|
zip->entry.offset += bufsize;
|
|
zip->entry.comp_size += bufsize;
|
|
} else {
|
|
status = tdefl_compress_buffer(&(zip->entry.comp), buf, bufsize, TDEFL_NO_FLUSH);
|
|
if (status != TDEFL_STATUS_DONE && status != TDEFL_STATUS_OKAY) {
|
|
// Cannot compress buffer
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int zip_entry_fwrite(struct zip_t *zip, const char *filename) {
|
|
int status = 0;
|
|
size_t n = 0;
|
|
FILE *stream = NULL;
|
|
mz_uint8 buf[MZ_ZIP_MAX_IO_BUF_SIZE] = { 0 };
|
|
|
|
if (!zip) {
|
|
// zip_t handler is not initialized
|
|
return -1;
|
|
}
|
|
|
|
stream = fopen(filename, "rb");
|
|
if (!stream) {
|
|
// Cannot open filename
|
|
return -1;
|
|
}
|
|
|
|
while((n = fread(buf, sizeof(mz_uint8), MZ_ZIP_MAX_IO_BUF_SIZE, stream)) > 0) {
|
|
if (zip_entry_write(zip, buf, n) < 0) {
|
|
status = -1;
|
|
break;
|
|
}
|
|
}
|
|
fclose(stream);
|
|
|
|
return status;
|
|
}
|
|
|
|
int zip_create(const char *zipname, const char *filenames[], size_t len) {
|
|
int status = 0;
|
|
size_t i;
|
|
mz_zip_archive zip_archive;
|
|
|
|
if (!zipname || strlen(zipname) < 1) {
|
|
// zip_t archive name is empty or NULL
|
|
return -1;
|
|
}
|
|
|
|
// Create a new archive.
|
|
if (!memset(&(zip_archive), 0, sizeof(zip_archive))) {
|
|
// Cannot memset zip archive
|
|
return -1;
|
|
}
|
|
|
|
if (!mz_zip_writer_init_file(&zip_archive, zipname, 0)) {
|
|
// Cannot initialize zip_archive writer
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < len; ++i) {
|
|
const char *name = filenames[i];
|
|
if (!name) {
|
|
status = -1;
|
|
break;
|
|
}
|
|
|
|
if (!mz_zip_writer_add_file(&zip_archive, basename(name), name, "", 0, ZIP_DEFAULT_COMPRESSION_LEVEL)) {
|
|
// Cannot add file to zip_archive
|
|
status = -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
mz_zip_writer_finalize_archive(&zip_archive);
|
|
mz_zip_writer_end(&zip_archive);
|
|
return status;
|
|
}
|
|
|
|
int zip_extract(const char *zipname, const char *dir, int (* on_extract)(const char *filename, void *arg), void *arg) {
|
|
int status = 0;
|
|
mz_uint i, n;
|
|
char path[MAX_PATH + 1] = { 0 };
|
|
mz_zip_archive zip_archive;
|
|
mz_zip_archive_file_stat info;
|
|
size_t dirlen = 0;
|
|
|
|
if (!memset(&(zip_archive), 0, sizeof(zip_archive))) {
|
|
// Cannot memset zip archive
|
|
return -1;
|
|
}
|
|
|
|
if (!zipname || !dir) {
|
|
// Cannot parse zip archive name
|
|
return -1;
|
|
}
|
|
|
|
dirlen = strlen(dir);
|
|
if (dirlen + 1 > MAX_PATH) {
|
|
return -1;
|
|
}
|
|
|
|
// Now try to open the archive.
|
|
if (!mz_zip_reader_init_file(&zip_archive, zipname, 0)) {
|
|
// Cannot initialize zip_archive reader
|
|
status = -1;
|
|
goto finally;
|
|
}
|
|
|
|
strcpy(path, dir);
|
|
if (!ISSLASH(path[dirlen -1])) {
|
|
#if defined _WIN32 || defined __WIN32__ || defined __EMX__ || defined __DJGPP__
|
|
path[dirlen] = '\\';
|
|
#else
|
|
path[dirlen] = '/';
|
|
#endif
|
|
++dirlen;
|
|
}
|
|
|
|
// Get and print information about each file in the archive.
|
|
n = mz_zip_reader_get_num_files(&zip_archive);
|
|
for (i = 0; i < n; ++i) {
|
|
if (!mz_zip_reader_file_stat(&zip_archive, i, &info)) {
|
|
// Cannot get information about zip archive;
|
|
//printf("Cant get information about fimes");
|
|
status = -1;
|
|
break;
|
|
}
|
|
|
|
strncpy(&path[dirlen], info.m_filename, MAX_PATH - dirlen);
|
|
if (mkpath(path) < 0) {
|
|
// Cannot make a path
|
|
//printf("Cant create path\n");
|
|
status = -1;
|
|
break;
|
|
}
|
|
|
|
// verify if the path is directory first
|
|
// issue fixed by xsang.le@gmail.com
|
|
struct stat path_stat;
|
|
if(stat(path, &path_stat) != 0 || !(path_stat.st_mode & S_IFDIR))
|
|
if (!mz_zip_reader_extract_to_file(&zip_archive, i, path, 0)) {
|
|
// Cannot extract zip archive to file
|
|
//printf("Cant extract %s\n", path);
|
|
status = -1;
|
|
break;
|
|
}
|
|
|
|
if (on_extract) {
|
|
if (on_extract(path, arg) < 0) {
|
|
status = -1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Close the archive, freeing any resources it was using
|
|
if (!mz_zip_reader_end(&zip_archive)) {
|
|
// Cannot end zip reader
|
|
status = -1;
|
|
}
|
|
|
|
finally:
|
|
return status;
|
|
}
|