|
1
|
/* |
|
2
|
** Copyright (c) 2007 D. Richard Hipp |
|
3
|
** |
|
4
|
** This program is free software; you can redistribute it and/or |
|
5
|
** modify it under the terms of the Simplified BSD License (also |
|
6
|
** known as the "2-Clause License" or "FreeBSD License".) |
|
7
|
|
|
8
|
** This program is distributed in the hope that it will be useful, |
|
9
|
** but without any warranty; without even the implied warranty of |
|
10
|
** merchantability or fitness for a particular purpose. |
|
11
|
** |
|
12
|
** Author contact information: |
|
13
|
** [email protected] |
|
14
|
** http://www.hwaci.com/drh/ |
|
15
|
** |
|
16
|
******************************************************************************* |
|
17
|
** |
|
18
|
** This file contains code used to generate ZIP and SQLAR archives. |
|
19
|
*/ |
|
20
|
#include "config.h" |
|
21
|
#include <assert.h> |
|
22
|
#include <zlib.h> |
|
23
|
#include "zip.h" |
|
24
|
|
|
25
|
/* |
|
26
|
** Type of archive to build. |
|
27
|
*/ |
|
28
|
#define ARCHIVE_ZIP 0 |
|
29
|
#define ARCHIVE_SQLAR 1 |
|
30
|
|
|
31
|
/* |
|
32
|
** Write a 16- or 32-bit integer as little-endian into the given buffer. |
|
33
|
*/ |
|
34
|
static void put16(char *z, int v){ |
|
35
|
z[0] = v & 0xff; |
|
36
|
z[1] = (v>>8) & 0xff; |
|
37
|
} |
|
38
|
static void put32(char *z, int v){ |
|
39
|
z[0] = v & 0xff; |
|
40
|
z[1] = (v>>8) & 0xff; |
|
41
|
z[2] = (v>>16) & 0xff; |
|
42
|
z[3] = (v>>24) & 0xff; |
|
43
|
} |
|
44
|
|
|
45
|
/* |
|
46
|
** Variables in which to accumulate a growing ZIP archive. |
|
47
|
*/ |
|
48
|
static Blob body; /* The body of the ZIP archive */ |
|
49
|
static Blob toc; /* The table of contents */ |
|
50
|
static int nEntry; /* Number of files */ |
|
51
|
static int dosTime; /* DOS-format time */ |
|
52
|
static int dosDate; /* DOS-format date */ |
|
53
|
static int unixTime; /* Seconds since 1970 */ |
|
54
|
static int nDir; /* Number of entries in azDir[] */ |
|
55
|
static char **azDir; /* Directory names already added to the archive */ |
|
56
|
|
|
57
|
typedef struct Archive Archive; |
|
58
|
struct Archive { |
|
59
|
int eType; /* Type of archive (SQLAR or ZIP) */ |
|
60
|
Blob *pBlob; /* Output blob */ |
|
61
|
Blob tmp; /* Blob used as temp space for compression */ |
|
62
|
sqlite3 *db; /* Db used to assemble sqlar archive */ |
|
63
|
sqlite3_stmt *pInsert; /* INSERT statement for SQLAR */ |
|
64
|
sqlite3_vfs vfs; /* VFS object */ |
|
65
|
}; |
|
66
|
|
|
67
|
/* |
|
68
|
** Ensure that blob pBlob is at least nMin bytes in size. |
|
69
|
*/ |
|
70
|
static void zip_blob_minsize(Blob *pBlob, int nMin){ |
|
71
|
if( (int)blob_size(pBlob)<nMin ){ |
|
72
|
blob_resize(pBlob, nMin); |
|
73
|
} |
|
74
|
} |
|
75
|
|
|
76
|
/************************************************************************* |
|
77
|
** Implementation of "archive" VFS. A VFS designed to store the contents |
|
78
|
** of a new database in a Blob. Used to construct sqlar archives in |
|
79
|
** memory. |
|
80
|
*/ |
|
81
|
typedef struct ArchiveFile ArchiveFile; |
|
82
|
struct ArchiveFile { |
|
83
|
sqlite3_file base; /* Base class */ |
|
84
|
Blob *pBlob; |
|
85
|
}; |
|
86
|
|
|
87
|
static int archiveClose(sqlite3_file *pFile){ |
|
88
|
return SQLITE_OK; |
|
89
|
} |
|
90
|
static int archiveRead( |
|
91
|
sqlite3_file *pFile, void *pBuf, int iAmt, sqlite3_int64 iOfst |
|
92
|
){ |
|
93
|
assert( iOfst==0 || iOfst==24 ); |
|
94
|
return SQLITE_IOERR_SHORT_READ; |
|
95
|
} |
|
96
|
static int archiveWrite( |
|
97
|
sqlite3_file *pFile, const void *pBuf, int iAmt, sqlite3_int64 iOfst |
|
98
|
){ |
|
99
|
ArchiveFile *pAF = (ArchiveFile*)pFile; |
|
100
|
int nMin = (int)iOfst + iAmt; |
|
101
|
char *aBlob; /* Output buffer */ |
|
102
|
|
|
103
|
zip_blob_minsize(pAF->pBlob, nMin); |
|
104
|
aBlob = blob_buffer(pAF->pBlob); |
|
105
|
memcpy(&aBlob[iOfst], pBuf, iAmt); |
|
106
|
return SQLITE_OK; |
|
107
|
} |
|
108
|
static int archiveTruncate(sqlite3_file *pFile, sqlite3_int64 size){ |
|
109
|
return SQLITE_OK; |
|
110
|
} |
|
111
|
static int archiveSync(sqlite3_file *pFile, int flags){ |
|
112
|
return SQLITE_OK; |
|
113
|
} |
|
114
|
static int archiveFileSize(sqlite3_file *pFile, sqlite3_int64 *pSize){ |
|
115
|
*pSize = 0; |
|
116
|
return SQLITE_OK; |
|
117
|
} |
|
118
|
static int archiveLock(sqlite3_file *pFile, int eLock){ |
|
119
|
return SQLITE_OK; |
|
120
|
} |
|
121
|
static int archiveUnlock(sqlite3_file *pFile, int eLock){ |
|
122
|
return SQLITE_OK; |
|
123
|
} |
|
124
|
static int archiveCheckReservedLock(sqlite3_file *pFile, int *pResOut){ |
|
125
|
*pResOut = 0; |
|
126
|
return SQLITE_OK; |
|
127
|
} |
|
128
|
static int archiveFileControl(sqlite3_file *pFile, int op, void *pArg){ |
|
129
|
if( op==SQLITE_FCNTL_SIZE_HINT ){ |
|
130
|
ArchiveFile *pAF = (ArchiveFile*)pFile; |
|
131
|
zip_blob_minsize(pAF->pBlob, (int)(*(sqlite3_int64*)pArg)); |
|
132
|
} |
|
133
|
return SQLITE_NOTFOUND; |
|
134
|
} |
|
135
|
static int archiveSectorSize(sqlite3_file *pFile){ |
|
136
|
return 512; |
|
137
|
} |
|
138
|
static int archiveDeviceCharacteristics(sqlite3_file *pFile){ |
|
139
|
return 0; |
|
140
|
} |
|
141
|
|
|
142
|
static int archiveOpen( |
|
143
|
sqlite3_vfs *pVfs, const char *zName, |
|
144
|
sqlite3_file *pFile, int flags, int *pOutFlags |
|
145
|
){ |
|
146
|
static struct sqlite3_io_methods methods = { |
|
147
|
1, /* iVersion */ |
|
148
|
archiveClose, |
|
149
|
archiveRead, |
|
150
|
archiveWrite, |
|
151
|
archiveTruncate, |
|
152
|
archiveSync, |
|
153
|
archiveFileSize, |
|
154
|
archiveLock, |
|
155
|
archiveUnlock, |
|
156
|
archiveCheckReservedLock, |
|
157
|
archiveFileControl, |
|
158
|
archiveSectorSize, |
|
159
|
archiveDeviceCharacteristics, |
|
160
|
0, 0, 0, 0, |
|
161
|
0, 0 |
|
162
|
}; |
|
163
|
|
|
164
|
ArchiveFile *pAF = (ArchiveFile*)pFile; |
|
165
|
assert( flags & SQLITE_OPEN_MAIN_DB ); |
|
166
|
|
|
167
|
pAF->base.pMethods = &methods; |
|
168
|
pAF->pBlob = (Blob*)pVfs->pAppData; |
|
169
|
|
|
170
|
return SQLITE_OK; |
|
171
|
} |
|
172
|
static int archiveDelete(sqlite3_vfs *pVfs, const char *zName, int syncDir){ |
|
173
|
return SQLITE_OK; |
|
174
|
} |
|
175
|
static int archiveAccess( |
|
176
|
sqlite3_vfs *pVfs, const char *zName, int flags, int *pResOut |
|
177
|
){ |
|
178
|
*pResOut = 0; |
|
179
|
return SQLITE_OK; |
|
180
|
} |
|
181
|
static int archiveFullPathname( |
|
182
|
sqlite3_vfs *pVfs, const char *zIn, int nOut, char *zOut |
|
183
|
){ |
|
184
|
int n = strlen(zIn); |
|
185
|
memcpy(zOut, zIn, n+1); |
|
186
|
return SQLITE_OK; |
|
187
|
} |
|
188
|
static int archiveRandomness(sqlite3_vfs *pVfs, int nByte, char *zOut){ |
|
189
|
memset(zOut, 0, nByte); |
|
190
|
return SQLITE_OK; |
|
191
|
} |
|
192
|
static int archiveSleep(sqlite3_vfs *pVfs, int microseconds){ |
|
193
|
return SQLITE_OK; |
|
194
|
} |
|
195
|
static int archiveCurrentTime(sqlite3_vfs *pVfs, double *prOut){ |
|
196
|
return SQLITE_OK; |
|
197
|
} |
|
198
|
static int archiveGetLastError(sqlite3_vfs *pVfs, int nBuf, char *aBuf){ |
|
199
|
return SQLITE_OK; |
|
200
|
} |
|
201
|
/* |
|
202
|
** End of "archive" VFS. |
|
203
|
*************************************************************************/ |
|
204
|
|
|
205
|
/* |
|
206
|
** Initialize a new ZIP archive. |
|
207
|
*/ |
|
208
|
void zip_open(void){ |
|
209
|
blob_zero(&body); |
|
210
|
blob_zero(&toc); |
|
211
|
nEntry = 0; |
|
212
|
dosTime = 0; |
|
213
|
dosDate = 0; |
|
214
|
unixTime = 0; |
|
215
|
} |
|
216
|
|
|
217
|
/* |
|
218
|
** Set the date and time values from an ISO8601 date string. |
|
219
|
*/ |
|
220
|
void zip_set_timedate_from_str(const char *zDate){ |
|
221
|
int y, m, d; |
|
222
|
int H, M, S; |
|
223
|
|
|
224
|
y = atoi(zDate); |
|
225
|
m = atoi(&zDate[5]); |
|
226
|
d = atoi(&zDate[8]); |
|
227
|
H = atoi(&zDate[11]); |
|
228
|
M = atoi(&zDate[14]); |
|
229
|
S = atoi(&zDate[17]); |
|
230
|
dosTime = (H<<11) + (M<<5) + (S>>1); |
|
231
|
dosDate = ((y-1980)<<9) + (m<<5) + d; |
|
232
|
} |
|
233
|
|
|
234
|
/* |
|
235
|
** Set the date and time from a Julian day number. |
|
236
|
*/ |
|
237
|
void zip_set_timedate(double rDate){ |
|
238
|
char *zDate = db_text(0, "SELECT datetime(%.17g)", rDate); |
|
239
|
zip_set_timedate_from_str(zDate); |
|
240
|
fossil_free(zDate); |
|
241
|
unixTime = (int)((rDate - 2440587.5)*86400.0); |
|
242
|
} |
|
243
|
|
|
244
|
/* |
|
245
|
** Append a single file to a growing ZIP archive. |
|
246
|
** |
|
247
|
** pFile is the file to be appended. zName is the name |
|
248
|
** that the file should be saved as. |
|
249
|
*/ |
|
250
|
static void zip_add_file_to_zip( |
|
251
|
Archive *p, |
|
252
|
const char *zName, |
|
253
|
const Blob *pFile, |
|
254
|
int mPerm |
|
255
|
){ |
|
256
|
z_stream stream; |
|
257
|
int nameLen; |
|
258
|
int toOut = 0; |
|
259
|
int iStart; |
|
260
|
unsigned long iCRC = 0; |
|
261
|
int nByte = 0; |
|
262
|
int nByteCompr = 0; |
|
263
|
int nBlob; /* Size of the blob */ |
|
264
|
int iMethod; /* Compression method. */ |
|
265
|
int iMode = 0644; /* Access permissions */ |
|
266
|
char *z; |
|
267
|
char zHdr[30]; |
|
268
|
char zExTime[13]; |
|
269
|
char zBuf[100]; |
|
270
|
char zOutBuf[100000]; |
|
271
|
|
|
272
|
/* Fill inasmuch of the header as we know. |
|
273
|
*/ |
|
274
|
nameLen = (int)strlen(zName); |
|
275
|
if( nameLen==0 ) return; |
|
276
|
nBlob = pFile ? blob_size(pFile) : 0; |
|
277
|
if( pFile ){ /* This is a file, possibly empty... */ |
|
278
|
iMethod = (nBlob>0) ? 8 : 0; /* Cannot compress zero bytes. */ |
|
279
|
switch( mPerm ){ |
|
280
|
case PERM_LNK: iMode = 0120755; break; |
|
281
|
case PERM_EXE: iMode = 0100755; break; |
|
282
|
default: iMode = 0100644; break; |
|
283
|
} |
|
284
|
}else{ /* This is a directory, no blob... */ |
|
285
|
iMethod = 0; |
|
286
|
iMode = 040755; |
|
287
|
} |
|
288
|
memset(zHdr, 0, sizeof(zHdr)); |
|
289
|
put32(&zHdr[0], 0x04034b50); |
|
290
|
put16(&zHdr[4], 0x000a); |
|
291
|
put16(&zHdr[6], 0x0800); |
|
292
|
put16(&zHdr[8], iMethod); |
|
293
|
put16(&zHdr[10], dosTime); |
|
294
|
put16(&zHdr[12], dosDate); |
|
295
|
put16(&zHdr[26], nameLen); |
|
296
|
put16(&zHdr[28], 13); |
|
297
|
|
|
298
|
put16(&zExTime[0], 0x5455); |
|
299
|
put16(&zExTime[2], 9); |
|
300
|
zExTime[4] = 3; |
|
301
|
put32(&zExTime[5], unixTime); |
|
302
|
put32(&zExTime[9], unixTime); |
|
303
|
|
|
304
|
|
|
305
|
/* Write the header and filename. |
|
306
|
*/ |
|
307
|
iStart = blob_size(&body); |
|
308
|
blob_append(&body, zHdr, 30); |
|
309
|
blob_append(&body, zName, nameLen); |
|
310
|
blob_append(&body, zExTime, 13); |
|
311
|
|
|
312
|
if( nBlob>0 ){ |
|
313
|
/* Write the compressed file. Compute the CRC as we progress. |
|
314
|
*/ |
|
315
|
stream.zalloc = (alloc_func)0; |
|
316
|
stream.zfree = (free_func)0; |
|
317
|
stream.opaque = 0; |
|
318
|
stream.avail_in = blob_size(pFile); |
|
319
|
stream.next_in = (unsigned char*)blob_buffer(pFile); |
|
320
|
stream.avail_out = sizeof(zOutBuf); |
|
321
|
stream.next_out = (unsigned char*)zOutBuf; |
|
322
|
deflateInit2(&stream, 9, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY); |
|
323
|
iCRC = crc32(0, stream.next_in, stream.avail_in); |
|
324
|
while( stream.avail_in>0 ){ |
|
325
|
deflate(&stream, 0); |
|
326
|
toOut = sizeof(zOutBuf) - stream.avail_out; |
|
327
|
blob_append(&body, zOutBuf, toOut); |
|
328
|
stream.avail_out = sizeof(zOutBuf); |
|
329
|
stream.next_out = (unsigned char*)zOutBuf; |
|
330
|
} |
|
331
|
do{ |
|
332
|
stream.avail_out = sizeof(zOutBuf); |
|
333
|
stream.next_out = (unsigned char*)zOutBuf; |
|
334
|
deflate(&stream, Z_FINISH); |
|
335
|
toOut = sizeof(zOutBuf) - stream.avail_out; |
|
336
|
blob_append(&body, zOutBuf, toOut); |
|
337
|
}while( stream.avail_out==0 ); |
|
338
|
nByte = stream.total_in; |
|
339
|
nByteCompr = stream.total_out; |
|
340
|
deflateEnd(&stream); |
|
341
|
|
|
342
|
/* Go back and write the header, now that we know the compressed file size. |
|
343
|
*/ |
|
344
|
z = &blob_buffer(&body)[iStart]; |
|
345
|
put32(&z[14], iCRC); |
|
346
|
put32(&z[18], nByteCompr); |
|
347
|
put32(&z[22], nByte); |
|
348
|
} |
|
349
|
|
|
350
|
/* Make an entry in the tables of contents |
|
351
|
*/ |
|
352
|
memset(zBuf, 0, sizeof(zBuf)); |
|
353
|
put32(&zBuf[0], 0x02014b50); |
|
354
|
put16(&zBuf[4], 0x0317); |
|
355
|
put16(&zBuf[6], 0x000a); |
|
356
|
put16(&zBuf[8], 0x0800); |
|
357
|
put16(&zBuf[10], iMethod); |
|
358
|
put16(&zBuf[12], dosTime); |
|
359
|
put16(&zBuf[14], dosDate); |
|
360
|
put32(&zBuf[16], iCRC); |
|
361
|
put32(&zBuf[20], nByteCompr); |
|
362
|
put32(&zBuf[24], nByte); |
|
363
|
put16(&zBuf[28], nameLen); |
|
364
|
put16(&zBuf[30], 9); |
|
365
|
put16(&zBuf[32], 0); |
|
366
|
put16(&zBuf[34], 0); |
|
367
|
put16(&zBuf[36], 0); |
|
368
|
put32(&zBuf[38], ((unsigned)iMode)<<16); |
|
369
|
put32(&zBuf[42], iStart); |
|
370
|
blob_append(&toc, zBuf, 46); |
|
371
|
blob_append(&toc, zName, nameLen); |
|
372
|
put16(&zExTime[2], 5); |
|
373
|
blob_append(&toc, zExTime, 9); |
|
374
|
nEntry++; |
|
375
|
} |
|
376
|
|
|
377
|
static void zip_add_file_to_sqlar( |
|
378
|
Archive *p, |
|
379
|
const char *zName, |
|
380
|
const Blob *pFile, |
|
381
|
int mPerm |
|
382
|
){ |
|
383
|
int nName = (int)strlen(zName); |
|
384
|
|
|
385
|
if( p->db==0 ){ |
|
386
|
assert( p->vfs.zName==0 ); |
|
387
|
p->vfs.zName = (const char*)mprintf("archivevfs%p", (void*)p); |
|
388
|
p->vfs.iVersion = 1; |
|
389
|
p->vfs.szOsFile = sizeof(ArchiveFile); |
|
390
|
p->vfs.mxPathname = 512; |
|
391
|
p->vfs.pAppData = (void*)p->pBlob; |
|
392
|
p->vfs.xOpen = archiveOpen; |
|
393
|
p->vfs.xDelete = archiveDelete; |
|
394
|
p->vfs.xAccess = archiveAccess; |
|
395
|
p->vfs.xFullPathname = archiveFullPathname; |
|
396
|
p->vfs.xRandomness = archiveRandomness; |
|
397
|
p->vfs.xSleep = archiveSleep; |
|
398
|
p->vfs.xCurrentTime = archiveCurrentTime; |
|
399
|
p->vfs.xGetLastError = archiveGetLastError; |
|
400
|
sqlite3_vfs_register(&p->vfs, 0); |
|
401
|
sqlite3_open_v2("file:xyz.db", &p->db, |
|
402
|
SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE, p->vfs.zName |
|
403
|
); |
|
404
|
assert( p->db ); |
|
405
|
blob_zero(&p->tmp); |
|
406
|
sqlite3_exec(p->db, |
|
407
|
"PRAGMA page_size=512;" |
|
408
|
"PRAGMA journal_mode = off;" |
|
409
|
"PRAGMA cache_spill = off;" |
|
410
|
"BEGIN;" |
|
411
|
"CREATE TABLE sqlar(\n" |
|
412
|
" name TEXT PRIMARY KEY, -- name of the file\n" |
|
413
|
" mode INT, -- access permissions\n" |
|
414
|
" mtime INT, -- last modification time\n" |
|
415
|
" sz INT, -- original file size\n" |
|
416
|
" data BLOB -- compressed content\n" |
|
417
|
");", 0, 0, 0 |
|
418
|
); |
|
419
|
sqlite3_prepare(p->db, |
|
420
|
"INSERT INTO sqlar VALUES(?, ?, ?, ?, ?)", -1, |
|
421
|
&p->pInsert, 0 |
|
422
|
); |
|
423
|
assert( p->pInsert ); |
|
424
|
|
|
425
|
sqlite3_bind_int64(p->pInsert, 3, unixTime); |
|
426
|
blob_zero(p->pBlob); |
|
427
|
} |
|
428
|
|
|
429
|
if( nName==0 ) return; |
|
430
|
if( pFile==0 ){ |
|
431
|
/* Directory. */ |
|
432
|
if( zName[nName-1]=='/' ) nName--; |
|
433
|
sqlite3_bind_text(p->pInsert, 1, zName, nName, SQLITE_STATIC); |
|
434
|
sqlite3_bind_int(p->pInsert, 2, 040755); |
|
435
|
sqlite3_bind_int(p->pInsert, 4, 0); |
|
436
|
sqlite3_bind_null(p->pInsert, 5); |
|
437
|
}else{ |
|
438
|
sqlite3_bind_text(p->pInsert, 1, zName, nName, SQLITE_STATIC); |
|
439
|
if( mPerm==PERM_LNK ){ |
|
440
|
sqlite3_bind_int(p->pInsert, 2, 0120755); |
|
441
|
sqlite3_bind_int(p->pInsert, 4, -1); |
|
442
|
sqlite3_bind_text(p->pInsert, 5, |
|
443
|
blob_buffer(pFile), blob_size(pFile), SQLITE_STATIC |
|
444
|
); |
|
445
|
}else{ |
|
446
|
unsigned int nIn = blob_size(pFile); |
|
447
|
unsigned long int nOut = nIn; |
|
448
|
sqlite3_bind_int(p->pInsert, 2, mPerm==PERM_EXE ? 0100755 : 0100644); |
|
449
|
sqlite3_bind_int(p->pInsert, 4, nIn); |
|
450
|
zip_blob_minsize(&p->tmp, nIn); |
|
451
|
compress( (unsigned char*) |
|
452
|
blob_buffer(&p->tmp), &nOut, (unsigned char*)blob_buffer(pFile), nIn |
|
453
|
); |
|
454
|
if( nOut>=(unsigned long)nIn ){ |
|
455
|
sqlite3_bind_blob(p->pInsert, 5, |
|
456
|
blob_buffer(pFile), blob_size(pFile), SQLITE_STATIC |
|
457
|
); |
|
458
|
}else{ |
|
459
|
sqlite3_bind_blob(p->pInsert, 5, |
|
460
|
blob_buffer(&p->tmp), nOut, SQLITE_STATIC |
|
461
|
); |
|
462
|
} |
|
463
|
} |
|
464
|
} |
|
465
|
|
|
466
|
sqlite3_step(p->pInsert); |
|
467
|
sqlite3_reset(p->pInsert); |
|
468
|
} |
|
469
|
|
|
470
|
static void zip_add_file( |
|
471
|
Archive *p, |
|
472
|
const char *zName, |
|
473
|
const Blob *pFile, |
|
474
|
int mPerm |
|
475
|
){ |
|
476
|
if( p->eType==ARCHIVE_ZIP ){ |
|
477
|
zip_add_file_to_zip(p, zName, pFile, mPerm); |
|
478
|
}else{ |
|
479
|
zip_add_file_to_sqlar(p, zName, pFile, mPerm); |
|
480
|
} |
|
481
|
} |
|
482
|
|
|
483
|
/* |
|
484
|
** If the given filename includes one or more directory entries, make |
|
485
|
** sure the directories are already in the archive. If they are not |
|
486
|
** in the archive, add them. |
|
487
|
*/ |
|
488
|
static void zip_add_folders(Archive *p, char *zName){ |
|
489
|
int i, c; |
|
490
|
int j; |
|
491
|
for(i=0; zName[i]; i++){ |
|
492
|
if( zName[i]=='/' ){ |
|
493
|
c = zName[i+1]; |
|
494
|
zName[i+1] = 0; |
|
495
|
for(j=0; j<nDir; j++){ |
|
496
|
if( fossil_strcmp(zName, azDir[j])==0 ) break; |
|
497
|
} |
|
498
|
if( j>=nDir ){ |
|
499
|
nDir++; |
|
500
|
azDir = fossil_realloc(azDir, sizeof(azDir[0])*nDir); |
|
501
|
azDir[j] = fossil_strdup(zName); |
|
502
|
zip_add_file(p, zName, 0, 0); |
|
503
|
} |
|
504
|
zName[i+1] = c; |
|
505
|
} |
|
506
|
} |
|
507
|
} |
|
508
|
|
|
509
|
/* |
|
510
|
** Free all the members of structure Archive allocated while processing |
|
511
|
** an SQLAR request. |
|
512
|
*/ |
|
513
|
static void free_archive(Archive *p){ |
|
514
|
if( p->vfs.zName ){ |
|
515
|
sqlite3_vfs_unregister(&p->vfs); |
|
516
|
fossil_free((char*)p->vfs.zName); |
|
517
|
p->vfs.zName = 0; |
|
518
|
} |
|
519
|
sqlite3_finalize(p->pInsert); |
|
520
|
p->pInsert = 0; |
|
521
|
sqlite3_close(p->db); |
|
522
|
p->db = 0; |
|
523
|
} |
|
524
|
|
|
525
|
/* |
|
526
|
** Write the ZIP archive into the given BLOB. |
|
527
|
*/ |
|
528
|
static void zip_close(Archive *p){ |
|
529
|
int i; |
|
530
|
if( p->eType==ARCHIVE_ZIP ){ |
|
531
|
int iTocStart; |
|
532
|
int iTocEnd; |
|
533
|
char zBuf[30]; |
|
534
|
|
|
535
|
iTocStart = blob_size(&body); |
|
536
|
blob_append(&body, blob_buffer(&toc), blob_size(&toc)); |
|
537
|
iTocEnd = blob_size(&body); |
|
538
|
|
|
539
|
memset(zBuf, 0, sizeof(zBuf)); |
|
540
|
put32(&zBuf[0], 0x06054b50); |
|
541
|
put16(&zBuf[4], 0); |
|
542
|
put16(&zBuf[6], 0); |
|
543
|
put16(&zBuf[8], nEntry); |
|
544
|
put16(&zBuf[10], nEntry); |
|
545
|
put32(&zBuf[12], iTocEnd - iTocStart); |
|
546
|
put32(&zBuf[16], iTocStart); |
|
547
|
put16(&zBuf[20], 0); |
|
548
|
blob_append(&body, zBuf, 22); |
|
549
|
blob_reset(&toc); |
|
550
|
*(p->pBlob) = body; |
|
551
|
blob_zero(&body); |
|
552
|
}else{ |
|
553
|
if( p->db ) sqlite3_exec(p->db, "COMMIT", 0, 0, 0); |
|
554
|
free_archive(p); |
|
555
|
blob_reset(&p->tmp); |
|
556
|
} |
|
557
|
|
|
558
|
nEntry = 0; |
|
559
|
for(i=0; i<nDir; i++){ |
|
560
|
fossil_free(azDir[i]); |
|
561
|
} |
|
562
|
fossil_free(azDir); |
|
563
|
nDir = 0; |
|
564
|
azDir = 0; |
|
565
|
} |
|
566
|
|
|
567
|
/* Functions found in shell.c */ |
|
568
|
extern int sqlite3_fileio_init(sqlite3*,char**,const sqlite3_api_routines*); |
|
569
|
extern int sqlite3_zipfile_init(sqlite3*,char**,const sqlite3_api_routines*); |
|
570
|
|
|
571
|
/* |
|
572
|
** COMMAND: test-filezip |
|
573
|
** |
|
574
|
** Usage: %fossil test-filezip [OPTIONS] ZIPFILE [FILENAME...] |
|
575
|
** |
|
576
|
** This command uses Fossil infrastructure or read or create a ZIP |
|
577
|
** archive named by the ZIPFILE argument. With no options, a new |
|
578
|
** ZIP archive is created and there must be at least one FILENAME |
|
579
|
** argument. If the -l option is used, the contents of the named ZIP |
|
580
|
** archive are listed on standard output. With the -x argument, the |
|
581
|
** contents of the ZIP archive are extracted. |
|
582
|
** |
|
583
|
** There are two purposes for this command: (1) To server as a test |
|
584
|
** platform for the Fossil ZIP archive generator, and (2) to provide |
|
585
|
** rudimentary ZIP archive creation capabilities on platforms that do |
|
586
|
** not have the "zip" command installed. |
|
587
|
** |
|
588
|
** Options: |
|
589
|
** |
|
590
|
** -h|--dereference Follow symlinks |
|
591
|
** -l|--list List the contents of the ZIP archive |
|
592
|
** -x|--extract Extract files from a ZIP archive |
|
593
|
*/ |
|
594
|
void filezip_cmd(void){ |
|
595
|
int eFType = SymFILE; |
|
596
|
int doList = 0; |
|
597
|
int doExtract = 0; |
|
598
|
char *zArchiveName; |
|
599
|
if( find_option("dereference","h",0)!=0 ){ |
|
600
|
eFType = ExtFILE; |
|
601
|
} |
|
602
|
if( find_option("list","l",0)!=0 ){ |
|
603
|
doList = 1; |
|
604
|
} |
|
605
|
if( find_option("extract","x",0)!=0 ){ |
|
606
|
if( doList ){ |
|
607
|
fossil_fatal("incompatible options: -l and -x"); |
|
608
|
} |
|
609
|
doExtract = 1; |
|
610
|
} |
|
611
|
if( g.argc<3 ){ |
|
612
|
usage("ARCHIVE FILES..."); |
|
613
|
} |
|
614
|
zArchiveName = g.argv[2]; |
|
615
|
sqlite3_open(":memory:", &g.db); |
|
616
|
if( doList ){ |
|
617
|
/* Do a content listing of a ZIP archive */ |
|
618
|
Stmt q; |
|
619
|
int nRow = 0; |
|
620
|
i64 szTotal = 0; |
|
621
|
if( file_size(zArchiveName, eFType)<0 ){ |
|
622
|
fossil_fatal("No such ZIP archive: %s", zArchiveName); |
|
623
|
} |
|
624
|
if( g.argc>3 ){ |
|
625
|
fossil_fatal("extra arguments after \"fossil test-filezip -l ARCHIVE\""); |
|
626
|
} |
|
627
|
sqlite3_zipfile_init(g.db, 0, 0); |
|
628
|
db_multi_exec("CREATE VIRTUAL TABLE z1 USING zipfile(%Q)", zArchiveName); |
|
629
|
db_prepare(&q, |
|
630
|
"SELECT sz, datetime(mtime,'unixepoch')," |
|
631
|
" if(((mode>>12)&15)==10,name||' -> '||data,name) FROM z1" |
|
632
|
); |
|
633
|
while( db_step(&q)==SQLITE_ROW ){ |
|
634
|
int sz = db_column_int(&q, 0); |
|
635
|
szTotal += sz; |
|
636
|
if( nRow==0 ){ |
|
637
|
fossil_print(" Length Date Time Name\n"); |
|
638
|
fossil_print("--------- ---------- ----- ----\n"); |
|
639
|
} |
|
640
|
nRow++; |
|
641
|
fossil_print("%9d %.16s %s\n", sz, db_column_text(&q,1), |
|
642
|
db_column_text(&q,2)); |
|
643
|
} |
|
644
|
if( nRow ){ |
|
645
|
fossil_print("--------- --------\n"); |
|
646
|
fossil_print("%9lld %16s %d files\n", szTotal, "", nRow); |
|
647
|
} |
|
648
|
db_finalize(&q); |
|
649
|
}else if( doExtract ){ |
|
650
|
/* Extract files from an existing ZIP archive */ |
|
651
|
if( file_size(zArchiveName, eFType)<0 ){ |
|
652
|
fossil_fatal("No such ZIP archive: %s", zArchiveName); |
|
653
|
} |
|
654
|
if( g.argc>3 ){ |
|
655
|
fossil_fatal("extra arguments after \"fossil test-filezip -x ARCHIVE\""); |
|
656
|
} |
|
657
|
sqlite3_zipfile_init(g.db, 0, 0); |
|
658
|
sqlite3_fileio_init(g.db, 0, 0); |
|
659
|
db_multi_exec("CREATE VIRTUAL TABLE z1 USING zipfile(%Q)", zArchiveName); |
|
660
|
db_multi_exec( |
|
661
|
"SELECT writefile(name,data) FROM z1" |
|
662
|
" WHERE ((mode>>12)&15)!=10" |
|
663
|
); |
|
664
|
}else{ |
|
665
|
/* Without the -x or -l options, construct a new ZIP archive */ |
|
666
|
int i; |
|
667
|
Blob zip; |
|
668
|
Blob file; |
|
669
|
Archive sArchive; |
|
670
|
memset(&sArchive, 0, sizeof(Archive)); |
|
671
|
sArchive.eType = ARCHIVE_ZIP; |
|
672
|
sArchive.pBlob = &zip; |
|
673
|
if( file_size(zArchiveName, eFType)>0 ){ |
|
674
|
fossil_fatal("ZIP archive %s already exists", zArchiveName); |
|
675
|
} |
|
676
|
zip_open(); |
|
677
|
for(i=3; i<g.argc; i++){ |
|
678
|
double rDate; |
|
679
|
i64 iDate; |
|
680
|
blob_zero(&file); |
|
681
|
blob_read_from_file(&file, g.argv[i], eFType); |
|
682
|
iDate = file_mtime(g.argv[i], eFType); |
|
683
|
rDate = ((double)iDate)/86400.0 + 2440587.5; |
|
684
|
zip_set_timedate(rDate); |
|
685
|
zip_add_file(&sArchive, g.argv[i], &file, file_perm(0,eFType)); |
|
686
|
blob_reset(&file); |
|
687
|
} |
|
688
|
zip_close(&sArchive); |
|
689
|
blob_write_to_file(&zip, g.argv[2]); |
|
690
|
} |
|
691
|
} |
|
692
|
|
|
693
|
/* |
|
694
|
** Given the RID for a manifest, construct a ZIP archive containing |
|
695
|
** all files in the corresponding baseline. |
|
696
|
** |
|
697
|
** If RID is for an object that is not a real manifest, then the |
|
698
|
** resulting ZIP archive contains a single file which is the RID |
|
699
|
** object. The pInclude and pExclude parameters are ignored in this case. |
|
700
|
** |
|
701
|
** If the RID object does not exist in the repository, then |
|
702
|
** pZip is zeroed. |
|
703
|
** |
|
704
|
** zDir is a "synthetic" subdirectory which all zipped files get |
|
705
|
** added to as part of the zip file. It may be 0 or an empty string, |
|
706
|
** in which case it is ignored. The intention is to create a zip which |
|
707
|
** politely expands into a subdir instead of filling your current dir |
|
708
|
** with source files. For example, pass a commit hash or "ProjectName". |
|
709
|
** |
|
710
|
*/ |
|
711
|
static void zip_of_checkin( |
|
712
|
int eType, /* Type of archive (ZIP or SQLAR) */ |
|
713
|
int rid, /* The RID of the check-in to build the archive from */ |
|
714
|
Blob *pZip, /* Write the archive content into this blob */ |
|
715
|
const char *zDir, /* Top-level directory of the archive */ |
|
716
|
Glob *pInclude, /* Only include files that match this pattern */ |
|
717
|
Glob *pExclude, /* Exclude files that match this pattern */ |
|
718
|
int listFlag /* Print each file on stdout */ |
|
719
|
){ |
|
720
|
Blob mfile, hash, file; |
|
721
|
Manifest *pManifest; |
|
722
|
ManifestFile *pFile; |
|
723
|
Blob filename; |
|
724
|
int nPrefix; |
|
725
|
|
|
726
|
Archive sArchive; |
|
727
|
memset(&sArchive, 0, sizeof(Archive)); |
|
728
|
sArchive.eType = eType; |
|
729
|
sArchive.pBlob = pZip; |
|
730
|
blob_zero(&sArchive.tmp); |
|
731
|
if( pZip ) blob_zero(pZip); |
|
732
|
|
|
733
|
content_get(rid, &mfile); |
|
734
|
if( blob_size(&mfile)==0 ){ |
|
735
|
return; |
|
736
|
} |
|
737
|
blob_set_dynamic(&hash, rid_to_uuid(rid)); |
|
738
|
blob_zero(&filename); |
|
739
|
if( pZip ) zip_open(); |
|
740
|
|
|
741
|
if( zDir && zDir[0] ){ |
|
742
|
blob_appendf(&filename, "%s/", zDir); |
|
743
|
} |
|
744
|
nPrefix = blob_size(&filename); |
|
745
|
|
|
746
|
pManifest = manifest_get(rid, CFTYPE_MANIFEST, 0); |
|
747
|
if( pManifest ){ |
|
748
|
int flg, eflg = 0; |
|
749
|
char *zName = 0; |
|
750
|
zip_set_timedate(pManifest->rDate); |
|
751
|
flg = db_get_manifest_setting(blob_str(&hash)); |
|
752
|
if( flg ){ |
|
753
|
/* eflg is the effective flags, taking include/exclude into account */ |
|
754
|
if( (pInclude==0 || glob_match(pInclude, "manifest")) |
|
755
|
&& !glob_match(pExclude, "manifest") |
|
756
|
&& (flg & MFESTFLG_RAW) ){ |
|
757
|
eflg |= MFESTFLG_RAW; |
|
758
|
} |
|
759
|
if( (pInclude==0 || glob_match(pInclude, "manifest.uuid")) |
|
760
|
&& !glob_match(pExclude, "manifest.uuid") |
|
761
|
&& (flg & MFESTFLG_UUID) ){ |
|
762
|
eflg |= MFESTFLG_UUID; |
|
763
|
} |
|
764
|
if( (pInclude==0 || glob_match(pInclude, "manifest.tags")) |
|
765
|
&& !glob_match(pExclude, "manifest.tags") |
|
766
|
&& (flg & MFESTFLG_TAGS) ){ |
|
767
|
eflg |= MFESTFLG_TAGS; |
|
768
|
} |
|
769
|
|
|
770
|
if( eflg & MFESTFLG_RAW ){ |
|
771
|
blob_append(&filename, "manifest", -1); |
|
772
|
zName = blob_str(&filename); |
|
773
|
if( listFlag ) fossil_print("%s\n", zName); |
|
774
|
if( pZip ){ |
|
775
|
zip_add_folders(&sArchive, zName); |
|
776
|
zip_add_file(&sArchive, zName, &mfile, 0); |
|
777
|
} |
|
778
|
} |
|
779
|
if( eflg & MFESTFLG_UUID ){ |
|
780
|
blob_append(&hash, "\n", 1); |
|
781
|
blob_resize(&filename, nPrefix); |
|
782
|
blob_append(&filename, "manifest.uuid", -1); |
|
783
|
zName = blob_str(&filename); |
|
784
|
if( listFlag ) fossil_print("%s\n", zName); |
|
785
|
if( pZip ){ |
|
786
|
zip_add_folders(&sArchive, zName); |
|
787
|
zip_add_file(&sArchive, zName, &hash, 0); |
|
788
|
} |
|
789
|
} |
|
790
|
if( eflg & MFESTFLG_TAGS ){ |
|
791
|
blob_resize(&filename, nPrefix); |
|
792
|
blob_append(&filename, "manifest.tags", -1); |
|
793
|
zName = blob_str(&filename); |
|
794
|
if( listFlag ) fossil_print("%s\n", zName); |
|
795
|
if( pZip ){ |
|
796
|
Blob tagslist; |
|
797
|
blob_zero(&tagslist); |
|
798
|
get_checkin_taglist(rid, &tagslist); |
|
799
|
zip_add_folders(&sArchive, zName); |
|
800
|
zip_add_file(&sArchive, zName, &tagslist, 0); |
|
801
|
blob_reset(&tagslist); |
|
802
|
} |
|
803
|
} |
|
804
|
} |
|
805
|
manifest_file_rewind(pManifest); |
|
806
|
if( pZip ) zip_add_file(&sArchive, "", 0, 0); |
|
807
|
while( (pFile = manifest_file_next(pManifest,0))!=0 ){ |
|
808
|
int fid; |
|
809
|
if( pInclude!=0 && !glob_match(pInclude, pFile->zName) ) continue; |
|
810
|
if( glob_match(pExclude, pFile->zName) ) continue; |
|
811
|
fid = uuid_to_rid(pFile->zUuid, 0); |
|
812
|
if( fid ){ |
|
813
|
blob_resize(&filename, nPrefix); |
|
814
|
blob_append(&filename, pFile->zName, -1); |
|
815
|
zName = blob_str(&filename); |
|
816
|
if( listFlag ) fossil_print("%s\n", zName); |
|
817
|
if( pZip ){ |
|
818
|
content_get(fid, &file); |
|
819
|
zip_add_folders(&sArchive, zName); |
|
820
|
zip_add_file(&sArchive, zName, &file, manifest_file_mperm(pFile)); |
|
821
|
blob_reset(&file); |
|
822
|
} |
|
823
|
} |
|
824
|
} |
|
825
|
} |
|
826
|
blob_reset(&mfile); |
|
827
|
manifest_destroy(pManifest); |
|
828
|
blob_reset(&filename); |
|
829
|
blob_reset(&hash); |
|
830
|
if( pZip ){ |
|
831
|
zip_close(&sArchive); |
|
832
|
} |
|
833
|
} |
|
834
|
|
|
835
|
/* |
|
836
|
** Implementation of zip_cmd and sqlar_cmd. |
|
837
|
*/ |
|
838
|
static void archive_cmd(int eType){ |
|
839
|
int rid; |
|
840
|
Blob zip; |
|
841
|
const char *zName; |
|
842
|
Glob *pInclude = 0; |
|
843
|
Glob *pExclude = 0; |
|
844
|
const char *zInclude; |
|
845
|
const char *zExclude; |
|
846
|
int listFlag = 0; |
|
847
|
const char *zOut; |
|
848
|
|
|
849
|
zName = find_option("name", 0, 1); |
|
850
|
zExclude = find_option("exclude", "X", 1); |
|
851
|
if( zExclude ) pExclude = glob_create(zExclude); |
|
852
|
zInclude = find_option("include", 0, 1); |
|
853
|
if( zInclude ) pInclude = glob_create(zInclude); |
|
854
|
listFlag = find_option("list","l",0)!=0; |
|
855
|
db_find_and_open_repository(0, 0); |
|
856
|
|
|
857
|
/* We should be done with options.. */ |
|
858
|
verify_all_options(); |
|
859
|
|
|
860
|
if( g.argc!=4 ){ |
|
861
|
usage("VERSION OUTPUTFILE"); |
|
862
|
} |
|
863
|
g.zOpenRevision = g.argv[2]; |
|
864
|
rid = name_to_typed_rid(g.argv[2], "ci"); |
|
865
|
if( rid==0 ){ |
|
866
|
fossil_fatal("Check-in not found: %s", g.argv[2]); |
|
867
|
return; |
|
868
|
} |
|
869
|
zOut = g.argv[3]; |
|
870
|
if( fossil_strcmp(zOut,"")==0 || fossil_strcmp(zOut,"/dev/null")==0 ){ |
|
871
|
zOut = 0; |
|
872
|
} |
|
873
|
|
|
874
|
if( zName==0 ){ |
|
875
|
zName = archive_base_name(rid); |
|
876
|
} |
|
877
|
zip_of_checkin(eType, rid, zOut ? &zip : 0, |
|
878
|
zName, pInclude, pExclude, listFlag); |
|
879
|
glob_free(pInclude); |
|
880
|
glob_free(pExclude); |
|
881
|
if( zOut ){ |
|
882
|
blob_write_to_file(&zip, zOut); |
|
883
|
blob_reset(&zip); |
|
884
|
} |
|
885
|
} |
|
886
|
|
|
887
|
/* |
|
888
|
** COMMAND: zip* |
|
889
|
** |
|
890
|
** Usage: %fossil zip VERSION OUTPUTFILE [OPTIONS] |
|
891
|
** |
|
892
|
** Generate a ZIP archive for a check-in. If the --name option is |
|
893
|
** used, its argument becomes the name of the top-level directory in the |
|
894
|
** resulting ZIP archive. If --name is omitted, the top-level directory |
|
895
|
** name is derived from the project name, the check-in date and time, and |
|
896
|
** the artifact ID of the check-in. |
|
897
|
** |
|
898
|
** The GLOBLIST argument to --exclude and --include can be a comma-separated |
|
899
|
** list of glob patterns, where each glob pattern may optionally be enclosed |
|
900
|
** in "..." or '...' so that it may contain commas. If a file matches both |
|
901
|
** --include and --exclude then it is excluded. |
|
902
|
** |
|
903
|
** If OUTPUTFILE is an empty string or "/dev/null" then no ZIP archive is |
|
904
|
** actually generated. This feature can be used in combination with |
|
905
|
** the --list option to get a list of the filenames that would be in the |
|
906
|
** ZIP archive had it actually been generated. |
|
907
|
** |
|
908
|
** Options: |
|
909
|
** -X|--exclude GLOBLIST Comma-separated list of GLOBs of files to exclude |
|
910
|
** --include GLOBLIST Comma-separated list of GLOBs of files to include |
|
911
|
** -l|--list Show archive content on stdout |
|
912
|
** --name DIRECTORYNAME The name of the top-level directory in the archive |
|
913
|
** -R REPOSITORY Specify a Fossil repository |
|
914
|
*/ |
|
915
|
void zip_cmd(void){ |
|
916
|
archive_cmd(ARCHIVE_ZIP); |
|
917
|
} |
|
918
|
|
|
919
|
/* |
|
920
|
** COMMAND: sqlar* |
|
921
|
** |
|
922
|
** Usage: %fossil sqlar VERSION OUTPUTFILE [OPTIONS] |
|
923
|
** |
|
924
|
** Generate an SQLAR archive for a check-in. If the --name option is |
|
925
|
** used, its argument becomes the name of the top-level directory in the |
|
926
|
** resulting SQLAR archive. If --name is omitted, the top-level directory |
|
927
|
** name is derived from the project name, the check-in date and time, and |
|
928
|
** the artifact ID of the check-in. |
|
929
|
** |
|
930
|
** The GLOBLIST argument to --exclude and --include can be a comma-separated |
|
931
|
** list of glob patterns, where each glob pattern may optionally be enclosed |
|
932
|
** in "..." or '...' so that it may contain commas. If a file matches both |
|
933
|
** --include and --exclude then it is excluded. |
|
934
|
** |
|
935
|
** If OUTPUTFILE is an empty string or "/dev/null" then no SQLAR archive is |
|
936
|
** actually generated. This feature can be used in combination with |
|
937
|
** the --list option to get a list of the filenames that would be in the |
|
938
|
** SQLAR archive had it actually been generated. |
|
939
|
** |
|
940
|
** Options: |
|
941
|
** -X|--exclude GLOBLIST Comma-separated list of GLOBs of files to exclude |
|
942
|
** --include GLOBLIST Comma-separated list of GLOBs of files to include |
|
943
|
** -l|--list Show archive content on stdout |
|
944
|
** --name DIRECTORYNAME The name of the top-level directory in the archive |
|
945
|
** -R REPOSITORY Specify a Fossil repository |
|
946
|
*/ |
|
947
|
void sqlar_cmd(void){ |
|
948
|
archive_cmd(ARCHIVE_SQLAR); |
|
949
|
} |
|
950
|
|
|
951
|
/* |
|
952
|
** WEBPAGE: sqlar |
|
953
|
** WEBPAGE: zip |
|
954
|
** |
|
955
|
** URLs: |
|
956
|
** |
|
957
|
** /zip/[VERSION/]NAME.zip |
|
958
|
** /sqlar/[VERSION/]NAME.sqlar |
|
959
|
** |
|
960
|
** Generate a ZIP Archive or an SQL Archive for the check-in specified by |
|
961
|
** VERSION. The archive is called NAME.zip or NAME.sqlar and has a top-level |
|
962
|
** directory called NAME. |
|
963
|
** |
|
964
|
** The optional VERSION element defaults to the name of the main branch |
|
965
|
** (usually "trunk") per the r= rules below. |
|
966
|
** All of the following URLs are equivalent: |
|
967
|
** |
|
968
|
** /zip/release/xyz.zip |
|
969
|
** /zip?r=release&name=xyz.zip |
|
970
|
** /zip/xyz.zip?r=release |
|
971
|
** /zip?name=release/xyz.zip |
|
972
|
** |
|
973
|
** Query parameters: |
|
974
|
** |
|
975
|
** name=[CKIN/]NAME The optional CKIN component of the name= parameter |
|
976
|
** identifies the check-in from which the archive is |
|
977
|
** constructed. If CKIN is omitted and there is no |
|
978
|
** r= query parameter, then use the name of the main |
|
979
|
** branch (usually "trunk"). NAME is the |
|
980
|
** name of the download file. The top-level directory |
|
981
|
** in the generated archive is called by NAME with the |
|
982
|
** file extension removed. |
|
983
|
** |
|
984
|
** r=TAG TAG identifies the check-in that is turned into an |
|
985
|
** SQL or ZIP archive. The default value is the name |
|
986
|
** of the main branch (usually "trunk"). |
|
987
|
** If r= is omitted and if the name= query parameter |
|
988
|
** contains one "/" character then the of part the |
|
989
|
** name= value before the / becomes the TAG and the |
|
990
|
** part of the name= value after the / is the download |
|
991
|
** filename. If no check-in is specified by either |
|
992
|
** name= or r=, then the name of the main branch |
|
993
|
** (usually "trunk") is used. |
|
994
|
** |
|
995
|
** in=PATTERN Only include files that match the comma-separated |
|
996
|
** list of GLOB patterns in PATTERN, as with ex= |
|
997
|
** |
|
998
|
** ex=PATTERN Omit any file that match PATTERN. PATTERN is a |
|
999
|
** comma-separated list of GLOB patterns, where each |
|
1000
|
** pattern can optionally be quoted using ".." or '..'. |
|
1001
|
** Any file matching both ex= and in= is excluded. |
|
1002
|
** |
|
1003
|
** Robot Defenses: |
|
1004
|
** |
|
1005
|
** * If "zip" appears in the robot-restrict setting, then robots are |
|
1006
|
** not allowed to access this page. Suspected robots will be |
|
1007
|
** presented with a captcha. |
|
1008
|
** |
|
1009
|
** * If "zipX" appears in the robot-restrict setting, then robots are |
|
1010
|
** restricted in the same way as with "zip", but with exceptions. |
|
1011
|
** If the check-in for which an archive is requested is a leaf check-in |
|
1012
|
** and if the robot-zip-leaf setting is true, then the request is |
|
1013
|
** allowed. Or if the check-in has a tag that matches any of the |
|
1014
|
** GLOB patterns on the list in the robot-zip-tag setting, then the |
|
1015
|
** request is allowed. Otherwise, the usual robot defenses are |
|
1016
|
** activated. |
|
1017
|
*/ |
|
1018
|
void baseline_zip_page(void){ |
|
1019
|
int rid; |
|
1020
|
const char *z; |
|
1021
|
char *zName, *zRid, *zKey; |
|
1022
|
int nName, nRid; |
|
1023
|
const char *zInclude; /* The in= query parameter */ |
|
1024
|
const char *zExclude; /* The ex= query parameter */ |
|
1025
|
Blob cacheKey; /* The key to cache */ |
|
1026
|
Glob *pInclude = 0; /* The compiled in= glob pattern */ |
|
1027
|
Glob *pExclude = 0; /* The compiled ex= glob pattern */ |
|
1028
|
Blob zip; /* ZIP archive accumulated here */ |
|
1029
|
int eType = ARCHIVE_ZIP; /* Type of archive to generate */ |
|
1030
|
char *zType; /* Human-readable archive type */ |
|
1031
|
|
|
1032
|
login_check_credentials(); |
|
1033
|
if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; } |
|
1034
|
if( robot_restrict("zip") ) return; |
|
1035
|
if( fossil_strcmp(g.zPath, "sqlar")==0 ){ |
|
1036
|
eType = ARCHIVE_SQLAR; |
|
1037
|
zType = "SQL"; |
|
1038
|
}else{ |
|
1039
|
eType = ARCHIVE_ZIP; |
|
1040
|
zType = "ZIP"; |
|
1041
|
} |
|
1042
|
fossil_nice_default(); |
|
1043
|
zName = fossil_strdup(PD("name","")); |
|
1044
|
z = P("r"); |
|
1045
|
if( z==0 ) z = P("uuid"); |
|
1046
|
if( z==0 ) z = tar_uuid_from_name(&zName); |
|
1047
|
if( z==0 ) z = fossil_strdup(db_main_branch()); |
|
1048
|
nName = strlen(zName); |
|
1049
|
g.zOpenRevision = zRid = fossil_strdup(z); |
|
1050
|
nRid = strlen(zRid); |
|
1051
|
zInclude = P("in"); |
|
1052
|
if( zInclude ) pInclude = glob_create(zInclude); |
|
1053
|
zExclude = P("ex"); |
|
1054
|
if( zExclude ) pExclude = glob_create(zExclude); |
|
1055
|
if( zInclude==0 && zExclude==0 ){ |
|
1056
|
etag_check_for_invariant_name(z); |
|
1057
|
} |
|
1058
|
if( eType==ARCHIVE_ZIP |
|
1059
|
&& nName>4 |
|
1060
|
&& fossil_strcmp(&zName[nName-4], ".zip")==0 |
|
1061
|
){ |
|
1062
|
/* Special case: Remove the ".zip" suffix. */ |
|
1063
|
nName -= 4; |
|
1064
|
zName[nName] = 0; |
|
1065
|
}else if( eType==ARCHIVE_SQLAR |
|
1066
|
&& nName>6 |
|
1067
|
&& fossil_strcmp(&zName[nName-6], ".sqlar")==0 |
|
1068
|
){ |
|
1069
|
/* Special case: Remove the ".sqlar" suffix. */ |
|
1070
|
nName -= 6; |
|
1071
|
zName[nName] = 0; |
|
1072
|
}else{ |
|
1073
|
/* If the file suffix is not ".zip" or ".sqlar" then just remove the |
|
1074
|
** suffix up to and including the last "." */ |
|
1075
|
for(nName=strlen(zName)-1; nName>5; nName--){ |
|
1076
|
if( zName[nName]=='.' ){ |
|
1077
|
zName[nName] = 0; |
|
1078
|
break; |
|
1079
|
} |
|
1080
|
} |
|
1081
|
} |
|
1082
|
rid = symbolic_name_to_rid(nRid?zRid:zName, "ci"); |
|
1083
|
if( rid<=0 ){ |
|
1084
|
cgi_set_status(404, "Not Found"); |
|
1085
|
@ Not found |
|
1086
|
return; |
|
1087
|
} |
|
1088
|
if( robot_restrict_zip(rid) ) return; |
|
1089
|
if( nRid==0 && nName>10 ) zName[10] = 0; |
|
1090
|
|
|
1091
|
/* Compute a unique key for the cache entry based on query parameters */ |
|
1092
|
blob_init(&cacheKey, 0, 0); |
|
1093
|
blob_appendf(&cacheKey, "/%s/%z", g.zPath, rid_to_uuid(rid)); |
|
1094
|
blob_appendf(&cacheKey, "/%q", zName); |
|
1095
|
if( zInclude ) blob_appendf(&cacheKey, ",in=%Q", zInclude); |
|
1096
|
if( zExclude ) blob_appendf(&cacheKey, ",ex=%Q", zExclude); |
|
1097
|
zKey = blob_str(&cacheKey); |
|
1098
|
etag_check(ETAG_HASH, zKey); |
|
1099
|
|
|
1100
|
style_set_current_feature("zip"); |
|
1101
|
if( P("debug")!=0 ){ |
|
1102
|
style_header("%s Archive Generator Debug Screen", zType); |
|
1103
|
@ zName = "%h(zName)"<br> |
|
1104
|
@ rid = %d(rid)<br> |
|
1105
|
if( zInclude ){ |
|
1106
|
@ zInclude = "%h(zInclude)"<br> |
|
1107
|
} |
|
1108
|
if( zExclude ){ |
|
1109
|
@ zExclude = "%h(zExclude)"<br> |
|
1110
|
} |
|
1111
|
@ zKey = "%h(zKey)" |
|
1112
|
style_finish_page(); |
|
1113
|
return; |
|
1114
|
} |
|
1115
|
if( referred_from_login() ){ |
|
1116
|
style_header("%s Archive Download", zType); |
|
1117
|
@ <form action='%R/%s(g.zPath)/%h(zName).%s(g.zPath)'> |
|
1118
|
cgi_query_parameters_to_hidden(); |
|
1119
|
@ <p>%s(zType) Archive named <b>%h(zName).%s(g.zPath)</b> |
|
1120
|
@ holding the content of check-in <b>%h(zRid)</b>: |
|
1121
|
@ <input type="submit" value="Download"> |
|
1122
|
@ </form> |
|
1123
|
style_finish_page(); |
|
1124
|
return; |
|
1125
|
} |
|
1126
|
cgi_check_for_malice(); |
|
1127
|
blob_zero(&zip); |
|
1128
|
if( cache_read(&zip, zKey)==0 ){ |
|
1129
|
zip_of_checkin(eType, rid, &zip, zName, pInclude, pExclude, 0); |
|
1130
|
cache_write(&zip, zKey); |
|
1131
|
} |
|
1132
|
glob_free(pInclude); |
|
1133
|
glob_free(pExclude); |
|
1134
|
fossil_free(zName); |
|
1135
|
fossil_free(zRid); |
|
1136
|
g.zOpenRevision = 0; |
|
1137
|
blob_reset(&cacheKey); |
|
1138
|
cgi_set_content(&zip); |
|
1139
|
if( eType==ARCHIVE_ZIP ){ |
|
1140
|
cgi_set_content_type("application/zip"); |
|
1141
|
}else{ |
|
1142
|
cgi_set_content_type("application/sqlar"); |
|
1143
|
} |
|
1144
|
} |
|
1145
|
|