Fossil SCM

fossil-scm / src / zip.c
Blame History Raw 1145 lines
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

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button