#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <zlib.h>
#ifndef SQLITE_OMIT_VIRTUALTABLE
#ifndef SQLITE_AMALGAMATION
#ifndef UINT32_TYPE
# ifdef HAVE_UINT32_T
# define UINT32_TYPE uint32_t
# else
# define UINT32_TYPE unsigned int
# endif
#endif
#ifndef UINT16_TYPE
# ifdef HAVE_UINT16_T
# define UINT16_TYPE uint16_t
# else
# define UINT16_TYPE unsigned short int
# endif
#endif
typedef sqlite3_int64 i64;
typedef unsigned char u8;
typedef UINT32_TYPE u32;
typedef UINT16_TYPE u16;
#define MIN(a,b) ((a)<(b) ? (a) : (b))
#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST)
# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1
#endif
#if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS)
# define ALWAYS(X) (1)
# define NEVER(X) (0)
#elif !defined(NDEBUG)
# define ALWAYS(X) ((X)?1:(assert(0),0))
# define NEVER(X) ((X)?(assert(0),1):0)
#else
# define ALWAYS(X) (X)
# define NEVER(X) (X)
#endif
#endif
#ifndef S_IFDIR
# define S_IFDIR 0040000
#endif
#ifndef S_IFREG
# define S_IFREG 0100000
#endif
#ifndef S_IFLNK
# define S_IFLNK 0120000
#endif
static const char ZIPFILE_SCHEMA[] =
"CREATE TABLE y("
"name PRIMARY KEY,"
"mode,"
"mtime,"
"sz,"
"rawdata,"
"data,"
"method,"
"z HIDDEN"
") WITHOUT ROWID;";
#define ZIPFILE_F_COLUMN_IDX 7
#define ZIPFILE_BUFFER_SIZE (64*1024)
#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
#define ZIPFILE_EOCD_FIXED_SZ 22
#define ZIPFILE_CDS_FIXED_SZ 46
typedef struct ZipfileEOCD ZipfileEOCD;
struct ZipfileEOCD {
u16 iDisk;
u16 iFirstDisk;
u16 nEntry;
u16 nEntryTotal;
u32 nSize;
u32 iOffset;
};
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;
};
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 {
ZipfileCDS cds;
u32 mUnixTime;
u8 *aExtra;
i64 iDataOff;
u8 *aData;
ZipfileEntry *pNext;
};
typedef struct ZipfileCsr ZipfileCsr;
struct ZipfileCsr {
sqlite3_vtab_cursor base;
i64 iId;
u8 bEof;
u8 bNoop;
FILE *pFile;
i64 iNextOff;
ZipfileEOCD eocd;
ZipfileEntry *pFreeEntry;
ZipfileEntry *pCurrent;
ZipfileCsr *pCsrNext;
};
typedef struct ZipfileTab ZipfileTab;
struct ZipfileTab {
sqlite3_vtab base;
char *zFile;
sqlite3 *db;
u8 *aBuffer;
ZipfileCsr *pCsrList;
i64 iNextCsrid;
ZipfileEntry *pFirstEntry;
ZipfileEntry *pLastEntry;
FILE *pWriteFd;
i64 szCurrent;
i64 szOrig;
};
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);
}
static void zipfileDequote(char *zIn){
char q = zIn[0];
if( q=='"' || q=='\'' || q=='`' || q=='[' ){
int iIn = 1;
int iOut = 0;
if( q=='[' ) q = ']';
while( ALWAYS(zIn[iIn]) ){
char c = zIn[iIn++];
if( c==q && zIn[iIn++]!=q ) break;
zIn[iOut++] = c;
}
zIn[iOut] = '\0';
}
}
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;
(void)pAux;
assert( 0==sqlite3_stricmp(argv[0], "zipfile") );
if( (0!=sqlite3_stricmp(argv[2], "zipfile") && argc<4) || argc>4 ){
*pzErr = sqlite3_mprintf("zipfile constructor requires one argument");
return SQLITE_ERROR;
}
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_malloc64((sqlite3_int64)nByte+nFile);
if( pNew==0 ) return SQLITE_NOMEM;
memset(pNew, 0, nByte+nFile);
pNew->db = db;
pNew->aBuffer = (u8*)&pNew[1];
if( zFile ){
pNew->zFile = (char*)&pNew->aBuffer[ZIPFILE_BUFFER_SIZE];
memcpy(pNew->zFile, zFile, nFile);
zipfileDequote(pNew->zFile);
}
}
sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY);
*ppVtab = (sqlite3_vtab*)pNew;
return rc;
}
static void zipfileEntryFree(ZipfileEntry *p){
if( p ){
sqlite3_free(p->cds.zFile);
sqlite3_free(p);
}
}
static void zipfileCleanupTransaction(ZipfileTab *pTab){
ZipfileEntry *pEntry;
ZipfileEntry *pNext;
if( pTab->pWriteFd ){
fclose(pTab->pWriteFd);
pTab->pWriteFd = 0;
}
for(pEntry=pTab->pFirstEntry; pEntry; pEntry=pNext){
pNext = pEntry->pNext;
zipfileEntryFree(pEntry);
}
pTab->pFirstEntry = 0;
pTab->pLastEntry = 0;
pTab->szCurrent = 0;
pTab->szOrig = 0;
}
static int zipfileDisconnect(sqlite3_vtab *pVtab){
zipfileCleanupTransaction((ZipfileTab*)pVtab);
sqlite3_free(pVtab);
return SQLITE_OK;
}
static int zipfileOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCsr){
ZipfileTab *pTab = (ZipfileTab*)p;
ZipfileCsr *pCsr;
pCsr = sqlite3_malloc(sizeof(*pCsr));
*ppCsr = (sqlite3_vtab_cursor*)pCsr;
if( pCsr==0 ){
return SQLITE_NOMEM;
}
memset(pCsr, 0, sizeof(*pCsr));
pCsr->iId = ++pTab->iNextCsrid;
pCsr->pCsrNext = pTab->pCsrList;
pTab->pCsrList = pCsr;
return SQLITE_OK;
}
static void zipfileResetCursor(ZipfileCsr *pCsr){
ZipfileEntry *p;
ZipfileEntry *pNext;
pCsr->bEof = 0;
if( pCsr->pFile ){
fclose(pCsr->pFile);
pCsr->pFile = 0;
zipfileEntryFree(pCsr->pCurrent);
pCsr->pCurrent = 0;
}
for(p=pCsr->pFreeEntry; p; p=pNext){
pNext = p->pNext;
zipfileEntryFree(p);
}
}
static int zipfileClose(sqlite3_vtab_cursor *cur){
ZipfileCsr *pCsr = (ZipfileCsr*)cur;
ZipfileTab *pTab = (ZipfileTab*)(pCsr->base.pVtab);
ZipfileCsr **pp;
zipfileResetCursor(pCsr);
for(pp=&pTab->pCsrList; *pp!=pCsr; pp=&((*pp)->pCsrNext));
*pp = pCsr->pCsrNext;
sqlite3_free(pCsr);
return SQLITE_OK;
}
static void zipfileTableErr(ZipfileTab *pTab, const char *zFmt, ...){
va_list ap;
va_start(ap, zFmt);
sqlite3_free(pTab->base.zErrMsg);
pTab->base.zErrMsg = sqlite3_vmprintf(zFmt, ap);
va_end(ap);
}
static void zipfileCursorErr(ZipfileCsr *pCsr, const char *zFmt, ...){
va_list ap;
va_start(ap, zFmt);
sqlite3_free(pCsr->base.pVtab->zErrMsg);
pCsr->base.pVtab->zErrMsg = sqlite3_vmprintf(zFmt, ap);
va_end(ap);
}
static int zipfileReadData(
FILE *pFile,
u8 *aRead,
int nRead,
i64 iOff,
char **pzErrmsg
){
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
){
if( nWrite>0 ){
size_t n = nWrite;
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){
if( aBuf==0 ) return 0;
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; }
#define ZIPFILE_CDS_NFILE_OFF 28
#define ZIPFILE_CDS_SZCOMPRESSED_OFF 20
static int zipfileReadCDS(u8 *aBuf, ZipfileCDS *pCDS){
u8 *aRead = aBuf;
u32 sig = zipfileRead32(aRead);
int rc = SQLITE_OK;
if( sig!=ZIPFILE_SIGNATURE_CDS ){
rc = SQLITE_ERROR;
}else{
pCDS->iVersionMadeBy = zipfileRead16(aRead);
pCDS->iVersionExtract = zipfileRead16(aRead);
pCDS->flags = zipfileRead16(aRead);
pCDS->iCompression = zipfileRead16(aRead);
pCDS->mTime = zipfileRead16(aRead);
pCDS->mDate = zipfileRead16(aRead);
pCDS->crc32 = zipfileRead32(aRead);
pCDS->szCompressed = zipfileRead32(aRead);
pCDS->szUncompressed = zipfileRead32(aRead);
assert( aRead==&aBuf[ZIPFILE_CDS_NFILE_OFF] );
pCDS->nFile = zipfileRead16(aRead);
pCDS->nExtra = zipfileRead16(aRead);
pCDS->nComment = zipfileRead16(aRead);
pCDS->iDiskStart = zipfileRead16(aRead);
pCDS->iInternalAttr = zipfileRead16(aRead);
pCDS->iExternalAttr = zipfileRead32(aRead);
pCDS->iOffset = zipfileRead32(aRead);
assert( aRead==&aBuf[ZIPFILE_CDS_FIXED_SZ] );
}
return rc;
}
static int zipfileReadLFH(
u8 *aBuffer,
ZipfileLFH *pLFH
){
u8 *aRead = aBuffer;
int rc = SQLITE_OK;
u32 sig = zipfileRead32(aRead);
if( sig!=ZIPFILE_SIGNATURE_LFH ){
rc = SQLITE_ERROR;
}else{
pLFH->iVersionExtract = zipfileRead16(aRead);
pLFH->flags = zipfileRead16(aRead);
pLFH->iCompression = zipfileRead16(aRead);
pLFH->mTime = zipfileRead16(aRead);
pLFH->mDate = zipfileRead16(aRead);
pLFH->crc32 = zipfileRead32(aRead);
pLFH->szCompressed = zipfileRead32(aRead);
pLFH->szUncompressed = zipfileRead32(aRead);
pLFH->nFile = zipfileRead16(aRead);
pLFH->nExtra = zipfileRead16(aRead);
}
return rc;
}
static int zipfileScanExtra(u8 *aExtra, int nExtra, u32 *pmTime){
int ret = 0;
u8 *p = aExtra;
u8 *pEnd = &aExtra[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 ){
*pmTime = zipfileGetU32(&p[1]);
ret = 1;
}
break;
}
}
p += nByte;
}
return ret;
}
static u32 zipfileMtime(ZipfileCDS *pCDS){
int Y,M,D,X1,X2,A,B,sec,min,hr;
i64 JDsec;
Y = (1980 + ((pCDS->mDate >> 9) & 0x7F));
M = ((pCDS->mDate >> 5) & 0x0F);
D = (pCDS->mDate & 0x1F);
sec = (pCDS->mTime & 0x1F)*2;
min = (pCDS->mTime >> 5) & 0x3F;
hr = (pCDS->mTime >> 11) & 0x1F;
if( M<=2 ){
Y--;
M += 12;
}
X1 = 36525*(Y+4716)/100;
X2 = 306001*(M+1)/10000;
A = Y/100;
B = 2 - A + (A/4);
JDsec = (i64)((X1 + X2 + D + B - 1524.5)*86400) + hr*3600 + min*60 + sec;
return (u32)(JDsec - (i64)24405875*(i64)8640);
}
static void zipfileMtimeToDos(ZipfileCDS *pCds, u32 mUnixTime){
i64 JD = (i64)2440588 + mUnixTime / (24*60*60);
int A, B, C, D, E;
int yr, mon, day;
int hr, min, sec;
A = (int)((JD - 1867216.25)/36524.25);
A = (int)(JD + 1 + A - (A/4));
B = A + 1524;
C = (int)((B - 122.1)/365.25);
D = (36525*(C&32767))/100;
E = (int)((B-D)/30.6001);
day = B - D - (int)(30.6001*E);
mon = (E<14 ? E-1 : E-13);
yr = mon>2 ? C-4716 : C-4715;
hr = (mUnixTime % (24*60*60)) / (60*60);
min = (mUnixTime % (60*60)) / 60;
sec = (mUnixTime % 60);
if( yr>=1980 ){
pCds->mDate = (u16)(day + (mon << 5) + ((yr-1980) << 9));
pCds->mTime = (u16)(sec/2 + (min<<5) + (hr<<11));
}else{
pCds->mDate = pCds->mTime = 0;
}
assert( mUnixTime<315507600
|| mUnixTime==zipfileMtime(pCds)
|| ((mUnixTime % 2) && mUnixTime-1==zipfileMtime(pCds))
);
}
static int zipfileGetEntry(
ZipfileTab *pTab,
const u8 *aBlob,
int nBlob,
FILE *pFile,
i64 iOff,
ZipfileEntry **ppEntry
){
u8 *aRead;
char **pzErr = &pTab->base.zErrMsg;
int rc = SQLITE_OK;
(void)nBlob;
if( aBlob==0 ){
aRead = pTab->aBuffer;
rc = zipfileReadData(pFile, aRead, ZIPFILE_CDS_FIXED_SZ, iOff, pzErr);
}else{
aRead = (u8*)&aBlob[iOff];
}
if( rc==SQLITE_OK ){
sqlite3_int64 nAlloc;
ZipfileEntry *pNew;
int nFile = zipfileGetU16(&aRead[ZIPFILE_CDS_NFILE_OFF]);
int nExtra = zipfileGetU16(&aRead[ZIPFILE_CDS_NFILE_OFF+2]);
nExtra += zipfileGetU16(&aRead[ZIPFILE_CDS_NFILE_OFF+4]);
nAlloc = sizeof(ZipfileEntry) + nExtra;
if( aBlob ){
nAlloc += zipfileGetU32(&aRead[ZIPFILE_CDS_SZCOMPRESSED_OFF]);
}
pNew = (ZipfileEntry*)sqlite3_malloc64(nAlloc);
if( pNew==0 ){
rc = SQLITE_NOMEM;
}else{
memset(pNew, 0, sizeof(ZipfileEntry));
rc = zipfileReadCDS(aRead, &pNew->cds);
if( rc!=SQLITE_OK ){
*pzErr = sqlite3_mprintf("failed to read CDS at offset %lld", iOff);
}else if( aBlob==0 ){
rc = zipfileReadData(
pFile, aRead, nExtra+nFile, iOff+ZIPFILE_CDS_FIXED_SZ, pzErr
);
}else{
aRead = (u8*)&aBlob[iOff + ZIPFILE_CDS_FIXED_SZ];
}
}
if( rc==SQLITE_OK ){
u32 *pt = &pNew->mUnixTime;
pNew->cds.zFile = sqlite3_mprintf("%.*s", nFile, aRead);
pNew->aExtra = (u8*)&pNew[1];
memcpy(pNew->aExtra, &aRead[nFile], nExtra);
if( pNew->cds.zFile==0 ){
rc = SQLITE_NOMEM;
}else if( 0==zipfileScanExtra(&aRead[nFile], pNew->cds.nExtra, pt) ){
pNew->mUnixTime = zipfileMtime(&pNew->cds);
}
}
if( rc==SQLITE_OK ){
static const int szFix = ZIPFILE_LFH_FIXED_SZ;
ZipfileLFH lfh;
if( pFile ){
rc = zipfileReadData(pFile, aRead, szFix, pNew->cds.iOffset, pzErr);
}else{
aRead = (u8*)&aBlob[pNew->cds.iOffset];
}
if( rc==SQLITE_OK ) rc = zipfileReadLFH(aRead, &lfh);
if( rc==SQLITE_OK ){
pNew->iDataOff = pNew->cds.iOffset + ZIPFILE_LFH_FIXED_SZ;
pNew->iDataOff += lfh.nFile + lfh.nExtra;
if( aBlob && pNew->cds.szCompressed ){
pNew->aData = &pNew->aExtra[nExtra];
memcpy(pNew->aData, &aBlob[pNew->iDataOff], pNew->cds.szCompressed);
}
}else{
*pzErr = sqlite3_mprintf("failed to read LFH at offset %d",
(int)pNew->cds.iOffset
);
}
}
if( rc!=SQLITE_OK ){
zipfileEntryFree(pNew);
}else{
*ppEntry = pNew;
}
}
return rc;
}
static int zipfileNext(sqlite3_vtab_cursor *cur){
ZipfileCsr *pCsr = (ZipfileCsr*)cur;
int rc = SQLITE_OK;
if( pCsr->pFile ){
i64 iEof = pCsr->eocd.iOffset + pCsr->eocd.nSize;
zipfileEntryFree(pCsr->pCurrent);
pCsr->pCurrent = 0;
if( pCsr->iNextOff>=iEof ){
pCsr->bEof = 1;
}else{
ZipfileEntry *p = 0;
ZipfileTab *pTab = (ZipfileTab*)(cur->pVtab);
rc = zipfileGetEntry(pTab, 0, 0, pCsr->pFile, pCsr->iNextOff, &p);
if( rc==SQLITE_OK ){
pCsr->iNextOff += ZIPFILE_CDS_FIXED_SZ;
pCsr->iNextOff += (int)p->cds.nExtra + p->cds.nFile + p->cds.nComment;
}
pCsr->pCurrent = p;
}
}else{
if( !pCsr->bNoop ){
pCsr->pCurrent = pCsr->pCurrent->pNext;
}
if( pCsr->pCurrent==0 ){
pCsr->bEof = 1;
}
}
pCsr->bNoop = 0;
return rc;
}
static void zipfileFree(void *p) {
sqlite3_free(p);
}
static void zipfileInflate(
sqlite3_context *pCtx,
const u8 *aIn,
int nIn,
int nOut
){
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, zipfileFree);
aRes = 0;
}
}
sqlite3_free(aRes);
inflateEnd(&str);
}
}
static int zipfileDeflate(
const u8 *aIn, int nIn,
u8 **ppOut, int *pnOut,
char **pzErr
){
int rc = SQLITE_OK;
sqlite3_int64 nAlloc;
z_stream str;
u8 *aOut;
memset(&str, 0, sizeof(str));
str.next_in = (Bytef*)aIn;
str.avail_in = nIn;
deflateInit2(&str, 9, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
nAlloc = deflateBound(&str, nIn);
aOut = (u8*)sqlite3_malloc64(nAlloc);
if( aOut==0 ){
rc = SQLITE_NOMEM;
}else{
int res;
str.next_out = aOut;
str.avail_out = nAlloc;
res = deflate(&str, Z_FINISH);
if( res==Z_STREAM_END ){
*ppOut = aOut;
*pnOut = (int)str.total_out;
}else{
sqlite3_free(aOut);
*pzErr = sqlite3_mprintf("zipfile: deflate() error");
rc = SQLITE_ERROR;
}
deflateEnd(&str);
}
return rc;
}
static int zipfileColumn(
sqlite3_vtab_cursor *cur,
sqlite3_context *ctx,
int i
){
ZipfileCsr *pCsr = (ZipfileCsr*)cur;
ZipfileCDS *pCDS = &pCsr->pCurrent->cds;
int rc = SQLITE_OK;
switch( i ){
case 0:
sqlite3_result_text(ctx, pCDS->zFile, -1, SQLITE_TRANSIENT);
break;
case 1:
sqlite3_result_int(ctx, pCDS->iExternalAttr >> 16);
break;
case 2: {
sqlite3_result_int64(ctx, pCsr->pCurrent->mUnixTime);
break;
}
case 3: {
if( sqlite3_vtab_nochange(ctx)==0 ){
sqlite3_result_int64(ctx, pCDS->szUncompressed);
}
break;
}
case 4:
if( sqlite3_vtab_nochange(ctx) ) break;
case 5: {
if( i==4 || pCDS->iCompression==0 || pCDS->iCompression==8 ){
int sz = pCDS->szCompressed;
int szFinal = pCDS->szUncompressed;
if( szFinal>0 ){
u8 *aBuf;
u8 *aFree = 0;
if( pCsr->pCurrent->aData ){
aBuf = pCsr->pCurrent->aData;
}else{
aBuf = aFree = sqlite3_malloc64(sz);
if( aBuf==0 ){
rc = SQLITE_NOMEM;
}else{
FILE *pFile = pCsr->pFile;
if( pFile==0 ){
pFile = ((ZipfileTab*)(pCsr->base.pVtab))->pWriteFd;
}
rc = zipfileReadData(pFile, aBuf, sz, pCsr->pCurrent->iDataOff,
&pCsr->base.pVtab->zErrMsg
);
}
}
if( rc==SQLITE_OK ){
if( i==5 && pCDS->iCompression ){
zipfileInflate(ctx, aBuf, sz, szFinal);
}else{
sqlite3_result_blob(ctx, aBuf, sz, SQLITE_TRANSIENT);
}
}
sqlite3_free(aFree);
}else{
u32 mode = pCDS->iExternalAttr >> 16;
if( !(mode & S_IFDIR)
&& pCDS->nFile>=1
&& pCDS->zFile[pCDS->nFile-1]!='/'
){
sqlite3_result_blob(ctx, "", 0, SQLITE_STATIC);
}
}
}
break;
}
case 6:
sqlite3_result_int(ctx, pCDS->iCompression);
break;
default:
assert( i==7 );
sqlite3_result_int64(ctx, pCsr->iId);
break;
}
return rc;
}
static int zipfileEof(sqlite3_vtab_cursor *cur){
ZipfileCsr *pCsr = (ZipfileCsr*)cur;
return pCsr->bEof;
}
static int zipfileReadEOCD(
ZipfileTab *pTab,
const u8 *aBlob,
int nBlob,
FILE *pFile,
ZipfileEOCD *pEOCD
){
u8 *aRead = pTab->aBuffer;
int nRead;
int rc = SQLITE_OK;
memset(pEOCD, 0, sizeof(ZipfileEOCD));
if( aBlob==0 ){
i64 iOff;
i64 szFile;
fseek(pFile, 0, SEEK_END);
szFile = (i64)ftell(pFile);
if( szFile==0 ){
return SQLITE_OK;
}
nRead = (int)(MIN(szFile, ZIPFILE_BUFFER_SIZE));
iOff = szFile - nRead;
rc = zipfileReadData(pFile, aRead, nRead, iOff, &pTab->base.zErrMsg);
}else{
nRead = (int)(MIN(nBlob, ZIPFILE_BUFFER_SIZE));
aRead = (u8*)&aBlob[nBlob-nRead];
}
if( rc==SQLITE_OK ){
int i;
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);
}
return rc;
}
static void zipfileAddEntry(
ZipfileTab *pTab,
ZipfileEntry *pBefore,
ZipfileEntry *pNew
){
assert( (pTab->pFirstEntry==0)==(pTab->pLastEntry==0) );
assert( pNew->pNext==0 );
if( pBefore==0 ){
if( pTab->pFirstEntry==0 ){
pTab->pFirstEntry = pTab->pLastEntry = pNew;
}else{
assert( pTab->pLastEntry->pNext==0 );
pTab->pLastEntry->pNext = pNew;
pTab->pLastEntry = pNew;
}
}else{
ZipfileEntry **pp;
for(pp=&pTab->pFirstEntry; *pp!=pBefore; pp=&((*pp)->pNext));
pNew->pNext = pBefore;
*pp = pNew;
}
}
static int zipfileLoadDirectory(ZipfileTab *pTab, const u8 *aBlob, int nBlob){
ZipfileEOCD eocd;
int rc;
int i;
i64 iOff;
rc = zipfileReadEOCD(pTab, aBlob, nBlob, pTab->pWriteFd, &eocd);
iOff = eocd.iOffset;
for(i=0; rc==SQLITE_OK && i<eocd.nEntry; i++){
ZipfileEntry *pNew = 0;
rc = zipfileGetEntry(pTab, aBlob, nBlob, pTab->pWriteFd, iOff, &pNew);
if( rc==SQLITE_OK ){
zipfileAddEntry(pTab, 0, pNew);
iOff += ZIPFILE_CDS_FIXED_SZ;
iOff += (int)pNew->cds.nExtra + pNew->cds.nFile + pNew->cds.nComment;
}
}
return rc;
}
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 = 0;
int rc = SQLITE_OK;
int bInMemory = 0;
(void)idxStr;
(void)argc;
zipfileResetCursor(pCsr);
if( pTab->zFile ){
zFile = pTab->zFile;
}else if( idxNum==0 ){
zipfileCursorErr(pCsr, "zipfile() function requires an argument");
return SQLITE_ERROR;
}else if( sqlite3_value_type(argv[0])==SQLITE_BLOB ){
static const u8 aEmptyBlob = 0;
const u8 *aBlob = (const u8*)sqlite3_value_blob(argv[0]);
int nBlob = sqlite3_value_bytes(argv[0]);
assert( pTab->pFirstEntry==0 );
if( aBlob==0 ){
aBlob = &aEmptyBlob;
nBlob = 0;
}
rc = zipfileLoadDirectory(pTab, aBlob, nBlob);
pCsr->pFreeEntry = pTab->pFirstEntry;
pTab->pFirstEntry = pTab->pLastEntry = 0;
if( rc!=SQLITE_OK ) return rc;
bInMemory = 1;
}else{
zFile = (const char*)sqlite3_value_text(argv[0]);
}
if( 0==pTab->pWriteFd && 0==bInMemory ){
pCsr->pFile = zFile ? fopen(zFile, "rb") : 0;
if( pCsr->pFile==0 ){
zipfileCursorErr(pCsr, "cannot open file: %s", zFile);
rc = SQLITE_ERROR;
}else{
rc = zipfileReadEOCD(pTab, 0, 0, pCsr->pFile, &pCsr->eocd);
if( rc==SQLITE_OK ){
if( pCsr->eocd.nEntry==0 ){
pCsr->bEof = 1;
}else{
pCsr->iNextOff = pCsr->eocd.iOffset;
rc = zipfileNext(cur);
}
}
}
}else{
pCsr->bNoop = 1;
pCsr->pCurrent = pCsr->pFreeEntry ? pCsr->pFreeEntry : pTab->pFirstEntry;
rc = zipfileNext(cur);
}
return rc;
}
static int zipfileBestIndex(
sqlite3_vtab *tab,
sqlite3_index_info *pIdxInfo
){
int i;
int idx = -1;
int unusable = 0;
(void)tab;
for(i=0; i<pIdxInfo->nConstraint; i++){
const struct sqlite3_index_constraint *pCons = &pIdxInfo->aConstraint[i];
if( pCons->iColumn!=ZIPFILE_F_COLUMN_IDX ) continue;
if( pCons->usable==0 ){
unusable = 1;
}else if( pCons->op==SQLITE_INDEX_CONSTRAINT_EQ ){
idx = i;
}
}
pIdxInfo->estimatedCost = 1000.0;
if( idx>=0 ){
pIdxInfo->aConstraintUsage[idx].argvIndex = 1;
pIdxInfo->aConstraintUsage[idx].omit = 1;
pIdxInfo->idxNum = 1;
}else if( unusable ){
return SQLITE_CONSTRAINT;
}
return SQLITE_OK;
}
static ZipfileEntry *zipfileNewEntry(const char *zPath){
ZipfileEntry *pNew;
pNew = sqlite3_malloc(sizeof(ZipfileEntry));
if( pNew ){
memset(pNew, 0, sizeof(ZipfileEntry));
pNew->cds.zFile = sqlite3_mprintf("%s", zPath);
if( pNew->cds.zFile==0 ){
sqlite3_free(pNew);
pNew = 0;
}
}
return pNew;
}
static int zipfileSerializeLFH(ZipfileEntry *pEntry, u8 *aBuf){
ZipfileCDS *pCds = &pEntry->cds;
u8 *a = aBuf;
pCds->nExtra = 9;
zipfileWrite32(a, ZIPFILE_SIGNATURE_LFH);
zipfileWrite16(a, pCds->iVersionExtract);
zipfileWrite16(a, pCds->flags);
zipfileWrite16(a, pCds->iCompression);
zipfileWrite16(a, pCds->mTime);
zipfileWrite16(a, pCds->mDate);
zipfileWrite32(a, pCds->crc32);
zipfileWrite32(a, pCds->szCompressed);
zipfileWrite32(a, pCds->szUncompressed);
zipfileWrite16(a, (u16)pCds->nFile);
zipfileWrite16(a, pCds->nExtra);
assert( a==&aBuf[ZIPFILE_LFH_FIXED_SZ] );
memcpy(a, pCds->zFile, (int)pCds->nFile);
a += (int)pCds->nFile;
zipfileWrite16(a, ZIPFILE_EXTRA_TIMESTAMP);
zipfileWrite16(a, 5);
*a++ = 0x01;
zipfileWrite32(a, pEntry->mUnixTime);
return a-aBuf;
}
static int zipfileAppendEntry(
ZipfileTab *pTab,
ZipfileEntry *pEntry,
const u8 *pData,
int nData
){
u8 *aBuf = pTab->aBuffer;
int nBuf;
int rc;
nBuf = zipfileSerializeLFH(pEntry, aBuf);
rc = zipfileAppendData(pTab, aBuf, nBuf);
if( rc==SQLITE_OK ){
pEntry->iDataOff = pTab->szCurrent;
rc = zipfileAppendData(pTab, pData, nData);
}
return rc;
}
static int zipfileGetMode(
sqlite3_value *pVal,
int bIsDir,
u32 *pMode,
char **pzErr
){
const char *z = (const char*)sqlite3_value_text(pVal);
u32 mode = 0;
if( z==0 ){
mode = (bIsDir ? (S_IFDIR + 0755) : (S_IFREG + 0644));
}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;
case 'l': mode |= S_IFLNK; break;
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;
}
}
if( ((mode & S_IFDIR)==0)==bIsDir ){
*pzErr = sqlite3_mprintf("zipfile: mode does not match data");
return SQLITE_CONSTRAINT;
}
*pMode = mode;
return SQLITE_OK;
parse_error:
*pzErr = sqlite3_mprintf("zipfile: parse error in mode: %s", z);
return SQLITE_ERROR;
}
static int zipfileComparePath(const char *zA, const char *zB, int nB){
int nA = (int)strlen(zA);
if( nA>0 && zA[nA-1]=='/' ) nA--;
if( nB>0 && zB[nB-1]=='/' ) nB--;
if( nA==nB && memcmp(zA, zB, nA)==0 ) return 0;
return 1;
}
static int zipfileBegin(sqlite3_vtab *pVtab){
ZipfileTab *pTab = (ZipfileTab*)pVtab;
int rc = SQLITE_OK;
assert( pTab->pWriteFd==0 );
if( pTab->zFile==0 || pTab->zFile[0]==0 ){
pTab->base.zErrMsg = sqlite3_mprintf("zipfile: missing filename");
return SQLITE_ERROR;
}
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, 0, 0);
}
if( rc!=SQLITE_OK ){
zipfileCleanupTransaction(pTab);
}
return rc;
}
static u32 zipfileTime(void){
sqlite3_vfs *pVfs = sqlite3_vfs_find(0);
u32 ret;
if( pVfs==0 ) return 0;
if( pVfs->iVersion>=2 && pVfs->xCurrentTimeInt64 ){
i64 ms;
pVfs->xCurrentTimeInt64(pVfs, &ms);
ret = (u32)((ms/1000) - ((i64)24405875 * 8640));
}else{
double day;
pVfs->xCurrentTime(pVfs, &day);
ret = (u32)((day - 2440587.5) * 86400);
}
return ret;
}
static u32 zipfileGetTime(sqlite3_value *pVal){
if( pVal==0 || sqlite3_value_type(pVal)==SQLITE_NULL ){
return zipfileTime();
}
return (u32)sqlite3_value_int64(pVal);
}
static void zipfileRemoveEntryFromList(ZipfileTab *pTab, ZipfileEntry *pOld){
if( pOld ){
if( pTab->pFirstEntry==pOld ){
pTab->pFirstEntry = pOld->pNext;
if( pTab->pLastEntry==pOld ) pTab->pLastEntry = 0;
}else{
ZipfileEntry *p;
for(p=pTab->pFirstEntry; p; p=p->pNext){
if( p->pNext==pOld ){
p->pNext = pOld->pNext;
if( pTab->pLastEntry==pOld ) pTab->pLastEntry = p;
break;
}
}
}
zipfileEntryFree(pOld);
}
}
static int zipfileUpdate(
sqlite3_vtab *pVtab,
int nVal,
sqlite3_value **apVal,
sqlite_int64 *pRowid
){
ZipfileTab *pTab = (ZipfileTab*)pVtab;
int rc = SQLITE_OK;
ZipfileEntry *pNew = 0;
u32 mode = 0;
u32 mTime = 0;
i64 sz = 0;
const char *zPath = 0;
int nPath = 0;
const u8 *pData = 0;
int nData = 0;
int iMethod = 0;
u8 *pFree = 0;
char *zFree = 0;
ZipfileEntry *pOld = 0;
ZipfileEntry *pOld2 = 0;
int bUpdate = 0;
int bIsDir = 0;
u32 iCrc32 = 0;
(void)pRowid;
if( pTab->pWriteFd==0 ){
rc = zipfileBegin(pVtab);
if( rc!=SQLITE_OK ) return rc;
}
if( sqlite3_value_type(apVal[0])!=SQLITE_NULL ){
const char *zDelete = (const char*)sqlite3_value_text(apVal[0]);
int nDelete = (int)strlen(zDelete);
if( nVal>1 ){
const char *zUpdate = (const char*)sqlite3_value_text(apVal[1]);
if( zUpdate && zipfileComparePath(zUpdate, zDelete, nDelete)!=0 ){
bUpdate = 1;
}
}
for(pOld=pTab->pFirstEntry; 1; pOld=pOld->pNext){
if( zipfileComparePath(pOld->cds.zFile, zDelete, nDelete)==0 ){
break;
}
assert( pOld->pNext );
}
}
if( nVal>1 ){
if( sqlite3_value_type(apVal[5])!=SQLITE_NULL ){
zipfileTableErr(pTab, "sz must be NULL");
rc = SQLITE_CONSTRAINT;
}
if( sqlite3_value_type(apVal[6])!=SQLITE_NULL ){
zipfileTableErr(pTab, "rawdata must be NULL");
rc = SQLITE_CONSTRAINT;
}
if( rc==SQLITE_OK ){
if( sqlite3_value_type(apVal[7])==SQLITE_NULL ){
bIsDir = 1;
}else{
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;
pData = aIn;
nData = nIn;
if( iMethod!=0 && iMethod!=8 ){
zipfileTableErr(pTab, "unknown compression method: %d", iMethod);
rc = SQLITE_CONSTRAINT;
}else{
if( bAuto || iMethod ){
int nCmp;
rc = zipfileDeflate(aIn, nIn, &pFree, &nCmp, &pTab->base.zErrMsg);
if( rc==SQLITE_OK ){
if( iMethod || nCmp<nIn ){
iMethod = 8;
pData = pFree;
nData = nCmp;
}
}
}
iCrc32 = crc32(0, aIn, nIn);
}
}
}
if( rc==SQLITE_OK ){
rc = zipfileGetMode(apVal[3], bIsDir, &mode, &pTab->base.zErrMsg);
}
if( rc==SQLITE_OK ){
zPath = (const char*)sqlite3_value_text(apVal[2]);
if( zPath==0 ) zPath = "";
nPath = (int)strlen(zPath);
mTime = zipfileGetTime(apVal[4]);
}
if( rc==SQLITE_OK && bIsDir ){
if( nPath<=0 || zPath[nPath-1]!='/' ){
zFree = sqlite3_mprintf("%s/", zPath);
zPath = (const char*)zFree;
if( zFree==0 ){
rc = SQLITE_NOMEM;
nPath = 0;
}else{
nPath = (int)strlen(zPath);
}
}
}
if( (pOld==0 || bUpdate) && rc==SQLITE_OK ){
ZipfileEntry *p;
for(p=pTab->pFirstEntry; p; p=p->pNext){
if( zipfileComparePath(p->cds.zFile, zPath, nPath)==0 ){
switch( sqlite3_vtab_on_conflict(pTab->db) ){
case SQLITE_IGNORE: {
goto zipfile_update_done;
}
case SQLITE_REPLACE: {
pOld2 = p;
break;
}
default: {
zipfileTableErr(pTab, "duplicate name: \"%s\"", zPath);
rc = SQLITE_CONSTRAINT;
break;
}
}
break;
}
}
}
if( rc==SQLITE_OK ){
pNew = zipfileNewEntry(zPath);
if( pNew==0 ){
rc = SQLITE_NOMEM;
}else{
pNew->cds.iVersionMadeBy = ZIPFILE_NEWENTRY_MADEBY;
pNew->cds.iVersionExtract = ZIPFILE_NEWENTRY_REQUIRED;
pNew->cds.flags = ZIPFILE_NEWENTRY_FLAGS;
pNew->cds.iCompression = (u16)iMethod;
zipfileMtimeToDos(&pNew->cds, mTime);
pNew->cds.crc32 = iCrc32;
pNew->cds.szCompressed = nData;
pNew->cds.szUncompressed = (u32)sz;
pNew->cds.iExternalAttr = (mode<<16);
pNew->cds.iOffset = (u32)pTab->szCurrent;
pNew->cds.nFile = (u16)nPath;
pNew->mUnixTime = (u32)mTime;
rc = zipfileAppendEntry(pTab, pNew, pData, nData);
zipfileAddEntry(pTab, pOld, pNew);
}
}
}
if( rc==SQLITE_OK && (pOld || pOld2) ){
ZipfileCsr *pCsr;
for(pCsr=pTab->pCsrList; pCsr; pCsr=pCsr->pCsrNext){
if( pCsr->pCurrent && (pCsr->pCurrent==pOld || pCsr->pCurrent==pOld2) ){
pCsr->pCurrent = pCsr->pCurrent->pNext;
pCsr->bNoop = 1;
}
}
zipfileRemoveEntryFromList(pTab, pOld);
zipfileRemoveEntryFromList(pTab, pOld2);
}
zipfile_update_done:
sqlite3_free(pFree);
sqlite3_free(zFree);
return rc;
}
static int zipfileSerializeEOCD(ZipfileEOCD *p, u8 *aBuf){
u8 *a = aBuf;
zipfileWrite32(a, ZIPFILE_SIGNATURE_EOCD);
zipfileWrite16(a, p->iDisk);
zipfileWrite16(a, p->iFirstDisk);
zipfileWrite16(a, p->nEntry);
zipfileWrite16(a, p->nEntryTotal);
zipfileWrite32(a, p->nSize);
zipfileWrite32(a, p->iOffset);
zipfileWrite16(a, 0);
return a-aBuf;
}
static int zipfileAppendEOCD(ZipfileTab *pTab, ZipfileEOCD *p){
int nBuf = zipfileSerializeEOCD(p, pTab->aBuffer);
assert( nBuf==ZIPFILE_EOCD_FIXED_SZ );
return zipfileAppendData(pTab, pTab->aBuffer, nBuf);
}
static int zipfileSerializeCDS(ZipfileEntry *pEntry, u8 *aBuf){
u8 *a = aBuf;
ZipfileCDS *pCDS = &pEntry->cds;
if( pEntry->aExtra==0 ){
pCDS->nExtra = 9;
}
zipfileWrite32(a, ZIPFILE_SIGNATURE_CDS);
zipfileWrite16(a, pCDS->iVersionMadeBy);
zipfileWrite16(a, pCDS->iVersionExtract);
zipfileWrite16(a, pCDS->flags);
zipfileWrite16(a, pCDS->iCompression);
zipfileWrite16(a, pCDS->mTime);
zipfileWrite16(a, pCDS->mDate);
zipfileWrite32(a, pCDS->crc32);
zipfileWrite32(a, pCDS->szCompressed);
zipfileWrite32(a, pCDS->szUncompressed);
assert( a==&aBuf[ZIPFILE_CDS_NFILE_OFF] );
zipfileWrite16(a, pCDS->nFile);
zipfileWrite16(a, pCDS->nExtra);
zipfileWrite16(a, pCDS->nComment);
zipfileWrite16(a, pCDS->iDiskStart);
zipfileWrite16(a, pCDS->iInternalAttr);
zipfileWrite32(a, pCDS->iExternalAttr);
zipfileWrite32(a, pCDS->iOffset);
memcpy(a, pCDS->zFile, pCDS->nFile);
a += pCDS->nFile;
if( pEntry->aExtra ){
int n = (int)pCDS->nExtra + (int)pCDS->nComment;
memcpy(a, pEntry->aExtra, n);
a += n;
}else{
assert( pCDS->nExtra==9 );
zipfileWrite16(a, ZIPFILE_EXTRA_TIMESTAMP);
zipfileWrite16(a, 5);
*a++ = 0x01;
zipfileWrite32(a, pEntry->mUnixTime);
}
return a-aBuf;
}
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;
for(p=pTab->pFirstEntry; rc==SQLITE_OK && p; p=p->pNext){
int n = zipfileSerializeCDS(p, pTab->aBuffer);
rc = zipfileAppendData(pTab, pTab->aBuffer, n);
nEntry++;
}
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);
}
static ZipfileCsr *zipfileFindCursor(ZipfileTab *pTab, i64 iId){
ZipfileCsr *pCsr;
for(pCsr=pTab->pCsrList; pCsr; pCsr=pCsr->pCsrNext){
if( iId==pCsr->iId ) break;
}
return pCsr;
}
static void zipfileFunctionCds(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
ZipfileCsr *pCsr;
ZipfileTab *pTab = (ZipfileTab*)sqlite3_user_data(context);
assert( argc>0 );
pCsr = zipfileFindCursor(pTab, sqlite3_value_int64(argv[0]));
if( pCsr ){
ZipfileCDS *p = &pCsr->pCurrent->cds;
char *zRes = sqlite3_mprintf("{"
"\"version-made-by\" : %u, "
"\"version-to-extract\" : %u, "
"\"flags\" : %u, "
"\"compression\" : %u, "
"\"time\" : %u, "
"\"date\" : %u, "
"\"crc32\" : %u, "
"\"compressed-size\" : %u, "
"\"uncompressed-size\" : %u, "
"\"file-name-length\" : %u, "
"\"extra-field-length\" : %u, "
"\"file-comment-length\" : %u, "
"\"disk-number-start\" : %u, "
"\"internal-attr\" : %u, "
"\"external-attr\" : %u, "
"\"offset\" : %u }",
(u32)p->iVersionMadeBy, (u32)p->iVersionExtract,
(u32)p->flags, (u32)p->iCompression,
(u32)p->mTime, (u32)p->mDate,
(u32)p->crc32, (u32)p->szCompressed,
(u32)p->szUncompressed, (u32)p->nFile,
(u32)p->nExtra, (u32)p->nComment,
(u32)p->iDiskStart, (u32)p->iInternalAttr,
(u32)p->iExternalAttr, (u32)p->iOffset
);
if( zRes==0 ){
sqlite3_result_error_nomem(context);
}else{
sqlite3_result_text(context, zRes, -1, SQLITE_TRANSIENT);
sqlite3_free(zRes);
}
}
}
static int zipfileFindFunction(
sqlite3_vtab *pVtab,
int nArg,
const char *zName,
void (**pxFunc)(sqlite3_context*,int,sqlite3_value**),
void **ppArg
){
(void)nArg;
if( sqlite3_stricmp("zipfile_cds", zName)==0 ){
*pxFunc = zipfileFunctionCds;
*ppArg = (void*)pVtab;
return 1;
}
return 0;
}
typedef struct ZipfileBuffer ZipfileBuffer;
struct ZipfileBuffer {
u8 *a;
int n;
int nAlloc;
};
typedef struct ZipfileCtx ZipfileCtx;
struct ZipfileCtx {
int nEntry;
ZipfileBuffer body;
ZipfileBuffer cds;
};
static int zipfileBufferGrow(ZipfileBuffer *pBuf, int nByte){
if( pBuf->n+nByte>pBuf->nAlloc ){
u8 *aNew;
sqlite3_int64 nNew = pBuf->n ? pBuf->n*2 : 512;
int nReq = pBuf->n + nByte;
while( nNew<nReq ) nNew = nNew*2;
aNew = sqlite3_realloc64(pBuf->a, nNew);
if( aNew==0 ) return SQLITE_NOMEM;
pBuf->a = aNew;
pBuf->nAlloc = (int)nNew;
}
return SQLITE_OK;
}
static void zipfileStep(sqlite3_context *pCtx, int nVal, sqlite3_value **apVal){
ZipfileCtx *p;
ZipfileEntry e;
sqlite3_value *pName = 0;
sqlite3_value *pMode = 0;
sqlite3_value *pMtime = 0;
sqlite3_value *pData = 0;
sqlite3_value *pMethod = 0;
int bIsDir = 0;
u32 mode;
int rc = SQLITE_OK;
char *zErr = 0;
int iMethod = -1;
const u8 *aData = 0;
int nData = 0;
int szUncompressed = 0;
u8 *aFree = 0;
u32 iCrc32 = 0;
char *zName = 0;
int nName = 0;
char *zFree = 0;
int nByte;
memset(&e, 0, sizeof(e));
p = (ZipfileCtx*)sqlite3_aggregate_context(pCtx, sizeof(ZipfileCtx));
if( p==0 ) return;
if( nVal!=2 && nVal!=4 && nVal!=5 ){
zErr = sqlite3_mprintf("wrong number of arguments to function zipfile()");
rc = SQLITE_ERROR;
goto zipfile_step_out;
}
pName = apVal[0];
if( nVal==2 ){
pData = apVal[1];
}else{
pMode = apVal[1];
pMtime = apVal[2];
pData = apVal[3];
if( nVal==5 ){
pMethod = apVal[4];
}
}
zName = (char*)sqlite3_value_text(pName);
nName = sqlite3_value_bytes(pName);
if( zName==0 ){
zErr = sqlite3_mprintf("first argument to zipfile() must be non-NULL");
rc = SQLITE_ERROR;
goto zipfile_step_out;
}
if( pMethod && SQLITE_NULL!=sqlite3_value_type(pMethod) ){
iMethod = (int)sqlite3_value_int64(pMethod);
if( iMethod!=0 && iMethod!=8 ){
zErr = sqlite3_mprintf("illegal method value: %d", iMethod);
rc = SQLITE_ERROR;
goto zipfile_step_out;
}
}
if( sqlite3_value_type(pData)==SQLITE_NULL ){
bIsDir = 1;
iMethod = 0;
}else{
aData = sqlite3_value_blob(pData);
szUncompressed = nData = sqlite3_value_bytes(pData);
iCrc32 = crc32(0, aData, nData);
if( iMethod<0 || iMethod==8 ){
int nOut = 0;
rc = zipfileDeflate(aData, nData, &aFree, &nOut, &zErr);
if( rc!=SQLITE_OK ){
goto zipfile_step_out;
}
if( iMethod==8 || nOut<nData ){
aData = aFree;
nData = nOut;
iMethod = 8;
}else{
iMethod = 0;
}
}
}
rc = zipfileGetMode(pMode, bIsDir, &mode, &zErr);
if( rc ) goto zipfile_step_out;
e.mUnixTime = zipfileGetTime(pMtime);
if( bIsDir==0 ){
if( nName>0 && zName[nName-1]=='/' ){
zErr = sqlite3_mprintf("non-directory name must not end with /");
rc = SQLITE_ERROR;
goto zipfile_step_out;
}
}else{
if( nName==0 || zName[nName-1]!='/' ){
zName = zFree = sqlite3_mprintf("%s/", zName);
if( zName==0 ){
rc = SQLITE_NOMEM;
goto zipfile_step_out;
}
nName = (int)strlen(zName);
}else{
while( nName>1 && zName[nName-2]=='/' ) nName--;
}
}
e.cds.iVersionMadeBy = ZIPFILE_NEWENTRY_MADEBY;
e.cds.iVersionExtract = ZIPFILE_NEWENTRY_REQUIRED;
e.cds.flags = ZIPFILE_NEWENTRY_FLAGS;
e.cds.iCompression = (u16)iMethod;
zipfileMtimeToDos(&e.cds, (u32)e.mUnixTime);
e.cds.crc32 = iCrc32;
e.cds.szCompressed = nData;
e.cds.szUncompressed = szUncompressed;
e.cds.iExternalAttr = (mode<<16);
e.cds.iOffset = p->body.n;
e.cds.nFile = (u16)nName;
e.cds.zFile = zName;
nByte = ZIPFILE_LFH_FIXED_SZ + e.cds.nFile + 9;
if( (rc = zipfileBufferGrow(&p->body, nByte)) ) goto zipfile_step_out;
p->body.n += zipfileSerializeLFH(&e, &p->body.a[p->body.n]);
if( nData>0 ){
if( (rc = zipfileBufferGrow(&p->body, nData)) ) goto zipfile_step_out;
memcpy(&p->body.a[p->body.n], aData, nData);
p->body.n += nData;
}
nByte = ZIPFILE_CDS_FIXED_SZ + e.cds.nFile + 9;
if( (rc = zipfileBufferGrow(&p->cds, nByte)) ) goto zipfile_step_out;
p->cds.n += zipfileSerializeCDS(&e, &p->cds.a[p->cds.n]);
p->nEntry++;
zipfile_step_out:
sqlite3_free(aFree);
sqlite3_free(zFree);
if( rc ){
if( zErr ){
sqlite3_result_error(pCtx, zErr, -1);
}else{
sqlite3_result_error_code(pCtx, rc);
}
}
sqlite3_free(zErr);
}
static void zipfileFinal(sqlite3_context *pCtx){
ZipfileCtx *p;
ZipfileEOCD eocd;
sqlite3_int64 nZip;
u8 *aZip;
p = (ZipfileCtx*)sqlite3_aggregate_context(pCtx, sizeof(ZipfileCtx));
if( p==0 ) return;
if( p->nEntry>0 ){
memset(&eocd, 0, sizeof(eocd));
eocd.nEntry = (u16)p->nEntry;
eocd.nEntryTotal = (u16)p->nEntry;
eocd.nSize = p->cds.n;
eocd.iOffset = p->body.n;
nZip = p->body.n + p->cds.n + ZIPFILE_EOCD_FIXED_SZ;
aZip = (u8*)sqlite3_malloc64(nZip);
if( aZip==0 ){
sqlite3_result_error_nomem(pCtx);
}else{
memcpy(aZip, p->body.a, p->body.n);
memcpy(&aZip[p->body.n], p->cds.a, p->cds.n);
zipfileSerializeEOCD(&eocd, &aZip[p->body.n + p->cds.n]);
sqlite3_result_blob(pCtx, aZip, (int)nZip, zipfileFree);
}
}
sqlite3_free(p->body.a);
sqlite3_free(p->cds.a);
}
static int zipfileRegister(sqlite3 *db){
static sqlite3_module zipfileModule = {
1,
zipfileConnect,
zipfileConnect,
zipfileBestIndex,
zipfileDisconnect,
zipfileDisconnect,
zipfileOpen,
zipfileClose,
zipfileFilter,
zipfileNext,
zipfileEof,
zipfileColumn,
0,
zipfileUpdate,
zipfileBegin,
0,
zipfileCommit,
zipfileRollback,
zipfileFindFunction,
0,
0,
0,
0,
0
};
int rc = sqlite3_create_module(db, "zipfile" , &zipfileModule, 0);
if( rc==SQLITE_OK ) rc = sqlite3_overload_function(db, "zipfile_cds", -1);
if( rc==SQLITE_OK ){
rc = sqlite3_create_function(db, "zipfile", -1, SQLITE_UTF8, 0, 0,
zipfileStep, zipfileFinal
);
}
assert( sizeof(i64)==8 );
assert( sizeof(u32)==4 );
assert( sizeof(u16)==2 );
assert( sizeof(u8)==1 );
return rc;
}
#else
# 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;
return zipfileRegister(db);
}