blob: 3141a4a87d9b6593137d8e2c8b7ba5128fb9d506 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include "DFPlatform.h"
#include "zlib.h"
#pragma pack(1)
typedef struct {
uint32_t signature; // 0x06054b50
uint16_t diskNumber; // NOT USED
uint16_t centralDirectoryDiskNumber; // NOT USED
uint16_t numEntriesThisDisk; // NOT USED
uint16_t numEntries; // Number of files in Zip
uint32_t centralDirectorySize; // NOT USED
uint32_t centralDirectoryOffset; // Offset of directory in file (fseek)
uint16_t zipCommentLength; // NOT USED
// Followed by .ZIP file comment (variable size)
} ZipEndRecord;
#pragma pack()
static const uint32_t ZipEndRecord_signature = 0x06054B50;
#pragma pack(1)
typedef struct {
uint32_t signature; // 0x02014B50
uint16_t versionMadeBy; // NOT USED
uint16_t versionNeededToExtract; // NOT USED
uint16_t generalPurposeBitFlag; // NOT USED
uint16_t compressionMethod; // NOT USED
uint16_t lastModFileTime; // NOT USED
uint16_t lastModFileDate; // NOT USED
uint32_t crc32; // crc32
uint32_t compressedSize; // NOT USED
uint32_t uncompressedSize; // NOT USED
uint16_t fileNameLength; // Only used to skip to next record
uint16_t extraFieldLength; // Only used to skip to next record
uint16_t fileCommentLength; // Only used to skip to next record
uint16_t diskNumberStart; // NOT USED
uint16_t internalFileAttributes; // NOT USED
uint32_t externalFileAttributes; // NOT USED
uint32_t relativeOffsetOflocalHeader; // offset to file header
} ZipDirectoryRecord;
#pragma pack()
static const uint32_t ZipDirectoryRecord_signature = 0x02014B50;
#pragma pack(1)
typedef struct {
uint32_t signature; // 0x04034B50
uint16_t versionNeededToExtract; // NOT USED
uint16_t generalPurposeBitFlag; // NOT USED
uint16_t compressionMethod; // flat file or zip compressed
uint16_t lastModFileTime; // NOT USED
uint16_t lastModFileDate; // NOT USED
uint32_t crc32; // NOT USED
uint32_t compressedSize; // size to read from file
uint32_t uncompressedSize; // size in memory
uint16_t fileNameLength; // Length of file name
uint16_t extraFieldLength; // NOT USED
} ZipFileHeader;
#pragma pack()
static const uint32_t ZipFileHeader_signature = 0x04034B50;
static const int FILECOUNT_ALLOC_SIZE = 5;
static int readDirectory(FILE *zipFile, DFextZipHandleP zipHandle)
{
unsigned long fileSize, readBytes;
unsigned char workBuf[4096];
int i, x, zipOffset;
//***** Read EndRecord *****
// the EndRecord contains information, where the directory is located
// find end of file, and calculate size
if (fseek(zipFile, 0, SEEK_END)
|| (fileSize = ftell(zipFile)) <= sizeof(ZipEndRecord))
return -1;
// Read size of workBuf of filesize from end of file to locate EndRecord
readBytes = (fileSize < sizeof(workBuf)) ? fileSize : sizeof(workBuf);
if (fseek(zipFile, fileSize - readBytes, SEEK_SET)
|| fread(workBuf, 1, readBytes, zipFile) < readBytes)
return -1;
// search for EndRecord signature
for (i = readBytes - sizeof(ZipEndRecord); i >= 0; i--) {
ZipEndRecord *recEnd = (ZipEndRecord *)(workBuf + i);
// check if we have a signature
if (recEnd->signature == ZipEndRecord_signature) {
// update zipHandle
zipOffset = recEnd->centralDirectoryOffset;
zipHandle->zipFileCount = recEnd->numEntries;
zipHandle->zipFileEntries = xmalloc(zipHandle->zipFileCount * sizeof(DFextZipDirEntry));
break;
}
}
if (i < 0)
return -1;
//***** Read Directory *****
// Each file has a global and a local entry, read both in a loop
// Find firs directory entry
if (fseek(zipFile, zipOffset, SEEK_SET))
return -1;
// loop through all entries
for (i = 0; i < zipHandle->zipFileCount; i++) {
ZipDirectoryRecord *recDir = (ZipDirectoryRecord *)workBuf;
DFextZipDirEntry *dirEntry = &zipHandle->zipFileEntries[i];
// Find next directory entry, read it and verify signature
if (fread(workBuf, 1, sizeof(ZipDirectoryRecord), zipFile) < sizeof(ZipDirectoryRecord)
|| recDir->signature != ZipDirectoryRecord_signature)
return -1;
dirEntry->compressedSize = recDir->compressedSize;
dirEntry->uncompressedSize = recDir->uncompressedSize;
dirEntry->compressionMethod = recDir->compressionMethod;
dirEntry->offset = recDir->relativeOffsetOflocalHeader;
dirEntry->crc32 = recDir->crc32;
// Add filename
dirEntry->fileName = xmalloc(recDir->fileNameLength + 1);
if (fread(dirEntry->fileName, 1, recDir->fileNameLength, zipFile) < (unsigned long)recDir->fileNameLength)
return -1;
dirEntry->fileName[recDir->fileNameLength] = '\0';
// Skip extra info and store pointer at next entry
x = recDir->extraFieldLength + recDir->fileCommentLength;
if (x && fseek(zipFile, x, SEEK_CUR))
return -1;
};
return 0;
}
static void releaseMemory(DFextZipHandleP zipHandle) {
if (zipHandle) {
int count = zipHandle->zipCreateMode ? zipHandle->zipCreateMode : zipHandle->zipFileCount;
if (count) {
for (int i = 0; i < count; i++) {
DFextZipDirEntry *zipDirEntry = &zipHandle->zipFileEntries[i];
if (zipDirEntry->fileName != NULL)
free(zipDirEntry->fileName);
}
free(zipHandle->zipFileEntries);
}
free(zipHandle);
}
}
static void writeGlobalDirAndEndRecord(DFextZipHandleP zipHandle) {
ZipDirectoryRecord dirRecord;
ZipEndRecord endRecord;
int i;
// Prepare constant part of records
endRecord.signature = ZipEndRecord_signature;
endRecord.diskNumber = endRecord.centralDirectoryDiskNumber = endRecord.numEntriesThisDisk = 0;
endRecord.numEntriesThisDisk =
endRecord.numEntries = zipHandle->zipFileCount;
endRecord.centralDirectoryOffset = ftell(zipHandle->zipFile);
endRecord.zipCommentLength = 0;
dirRecord.signature = ZipDirectoryRecord_signature;
dirRecord.versionMadeBy = 0;
dirRecord.versionNeededToExtract = 20;
dirRecord.lastModFileDate = 32;
dirRecord.lastModFileTime = dirRecord.crc32 = dirRecord.extraFieldLength =
dirRecord.fileCommentLength = dirRecord.diskNumberStart = 0;
dirRecord.internalFileAttributes = 1;
dirRecord.generalPurposeBitFlag = 0x0000;
dirRecord.externalFileAttributes = 0;
// loop through all directory entries, write to disk while collecting size
endRecord.centralDirectorySize = 0;
for (i = 0; i < zipHandle->zipFileCount; i++) {
dirRecord.compressionMethod = zipHandle->zipFileEntries[i].compressionMethod;
dirRecord.compressedSize = zipHandle->zipFileEntries[i].compressedSize;
dirRecord.uncompressedSize = zipHandle->zipFileEntries[i].uncompressedSize;
dirRecord.fileNameLength = strlen(zipHandle->zipFileEntries[i].fileName);
dirRecord.relativeOffsetOflocalHeader = zipHandle->zipFileEntries[i].offset;
dirRecord.crc32 = zipHandle->zipFileEntries[i].crc32;
endRecord.centralDirectorySize += sizeof(ZipDirectoryRecord) + dirRecord.fileNameLength;
fwrite(&dirRecord, 1, sizeof(ZipDirectoryRecord), zipHandle->zipFile);
fwrite(zipHandle->zipFileEntries[i].fileName, 1, dirRecord.fileNameLength, zipHandle->zipFile);
}
// and finally the end record
fwrite(&endRecord, 1, sizeof(ZipEndRecord), zipHandle->zipFile);
}
DFextZipHandleP DFextZipOpen(const char *zipFilename) {
DFextZipHandleP zipHandle = xmalloc(sizeof(DFextZipHandle));
// open zip file for reading
zipHandle->zipCreateMode = zipHandle->zipFileCount = 0;
zipHandle->zipFile = fopen(zipFilename, "rb");
if (zipHandle->zipFile
&& !readDirectory(zipHandle->zipFile, zipHandle))
return zipHandle;
// release memory
releaseMemory(zipHandle);
return NULL;
}
unsigned char *DFextZipReadFile(DFextZipHandleP zipHandle, DFextZipDirEntryP zipEntry) {
unsigned char *fileBuf = xmalloc(zipEntry->uncompressedSize);
ZipFileHeader recFile;
z_stream strm;
// Position in front of file
if (fseek(zipHandle->zipFile, zipEntry->offset, SEEK_SET)
|| fread(&recFile, 1, sizeof(ZipFileHeader), zipHandle->zipFile) < sizeof(ZipFileHeader)
|| recFile.signature != ZipFileHeader_signature
|| fseek(zipHandle->zipFile, recFile.extraFieldLength + recFile.fileNameLength, SEEK_CUR)) {
free(fileBuf);
return NULL;
}
// interesting a zip file that is uncompressed, have to handle that
if (zipEntry->compressionMethod != Z_DEFLATED) {
if (fread(fileBuf, 1, zipEntry->uncompressedSize, zipHandle->zipFile) < (unsigned long)zipEntry->uncompressedSize
|| ferror(zipHandle->zipFile)) {
free(fileBuf);
fileBuf = NULL;
}
return fileBuf;
}
//***** Handle zlib inflate *****
strm.zalloc = Z_NULL;
strm.zfree = strm.opaque = strm.next_in = Z_NULL;
strm.avail_in = 0;
// Use inflateInit2 with negative window bits to indicate raw data
if (inflateInit2(&strm, -MAX_WBITS) != Z_OK) {
free(fileBuf);
return NULL;
}
// Read compressed data
unsigned char *comprBuf = xmalloc(zipEntry->compressedSize);
if (fread(comprBuf, 1, zipEntry->compressedSize, zipHandle->zipFile) < (unsigned long)zipEntry->compressedSize
|| ferror(zipHandle->zipFile)) {
free(fileBuf);
free(comprBuf);
return NULL;
}
// and inflate data
strm.avail_in = zipEntry->compressedSize;
strm.next_in = comprBuf;
strm.avail_out = zipEntry->uncompressedSize;
strm.next_out = fileBuf;
if (inflate(&strm, Z_NO_FLUSH) == Z_STREAM_ERROR) {
free(fileBuf);
free(comprBuf);
return NULL;
}
free(comprBuf);
inflateEnd(&strm);
return fileBuf;
}
DFextZipHandleP DFextZipCreate(const char *zipFilename) {
DFextZipHandleP zipHandle = xmalloc(sizeof(DFextZipHandle));
int memSize;
// Open file for write
if ((zipHandle->zipFile = fopen(zipFilename, "wb")) == NULL) {
free(zipHandle);
return NULL;
}
// prepare to add files
zipHandle->zipFileCount = 0;
zipHandle->zipCreateMode = FILECOUNT_ALLOC_SIZE;
memSize = zipHandle->zipCreateMode * sizeof(DFextZipDirEntry);
zipHandle->zipFileEntries = xmalloc(memSize);
bzero(zipHandle->zipFileEntries, memSize);
return zipHandle;
}
DFextZipDirEntryP DFextZipWriteFile(DFextZipHandleP zipHandle, const char *fileName, const void *buf, const int len) {
z_stream strm;
unsigned char *outbuf;
ZipFileHeader header;
int fileNameLength = strlen(fileName);
// do we have space for one more entry ?
if (zipHandle->zipFileCount >= zipHandle->zipCreateMode) {
zipHandle->zipCreateMode += FILECOUNT_ALLOC_SIZE;
zipHandle->zipFileEntries = xrealloc(zipHandle->zipFileEntries, zipHandle->zipCreateMode * sizeof(DFextZipDirEntry));
bzero(&zipHandle->zipFileEntries[zipHandle->zipFileCount], FILECOUNT_ALLOC_SIZE * sizeof(DFextZipDirEntry));
}
// prepare local and global file entry
DFextZipDirEntryP entryPtr = &zipHandle->zipFileEntries[zipHandle->zipFileCount++];
entryPtr->offset = ftell(zipHandle->zipFile);
entryPtr->uncompressedSize = len;
entryPtr->fileName = xmalloc(fileNameLength + 1);
entryPtr->compressionMethod = Z_DEFLATED;
entryPtr->crc32 = crc32(0L, Z_NULL, 0);
entryPtr->crc32 = crc32(entryPtr->crc32, buf, len);
strcpy(entryPtr->fileName, fileName);
// prepare to deflate
strm.zalloc = Z_NULL;
strm.zfree = strm.opaque = Z_NULL;
if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK)
return NULL;
// deflate buffer
strm.next_in = buf;
strm.avail_in = len;
strm.avail_out = deflateBound(&strm, len);
strm.next_out = (Bytef *)xmalloc(strm.avail_out);
outbuf = strm.next_out;
if (deflate(&strm, Z_FINISH) != Z_STREAM_END) {
free(outbuf);
return NULL;
}
deflateEnd(&strm);
entryPtr->compressedSize = strm.total_out;
// prepare local header
header.versionNeededToExtract = 20;
header.generalPurposeBitFlag = 0;
header.lastModFileDate = 32;
header.lastModFileTime = header.extraFieldLength = header.crc32 = 0;
header.signature = ZipFileHeader_signature;
header.compressionMethod = entryPtr->compressionMethod;
header.compressedSize = entryPtr->compressedSize;
header.uncompressedSize = entryPtr->uncompressedSize;
header.fileNameLength = fileNameLength;
header.crc32 = entryPtr->crc32;
// put data to file
fwrite(&header, 1, sizeof(header), zipHandle->zipFile);
fwrite(entryPtr->fileName, 1, fileNameLength, zipHandle->zipFile);
fwrite(outbuf, 1, header.compressedSize, zipHandle->zipFile); // skip CMD bytes in front
// cleanup
free(outbuf);
return entryPtr;
}
void DFextZipClose(DFextZipHandleP zipHandle)
{
if (zipHandle->zipCreateMode)
writeGlobalDirAndEndRecord(zipHandle);
fclose(zipHandle->zipFile);
releaseMemory(zipHandle);
}