3 # The author disclaims copyright to this source code. In place of
4 # a legal notice, here is a blessing:
6 # May you do good and not evil.
7 # May you find forgiveness for yourself and forgive others.
8 # May you share freely, never taking more than you give.
10 #***********************************************************************
13 package require Tcl 8.6
15 set testdir [file dirname $argv0]
16 source $testdir/tester.tcl
17 set testprefix zipfile
22 if {[catch {load_static_extension db zipfile} error]} {
23 puts "Skipping zipfile tests, hit load error: $error"
26 if {[catch {load_static_extension db fileio} error]} {
27 puts "Skipping zipfile tests, hit load error: $error"
33 fconfigure $fd -translation binary -encoding binary
39 unset -nocomplain ::UNZIP
41 if {[catch {exec unzip} msg]==0 && \
42 [regexp -line {^UnZip \d+\.\d+ .*? Info-ZIP\.} $msg]} {
44 proc fix_stat_mode {name mode} {
45 if {$::tcl_platform(platform)=="windows"} {
47 # NOTE: Set or unset the write bits of the file permissions
48 # based on the read-only attribute because the Win32
49 # version of UnZip does this.
51 set writebits 0x12; # 0o22
53 if {[file attributes $name -readonly]} {
54 set result [expr {$result | $writebits}]
56 set result [expr {$result & ~$writebits}]
63 proc do_unzip {file} {
64 forcedelete test_unzip
66 exec $::UNZIP -d test_unzip $file
68 db func modefix fix_stat_mode
71 SELECT replace(name,'test_unzip/',''),modefix(name,mode),mtime,data
72 FROM fsdir('test_unzip')
73 WHERE name!='test_unzip'
81 # The argument is a blob (not a hex string) containing a zip archive.
82 # This proc removes the extended timestamp fields from the archive
83 # and returns the result.
85 proc remove_timestamps {blob} {
86 set hex [binary encode hex $blob]
87 set hex [string map {55540500 00000500} $hex]
88 binary decode hex $hex
92 # Argument $file is the name of a zip archive on disk. This function
93 # executes test cases to check that the results of each of the following
96 # SELECT * FROM zipfile($file)
97 # SELECT * FROM zipfile( readfile($file) )
98 # SELECT * FROM zipfile(
99 # (SELECT zipfile(name,mode,mtime,data,method) FROM zipfile($file))
102 proc do_zipfile_blob_test {tn file} {
105 set q1 {SELECT name,mode,mtime,method,quote(data) FROM zipfile($file)}
106 set q2 {SELECT name,mode,mtime,method,quote(data) FROM zipfile( r($file) )}
107 set q3 {SELECT name,mode,mtime,method,quote(data) FROM zipfile(
108 ( SELECT zipfile(name,mode,mtime,data,method) FROM zipfile($file) )
119 uplevel [list do_test $tn.1 [list set {} $r2] $r1]
120 uplevel [list do_test $tn.2 [list set {} $r3] $r1]
123 # Argument $file is a zip file on disk. This command runs tests to:
125 # 1. Unpack the archive with unix command [unzip] and compare the
126 # results to reading the same archive using the zipfile() table
129 # 2. Creates a new archive with the same contents using the zipfile()
130 # aggregate function as follows:
132 # SELECT writefile('test_unzip.zip',
133 # ( SELECT zipfile(name,mode,mtime,data,method) FROM zipfile($file) )
136 # Then tests that unpacking the new archive using [unzip] produces
137 # the same results as in (1).
139 proc do_unzip_test {tn file} {
140 db func sss strip_slash
143 SELECT writefile('test_unzip.zip',
144 ( SELECT zipfile(name,mode,mtime,data,method) FROM zipfile($file) )
149 SELECT sss(name),mode,mtime,data FROM zipfile($file) ORDER BY name
151 set r2 [do_unzip $file]
152 set r3 [do_unzip test_unzip.zip]
154 uplevel [list do_test $tn.1 [list set {} $r2] $r1]
155 uplevel [list do_test $tn.2 [list set {} $r3] $r1]
157 proc strip_slash {in} { regsub {/$} $in {} }
159 proc do_zip_tests {tn file} {
160 uplevel do_zipfile_blob_test $tn.1 $file
161 if {[info exists ::UNZIP]} {
162 uplevel do_unzip_test $tn.2 $file
167 do_execsql_test 1.0 {
168 CREATE VIRTUAL TABLE temp.zz USING zipfile('test.zip');
169 PRAGMA table_info(zz);
180 do_catchsql_test 1.1.0.1 {
181 INSERT INTO zz(name, mode, mtime, sz, rawdata, method)
182 VALUES('f.txt', '-rw-r--r--', 1000000000, 5, 'abcde', 0);
183 } {1 {rawdata must be NULL}}
184 do_catchsql_test 1.1.0.2 {
185 INSERT INTO zz(name, mtime, sz, data, method)
186 VALUES('g.txt', 1000000002, 5, '12345', 0);
187 } {1 {sz must be NULL}}
188 do_catchsql_test 1.1.0.3 {
189 INSERT INTO zz(name, mtime, rawdata, method)
190 VALUES('g.txt', 1000000002, '12345', 0);
191 } {1 {rawdata must be NULL}}
192 do_catchsql_test 1.1.0.4 {
193 INSERT INTO zz(name, data, method)
194 VALUES('g.txt', '12345', 7);
195 } {1 {unknown compression method: 7}}
197 do_execsql_test 1.1.1 {
198 INSERT INTO zz(name, mode, mtime, data, method)
199 VALUES('f.txt', '-rw-r--r--', 1000000000, 'abcde', 0);
201 do_execsql_test 1.1.2 {
202 INSERT INTO zz(name, mode, mtime, data, method)
203 VALUES('g.txt', NULL, 1000000002, '12345', 0);
206 do_execsql_test 1.2 {
207 SELECT name, mtime, data FROM zipfile('test.zip')
209 f.txt 1000000000 abcde
210 g.txt 1000000002 12345
212 do_zip_tests 1.2a test.zip
214 do_execsql_test 1.3 {
215 INSERT INTO zz(name, mode, mtime, data) VALUES('h.txt',
216 '-rw-r--r--', 1000000004, 'aaaaaaaaaabbbbbbbbbb'
219 do_zip_tests 1.3a test.zip
221 do_execsql_test 1.4 {
222 SELECT name, mtime, data, method FROM zipfile('test.zip');
224 f.txt 1000000000 abcde 0
225 g.txt 1000000002 12345 0
226 h.txt 1000000004 aaaaaaaaaabbbbbbbbbb 8
230 do_execsql_test 1.4.1 {
231 SELECT name, json_extract( zipfile_cds(z) , '$.crc32')!=0
232 FROM zipfile('test.zip');
239 do_catchsql_test 1.4.2 {
240 SELECT zipfile_cds(mode) FROM zipfile('test.zip');
243 do_execsql_test 1.5.1 {
245 INSERT INTO zz(name, mode, mtime, data, method)
246 VALUES('i.txt', '-rw-r--r--', 1000000006, 'zxcvb', 0);
249 } {f.txt g.txt h.txt i.txt}
250 do_execsql_test 1.5.2 {
252 } {f.txt g.txt h.txt i.txt}
253 do_execsql_test 1.5.3 {
254 SELECT data FROM zz WHERE name='i.txt';
257 do_execsql_test 1.6.0 {
258 DELETE FROM zz WHERE name='g.txt';
260 } {f.txt h.txt i.txt}
262 do_execsql_test 1.6.1 {
263 SELECT name, mode, mtime, data, method FROM zipfile('test.zip');
265 f.txt 33188 1000000000 abcde 0
266 h.txt 33188 1000000004 aaaaaaaaaabbbbbbbbbb 8
267 i.txt 33188 1000000006 zxcvb 0
269 do_zip_tests 1.6.1a test.zip
271 do_execsql_test 1.6.2 {
272 UPDATE zz SET mtime=4 WHERE name='i.txt';
273 SELECT name, mode, mtime, data, method FROM zipfile('test.zip');
275 f.txt 33188 1000000000 abcde 0
276 h.txt 33188 1000000004 aaaaaaaaaabbbbbbbbbb 8
277 i.txt 33188 4 zxcvb 0
280 if {$::tcl_platform(platform)=="unix"} {
284 set modes -rw-r--r--; # no execute bits on Win32
288 do_execsql_test 1.6.3 {
289 UPDATE zz SET mode=$modes WHERE name='h.txt';
290 SELECT name, mode, mtime, data, method FROM zipfile('test.zip');
291 } [string map [list %perms% $perms] {
292 f.txt 33188 1000000000 abcde 0
293 h.txt %perms% 1000000004 aaaaaaaaaabbbbbbbbbb 8
294 i.txt 33188 4 zxcvb 0
296 do_zip_tests 1.6.3a test.zip
298 do_execsql_test 1.6.4 {
299 UPDATE zz SET name = 'blue.txt' WHERE name='f.txt';
300 SELECT name, mode, mtime, data, method FROM zipfile('test.zip');
301 } [string map [list %perms% $perms] {
302 blue.txt 33188 1000000000 abcde 0
303 h.txt %perms% 1000000004 aaaaaaaaaabbbbbbbbbb 8
304 i.txt 33188 4 zxcvb 0
306 do_zip_tests 1.6.4a test.zip
308 do_execsql_test 1.6.5 {
309 UPDATE zz SET data = 'edcba' WHERE name='blue.txt';
310 SELECT name, mode, mtime, data, method FROM zipfile('test.zip');
311 } [string map [list %perms% $perms] {
312 blue.txt 33188 1000000000 edcba 0
313 h.txt %perms% 1000000004 aaaaaaaaaabbbbbbbbbb 8
314 i.txt 33188 4 zxcvb 0
317 do_execsql_test 1.6.6 {
318 UPDATE zz SET mode=NULL, data = NULL WHERE name='blue.txt';
319 SELECT name, mode, mtime, data, method FROM zipfile('test.zip');
320 } [string map [list %perms% $perms] {
321 blue.txt/ 16877 1000000000 {} 0
322 h.txt %perms% 1000000004 aaaaaaaaaabbbbbbbbbb 8
323 i.txt 33188 4 zxcvb 0
326 do_catchsql_test 1.6.7 {
327 UPDATE zz SET data=NULL WHERE name='i.txt'
328 } {1 {zipfile: mode does not match data}}
329 do_execsql_test 1.6.8 {
330 SELECT name, mode, mtime, data, method FROM zipfile('test.zip');
331 } [string map [list %perms% $perms] {
332 blue.txt/ 16877 1000000000 {} 0
333 h.txt %perms% 1000000004 aaaaaaaaaabbbbbbbbbb 8
334 i.txt 33188 4 zxcvb 0
337 do_execsql_test 1.6.9 {
338 UPDATE zz SET data = '' WHERE name='i.txt';
339 SELECT name,mode,mtime,data,method from zipfile('test.zip');
340 } [string map [list %perms% $perms] {
341 blue.txt/ 16877 1000000000 {} 0
342 h.txt %perms% 1000000004 aaaaaaaaaabbbbbbbbbb 8
346 do_execsql_test 1.6.10 {
347 SELECT a.name, a.data
348 FROM zz AS a, zz AS b
349 WHERE a.name=+b.name AND +a.mode=b.mode
352 h.txt aaaaaaaaaabbbbbbbbbb
356 do_execsql_test 1.6.11 {
357 SELECT name, data FROM zz WHERE name LIKE '%txt'
359 h.txt aaaaaaaaaabbbbbbbbbb
363 do_execsql_test 1.7 {
368 #-------------------------------------------------------------------------
372 load_static_extension db fileio
373 load_static_extension db zipfile
374 do_execsql_test 2.1 {
375 CREATE VIRTUAL TABLE zzz USING zipfile('test.zip');
376 INSERT INTO zzz(name, mode) VALUES('dirname', 'drwxr-xr-x');
377 SELECT name, mode, data FROM zzz;
378 } {dirname/ 16877 {}}
379 do_execsql_test 2.2 {
380 INSERT INTO zzz(name, data) VALUES('dirname2', NULL);
381 INSERT INTO zzz(name, data) VALUES('dirname2/file1.txt', 'abcdefghijklmnop');
382 SELECT name, mode, data FROM zzz;
386 dirname2/file1.txt 33188 abcdefghijklmnop
389 do_catchsql_test 2.3 {
390 UPDATE zzz SET name = 'dirname3' WHERE name = 'dirname/';
392 do_execsql_test 2.4 {
393 SELECT name, mode, data FROM zzz;
397 dirname2/file1.txt 33188 abcdefghijklmnop
399 do_zip_tests 2.4a test.zip
401 # Check that the [unzip] utility can unpack our archive.
403 if {[info exists ::UNZIP]} {
407 if {$::tcl_platform(platform)=="unix"} {
412 set rc [catch { exec $::UNZIP test.zip > $null } msg]
415 do_test 2.5.2 { file isdir dirname3 } 1
416 do_test 2.5.3 { file isdir dirname2 } 1
417 do_test 2.5.4 { file isdir dirname2/file1.txt } 0
419 set fd [open dirname2/file1.txt]
426 #-------------------------------------------------------------------------
429 load_static_extension db zipfile
430 load_static_extension db fileio
432 do_execsql_test 3.0 {
433 CREATE VIRTUAL TABLE temp.x1 USING zipfile('test.zip');
434 INSERT INTO x1(name, data) VALUES('dir1/', NULL);
435 INSERT INTO x1(name, data) VALUES('file1', '1234');
436 INSERT INTO x1(name, data) VALUES('dir1/file2', '5678');
443 do_catchsql_test 3.1.$tn.0 {
444 INSERT INTO x1(name, data) VALUES($fname, NULL);
445 } [list 1 "duplicate name: \"$fname/\""]
446 do_catchsql_test 3.1.$tn.1 {
447 INSERT INTO x1(name, data) VALUES($fname || '/', NULL);
448 } [list 1 "duplicate name: \"$fname/\""]
449 do_catchsql_test 3.1.$tn.2 {
450 INSERT INTO x1(name, data) VALUES($fname, 'abcd');
451 } [list 1 "duplicate name: \"$fname\""]
454 do_catchsql_test 3.2 {
456 } {1 {no such column: rowid}}
458 #-------------------------------------------------------------------------
459 # Test some error conditions.
461 do_catchsql_test 4.1 {
462 CREATE VIRTUAL TABLE yyy USING zipfile();
463 } {1 {zipfile constructor requires one argument}}
464 do_catchsql_test 4.2 {
465 CREATE VIRTUAL TABLE yyy USING zipfile('test.zip', 'test.zip');
466 } {1 {zipfile constructor requires one argument}}
468 do_catchsql_test 4.3 {
469 SELECT * FROM zipfile()
470 } {1 {zipfile() function requires an argument}}
472 do_catchsql_test 4.4 {
473 SELECT * FROM zipfile('/path/that/does/not/exist')
474 } {1 {cannot open file: /path/that/does/not/exist}}
481 do_catchsql_test 4.5.$tn {
482 WITH m(m) AS ( SELECT $mode)
483 SELECT zipfile('a.txt', m, 1000, 'xyz') FROM m
484 } [list 1 "zipfile: parse error in mode: $mode"]
487 do_catchsql_test 4.6 {
488 WITH c(name,data) AS ( SELECT 'a.txt', 'abc')
489 SELECT zipfile(name) FROM c
490 } {1 {wrong number of arguments to function zipfile()}}
492 do_catchsql_test 4.7 {
493 WITH c(name,data) AS (
494 SELECT 'a.txt', 'abc' UNION ALL
497 SELECT zipfile(name,data) FROM c
498 } {1 {first argument to zipfile() must be non-NULL}}
500 do_catchsql_test 4.8 {
501 WITH c(name,data,method) AS (
502 SELECT 'a.txt', 'abc', 0
503 UNION SELECT 'b.txt', 'def', 8
504 UNION SELECT 'c.txt', 'ghi', 16
506 SELECT zipfile(name,NULL,NULL,data,method) FROM c
507 } {1 {illegal method value: 16}}
509 do_catchsql_test 4.9 {
510 WITH c(name,data) AS (
511 SELECT 'a.txt', 'abc'
512 UNION SELECT 'b.txt', 'def'
513 UNION SELECT 'c.txt/', 'ghi'
515 SELECT zipfile(name,NULL,NULL,data) FROM c
516 } {1 {non-directory name must not end with /}}
518 #--------------------------------------------------------------------------
520 db func rt remove_timestamps
521 do_execsql_test 5.0 {
522 WITH c(name,mtime,data) AS (
523 SELECT 'a.txt', 946684800, 'abc'
525 SELECT name,mtime,data FROM zipfile(
526 ( SELECT rt( zipfile(name,NULL,mtime,data,NULL) ) FROM c )
532 if {[info exists ::UNZIP]} {
534 forcedelete test1.zip test2.zip
537 WITH c(name,mtime,data) AS (
538 SELECT 'a.txt', 946684800, 'abc' UNION ALL
539 SELECT 'b.txt', 1000000000, 'abc' UNION ALL
540 SELECT 'c.txt', 1111111000, 'abc'
542 SELECT writefile('test1.zip', rt( zipfile(name, NULL, mtime, data) ) ),
543 writefile('test2.zip', ( zipfile(name, NULL, mtime, data) ) )
546 forcedelete test_unzip
547 file mkdir test_unzip
548 exec $::UNZIP -d test_unzip test1.zip
551 SELECT name, strftime('%s', mtime, 'unixepoch', 'localtime')
552 FROM fsdir('test_unzip') WHERE name!='test_unzip'
556 test_unzip/a.txt 946684800
557 test_unzip/b.txt 1000000000
558 test_unzip/c.txt 1111111000
561 # fsdir() issue reported on the mailing list on 2018-03-14 by Jack Thaw.
564 SELECT sum(name LIKE '%/a.txt')
565 FROM (VALUES(1),(2),(3)) CROSS JOIN fsdir('test_unzip')
569 do_execsql_test 6.1 {
570 SELECT name, mtime, data FROM zipfile('test1.zip')
578 forcedelete test_unzip
579 file mkdir test_unzip
580 exec $::UNZIP -d test_unzip test2.zip
584 FROM fsdir('test_unzip') WHERE name!='test_unzip'
588 test_unzip/a.txt 946684800
589 test_unzip/b.txt 1000000000
590 test_unzip/c.txt 1111111000
593 do_execsql_test 6.3 {
594 SELECT name, mtime, sz, rawdata, data FROM zipfile('test2.zip')
596 a.txt 946684800 3 abc abc
597 b.txt 1000000000 3 abc abc
598 c.txt 1111111000 3 abc abc
603 #-------------------------------------------------------------------------
604 # Force an IO error by truncating the zip archive to zero bytes in size
605 # while it is being read.
609 WITH c(name,data) AS (
610 SELECT '1', randomblob(1000000) UNION ALL
611 SELECT '2', randomblob(1000000) UNION ALL
612 SELECT '3', randomblob(1000000)
614 SELECT writefile('test.zip', zipfile(name, data) ) FROM c;
618 db eval { SELECT name, data FROM zipfile('test.zip') } {
619 if {$name==2} { close [open test.zip w+] }
622 } {1 {error in fread()}}
625 do_execsql_test 8.0.1 {
626 CREATE VIRTUAL TABLE zz USING zipfile('test.zip');
628 INSERT INTO zz(name, data) VALUES('a.txt', '1');
629 INSERT INTO zz(name, data) VALUES('b.txt', '2');
630 INSERT INTO zz(name, data) VALUES('c.txt', '1');
631 INSERT INTO zz(name, data) VALUES('d.txt', '2');
632 SELECT name, data FROM zz;
634 a.txt 1 b.txt 2 c.txt 1 d.txt 2
637 db eval { SELECT name, data FROM zz } {
638 if { $data=="2" } { db eval { DELETE FROM zz WHERE name=$name } }
640 execsql { SELECT name, data FROM zz }
643 db eval { SELECT name, data FROM zz } {
644 db eval { DELETE FROM zz WHERE name=$name }
646 execsql { SELECT name, data FROM zz }
650 catch { forcedelete test_unzip }
651 catch { file mkdir test_unzip }
652 do_execsql_test 8.1.1 {
653 CREATE VIRTUAL TABLE nogood USING zipfile('test_unzip');
655 do_catchsql_test 8.1.2 {
656 INSERT INTO nogood(name, data) VALUES('abc', 'def');
657 } {1 {zipfile: failed to open file test_unzip for writing}}
659 do_execsql_test 8.2.1 {
662 CREATE VIRTUAL TABLE nogood USING zipfile('test_unzip');
664 do_catchsql_test 8.2.2 {
665 INSERT INTO nogood(name, data) VALUES('abc', 'def');
666 } {1 {zipfile: failed to open file test_unzip for writing}}
667 do_execsql_test 8.2.3 {
672 do_execsql_test 8.3.1 {
674 CREATE VIRTUAL TABLE ok USING zipfile('test.zip');
675 INSERT INTO ok(name, data) VALUES ('sqlite3', 'elf');
679 #-------------------------------------------------------------------------
680 # Test that the zipfile aggregate correctly adds and removes "/" from
681 # the ends of directory file names.
682 do_execsql_test 9.0 {
684 VALUES('dir1') UNION ALL
685 VALUES('dir2/') UNION ALL
686 VALUES('dir3//') UNION ALL
687 VALUES('dir4///') UNION ALL
690 SELECT name FROM zipfile((SELECT zipfile(nm, NULL) FROM src))
691 } {dir1/ dir2/ dir3/ dir4/ /}
693 #-------------------------------------------------------------------------
694 # INSERT OR REPLACE and INSERT OR IGNORE
697 forcedelete test.zip test.db
699 load_static_extension db zipfile
700 load_static_extension db fileio
702 do_execsql_test 10.0 {
703 CREATE VIRTUAL TABLE z USING zipfile('test.zip');
705 do_catchsql_test 10.1 {
706 INSERT INTO z(name,data) VALUES('a0','one'),('a0','two');
707 } {1 {duplicate name: "a0"}}
708 do_execsql_test 10.2 {
709 SELECT name, data FROM z;
711 do_execsql_test 10.3 {
712 REPLACE INTO z(name,data) VALUES('a0','three'),('a0','four');
714 do_execsql_test 10.4 {
715 SELECT name, data FROM z;
717 do_execsql_test 10.5 {
718 INSERT OR IGNORE INTO z(name,data) VALUES('a0','five'),('a0','six');
720 do_execsql_test 10.6 {
721 SELECT name, data FROM z;
724 do_execsql_test 11.1 {
727 do_execsql_test 11.2 {
728 SELECT name, data FROM z;
730 do_execsql_test 11.3 {
731 INSERT INTO z (name,data) VALUES ('b0','one');
732 SELECT name, data FROM z;
734 do_execsql_test 11.4 {
735 UPDATE z SET name = 'b1' WHERE name = 'b0';
736 SELECT name, data FROM z;
738 do_execsql_test 11.5 {
739 INSERT INTO z (name,data) VALUES ('b0','one');
740 SELECT name, data FROM z ORDER BY name;
742 do_catchsql_test 11.6 {
743 UPDATE z SET name = 'b1' WHERE name = 'b0';
744 } {1 {duplicate name: "b1"}}
745 do_execsql_test 11.7 {
746 UPDATE z SET data = 'two' WHERE name = 'b0';
747 SELECT name, data FROM z ORDER BY name;
749 do_catchsql_test 11.8 {
750 UPDATE z SET name = 'b1';
751 } {1 {duplicate name: "b1"}}
752 do_catchsql_test 11.9 {
753 UPDATE z SET name = 'b2';
754 } {1 {duplicate name: "b2"}}
755 do_execsql_test 11.10 {
756 UPDATE z SET name = name;
757 SELECT name, data FROM z ORDER BY name;
759 do_execsql_test 11.11 {
760 UPDATE z SET name = name || 'suffix';
761 SELECT name, data FROM z ORDER BY name;
762 } {b0suffix two b2suffix one}