sqlite/ext/misc/zipfile.c
dan 66a3a91aa6 Change zipfile to be a WITHOUT ROWID virtual table and table-valued function.
FossilOrigin-Name: 931201f64e04247ed613a0301fcc86c3a337c2ed162c6370a80c67a1dd919e7c
2018-01-11 17:33:48 +00:00

1535 lines
43 KiB
C

/*
** 2017-12-26
**
** The author disclaims copyright to this source code. In place of
** a legal notice, here is a blessing:
**
** May you do good and not evil.
** May you find forgiveness for yourself and forgive others.
** May you share freely, never taking more than you give.
**
******************************************************************************
**
** This file implements a virtual table for reading and writing ZIP archive
** files.
**
** Usage example:
**
** SELECT name, sz, datetime(mtime,'unixepoch') FROM zipfile($filename);
**
** Current limitations:
**
** * No support for encryption
** * No support for ZIP archives spanning multiple files
** * No support for zip64 extensions
** * Only the "inflate/deflate" (zlib) compression method is supported
*/
#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#if !defined(_WIN32) && !defined(WIN32)
# include <unistd.h>
# include <dirent.h>
# include <utime.h>
#else
# include <io.h>
#endif
#include <time.h>
#include <errno.h>
#include <zlib.h>
#ifndef SQLITE_OMIT_VIRTUALTABLE
#ifndef SQLITE_AMALGAMATION
typedef sqlite3_int64 i64;
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned long u32;
#define MIN(a,b) ((a)<(b) ? (a) : (b))
#endif
static const char ZIPFILE_SCHEMA[] =
"CREATE TABLE y("
"name PRIMARY KEY," /* 0: Name of file in zip archive */
"mode," /* 1: POSIX mode for file */
"mtime," /* 2: Last modification time (secs since 1970)*/
"sz," /* 3: Size of object */
"rawdata," /* 4: Raw data */
"data," /* 5: Uncompressed data */
"method," /* 6: Compression method (integer) */
"file HIDDEN" /* 7: Name of zip file */
") WITHOUT ROWID;";
#define ZIPFILE_F_COLUMN_IDX 7 /* Index of column "file" in the above */
#define ZIPFILE_BUFFER_SIZE (64*1024)
/*
** Magic numbers used to read and write zip files.
**
** ZIPFILE_NEWENTRY_MADEBY:
** Use this value for the "version-made-by" field in new zip file
** entries. The upper byte indicates "unix", and the lower byte
** indicates that the zip file matches pkzip specification 3.0.
** This is what info-zip seems to do.
**
** ZIPFILE_NEWENTRY_REQUIRED:
** Value for "version-required-to-extract" field of new entries.
** Version 2.0 is required to support folders and deflate compression.
**
** ZIPFILE_NEWENTRY_FLAGS:
** Value for "general-purpose-bit-flags" field of new entries. Bit
** 11 means "utf-8 filename and comment".
**
** ZIPFILE_SIGNATURE_CDS:
** First 4 bytes of a valid CDS record.
**
** ZIPFILE_SIGNATURE_LFH:
** First 4 bytes of a valid LFH record.
*/
#define ZIPFILE_EXTRA_TIMESTAMP 0x5455
#define ZIPFILE_NEWENTRY_MADEBY ((3<<8) + 30)
#define ZIPFILE_NEWENTRY_REQUIRED 20
#define ZIPFILE_NEWENTRY_FLAGS 0x800
#define ZIPFILE_SIGNATURE_CDS 0x02014b50
#define ZIPFILE_SIGNATURE_LFH 0x04034b50
#define ZIPFILE_SIGNATURE_EOCD 0x06054b50
#define ZIPFILE_LFH_FIXED_SZ 30
/*
** Set the error message contained in context ctx to the results of
** vprintf(zFmt, ...).
*/
static void zipfileCtxErrorMsg(sqlite3_context *ctx, const char *zFmt, ...){
char *zMsg = 0;
va_list ap;
va_start(ap, zFmt);
zMsg = sqlite3_vmprintf(zFmt, ap);
sqlite3_result_error(ctx, zMsg, -1);
sqlite3_free(zMsg);
va_end(ap);
}
/*
*** 4.3.16 End of central directory record:
***
*** end of central dir signature 4 bytes (0x06054b50)
*** number of this disk 2 bytes
*** number of the disk with the
*** start of the central directory 2 bytes
*** total number of entries in the
*** central directory on this disk 2 bytes
*** total number of entries in
*** the central directory 2 bytes
*** size of the central directory 4 bytes
*** offset of start of central
*** directory with respect to
*** the starting disk number 4 bytes
*** .ZIP file comment length 2 bytes
*** .ZIP file comment (variable size)
*/
typedef struct ZipfileEOCD ZipfileEOCD;
struct ZipfileEOCD {
u16 iDisk;
u16 iFirstDisk;
u16 nEntry;
u16 nEntryTotal;
u32 nSize;
u32 iOffset;
};
/*
*** 4.3.12 Central directory structure:
***
*** ...
***
*** central file header signature 4 bytes (0x02014b50)
*** version made by 2 bytes
*** version needed to extract 2 bytes
*** general purpose bit flag 2 bytes
*** compression method 2 bytes
*** last mod file time 2 bytes
*** last mod file date 2 bytes
*** crc-32 4 bytes
*** compressed size 4 bytes
*** uncompressed size 4 bytes
*** file name length 2 bytes
*** extra field length 2 bytes
*** file comment length 2 bytes
*** disk number start 2 bytes
*** internal file attributes 2 bytes
*** external file attributes 4 bytes
*** relative offset of local header 4 bytes
*/
typedef struct ZipfileCDS ZipfileCDS;
struct ZipfileCDS {
u16 iVersionMadeBy;
u16 iVersionExtract;
u16 flags;
u16 iCompression;
u16 mTime;
u16 mDate;
u32 crc32;
u32 szCompressed;
u32 szUncompressed;
u16 nFile;
u16 nExtra;
u16 nComment;
u16 iDiskStart;
u16 iInternalAttr;
u32 iExternalAttr;
u32 iOffset;
char *zFile; /* Filename (sqlite3_malloc()) */
};
/*
*** 4.3.7 Local file header:
***
*** local file header signature 4 bytes (0x04034b50)
*** version needed to extract 2 bytes
*** general purpose bit flag 2 bytes
*** compression method 2 bytes
*** last mod file time 2 bytes
*** last mod file date 2 bytes
*** crc-32 4 bytes
*** compressed size 4 bytes
*** uncompressed size 4 bytes
*** file name length 2 bytes
*** extra field length 2 bytes
***
*/
typedef struct ZipfileLFH ZipfileLFH;
struct ZipfileLFH {
u16 iVersionExtract;
u16 flags;
u16 iCompression;
u16 mTime;
u16 mDate;
u32 crc32;
u32 szCompressed;
u32 szUncompressed;
u16 nFile;
u16 nExtra;
};
typedef struct ZipfileEntry ZipfileEntry;
struct ZipfileEntry {
char *zPath; /* Path of zipfile entry */
u8 *aCdsEntry; /* Buffer containing entire CDS entry */
int nCdsEntry; /* Size of buffer aCdsEntry[] in bytes */
int bDeleted; /* True if entry has been deleted */
ZipfileEntry *pNext; /* Next element in in-memory CDS */
};
/*
** Cursor type for recursively iterating through a directory structure.
*/
typedef struct ZipfileCsr ZipfileCsr;
struct ZipfileCsr {
sqlite3_vtab_cursor base; /* Base class - must be first */
int bEof; /* True when at EOF */
/* Used outside of write transactions */
FILE *pFile; /* Zip file */
i64 iNextOff; /* Offset of next record in central directory */
ZipfileEOCD eocd; /* Parse of central directory record */
/* Used inside write transactions */
ZipfileEntry *pCurrent;
ZipfileCDS cds; /* Central Directory Structure */
ZipfileLFH lfh; /* Local File Header for current entry */
i64 iDataOff; /* Offset in zipfile to data */
u32 mTime; /* Extended mtime value */
int flags; /* Flags byte (see below for bits) */
ZipfileCsr *pCsrNext; /* Next cursor on same virtual table */
};
/*
** Values for ZipfileCsr.flags.
*/
#define ZIPFILE_MTIME_VALID 0x0001
typedef struct ZipfileTab ZipfileTab;
struct ZipfileTab {
sqlite3_vtab base; /* Base class - must be first */
char *zFile; /* Zip file this table accesses (may be NULL) */
u8 *aBuffer; /* Temporary buffer used for various tasks */
/* The following are used by write transactions only */
ZipfileCsr *pCsrList; /* List of cursors */
ZipfileEntry *pFirstEntry; /* Linked list of all files (if pWriteFd!=0) */
ZipfileEntry *pLastEntry; /* Last element in pFirstEntry list */
FILE *pWriteFd; /* File handle open on zip archive */
i64 szCurrent; /* Current size of zip archive */
i64 szOrig; /* Size of archive at start of transaction */
};
static void zipfileDequote(char *zIn){
char q = zIn[0];
if( q=='"' || q=='\'' || q=='`' || q=='[' ){
char c;
int iIn = 1;
int iOut = 0;
if( q=='[' ) q = ']';
while( (c = zIn[iIn++]) ){
if( c==q ){
if( zIn[iIn++]!=q ) break;
}
zIn[iOut++] = c;
}
zIn[iOut] = '\0';
}
}
/*
** Construct a new ZipfileTab virtual table object.
**
** argv[0] -> module name ("zipfile")
** argv[1] -> database name
** argv[2] -> table name
** argv[...] -> "column name" and other module argument fields.
*/
static int zipfileConnect(
sqlite3 *db,
void *pAux,
int argc, const char *const*argv,
sqlite3_vtab **ppVtab,
char **pzErr
){
int nByte = sizeof(ZipfileTab) + ZIPFILE_BUFFER_SIZE;
int nFile = 0;
const char *zFile = 0;
ZipfileTab *pNew = 0;
int rc;
if( argc>3 ){
zFile = argv[3];
nFile = (int)strlen(zFile)+1;
}
rc = sqlite3_declare_vtab(db, ZIPFILE_SCHEMA);
if( rc==SQLITE_OK ){
pNew = (ZipfileTab*)sqlite3_malloc(nByte+nFile);
if( pNew==0 ) return SQLITE_NOMEM;
memset(pNew, 0, nByte+nFile);
pNew->aBuffer = (u8*)&pNew[1];
if( zFile ){
pNew->zFile = (char*)&pNew->aBuffer[ZIPFILE_BUFFER_SIZE];
memcpy(pNew->zFile, zFile, nFile);
zipfileDequote(pNew->zFile);
}
}
*ppVtab = (sqlite3_vtab*)pNew;
return rc;
}
/*
** This method is the destructor for zipfile vtab objects.
*/
static int zipfileDisconnect(sqlite3_vtab *pVtab){
sqlite3_free(pVtab);
return SQLITE_OK;
}
/*
** Constructor for a new ZipfileCsr object.
*/
static int zipfileOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCsr){
ZipfileCsr *pCsr;
pCsr = sqlite3_malloc(sizeof(*pCsr));
*ppCsr = (sqlite3_vtab_cursor*)pCsr;
if( pCsr==0 ){
return SQLITE_NOMEM;
}
memset(pCsr, 0, sizeof(*pCsr));
return SQLITE_OK;
}
/*
** Reset a cursor back to the state it was in when first returned
** by zipfileOpen().
*/
static void zipfileResetCursor(ZipfileCsr *pCsr){
ZipfileTab *pTab = (ZipfileTab*)(pCsr->base.pVtab);
ZipfileCsr **pp;
/* Remove this cursor from the ZipfileTab.pCsrList list. */
for(pp=&pTab->pCsrList; *pp; pp=&((*pp)->pCsrNext)){
if( *pp==pCsr ) *pp = pCsr->pCsrNext;
}
sqlite3_free(pCsr->cds.zFile);
pCsr->cds.zFile = 0;
pCsr->bEof = 0;
if( pCsr->pFile ){
fclose(pCsr->pFile);
pCsr->pFile = 0;
}
}
/*
** Destructor for an ZipfileCsr.
*/
static int zipfileClose(sqlite3_vtab_cursor *cur){
ZipfileCsr *pCsr = (ZipfileCsr*)cur;
zipfileResetCursor(pCsr);
sqlite3_free(pCsr);
return SQLITE_OK;
}
/*
** Set the error message for the virtual table associated with cursor
** pCsr to the results of vprintf(zFmt, ...).
*/
static void zipfileSetErrmsg(ZipfileCsr *pCsr, const char *zFmt, ...){
va_list ap;
va_start(ap, zFmt);
pCsr->base.pVtab->zErrMsg = sqlite3_vmprintf(zFmt, ap);
va_end(ap);
}
static int zipfileReadData(
FILE *pFile, /* Read from this file */
u8 *aRead, /* Read into this buffer */
int nRead, /* Number of bytes to read */
i64 iOff, /* Offset to read from */
char **pzErrmsg /* OUT: Error message (from sqlite3_malloc) */
){
size_t n;
fseek(pFile, (long)iOff, SEEK_SET);
n = fread(aRead, 1, nRead, pFile);
if( (int)n!=nRead ){
*pzErrmsg = sqlite3_mprintf("error in fread()");
return SQLITE_ERROR;
}
return SQLITE_OK;
}
static int zipfileAppendData(
ZipfileTab *pTab,
const u8 *aWrite,
int nWrite
){
size_t n;
fseek(pTab->pWriteFd, (long)pTab->szCurrent, SEEK_SET);
n = fwrite(aWrite, 1, nWrite, pTab->pWriteFd);
if( (int)n!=nWrite ){
pTab->base.zErrMsg = sqlite3_mprintf("error in fwrite()");
return SQLITE_ERROR;
}
pTab->szCurrent += nWrite;
return SQLITE_OK;
}
static u16 zipfileGetU16(const u8 *aBuf){
return (aBuf[1] << 8) + aBuf[0];
}
static u32 zipfileGetU32(const u8 *aBuf){
return ((u32)(aBuf[3]) << 24)
+ ((u32)(aBuf[2]) << 16)
+ ((u32)(aBuf[1]) << 8)
+ ((u32)(aBuf[0]) << 0);
}
static void zipfilePutU16(u8 *aBuf, u16 val){
aBuf[0] = val & 0xFF;
aBuf[1] = (val>>8) & 0xFF;
}
static void zipfilePutU32(u8 *aBuf, u32 val){
aBuf[0] = val & 0xFF;
aBuf[1] = (val>>8) & 0xFF;
aBuf[2] = (val>>16) & 0xFF;
aBuf[3] = (val>>24) & 0xFF;
}
#define zipfileRead32(aBuf) ( aBuf+=4, zipfileGetU32(aBuf-4) )
#define zipfileRead16(aBuf) ( aBuf+=2, zipfileGetU16(aBuf-2) )
#define zipfileWrite32(aBuf,val) { zipfilePutU32(aBuf,val); aBuf+=4; }
#define zipfileWrite16(aBuf,val) { zipfilePutU16(aBuf,val); aBuf+=2; }
static u8* zipfileCsrBuffer(ZipfileCsr *pCsr){
return ((ZipfileTab*)(pCsr->base.pVtab))->aBuffer;
}
/*
** Magic numbers used to read CDS records.
*/
#define ZIPFILE_CDS_FIXED_SZ 46
#define ZIPFILE_CDS_NFILE_OFF 28
static int zipfileReadCDS(ZipfileCsr *pCsr){
char **pzErr = &pCsr->base.pVtab->zErrMsg;
u8 *aRead;
int rc = SQLITE_OK;
sqlite3_free(pCsr->cds.zFile);
pCsr->cds.zFile = 0;
if( pCsr->pCurrent==0 ){
aRead = zipfileCsrBuffer(pCsr);
rc = zipfileReadData(
pCsr->pFile, aRead, ZIPFILE_CDS_FIXED_SZ, pCsr->iNextOff, pzErr
);
}else{
aRead = pCsr->pCurrent->aCdsEntry;
}
if( rc==SQLITE_OK ){
u32 sig = zipfileRead32(aRead);
if( sig!=ZIPFILE_SIGNATURE_CDS ){
assert( pCsr->pCurrent==0 );
zipfileSetErrmsg(pCsr,"failed to read CDS at offset %lld",pCsr->iNextOff);
rc = SQLITE_ERROR;
}else{
int nRead;
pCsr->cds.iVersionMadeBy = zipfileRead16(aRead);
pCsr->cds.iVersionExtract = zipfileRead16(aRead);
pCsr->cds.flags = zipfileRead16(aRead);
pCsr->cds.iCompression = zipfileRead16(aRead);
pCsr->cds.mTime = zipfileRead16(aRead);
pCsr->cds.mDate = zipfileRead16(aRead);
pCsr->cds.crc32 = zipfileRead32(aRead);
pCsr->cds.szCompressed = zipfileRead32(aRead);
pCsr->cds.szUncompressed = zipfileRead32(aRead);
assert( pCsr->pCurrent
|| aRead==zipfileCsrBuffer(pCsr)+ZIPFILE_CDS_NFILE_OFF
);
pCsr->cds.nFile = zipfileRead16(aRead);
pCsr->cds.nExtra = zipfileRead16(aRead);
pCsr->cds.nComment = zipfileRead16(aRead);
pCsr->cds.iDiskStart = zipfileRead16(aRead);
pCsr->cds.iInternalAttr = zipfileRead16(aRead);
pCsr->cds.iExternalAttr = zipfileRead32(aRead);
pCsr->cds.iOffset = zipfileRead32(aRead);
assert( pCsr->pCurrent
|| aRead==zipfileCsrBuffer(pCsr)+ZIPFILE_CDS_FIXED_SZ
);
if( pCsr->pCurrent==0 ){
nRead = pCsr->cds.nFile + pCsr->cds.nExtra;
aRead = zipfileCsrBuffer(pCsr);
pCsr->iNextOff += ZIPFILE_CDS_FIXED_SZ;
rc = zipfileReadData(pCsr->pFile, aRead, nRead, pCsr->iNextOff, pzErr);
}
if( rc==SQLITE_OK ){
pCsr->cds.zFile = sqlite3_mprintf("%.*s", (int)pCsr->cds.nFile, aRead);
pCsr->iNextOff += pCsr->cds.nFile;
pCsr->iNextOff += pCsr->cds.nExtra;
pCsr->iNextOff += pCsr->cds.nComment;
}
/* Scan the cds.nExtra bytes of "extra" fields for any that can
** be interpreted. The general format of an extra field is:
**
** Header ID 2 bytes
** Data Size 2 bytes
** Data N bytes
**
*/
if( rc==SQLITE_OK ){
u8 *p = &aRead[pCsr->cds.nFile];
u8 *pEnd = &p[pCsr->cds.nExtra];
while( p<pEnd ){
u16 id = zipfileRead16(p);
u16 nByte = zipfileRead16(p);
switch( id ){
case ZIPFILE_EXTRA_TIMESTAMP: {
u8 b = p[0];
if( b & 0x01 ){ /* 0x01 -> modtime is present */
pCsr->mTime = zipfileGetU32(&p[1]);
pCsr->flags |= ZIPFILE_MTIME_VALID;
}
break;
}
}
p += nByte;
}
}
}
}
return rc;
}
static FILE *zipfileGetFd(ZipfileCsr *pCsr){
if( pCsr->pFile ) return pCsr->pFile;
return ((ZipfileTab*)(pCsr->base.pVtab))->pWriteFd;
}
static int zipfileReadLFH(ZipfileCsr *pCsr){
FILE *pFile = zipfileGetFd(pCsr);
char **pzErr = &pCsr->base.pVtab->zErrMsg;
static const int szFix = ZIPFILE_LFH_FIXED_SZ;
u8 *aRead = zipfileCsrBuffer(pCsr);
int rc;
rc = zipfileReadData(pFile, aRead, szFix, pCsr->cds.iOffset, pzErr);
if( rc==SQLITE_OK ){
u32 sig = zipfileRead32(aRead);
if( sig!=ZIPFILE_SIGNATURE_LFH ){
zipfileSetErrmsg(pCsr, "failed to read LFH at offset %d",
(int)pCsr->cds.iOffset
);
rc = SQLITE_ERROR;
}else{
pCsr->lfh.iVersionExtract = zipfileRead16(aRead);
pCsr->lfh.flags = zipfileRead16(aRead);
pCsr->lfh.iCompression = zipfileRead16(aRead);
pCsr->lfh.mTime = zipfileRead16(aRead);
pCsr->lfh.mDate = zipfileRead16(aRead);
pCsr->lfh.crc32 = zipfileRead32(aRead);
pCsr->lfh.szCompressed = zipfileRead32(aRead);
pCsr->lfh.szUncompressed = zipfileRead32(aRead);
pCsr->lfh.nFile = zipfileRead16(aRead);
pCsr->lfh.nExtra = zipfileRead16(aRead);
assert( aRead==zipfileCsrBuffer(pCsr)+szFix );
pCsr->iDataOff = pCsr->cds.iOffset+szFix+pCsr->lfh.nFile+pCsr->lfh.nExtra;
}
}
return rc;
}
/*
** Advance an ZipfileCsr to its next row of output.
*/
static int zipfileNext(sqlite3_vtab_cursor *cur){
ZipfileCsr *pCsr = (ZipfileCsr*)cur;
int rc = SQLITE_OK;
pCsr->flags = 0;
if( pCsr->pCurrent==0 ){
i64 iEof = pCsr->eocd.iOffset + pCsr->eocd.nSize;
if( pCsr->iNextOff>=iEof ){
pCsr->bEof = 1;
}
}else{
assert( pCsr->pFile==0 );
do {
pCsr->pCurrent = pCsr->pCurrent->pNext;
}while( pCsr->pCurrent && pCsr->pCurrent->bDeleted );
if( pCsr->pCurrent==0 ){
pCsr->bEof = 1;
}
}
if( pCsr->bEof==0 ){
rc = zipfileReadCDS(pCsr);
if( rc==SQLITE_OK ){
rc = zipfileReadLFH(pCsr);
}
}
return rc;
}
/*
** "Standard" MS-DOS time format:
**
** File modification time:
** Bits 00-04: seconds divided by 2
** Bits 05-10: minute
** Bits 11-15: hour
** File modification date:
** Bits 00-04: day
** Bits 05-08: month (1-12)
** Bits 09-15: years from 1980
*/
static time_t zipfileMtime(ZipfileCsr *pCsr){
struct tm t;
memset(&t, 0, sizeof(t));
t.tm_sec = (pCsr->cds.mTime & 0x1F)*2;
t.tm_min = (pCsr->cds.mTime >> 5) & 0x2F;
t.tm_hour = (pCsr->cds.mTime >> 11) & 0x1F;
t.tm_mday = (pCsr->cds.mDate & 0x1F);
t.tm_mon = ((pCsr->cds.mDate >> 5) & 0x0F) - 1;
t.tm_year = 80 + ((pCsr->cds.mDate >> 9) & 0x7F);
return mktime(&t);
}
static void zipfileMtimeToDos(ZipfileCDS *pCds, u32 mTime){
time_t t = (time_t)mTime;
struct tm res;
#if !defined(_WIN32) && !defined(WIN32)
localtime_r(&t, &res);
#else
memcpy(&res, localtime(&t), sizeof(struct tm));
#endif
pCds->mTime = (u16)(
(res.tm_sec / 2) +
(res.tm_min << 5) +
(res.tm_hour << 11));
pCds->mDate = (u16)(
(res.tm_mday-1) +
((res.tm_mon+1) << 5) +
((res.tm_year-80) << 9));
}
static void zipfileInflate(
sqlite3_context *pCtx, /* Store error here, if any */
const u8 *aIn, /* Compressed data */
int nIn, /* Size of buffer aIn[] in bytes */
int nOut /* Expected output size */
){
u8 *aRes = sqlite3_malloc(nOut);
if( aRes==0 ){
sqlite3_result_error_nomem(pCtx);
}else{
int err;
z_stream str;
memset(&str, 0, sizeof(str));
str.next_in = (Byte*)aIn;
str.avail_in = nIn;
str.next_out = (Byte*)aRes;
str.avail_out = nOut;
err = inflateInit2(&str, -15);
if( err!=Z_OK ){
zipfileCtxErrorMsg(pCtx, "inflateInit2() failed (%d)", err);
}else{
err = inflate(&str, Z_NO_FLUSH);
if( err!=Z_STREAM_END ){
zipfileCtxErrorMsg(pCtx, "inflate() failed (%d)", err);
}else{
sqlite3_result_blob(pCtx, aRes, nOut, SQLITE_TRANSIENT);
}
}
sqlite3_free(aRes);
inflateEnd(&str);
}
}
static int zipfileDeflate(
ZipfileTab *pTab, /* Set error message here */
const u8 *aIn, int nIn, /* Input */
u8 **ppOut, int *pnOut /* Output */
){
int nAlloc = (int)compressBound(nIn);
u8 *aOut;
int rc = SQLITE_OK;
aOut = (u8*)sqlite3_malloc(nAlloc);
if( aOut==0 ){
rc = SQLITE_NOMEM;
}else{
int res;
z_stream str;
memset(&str, 0, sizeof(str));
str.next_in = (z_const Bytef*)aIn;
str.avail_in = nIn;
str.next_out = aOut;
str.avail_out = nAlloc;
deflateInit2(&str, 9, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
res = deflate(&str, Z_FINISH);
if( res==Z_STREAM_END ){
*ppOut = aOut;
*pnOut = (int)str.total_out;
}else{
sqlite3_free(aOut);
pTab->base.zErrMsg = sqlite3_mprintf("zipfile: deflate() error");
rc = SQLITE_ERROR;
}
deflateEnd(&str);
}
return rc;
}
/*
** Return values of columns for the row at which the series_cursor
** is currently pointing.
*/
static int zipfileColumn(
sqlite3_vtab_cursor *cur, /* The cursor */
sqlite3_context *ctx, /* First argument to sqlite3_result_...() */
int i /* Which column to return */
){
ZipfileCsr *pCsr = (ZipfileCsr*)cur;
int rc = SQLITE_OK;
switch( i ){
case 0: /* name */
sqlite3_result_text(ctx, pCsr->cds.zFile, -1, SQLITE_TRANSIENT);
break;
case 1: /* mode */
/* TODO: Whether or not the following is correct surely depends on
** the platform on which the archive was created. */
sqlite3_result_int(ctx, pCsr->cds.iExternalAttr >> 16);
break;
case 2: { /* mtime */
if( pCsr->flags & ZIPFILE_MTIME_VALID ){
sqlite3_result_int64(ctx, pCsr->mTime);
}else{
sqlite3_result_int64(ctx, zipfileMtime(pCsr));
}
break;
}
case 3: { /* sz */
sqlite3_result_int64(ctx, pCsr->cds.szUncompressed);
break;
}
case 4: /* rawdata */
case 5: { /* data */
if( i==4 || pCsr->cds.iCompression==0 || pCsr->cds.iCompression==8 ){
int sz = pCsr->cds.szCompressed;
if( sz>0 ){
u8 *aBuf = sqlite3_malloc(sz);
if( aBuf==0 ){
rc = SQLITE_NOMEM;
}else{
FILE *pFile = zipfileGetFd(pCsr);
rc = zipfileReadData(pFile, aBuf, sz, pCsr->iDataOff,
&pCsr->base.pVtab->zErrMsg
);
}
if( rc==SQLITE_OK ){
if( i==5 && pCsr->cds.iCompression ){
zipfileInflate(ctx, aBuf, sz, pCsr->cds.szUncompressed);
}else{
sqlite3_result_blob(ctx, aBuf, sz, SQLITE_TRANSIENT);
}
sqlite3_free(aBuf);
}
}
}
break;
}
case 6: /* method */
sqlite3_result_int(ctx, pCsr->cds.iCompression);
break;
}
return SQLITE_OK;
}
/*
** Return the rowid for the current row.
*/
static int zipfileRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
assert( 0 );
return SQLITE_OK;
}
/*
** Return TRUE if the cursor has been moved off of the last
** row of output.
*/
static int zipfileEof(sqlite3_vtab_cursor *cur){
ZipfileCsr *pCsr = (ZipfileCsr*)cur;
return pCsr->bEof;
}
/*
*/
static int zipfileReadEOCD(
ZipfileTab *pTab, /* Return errors here */
FILE *pFile, /* Read from this file */
ZipfileEOCD *pEOCD /* Object to populate */
){
u8 *aRead = pTab->aBuffer; /* Temporary buffer */
i64 szFile; /* Total size of file in bytes */
int nRead; /* Bytes to read from file */
i64 iOff; /* Offset to read from */
int rc;
fseek(pFile, 0, SEEK_END);
szFile = (i64)ftell(pFile);
if( szFile==0 ){
return SQLITE_EMPTY;
}
nRead = (int)(MIN(szFile, ZIPFILE_BUFFER_SIZE));
iOff = szFile - nRead;
rc = zipfileReadData(pFile, aRead, nRead, iOff, &pTab->base.zErrMsg);
if( rc==SQLITE_OK ){
int i;
/* Scan backwards looking for the signature bytes */
for(i=nRead-20; i>=0; i--){
if( aRead[i]==0x50 && aRead[i+1]==0x4b
&& aRead[i+2]==0x05 && aRead[i+3]==0x06
){
break;
}
}
if( i<0 ){
pTab->base.zErrMsg = sqlite3_mprintf(
"cannot find end of central directory record"
);
return SQLITE_ERROR;
}
aRead += i+4;
pEOCD->iDisk = zipfileRead16(aRead);
pEOCD->iFirstDisk = zipfileRead16(aRead);
pEOCD->nEntry = zipfileRead16(aRead);
pEOCD->nEntryTotal = zipfileRead16(aRead);
pEOCD->nSize = zipfileRead32(aRead);
pEOCD->iOffset = zipfileRead32(aRead);
#if 0
printf("iDisk=%d iFirstDisk=%d nEntry=%d "
"nEntryTotal=%d nSize=%d iOffset=%d",
(int)pEOCD->iDisk, (int)pEOCD->iFirstDisk, (int)pEOCD->nEntry,
(int)pEOCD->nEntryTotal, (int)pEOCD->nSize, (int)pEOCD->iOffset
);
#endif
}
return SQLITE_OK;
}
/*
** xFilter callback.
*/
static int zipfileFilter(
sqlite3_vtab_cursor *cur,
int idxNum, const char *idxStr,
int argc, sqlite3_value **argv
){
ZipfileTab *pTab = (ZipfileTab*)cur->pVtab;
ZipfileCsr *pCsr = (ZipfileCsr*)cur;
const char *zFile; /* Zip file to scan */
int rc = SQLITE_OK; /* Return Code */
zipfileResetCursor(pCsr);
if( pTab->zFile ){
zFile = pTab->zFile;
}else if( idxNum==0 ){
/* Error. This is an eponymous virtual table and the user has not
** supplied a file name. */
zipfileSetErrmsg(pCsr, "table function zipfile() requires an argument");
return SQLITE_ERROR;
}else{
zFile = (const char*)sqlite3_value_text(argv[0]);
}
if( pTab->pWriteFd==0 ){
pCsr->pFile = fopen(zFile, "rb");
if( pCsr->pFile==0 ){
zipfileSetErrmsg(pCsr, "cannot open file: %s", zFile);
rc = SQLITE_ERROR;
}else{
rc = zipfileReadEOCD(pTab, pCsr->pFile, &pCsr->eocd);
if( rc==SQLITE_OK ){
pCsr->iNextOff = pCsr->eocd.iOffset;
rc = zipfileNext(cur);
}else if( rc==SQLITE_EMPTY ){
rc = SQLITE_OK;
pCsr->bEof = 1;
}
}
}else{
ZipfileEntry e;
memset(&e, 0, sizeof(e));
e.pNext = pTab->pFirstEntry;
pCsr->pCurrent = &e;
rc = zipfileNext(cur);
assert( pCsr->pCurrent!=&e );
}
return rc;
}
/*
** xBestIndex callback.
*/
static int zipfileBestIndex(
sqlite3_vtab *tab,
sqlite3_index_info *pIdxInfo
){
int i;
for(i=0; i<pIdxInfo->nConstraint; i++){
const struct sqlite3_index_constraint *pCons = &pIdxInfo->aConstraint[i];
if( pCons->usable==0 ) continue;
if( pCons->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue;
if( pCons->iColumn!=ZIPFILE_F_COLUMN_IDX ) continue;
break;
}
if( i<pIdxInfo->nConstraint ){
pIdxInfo->aConstraintUsage[i].argvIndex = 1;
pIdxInfo->aConstraintUsage[i].omit = 1;
pIdxInfo->estimatedCost = 1000.0;
pIdxInfo->idxNum = 1;
}else{
pIdxInfo->estimatedCost = (double)(((sqlite3_int64)1) << 50);
pIdxInfo->idxNum = 0;
}
return SQLITE_OK;
}
/*
** Add object pNew to the end of the linked list that begins at
** ZipfileTab.pFirstEntry and ends with pLastEntry.
*/
static void zipfileAddEntry(ZipfileTab *pTab, ZipfileEntry *pNew){
assert( (pTab->pFirstEntry==0)==(pTab->pLastEntry==0) );
assert( pNew->pNext==0 );
if( pTab->pFirstEntry==0 ){
pTab->pFirstEntry = pTab->pLastEntry = pNew;
}else{
assert( pTab->pLastEntry->pNext==0 );
pTab->pLastEntry->pNext = pNew;
pTab->pLastEntry = pNew;
}
}
static int zipfileLoadDirectory(ZipfileTab *pTab){
ZipfileEOCD eocd;
int rc;
rc = zipfileReadEOCD(pTab, pTab->pWriteFd, &eocd);
if( rc==SQLITE_OK ){
int i;
int iOff = 0;
u8 *aBuf = sqlite3_malloc(eocd.nSize);
if( aBuf==0 ){
rc = SQLITE_NOMEM;
}else{
rc = zipfileReadData(
pTab->pWriteFd, aBuf, eocd.nSize, eocd.iOffset, &pTab->base.zErrMsg
);
}
for(i=0; rc==SQLITE_OK && i<eocd.nEntry; i++){
u16 nFile;
u16 nExtra;
u16 nComment;
ZipfileEntry *pNew;
u8 *aRec = &aBuf[iOff];
nFile = zipfileGetU16(&aRec[ZIPFILE_CDS_NFILE_OFF]);
nExtra = zipfileGetU16(&aRec[ZIPFILE_CDS_NFILE_OFF+2]);
nComment = zipfileGetU16(&aRec[ZIPFILE_CDS_NFILE_OFF+4]);
pNew = sqlite3_malloc(
sizeof(ZipfileEntry)
+ nFile+1
+ ZIPFILE_CDS_FIXED_SZ+nFile+nExtra+nComment
);
if( pNew==0 ){
rc = SQLITE_NOMEM;
}else{
memset(pNew, 0, sizeof(ZipfileEntry));
pNew->zPath = (char*)&pNew[1];
memcpy(pNew->zPath, &aRec[ZIPFILE_CDS_FIXED_SZ], nFile);
pNew->zPath[nFile] = '\0';
pNew->aCdsEntry = (u8*)&pNew->zPath[nFile+1];
pNew->nCdsEntry = ZIPFILE_CDS_FIXED_SZ+nFile+nExtra+nComment;
memcpy(pNew->aCdsEntry, aRec, pNew->nCdsEntry);
zipfileAddEntry(pTab, pNew);
}
iOff += ZIPFILE_CDS_FIXED_SZ+nFile+nExtra+nComment;
}
sqlite3_free(aBuf);
}else if( rc==SQLITE_EMPTY ){
rc = SQLITE_OK;
}
return rc;
}
static ZipfileEntry *zipfileNewEntry(
ZipfileCDS *pCds, /* Values for fixed size part of CDS */
const char *zPath, /* Path for new entry */
int nPath, /* strlen(zPath) */
u32 mTime /* Modification time (or 0) */
){
u8 *aWrite;
ZipfileEntry *pNew;
pCds->nFile = (u16)nPath;
pCds->nExtra = mTime ? 9 : 0;
pNew = (ZipfileEntry*)sqlite3_malloc(
sizeof(ZipfileEntry) +
nPath+1 +
ZIPFILE_CDS_FIXED_SZ + nPath + pCds->nExtra
);
if( pNew ){
memset(pNew, 0, sizeof(ZipfileEntry));
pNew->zPath = (char*)&pNew[1];
pNew->aCdsEntry = (u8*)&pNew->zPath[nPath+1];
pNew->nCdsEntry = ZIPFILE_CDS_FIXED_SZ + nPath + pCds->nExtra;
memcpy(pNew->zPath, zPath, nPath+1);
aWrite = pNew->aCdsEntry;
zipfileWrite32(aWrite, ZIPFILE_SIGNATURE_CDS);
zipfileWrite16(aWrite, pCds->iVersionMadeBy);
zipfileWrite16(aWrite, pCds->iVersionExtract);
zipfileWrite16(aWrite, pCds->flags);
zipfileWrite16(aWrite, pCds->iCompression);
zipfileWrite16(aWrite, pCds->mTime);
zipfileWrite16(aWrite, pCds->mDate);
zipfileWrite32(aWrite, pCds->crc32);
zipfileWrite32(aWrite, pCds->szCompressed);
zipfileWrite32(aWrite, pCds->szUncompressed);
zipfileWrite16(aWrite, pCds->nFile);
zipfileWrite16(aWrite, pCds->nExtra);
zipfileWrite16(aWrite, pCds->nComment); assert( pCds->nComment==0 );
zipfileWrite16(aWrite, pCds->iDiskStart);
zipfileWrite16(aWrite, pCds->iInternalAttr);
zipfileWrite32(aWrite, pCds->iExternalAttr);
zipfileWrite32(aWrite, pCds->iOffset);
assert( aWrite==&pNew->aCdsEntry[ZIPFILE_CDS_FIXED_SZ] );
memcpy(aWrite, zPath, nPath);
if( pCds->nExtra ){
aWrite += nPath;
zipfileWrite16(aWrite, ZIPFILE_EXTRA_TIMESTAMP);
zipfileWrite16(aWrite, 5);
*aWrite++ = 0x01;
zipfileWrite32(aWrite, mTime);
}
}
return pNew;
}
static int zipfileAppendEntry(
ZipfileTab *pTab,
ZipfileCDS *pCds,
const char *zPath, /* Path for new entry */
int nPath, /* strlen(zPath) */
const u8 *pData,
int nData,
u32 mTime
){
u8 *aBuf = pTab->aBuffer;
int rc;
zipfileWrite32(aBuf, ZIPFILE_SIGNATURE_LFH);
zipfileWrite16(aBuf, pCds->iVersionExtract);
zipfileWrite16(aBuf, pCds->flags);
zipfileWrite16(aBuf, pCds->iCompression);
zipfileWrite16(aBuf, pCds->mTime);
zipfileWrite16(aBuf, pCds->mDate);
zipfileWrite32(aBuf, pCds->crc32);
zipfileWrite32(aBuf, pCds->szCompressed);
zipfileWrite32(aBuf, pCds->szUncompressed);
zipfileWrite16(aBuf, (u16)nPath);
zipfileWrite16(aBuf, pCds->nExtra);
assert( aBuf==&pTab->aBuffer[ZIPFILE_LFH_FIXED_SZ] );
rc = zipfileAppendData(pTab, pTab->aBuffer, (int)(aBuf - pTab->aBuffer));
if( rc==SQLITE_OK ){
rc = zipfileAppendData(pTab, (const u8*)zPath, nPath);
}
if( rc==SQLITE_OK && pCds->nExtra ){
aBuf = pTab->aBuffer;
zipfileWrite16(aBuf, ZIPFILE_EXTRA_TIMESTAMP);
zipfileWrite16(aBuf, 5);
*aBuf++ = 0x01;
zipfileWrite32(aBuf, mTime);
rc = zipfileAppendData(pTab, pTab->aBuffer, 9);
}
if( rc==SQLITE_OK ){
rc = zipfileAppendData(pTab, pData, nData);
}
return rc;
}
static int zipfileGetMode(
ZipfileTab *pTab,
sqlite3_value *pVal,
u32 defaultMode, /* Value to use if pVal IS NULL */
u32 *pMode
){
const char *z = (const char*)sqlite3_value_text(pVal);
u32 mode = 0;
if( z==0 ){
mode = defaultMode;
}else if( z[0]>=0 && z[0]<=9 ){
mode = (unsigned int)sqlite3_value_int(pVal);
}else{
const char zTemplate[11] = "-rwxrwxrwx";
int i;
if( strlen(z)!=10 ) goto parse_error;
switch( z[0] ){
case '-': mode |= S_IFREG; break;
case 'd': mode |= S_IFDIR; break;
#if !defined(_WIN32) && !defined(WIN32)
case 'l': mode |= S_IFLNK; break;
#endif
default: goto parse_error;
}
for(i=1; i<10; i++){
if( z[i]==zTemplate[i] ) mode |= 1 << (9-i);
else if( z[i]!='-' ) goto parse_error;
}
}
*pMode = mode;
return SQLITE_OK;
parse_error:
pTab->base.zErrMsg = sqlite3_mprintf("zipfile: parse error in mode: %s", z);
return SQLITE_ERROR;
}
/*
** Both (const char*) arguments point to nul-terminated strings. Argument
** nB is the value of strlen(zB). This function returns 0 if the strings are
** identical, ignoring any trailing '/' character in either path. */
static int zipfileComparePath(const char *zA, const char *zB, int nB){
int nA = (int)strlen(zA);
if( zA[nA-1]=='/' ) nA--;
if( zB[nB-1]=='/' ) nB--;
if( nA==nB && memcmp(zA, zB, nA)==0 ) return 0;
return 1;
}
/*
** xUpdate method.
*/
static int zipfileUpdate(
sqlite3_vtab *pVtab,
int nVal,
sqlite3_value **apVal,
sqlite_int64 *pRowid
){
ZipfileTab *pTab = (ZipfileTab*)pVtab;
int rc = SQLITE_OK; /* Return Code */
ZipfileEntry *pNew = 0; /* New in-memory CDS entry */
u32 mode = 0; /* Mode for new entry */
i64 mTime = 0; /* Modification time for new entry */
i64 sz = 0; /* Uncompressed size */
const char *zPath = 0; /* Path for new entry */
int nPath = 0; /* strlen(zPath) */
const u8 *pData = 0; /* Pointer to buffer containing content */
int nData = 0; /* Size of pData buffer in bytes */
int iMethod = 0; /* Compression method for new entry */
u8 *pFree = 0; /* Free this */
char *zFree = 0; /* Also free this */
ZipfileCDS cds; /* New Central Directory Structure entry */
int bIsDir = 0;
int mNull;
assert( pTab->zFile );
assert( pTab->pWriteFd );
if( sqlite3_value_type(apVal[0])!=SQLITE_NULL ){
if( nVal>1 ){
return SQLITE_CONSTRAINT;
}else{
const char *zDelete = (const char*)sqlite3_value_text(apVal[0]);
int nDelete = strlen(zDelete);
ZipfileEntry *p;
for(p=pTab->pFirstEntry; p; p=p->pNext){
if( zipfileComparePath(p->zPath, zDelete, nDelete)==0 ){
p->bDeleted = 1;
break;
}
}
return SQLITE_OK;
}
}
mNull = (sqlite3_value_type(apVal[5])==SQLITE_NULL ? 0x0 : 0x8) /* sz */
+ (sqlite3_value_type(apVal[6])==SQLITE_NULL ? 0x0 : 0x4) /* rawdata */
+ (sqlite3_value_type(apVal[7])==SQLITE_NULL ? 0x0 : 0x2) /* data */
+ (sqlite3_value_type(apVal[8])==SQLITE_NULL ? 0x0 : 0x1); /* method */
if( mNull==0x00 ){
/* All four are NULL - this must be a directory */
bIsDir = 1;
}
else if( mNull==0x2 || mNull==0x3 ){
/* Value specified for "data", and possibly "method". This must be
** a regular file or a symlink. */
const u8 *aIn = sqlite3_value_blob(apVal[7]);
int nIn = sqlite3_value_bytes(apVal[7]);
int bAuto = sqlite3_value_type(apVal[8])==SQLITE_NULL;
iMethod = sqlite3_value_int(apVal[8]);
sz = nIn;
if( iMethod!=0 && iMethod!=8 ){
rc = SQLITE_CONSTRAINT;
}else if( bAuto || iMethod ){
rc = zipfileDeflate(pTab, aIn, nIn, &pFree, &nData);
if( rc==SQLITE_OK ){
if( iMethod || nData<nIn ){
iMethod = 8;
pData = pFree;
}else{
pData = aIn;
nData = nIn;
}
}
}
}
else if( mNull==0x0D ){
/* Values specified for "sz", "rawdata" and "method". In other words,
** pre-compressed data is being inserted. */
pData = sqlite3_value_blob(apVal[6]);
nData = sqlite3_value_bytes(apVal[6]);
sz = sqlite3_value_int(apVal[5]);
iMethod = sqlite3_value_int(apVal[8]);
if( iMethod<0 || iMethod>65535 ){
pTab->base.zErrMsg = sqlite3_mprintf(
"zipfile: invalid compression method: %d", iMethod
);
rc = SQLITE_ERROR;
}
}
else{
rc = SQLITE_CONSTRAINT;
}
if( rc==SQLITE_OK ){
rc = zipfileGetMode(pTab, apVal[3],
(bIsDir ? (S_IFDIR + 0755) : (S_IFREG + 0644)), &mode
);
if( rc==SQLITE_OK && (bIsDir == ((mode & S_IFDIR)==0)) ){
/* The "mode" attribute is a directory, but data has been specified.
** Or vice-versa - no data but "mode" is a file or symlink. */
rc = SQLITE_CONSTRAINT;
}
}
if( rc==SQLITE_OK ){
zPath = (const char*)sqlite3_value_text(apVal[2]);
nPath = (int)strlen(zPath);
if( sqlite3_value_type(apVal[4])==SQLITE_NULL ){
mTime = (sqlite3_int64)time(0);
}else{
mTime = sqlite3_value_int64(apVal[4]);
}
}
if( rc==SQLITE_OK && bIsDir ){
/* For a directory, check that the last character in the path is a
** '/'. This appears to be required for compatibility with info-zip
** (the unzip command on unix). It does not create directories
** otherwise. */
if( zPath[nPath-1]!='/' ){
zFree = sqlite3_mprintf("%s/", zPath);
if( zFree==0 ){ rc = SQLITE_NOMEM; }
zPath = (const char*)zFree;
nPath++;
}
}
/* Check that we're not inserting a duplicate entry */
if( rc==SQLITE_OK ){
ZipfileEntry *p;
for(p=pTab->pFirstEntry; p; p=p->pNext){
if( zipfileComparePath(p->zPath, zPath, nPath)==0 ){
rc = SQLITE_CONSTRAINT;
break;
}
}
}
if( rc==SQLITE_OK ){
/* Create the new CDS record. */
memset(&cds, 0, sizeof(cds));
cds.iVersionMadeBy = ZIPFILE_NEWENTRY_MADEBY;
cds.iVersionExtract = ZIPFILE_NEWENTRY_REQUIRED;
cds.flags = ZIPFILE_NEWENTRY_FLAGS;
cds.iCompression = (u16)iMethod;
zipfileMtimeToDos(&cds, (u32)mTime);
cds.crc32 = crc32(0, pData, nData);
cds.szCompressed = nData;
cds.szUncompressed = (u32)sz;
cds.iExternalAttr = (mode<<16);
cds.iOffset = (u32)pTab->szCurrent;
pNew = zipfileNewEntry(&cds, zPath, nPath, (u32)mTime);
if( pNew==0 ){
rc = SQLITE_NOMEM;
}else{
zipfileAddEntry(pTab, pNew);
}
}
/* Append the new header+file to the archive */
if( rc==SQLITE_OK ){
rc = zipfileAppendEntry(pTab, &cds, zPath, nPath, pData, nData, (u32)mTime);
}
sqlite3_free(pFree);
sqlite3_free(zFree);
return rc;
}
static int zipfileAppendEOCD(ZipfileTab *pTab, ZipfileEOCD *p){
u8 *aBuf = pTab->aBuffer;
zipfileWrite32(aBuf, ZIPFILE_SIGNATURE_EOCD);
zipfileWrite16(aBuf, p->iDisk);
zipfileWrite16(aBuf, p->iFirstDisk);
zipfileWrite16(aBuf, p->nEntry);
zipfileWrite16(aBuf, p->nEntryTotal);
zipfileWrite32(aBuf, p->nSize);
zipfileWrite32(aBuf, p->iOffset);
zipfileWrite16(aBuf, 0); /* Size of trailing comment in bytes*/
assert( (aBuf-pTab->aBuffer)==22 );
return zipfileAppendData(pTab, pTab->aBuffer, (int)(aBuf - pTab->aBuffer));
}
static void zipfileCleanupTransaction(ZipfileTab *pTab){
ZipfileEntry *pEntry;
ZipfileEntry *pNext;
for(pEntry=pTab->pFirstEntry; pEntry; pEntry=pNext){
pNext = pEntry->pNext;
sqlite3_free(pEntry);
}
pTab->pFirstEntry = 0;
pTab->pLastEntry = 0;
fclose(pTab->pWriteFd);
pTab->pWriteFd = 0;
pTab->szCurrent = 0;
pTab->szOrig = 0;
}
static int zipfileBegin(sqlite3_vtab *pVtab){
ZipfileTab *pTab = (ZipfileTab*)pVtab;
int rc = SQLITE_OK;
assert( pTab->pWriteFd==0 );
/* This table is only writable if a default archive path was specified
** as part of the CREATE VIRTUAL TABLE statement. */
if( pTab->zFile==0 ){
pTab->base.zErrMsg = sqlite3_mprintf(
"zipfile: writing requires a default archive"
);
return SQLITE_ERROR;
}
/* Open a write fd on the file. Also load the entire central directory
** structure into memory. During the transaction any new file data is
** appended to the archive file, but the central directory is accumulated
** in main-memory until the transaction is committed. */
pTab->pWriteFd = fopen(pTab->zFile, "ab+");
if( pTab->pWriteFd==0 ){
pTab->base.zErrMsg = sqlite3_mprintf(
"zipfile: failed to open file %s for writing", pTab->zFile
);
rc = SQLITE_ERROR;
}else{
fseek(pTab->pWriteFd, 0, SEEK_END);
pTab->szCurrent = pTab->szOrig = (i64)ftell(pTab->pWriteFd);
rc = zipfileLoadDirectory(pTab);
}
if( rc!=SQLITE_OK ){
zipfileCleanupTransaction(pTab);
}
return rc;
}
static int zipfileCommit(sqlite3_vtab *pVtab){
ZipfileTab *pTab = (ZipfileTab*)pVtab;
int rc = SQLITE_OK;
if( pTab->pWriteFd ){
i64 iOffset = pTab->szCurrent;
ZipfileEntry *p;
ZipfileEOCD eocd;
int nEntry = 0;
/* Write out all undeleted entries */
for(p=pTab->pFirstEntry; rc==SQLITE_OK && p; p=p->pNext){
if( p->bDeleted ) continue;
rc = zipfileAppendData(pTab, p->aCdsEntry, p->nCdsEntry);
nEntry++;
}
/* Write out the EOCD record */
eocd.iDisk = 0;
eocd.iFirstDisk = 0;
eocd.nEntry = (u16)nEntry;
eocd.nEntryTotal = (u16)nEntry;
eocd.nSize = (u32)(pTab->szCurrent - iOffset);
eocd.iOffset = (u32)iOffset;
rc = zipfileAppendEOCD(pTab, &eocd);
zipfileCleanupTransaction(pTab);
}
return rc;
}
static int zipfileRollback(sqlite3_vtab *pVtab){
return zipfileCommit(pVtab);
}
/*
** Register the "zipfile" virtual table.
*/
static int zipfileRegister(sqlite3 *db){
static sqlite3_module zipfileModule = {
1, /* iVersion */
zipfileConnect, /* xCreate */
zipfileConnect, /* xConnect */
zipfileBestIndex, /* xBestIndex */
zipfileDisconnect, /* xDisconnect */
zipfileDisconnect, /* xDestroy */
zipfileOpen, /* xOpen - open a cursor */
zipfileClose, /* xClose - close a cursor */
zipfileFilter, /* xFilter - configure scan constraints */
zipfileNext, /* xNext - advance a cursor */
zipfileEof, /* xEof - check for end of scan */
zipfileColumn, /* xColumn - read data */
zipfileRowid, /* xRowid - read data */
zipfileUpdate, /* xUpdate */
zipfileBegin, /* xBegin */
0, /* xSync */
zipfileCommit, /* xCommit */
zipfileRollback, /* xRollback */
0, /* xFindMethod */
0, /* xRename */
};
int rc = sqlite3_create_module(db, "zipfile" , &zipfileModule, 0);
return rc;
}
#else /* SQLITE_OMIT_VIRTUALTABLE */
# define zipfileRegister(x) SQLITE_OK
#endif
#ifdef _WIN32
__declspec(dllexport)
#endif
int sqlite3_zipfile_init(
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
SQLITE_EXTENSION_INIT2(pApi);
(void)pzErrMsg; /* Unused parameter */
return zipfileRegister(db);
}