1# 2017 December 9 2# 3# The author disclaims copyright to this source code. In place of 4# a legal notice, here is a blessing: 5# 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. 9# 10#*********************************************************************** 11# 12 13set testdir [file dirname $argv0] 14source $testdir/tester.tcl 15set testprefix zipfile 16 17ifcapable !vtab { 18 finish_test; return 19} 20if {[catch {load_static_extension db zipfile} error]} { 21 puts "Skipping zipfile tests, hit load error: $error" 22 finish_test; return 23} 24 25proc readfile {f} { 26 set fd [open $f] 27 fconfigure $fd -translation binary -encoding binary 28 set data [read $fd] 29 close $fd 30 set data 31} 32 33if {$::tcl_platform(platform)=="unix" && [catch {exec unzip}]==0} { 34 set ::UNZIP 1 35 load_static_extension db fileio 36 proc do_unzip {file} { 37 forcedelete test_unzip 38 file mkdir test_unzip 39 exec unzip -d test_unzip $file 40 41 set res [db eval { 42 SELECT replace(name,'test_unzip/',''),mode,mtime,data 43 FROM fsdir('test_unzip') 44 WHERE name!='test_unzip' 45 ORDER BY name 46 }] 47 set res 48 } 49} 50 51 52# The argument is a blob (not a hex string) containing a zip archive. 53# This proc removes the extended timestamp fields from the archive 54# and returns the result. 55# 56proc remove_timestamps {blob} { 57 set hex [binary encode hex $blob] 58 set hex [string map {55540500 00000500} $hex] 59 binary decode hex $hex 60} 61 62 63# Argument $file is the name of a zip archive on disk. This function 64# executes test cases to check that the results of each of the following 65# are the same: 66# 67# SELECT * FROM zipfile($file) 68# SELECT * FROM zipfile( readfile($file) ) 69# SELECT * FROM zipfile( 70# (SELECT zipfile(name,mode,mtime,data,method) FROM zipfile($file)) 71# ) 72# 73proc do_zipfile_blob_test {tn file} { 74 75 db func r readfile 76 set q1 {SELECT name,mode,mtime,method,quote(data) FROM zipfile($file)} 77 set q2 {SELECT name,mode,mtime,method,quote(data) FROM zipfile( r($file) )} 78 set q3 {SELECT name,mode,mtime,method,quote(data) FROM zipfile( 79 ( SELECT zipfile(name,mode,mtime,data,method) FROM zipfile($file) ) 80 )} 81 82 83 set r1 [db eval $q1] 84 set r2 [db eval $q2] 85 set r3 [db eval $q3] 86 #puts $r1 87 #puts $r2 88 #puts $r3 89 90 uplevel [list do_test $tn.1 [list set {} $r2] $r1] 91 uplevel [list do_test $tn.2 [list set {} $r3] $r1] 92} 93 94# Argument $file is a zip file on disk. This command runs tests to: 95# 96# 1. Unpack the archive with unix command [unzip] and compare the 97# results to reading the same archive using the zipfile() table 98# valued function. 99# 100# 2. Creates a new archive with the same contents using the zipfile() 101# aggregate function as follows: 102# 103# SELECT writefile('test_unzip.zip', 104# ( SELECT zipfile(name,mode,mtime,data,method) FROM zipfile($file) ) 105# ); 106# 107# Then tests that unpacking the new archive using [unzip] produces 108# the same results as in (1). 109# 110proc do_unzip_test {tn file} { 111 if {[info vars ::UNZIP]==""} { return } 112 db func sss strip_slash 113 114 db eval { 115 SELECT writefile('test_unzip.zip', 116 ( SELECT zipfile(name,mode,mtime,data,method) FROM zipfile($file) ) 117 ); 118 } 119 120 set r1 [db eval { 121 SELECT sss(name),mode,mtime,data FROM zipfile($file) ORDER BY name 122 }] 123 set r2 [do_unzip $file] 124 set r3 [do_unzip test_unzip.zip] 125 126 uplevel [list do_test $tn.1 [list set {} $r2] $r1] 127 uplevel [list do_test $tn.2 [list set {} $r3] $r1] 128} 129proc strip_slash {in} { regsub {/$} $in {} } 130 131proc do_zip_tests {tn file} { 132 uplevel do_zipfile_blob_test $tn.1 $file 133 uplevel do_unzip_test $tn.2 $file 134} 135 136forcedelete test.zip 137do_execsql_test 1.0 { 138 CREATE VIRTUAL TABLE temp.zz USING zipfile('test.zip'); 139 PRAGMA table_info(zz); 140} { 141 0 name {} 1 {} 1 142 1 mode {} 0 {} 0 143 2 mtime {} 0 {} 0 144 3 sz {} 0 {} 0 145 4 rawdata {} 0 {} 0 146 5 data {} 0 {} 0 147 6 method {} 0 {} 0 148} 149 150do_catchsql_test 1.1.0.1 { 151 INSERT INTO zz(name, mode, mtime, sz, rawdata, method) 152 VALUES('f.txt', '-rw-r--r--', 1000000000, 5, 'abcde', 0); 153} {1 {rawdata must be NULL}} 154do_catchsql_test 1.1.0.2 { 155 INSERT INTO zz(name, mtime, sz, data, method) 156 VALUES('g.txt', 1000000002, 5, '12345', 0); 157} {1 {sz must be NULL}} 158do_catchsql_test 1.1.0.3 { 159 INSERT INTO zz(name, mtime, rawdata, method) 160 VALUES('g.txt', 1000000002, '12345', 0); 161} {1 {rawdata must be NULL}} 162do_catchsql_test 1.1.0.4 { 163 INSERT INTO zz(name, data, method) 164 VALUES('g.txt', '12345', 7); 165} {1 {unknown compression method: 7}} 166 167do_execsql_test 1.1.1 { 168 INSERT INTO zz(name, mode, mtime, data, method) 169 VALUES('f.txt', '-rw-r--r--', 1000000000, 'abcde', 0); 170} 171do_execsql_test 1.1.2 { 172 INSERT INTO zz(name, mode, mtime, data, method) 173 VALUES('g.txt', NULL, 1000000002, '12345', 0); 174} 175 176do_execsql_test 1.2 { 177 SELECT name, mtime, data FROM zipfile('test.zip') 178} { 179 f.txt 1000000000 abcde 180 g.txt 1000000002 12345 181} 182do_zip_tests 1.2a test.zip 183 184do_execsql_test 1.3 { 185 INSERT INTO zz(name, mode, mtime, data) VALUES('h.txt', 186 '-rw-r--r--', 1000000004, 'aaaaaaaaaabbbbbbbbbb' 187 ); 188} 189do_zip_tests 1.3a test.zip 190 191do_execsql_test 1.4 { 192 SELECT name, mtime, data, method FROM zipfile('test.zip'); 193} { 194 f.txt 1000000000 abcde 0 195 g.txt 1000000002 12345 0 196 h.txt 1000000004 aaaaaaaaaabbbbbbbbbb 8 197} 198 199ifcapable json1 { 200 do_execsql_test 1.4.1 { 201 SELECT name, json_extract( zipfile_cds(z) , '$.crc32')!=0 202 FROM zipfile('test.zip'); 203 } { 204 f.txt 1 205 g.txt 1 206 h.txt 1 207 } 208} 209do_catchsql_test 1.4.2 { 210 SELECT zipfile_cds(mode) FROM zipfile('test.zip'); 211} {0 {{} {} {}}} 212 213do_execsql_test 1.5.1 { 214 BEGIN; 215 INSERT INTO zz(name, mode, mtime, data, method) 216 VALUES('i.txt', '-rw-r--r--', 1000000006, 'zxcvb', 0); 217 SELECT name FROM zz; 218 COMMIT; 219} {f.txt g.txt h.txt i.txt} 220do_execsql_test 1.5.2 { 221 SELECT name FROM zz; 222} {f.txt g.txt h.txt i.txt} 223do_execsql_test 1.5.3 { 224 SELECT data FROM zz WHERE name='i.txt'; 225} {zxcvb} 226 227do_execsql_test 1.6.0 { 228 DELETE FROM zz WHERE name='g.txt'; 229 SELECT name FROM zz; 230} {f.txt h.txt i.txt} 231 232do_execsql_test 1.6.1 { 233 SELECT name, mode, mtime, data, method FROM zipfile('test.zip'); 234} { 235 f.txt 33188 1000000000 abcde 0 236 h.txt 33188 1000000004 aaaaaaaaaabbbbbbbbbb 8 237 i.txt 33188 1000000006 zxcvb 0 238} 239do_zip_tests 1.6.1a test.zip 240 241do_execsql_test 1.6.2 { 242 UPDATE zz SET mtime=4 WHERE name='i.txt'; 243 SELECT name, mode, mtime, data, method FROM zipfile('test.zip'); 244} { 245 f.txt 33188 1000000000 abcde 0 246 h.txt 33188 1000000004 aaaaaaaaaabbbbbbbbbb 8 247 i.txt 33188 4 zxcvb 0 248} 249 250do_execsql_test 1.6.3 { 251 UPDATE zz SET mode='-rw-r--r-x' WHERE name='h.txt'; 252 SELECT name, mode, mtime, data, method FROM zipfile('test.zip'); 253} { 254 f.txt 33188 1000000000 abcde 0 255 h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8 256 i.txt 33188 4 zxcvb 0 257} 258do_zip_tests 1.6.3a test.zip 259 260do_execsql_test 1.6.4 { 261 UPDATE zz SET name = 'blue.txt' WHERE name='f.txt'; 262 SELECT name, mode, mtime, data, method FROM zipfile('test.zip'); 263} { 264 blue.txt 33188 1000000000 abcde 0 265 h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8 266 i.txt 33188 4 zxcvb 0 267} 268do_zip_tests 1.6.4a test.zip 269 270do_execsql_test 1.6.5 { 271 UPDATE zz SET data = 'edcba' WHERE name='blue.txt'; 272 SELECT name, mode, mtime, data, method FROM zipfile('test.zip'); 273} { 274 blue.txt 33188 1000000000 edcba 0 275 h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8 276 i.txt 33188 4 zxcvb 0 277} 278 279do_execsql_test 1.6.6 { 280 UPDATE zz SET mode=NULL, data = NULL WHERE name='blue.txt'; 281 SELECT name, mode, mtime, data, method FROM zipfile('test.zip'); 282} { 283 blue.txt/ 16877 1000000000 {} 0 284 h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8 285 i.txt 33188 4 zxcvb 0 286} 287 288do_catchsql_test 1.6.7 { 289 UPDATE zz SET data=NULL WHERE name='i.txt' 290} {1 {zipfile: mode does not match data}} 291do_execsql_test 1.6.8 { 292 SELECT name, mode, mtime, data, method FROM zipfile('test.zip'); 293} { 294 blue.txt/ 16877 1000000000 {} 0 295 h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8 296 i.txt 33188 4 zxcvb 0 297} 298 299do_execsql_test 1.6.8 { 300 UPDATE zz SET data = '' WHERE name='i.txt'; 301 SELECT name,mode,mtime,data,method from zipfile('test.zip'); 302} { 303 blue.txt/ 16877 1000000000 {} 0 304 h.txt 33189 1000000004 aaaaaaaaaabbbbbbbbbb 8 305 i.txt 33188 4 {} 0 306} 307 308do_execsql_test 1.6.9 { 309 SELECT a.name, a.data 310 FROM zz AS a, zz AS b 311 WHERE a.name=+b.name AND +a.mode=b.mode 312} { 313 blue.txt/ {} 314 h.txt aaaaaaaaaabbbbbbbbbb 315 i.txt {} 316} 317 318do_execsql_test 1.6.10 { 319 SELECT name, data FROM zz WHERE name LIKE '%txt' 320} { 321 h.txt aaaaaaaaaabbbbbbbbbb 322 i.txt {} 323} 324 325do_execsql_test 1.7 { 326 DELETE FROM zz; 327 SELECT * FROM zz; 328} {} 329 330#------------------------------------------------------------------------- 331db close 332forcedelete test.zip 333reset_db 334load_static_extension db fileio 335load_static_extension db zipfile 336do_execsql_test 2.1 { 337 CREATE VIRTUAL TABLE zzz USING zipfile('test.zip'); 338 INSERT INTO zzz(name, mode) VALUES('dirname', 'drwxr-xr-x'); 339 SELECT name, mode, data FROM zzz; 340} {dirname/ 16877 {}} 341do_execsql_test 2.2 { 342 INSERT INTO zzz(name, data) VALUES('dirname2', NULL); 343 INSERT INTO zzz(name, data) VALUES('dirname2/file1.txt', 'abcdefghijklmnop'); 344 SELECT name, mode, data FROM zzz; 345} { 346 dirname/ 16877 {} 347 dirname2/ 16877 {} 348 dirname2/file1.txt 33188 abcdefghijklmnop 349} 350 351do_catchsql_test 2.3 { 352 UPDATE zzz SET name = 'dirname3' WHERE name = 'dirname/'; 353} {0 {}} 354do_execsql_test 2.4 { 355 SELECT name, mode, data FROM zzz; 356} { 357 dirname3/ 16877 {} 358 dirname2/ 16877 {} 359 dirname2/file1.txt 33188 abcdefghijklmnop 360} 361do_zip_tests 2.4a test.zip 362 363# If on unix, check that the [unzip] utility can unpack our archive. 364# 365if {$::tcl_platform(platform)=="unix"} { 366 do_test 2.5.1 { 367 forcedelete dirname 368 forcedelete dirname2 369 set rc [catch { exec unzip test.zip > /dev/null } msg] 370 list $rc $msg 371 } {0 {}} 372 do_test 2.5.2 { file isdir dirname3 } 1 373 do_test 2.5.3 { file isdir dirname2 } 1 374 do_test 2.5.4 { file isdir dirname2/file1.txt } 0 375 do_test 2.5.5 { 376 set fd [open dirname2/file1.txt] 377 set data [read $fd] 378 close $fd 379 set data 380 } {abcdefghijklmnop} 381} 382 383#------------------------------------------------------------------------- 384reset_db 385forcedelete test.zip 386load_static_extension db zipfile 387 388do_execsql_test 3.0 { 389 CREATE VIRTUAL TABLE temp.x1 USING zipfile('test.zip'); 390 INSERT INTO x1(name, data) VALUES('dir1/', NULL); 391 INSERT INTO x1(name, data) VALUES('file1', '1234'); 392 INSERT INTO x1(name, data) VALUES('dir1/file2', '5678'); 393} 394foreach {tn fname} { 395 1 dir1 396 2 file1 397 3 dir1/file2 398} { 399 do_catchsql_test 3.1.$tn.0 { 400 INSERT INTO x1(name, data) VALUES($fname, NULL); 401 } [list 1 "duplicate name: \"$fname/\""] 402 do_catchsql_test 3.1.$tn.1 { 403 INSERT INTO x1(name, data) VALUES($fname || '/', NULL); 404 } [list 1 "duplicate name: \"$fname/\""] 405 do_catchsql_test 3.1.$tn.2 { 406 INSERT INTO x1(name, data) VALUES($fname, 'abcd'); 407 } [list 1 "duplicate name: \"$fname\""] 408} 409 410do_catchsql_test 3.2 { 411 SELECT rowid FROM x1 412} {1 {no such column: rowid}} 413 414#------------------------------------------------------------------------- 415# Test some error conditions. 416# 417do_catchsql_test 4.1 { 418 CREATE VIRTUAL TABLE yyy USING zipfile(); 419} {1 {zipfile constructor requires one argument}} 420do_catchsql_test 4.2 { 421 CREATE VIRTUAL TABLE yyy USING zipfile('test.zip', 'test.zip'); 422} {1 {zipfile constructor requires one argument}} 423 424do_catchsql_test 4.3 { 425 SELECT * FROM zipfile() 426} {1 {zipfile() function requires an argument}} 427 428do_catchsql_test 4.4 { 429 SELECT * FROM zipfile('/path/that/does/not/exist') 430} {1 {cannot open file: /path/that/does/not/exist}} 431 432foreach {tn mode} { 433 1 abcd 434 2 brwxrwxrwx 435 3 lrwxrrxrwx 436} { 437 do_catchsql_test 4.5.$tn { 438 WITH m(m) AS ( SELECT $mode) 439 SELECT zipfile('a.txt', m, 1000, 'xyz') FROM m 440 } [list 1 "zipfile: parse error in mode: $mode"] 441} 442 443do_catchsql_test 4.6 { 444 WITH c(name,data) AS ( SELECT 'a.txt', 'abc') 445 SELECT zipfile(name) FROM c 446} {1 {wrong number of arguments to function zipfile()}} 447 448do_catchsql_test 4.7 { 449 WITH c(name,data) AS ( 450 SELECT 'a.txt', 'abc' UNION ALL 451 SELECT NULL, 'def' 452 ) 453 SELECT zipfile(name,data) FROM c 454} {1 {first argument to zipfile() must be non-NULL}} 455 456do_catchsql_test 4.7 { 457 WITH c(name,data,method) AS ( 458 SELECT 'a.txt', 'abc', 0 459 UNION SELECT 'b.txt', 'def', 8 460 UNION SELECT 'c.txt', 'ghi', 16 461 ) 462 SELECT zipfile(name,NULL,NULL,data,method) FROM c 463} {1 {illegal method value: 16}} 464 465do_catchsql_test 4.8 { 466 WITH c(name,data) AS ( 467 SELECT 'a.txt', 'abc' 468 UNION SELECT 'b.txt', 'def' 469 UNION SELECT 'c.txt/', 'ghi' 470 ) 471 SELECT zipfile(name,NULL,NULL,data) FROM c 472} {1 {non-directory name must not end with /}} 473 474#-------------------------------------------------------------------------- 475 476db func rt remove_timestamps 477do_execsql_test 5.0 { 478 WITH c(name,mtime,data) AS ( 479 SELECT 'a.txt', 946684800, 'abc' 480 ) 481 SELECT name,mtime,data FROM zipfile( 482 ( SELECT rt( zipfile(name,NULL,mtime,data,NULL) ) FROM c ) 483 ) 484} { 485 a.txt 946684800 abc 486} 487 488if {[info vars ::UNZIP]!=""} { 489ifcapable datetime { 490 load_static_extension db fileio 491 forcedelete test1.zip test2.zip 492 do_test 6.0 { 493 execsql { 494 WITH c(name,mtime,data) AS ( 495 SELECT 'a.txt', 946684800, 'abc' UNION ALL 496 SELECT 'b.txt', 1000000000, 'abc' UNION ALL 497 SELECT 'c.txt', 1111111000, 'abc' 498 ) 499 SELECT writefile('test1.zip', rt( zipfile(name, NULL, mtime, data) ) ), 500 writefile('test2.zip', ( zipfile(name, NULL, mtime, data) ) ) 501 FROM c; 502 } 503 forcedelete test_unzip 504 file mkdir test_unzip 505 exec unzip -d test_unzip test1.zip 506 507 db eval { 508 SELECT name, strftime('%s', mtime, 'unixepoch', 'localtime') 509 FROM fsdir('test_unzip') WHERE name!='test_unzip' 510 ORDER BY name 511 } 512 } [list {*}{ 513 test_unzip/a.txt 946684800 514 test_unzip/b.txt 1000000000 515 test_unzip/c.txt 1111111000 516 }] 517 518 do_execsql_test 6.1 { 519 SELECT name, mtime, data FROM zipfile('test1.zip') 520 } { 521 a.txt 946684800 abc 522 b.txt 1000000000 abc 523 c.txt 1111111000 abc 524 } 525 526 do_test 6.2 { 527 forcedelete test_unzip 528 file mkdir test_unzip 529 exec unzip -d test_unzip test2.zip 530 531 db eval { 532 SELECT name, mtime 533 FROM fsdir('test_unzip') WHERE name!='test_unzip' 534 ORDER BY name 535 } 536 } [list {*}{ 537 test_unzip/a.txt 946684800 538 test_unzip/b.txt 1000000000 539 test_unzip/c.txt 1111111000 540 }] 541 542 do_execsql_test 6.3 { 543 SELECT name, mtime, sz, rawdata, data FROM zipfile('test2.zip') 544 } { 545 a.txt 946684800 3 abc abc 546 b.txt 1000000000 3 abc abc 547 c.txt 1111111000 3 abc abc 548 } 549} 550} 551 552#------------------------------------------------------------------------- 553# Force an IO error by truncating the zip archive to zero bytes in size 554# while it is being read. 555forcedelete test.zip 556do_test 7.0 { 557 execsql { 558 WITH c(name,data) AS ( 559 SELECT '1', randomblob(1000000) UNION ALL 560 SELECT '2', randomblob(1000000) UNION ALL 561 SELECT '3', randomblob(1000000) 562 ) 563 SELECT writefile('test.zip', zipfile(name, data) ) FROM c; 564 } 565 566 list [catch { 567 db eval { SELECT name, data FROM zipfile('test.zip') } { 568 if {$name==2} { close [open test.zip w+] } 569 } 570 } msg] $msg 571} {1 {error in fread()}} 572 573forcedelete test.zip 574do_execsql_test 8.0.1 { 575 CREATE VIRTUAL TABLE zz USING zipfile('test.zip'); 576 BEGIN; 577 INSERT INTO zz(name, data) VALUES('a.txt', '1'); 578 INSERT INTO zz(name, data) VALUES('b.txt', '2'); 579 INSERT INTO zz(name, data) VALUES('c.txt', '1'); 580 INSERT INTO zz(name, data) VALUES('d.txt', '2'); 581 SELECT name, data FROM zz; 582} { 583 a.txt 1 b.txt 2 c.txt 1 d.txt 2 584} 585do_test 8.0.2 { 586 db eval { SELECT name, data FROM zz } { 587 if { $data=="2" } { db eval { DELETE FROM zz WHERE name=$name } } 588 } 589 execsql { SELECT name, data FROM zz } 590} {a.txt 1 c.txt 1} 591do_test 8.0.3 { 592 db eval { SELECT name, data FROM zz } { 593 db eval { DELETE FROM zz WHERE name=$name } 594 } 595 execsql { SELECT name, data FROM zz } 596} {} 597execsql COMMIT 598 599do_execsql_test 8.1.1 { 600 CREATE VIRTUAL TABLE nogood USING zipfile('test_unzip'); 601} 602do_catchsql_test 8.1.2 { 603 INSERT INTO nogood(name, data) VALUES('abc', 'def'); 604} {1 {zipfile: failed to open file test_unzip for writing}} 605 606do_execsql_test 8.2.1 { 607 DROP TABLE nogood; 608 BEGIN; 609 CREATE VIRTUAL TABLE nogood USING zipfile('test_unzip'); 610} 611do_catchsql_test 8.2.2 { 612 INSERT INTO nogood(name, data) VALUES('abc', 'def'); 613} {1 {zipfile: failed to open file test_unzip for writing}} 614do_execsql_test 8.2.3 { 615 COMMIT; 616} 617 618forcedelete test.zip 619do_execsql_test 8.3.1 { 620 BEGIN; 621 CREATE VIRTUAL TABLE ok USING zipfile('test.zip'); 622 INSERT INTO ok(name, data) VALUES ('sqlite3', 'elf'); 623 COMMIT; 624} 625 626#------------------------------------------------------------------------- 627# Test that the zipfile aggregate correctly adds and removes "/" from 628# the ends of directory file names. 629do_execsql_test 9.0 { 630 WITH src(nm) AS ( 631 VALUES('dir1') UNION ALL 632 VALUES('dir2/') UNION ALL 633 VALUES('dir3//') UNION ALL 634 VALUES('dir4///') UNION ALL 635 VALUES('/') 636 ) 637 SELECT name FROM zipfile((SELECT zipfile(nm, NULL) FROM src)) 638} {dir1/ dir2/ dir3/ dir4/ /} 639finish_test 640