Zip en download folder
Voor een website wil ik graag de mogelijkheid creëren om een map (met afbeeldingen) te downloaden als zip. Nu heb ik hiervoor al wat gevonden op deze site: travis berry
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
// WARNING
// This code should NOT be used as is. It is vulnerable to path traversal. https://www.owasp.org/index.php/Path_Traversal
// You should sanitize $_GET['directtozip']
// For tips to get started see http://stackoverflow.com/questions/4205141/preventing-directory-traversal-in-php-but-allowing-paths
//Get the directory to zip
$filename_no_ext= $_GET['directtozip'];
// we deliver a zip file
header("Content-Type: archive/zip");
// filename for the browser to save the zip file
header("Content-Disposition: attachment; filename=$filename_no_ext".".zip");
// get a tmp name for the .zip
$tmp_zip = tempnam ("tmp", "tempname") . ".zip";
//change directory so the zip file doesnt have a tree structure in it.
chdir('user_uploads/'.$_GET['directtozip']);
// zip the stuff (dir and all in there) into the tmp_zip file
exec('zip '.$tmp_zip.' *');
// calc the length of the zip. it is needed for the progress bar of the browser
$filesize = filesize($tmp_zip);
header("Content-Length: $filesize");
// deliver the zip file
$fp = fopen("$tmp_zip","r");
echo fpassthru($fp);
// clean up the tmp zip file
unlink($tmp_zip);
?>
// WARNING
// This code should NOT be used as is. It is vulnerable to path traversal. https://www.owasp.org/index.php/Path_Traversal
// You should sanitize $_GET['directtozip']
// For tips to get started see http://stackoverflow.com/questions/4205141/preventing-directory-traversal-in-php-but-allowing-paths
//Get the directory to zip
$filename_no_ext= $_GET['directtozip'];
// we deliver a zip file
header("Content-Type: archive/zip");
// filename for the browser to save the zip file
header("Content-Disposition: attachment; filename=$filename_no_ext".".zip");
// get a tmp name for the .zip
$tmp_zip = tempnam ("tmp", "tempname") . ".zip";
//change directory so the zip file doesnt have a tree structure in it.
chdir('user_uploads/'.$_GET['directtozip']);
// zip the stuff (dir and all in there) into the tmp_zip file
exec('zip '.$tmp_zip.' *');
// calc the length of the zip. it is needed for the progress bar of the browser
$filesize = filesize($tmp_zip);
header("Content-Length: $filesize");
// deliver the zip file
$fp = fopen("$tmp_zip","r");
echo fpassthru($fp);
// clean up the tmp zip file
unlink($tmp_zip);
?>
Met als link naar dit script:
<a href="zip_folders.php?directtozip=img">Download All As Zip</a>
Het downloaden lukt, maar wanneer ik het bestand open is de melding: Kan bestand img niet openen, dit archief is mogelijk beschadigd.
Heeft iemand een idee waaraan dit liggen kan, en hoe ik het op zou kunnen lossen?
bvd,
groeten Wouter
Verder kan ik weinig zeggen over bovenstaand script. Zelf gebruik ik heel andere methode gebaseerd op het script in 'zip.lib.php' wat je in de phpmyadmin directory kan vinden. Dat script werkte ook niet helemaal overigens, maar met enige aanpassingen heb ik er een class van kunnen maken die voor mij in elk geval wel altijd werkt. Mocht je bovenstaande dus niet aan de praat krijgen dan wil ik die class nog wel delen.
ZipArchive, maar het is mogelijk dat deze class niet op alle servers werkt, omdat deze PECL vereist.
Mocht je server het wel ondersteunen, wil ik mijn functie ook wel delen
Ik gebruik altijd zonder problemen de Mocht je server het wel ondersteunen, wil ik mijn functie ook wel delen
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?php
function Zip($source, $destination, $readme = '')
{
if (is_string($source)) $source_arr = array($source);
if (!extension_loaded('zip')){
return false;
}
$zip = new ZipArchive();
if (!$zip->open($destination, ZIPARCHIVE::CREATE)){
return false;
}
if(isset($readme) && strlen($readme) > 0){
$zip->addFromString('README.txt', $readme);
}
foreach ($source_arr as $source){
if (!file_exists($source)) continue;
$source = str_replace('\\', '/', realpath($source));
if (is_dir($source) === true){
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);
foreach ($files as $file){
$file = str_replace('\\', '/', realpath($file));
if (is_dir($file) === true){
$zip->addEmptyDir(str_replace($source . '/', '', $file . '/'));
}
else if (is_file($file) === true){
$zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file));
}
}
}
else if (is_file($source) === true){
$zip->addFromString(basename($source), file_get_contents($source));
}
}
return $zip->close();
}
?>
function Zip($source, $destination, $readme = '')
{
if (is_string($source)) $source_arr = array($source);
if (!extension_loaded('zip')){
return false;
}
$zip = new ZipArchive();
if (!$zip->open($destination, ZIPARCHIVE::CREATE)){
return false;
}
if(isset($readme) && strlen($readme) > 0){
$zip->addFromString('README.txt', $readme);
}
foreach ($source_arr as $source){
if (!file_exists($source)) continue;
$source = str_replace('\\', '/', realpath($source));
if (is_dir($source) === true){
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST);
foreach ($files as $file){
$file = str_replace('\\', '/', realpath($file));
if (is_dir($file) === true){
$zip->addEmptyDir(str_replace($source . '/', '', $file . '/'));
}
else if (is_file($file) === true){
$zip->addFromString(str_replace($source . '/', '', $file), file_get_contents($file));
}
}
}
else if (is_file($source) === true){
$zip->addFromString(basename($source), file_get_contents($source));
}
}
return $zip->close();
}
?>
Met ziparchive kan je toch geen zip files streamen? Alleen naar disk opslaan, of heb ik dat verkeerd?
Ik maak alleen een zip van een bepaalde map en sla die ergens anders op op de server en geef een download link weer. Dat laatste kun je natuurlijk ook vervangen door een 'attachment header', waarbij je dan een gelijk idee hebt als TS.
Wat ik bedoel is dat je niet eerst het bestand op de server hoeft op te slaan. Wat de TS precies wil weet ik ook niet, maar de reden dat ik ziparchive zelf niet gebruik is het feit dat ik geen manier kon vinden om zonder de zip op te slaan het als download aan te kunnen bieden. De vraag is dan ook meer mijn vraag dan dat het betrekking heeft op de vraag van de TS.
Maar elk ander script zou ook iets opslaan, dan wel tijdelijk, dus in dat opzicht vind ik het niet erg en voor mijn doel vind ik het wel fijn juist.
Je kunt altijd een attachment header sturen met de zip en de zip verwijderen met unlink. Dat is inprincipe ook was TS' script doet.
Niet elk ander script dus. Ik heb zelf een class die de zip aanmaakt in geheugen en naar de browser pusht. Er wordt niets op de server opgeslagen. Niet ideaal voor elke situatie (want als de gebruiker de download niet accepteert is het bestand weg), maar voor mijn applicatie wel nodig. Hier is dus even de vraag wat de TS wil.
bedankt voor je reactie. De code is nog helemaal clean, er wordt niets anders uitgegooid van te voren. Nog steeds krijg ik een error. Ik ben benieuwd naar wat je hebt.
En Michael ook bedankt voor je reactie.
Met deze Ziparchive en PECL wordt het erg ingewikkeld voor mij. Uiteraard wil ik het er wel mee gaan proberen. Ik ben al wat aan het kijken, maar is er niet een andere, eenvoudigere weg?
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
<?php
/**
* This class creates a ZIP file, but in a string, so it can be streamed to the
* browser without first having to be saved on disk.
* This class is based on the zipfile class in zip.lib.php which can be found in
* the phpmyadmin directory. Some minor changes have been made to get it to work
* though.
*/
class Zip_Streamer_Class{
/**
* Array to store compressed data
* @var array
*/
private $datasec;
/**
* Central directory
* @var array
*/
private $ctrl_dir;
/**
* End of central directory record
* @var string
*/
private $eof_ctrl_dir;
/**
* Last offset position
* @var int
*/
private $old_offset;
/* -- constructor ----------------------------------------------------------- */
/**
* Constructor which initializes the properties
*/
public function __construct(){
$this->resetProperties();
}
/* -- private methods ------------------------------------------------------- */
/**
* Converts an Unix timestamp to a four byte DOS date and time format (date
* in high two bytes, time in low two bytes allowing magnitude comparison).
* @param int $unixtime
* @return int the current date in a four byte DOS format
*/
private function unix2DosTime( $unixtime = 0 ){
$timearray = ($unixtime == 0) ? getdate() : getdate($unixtime);
if ($timearray['year'] < 1980) {
$timearray['year'] = 1980;
$timearray['mon'] = 1;
$timearray['mday'] = 1;
$timearray['hours'] = 0;
$timearray['minutes'] = 0;
$timearray['seconds'] = 0;
}
return (($timearray['year'] - 1980) << 25) | ($timearray['mon'] << 21) | ($timearray['mday'] << 16) |
($timearray['hours'] << 11) | ($timearray['minutes'] << 5) | ($timearray['seconds'] >> 1);
}
/**
* Fully convert the time from unix timestamp.
* @param int $time
* @return string
*/
private function convertTime( $time ){
$dtime = dechex($this->unix2DosTime($time));
$hexdtime = '\x' . $dtime[6] . $dtime[7]
. '\x' . $dtime[4] . $dtime[5]
. '\x' . $dtime[2] . $dtime[3]
. '\x' . $dtime[0] . $dtime[1];
eval('$hexdtime = "' . $hexdtime . '";');
return $hexdtime;
}
/**
* Reset all properties to their initial value. This will effectively clear
* the contents of the zip.
*/
private function resetProperties(){
$this->datasec = array();
$this->ctrl_dir = array();
$this->eof_ctrl_dir = "\x50\x4b\x05\x06\x00\x00\x00\x00";
$this->old_offset = 0;
}
/* -- public methods -------------------------------------------------------- */
/**
* Adds "file" to archive. The file is given with the name that it should get
* in the archive and the contents of the file ($data). $time is the current
* timestamp (can be ommited)
* @param string $data
* @param string $name
* @param int $time
*/
public function addFile( $data, $name, $time = 0 ){
$name = str_replace('\\', '/', $name);
$hexdtime = $this->convertTime( $time );
/*$dtime = dechex($this->unix2DosTime($time));
$hexdtime = '\x' . $dtime[6] . $dtime[7]
. '\x' . $dtime[4] . $dtime[5]
. '\x' . $dtime[2] . $dtime[3]
. '\x' . $dtime[0] . $dtime[1];
eval('$hexdtime = "' . $hexdtime . '";');*/
$fr = "\x50\x4b\x03\x04";
$fr .= "\x14\x00"; // ver needed to extract
$fr .= "\x00\x00"; // gen purpose bit flag
$fr .= "\x08\x00"; // compression method
$fr .= $hexdtime; // last mod time and date
// "local file header" segment
$unc_len = strlen($data);
$crc = crc32($data);
$zdata = gzcompress($data);
$zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2); // fix crc bug
$c_len = strlen($zdata);
$fr .= pack('V', $crc); // crc32
$fr .= pack('V', $c_len); // compressed filesize
$fr .= pack('V', $unc_len); // uncompressed filesize
$fr .= pack('v', strlen($name)); // length of filename
$fr .= pack('v', 0); // extra field length
$fr .= $name;
// "file data" segment
$fr .= $zdata;
// "data descriptor" segment (optional but necessary if archive is not
// served as file)
// nijel(2004-10-19): this seems not to be needed at all and causes
// problems in some cases (bug #1037737)
// EH: doesn't work without this part....
$fr .= pack('V', $crc); // crc32
$fr .= pack('V', $c_len); // compressed filesize
$fr .= pack('V', $unc_len); // uncompressed filesize
// add this entry to array
$this->datasec[] = $fr;
// now add to central directory record
$cdrec = "\x50\x4b\x01\x02";
$cdrec .= "\x00\x00"; // version made by
$cdrec .= "\x14\x00"; // version needed to extract
$cdrec .= "\x00\x00"; // gen purpose bit flag
$cdrec .= "\x08\x00"; // compression method
$cdrec .= $hexdtime; // last mod time & date
$cdrec .= pack('V', $crc); // crc32
$cdrec .= pack('V', $c_len); // compressed filesize
$cdrec .= pack('V', $unc_len); // uncompressed filesize
$cdrec .= pack('v', strlen($name)); // length of filename
$cdrec .= pack('v', 0); // extra field length
$cdrec .= pack('v', 0); // file comment length
$cdrec .= pack('v', 0); // disk number start
$cdrec .= pack('v', 0); // internal file attributes
$cdrec .= pack('V', 32); // external file attributes - 'archive' bit set
$cdrec .= pack('V', $this->old_offset); // relative offset of local header
$this->old_offset += strlen($fr);
$cdrec .= $name;
// optional extra field, file comment goes here
// save to central directory
$this->ctrl_dir[] = $cdrec;
}
/**
* Dumps out file
* @return string
*/
public function file(){
$data = implode('', $this -> datasec);
$ctrldir = implode('', $this -> ctrl_dir);
return
$data .
$ctrldir .
$this -> eof_ctrl_dir .
pack('v', sizeof($this -> ctrl_dir)) . // total # of entries "on this disk"
pack('v', sizeof($this -> ctrl_dir)) . // total # of entries overall
pack('V', strlen($ctrldir)) . // size of central dir
pack('V', strlen($data)) . // offset to start of central dir
"\x00\x00"; // .zip file comment length
}
/**
* Empty all content that is added to the zip already. This will reset all
* values and clears everything.
*/
public function emptyZip(){
$this->resetProperties();
}
}
?>
/**
* This class creates a ZIP file, but in a string, so it can be streamed to the
* browser without first having to be saved on disk.
* This class is based on the zipfile class in zip.lib.php which can be found in
* the phpmyadmin directory. Some minor changes have been made to get it to work
* though.
*/
class Zip_Streamer_Class{
/**
* Array to store compressed data
* @var array
*/
private $datasec;
/**
* Central directory
* @var array
*/
private $ctrl_dir;
/**
* End of central directory record
* @var string
*/
private $eof_ctrl_dir;
/**
* Last offset position
* @var int
*/
private $old_offset;
/* -- constructor ----------------------------------------------------------- */
/**
* Constructor which initializes the properties
*/
public function __construct(){
$this->resetProperties();
}
/* -- private methods ------------------------------------------------------- */
/**
* Converts an Unix timestamp to a four byte DOS date and time format (date
* in high two bytes, time in low two bytes allowing magnitude comparison).
* @param int $unixtime
* @return int the current date in a four byte DOS format
*/
private function unix2DosTime( $unixtime = 0 ){
$timearray = ($unixtime == 0) ? getdate() : getdate($unixtime);
if ($timearray['year'] < 1980) {
$timearray['year'] = 1980;
$timearray['mon'] = 1;
$timearray['mday'] = 1;
$timearray['hours'] = 0;
$timearray['minutes'] = 0;
$timearray['seconds'] = 0;
}
return (($timearray['year'] - 1980) << 25) | ($timearray['mon'] << 21) | ($timearray['mday'] << 16) |
($timearray['hours'] << 11) | ($timearray['minutes'] << 5) | ($timearray['seconds'] >> 1);
}
/**
* Fully convert the time from unix timestamp.
* @param int $time
* @return string
*/
private function convertTime( $time ){
$dtime = dechex($this->unix2DosTime($time));
$hexdtime = '\x' . $dtime[6] . $dtime[7]
. '\x' . $dtime[4] . $dtime[5]
. '\x' . $dtime[2] . $dtime[3]
. '\x' . $dtime[0] . $dtime[1];
eval('$hexdtime = "' . $hexdtime . '";');
return $hexdtime;
}
/**
* Reset all properties to their initial value. This will effectively clear
* the contents of the zip.
*/
private function resetProperties(){
$this->datasec = array();
$this->ctrl_dir = array();
$this->eof_ctrl_dir = "\x50\x4b\x05\x06\x00\x00\x00\x00";
$this->old_offset = 0;
}
/* -- public methods -------------------------------------------------------- */
/**
* Adds "file" to archive. The file is given with the name that it should get
* in the archive and the contents of the file ($data). $time is the current
* timestamp (can be ommited)
* @param string $data
* @param string $name
* @param int $time
*/
public function addFile( $data, $name, $time = 0 ){
$name = str_replace('\\', '/', $name);
$hexdtime = $this->convertTime( $time );
/*$dtime = dechex($this->unix2DosTime($time));
$hexdtime = '\x' . $dtime[6] . $dtime[7]
. '\x' . $dtime[4] . $dtime[5]
. '\x' . $dtime[2] . $dtime[3]
. '\x' . $dtime[0] . $dtime[1];
eval('$hexdtime = "' . $hexdtime . '";');*/
$fr = "\x50\x4b\x03\x04";
$fr .= "\x14\x00"; // ver needed to extract
$fr .= "\x00\x00"; // gen purpose bit flag
$fr .= "\x08\x00"; // compression method
$fr .= $hexdtime; // last mod time and date
// "local file header" segment
$unc_len = strlen($data);
$crc = crc32($data);
$zdata = gzcompress($data);
$zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2); // fix crc bug
$c_len = strlen($zdata);
$fr .= pack('V', $crc); // crc32
$fr .= pack('V', $c_len); // compressed filesize
$fr .= pack('V', $unc_len); // uncompressed filesize
$fr .= pack('v', strlen($name)); // length of filename
$fr .= pack('v', 0); // extra field length
$fr .= $name;
// "file data" segment
$fr .= $zdata;
// "data descriptor" segment (optional but necessary if archive is not
// served as file)
// nijel(2004-10-19): this seems not to be needed at all and causes
// problems in some cases (bug #1037737)
// EH: doesn't work without this part....
$fr .= pack('V', $crc); // crc32
$fr .= pack('V', $c_len); // compressed filesize
$fr .= pack('V', $unc_len); // uncompressed filesize
// add this entry to array
$this->datasec[] = $fr;
// now add to central directory record
$cdrec = "\x50\x4b\x01\x02";
$cdrec .= "\x00\x00"; // version made by
$cdrec .= "\x14\x00"; // version needed to extract
$cdrec .= "\x00\x00"; // gen purpose bit flag
$cdrec .= "\x08\x00"; // compression method
$cdrec .= $hexdtime; // last mod time & date
$cdrec .= pack('V', $crc); // crc32
$cdrec .= pack('V', $c_len); // compressed filesize
$cdrec .= pack('V', $unc_len); // uncompressed filesize
$cdrec .= pack('v', strlen($name)); // length of filename
$cdrec .= pack('v', 0); // extra field length
$cdrec .= pack('v', 0); // file comment length
$cdrec .= pack('v', 0); // disk number start
$cdrec .= pack('v', 0); // internal file attributes
$cdrec .= pack('V', 32); // external file attributes - 'archive' bit set
$cdrec .= pack('V', $this->old_offset); // relative offset of local header
$this->old_offset += strlen($fr);
$cdrec .= $name;
// optional extra field, file comment goes here
// save to central directory
$this->ctrl_dir[] = $cdrec;
}
/**
* Dumps out file
* @return string
*/
public function file(){
$data = implode('', $this -> datasec);
$ctrldir = implode('', $this -> ctrl_dir);
return
$data .
$ctrldir .
$this -> eof_ctrl_dir .
pack('v', sizeof($this -> ctrl_dir)) . // total # of entries "on this disk"
pack('v', sizeof($this -> ctrl_dir)) . // total # of entries overall
pack('V', strlen($ctrldir)) . // size of central dir
pack('V', strlen($data)) . // offset to start of central dir
"\x00\x00"; // .zip file comment length
}
/**
* Empty all content that is added to the zip already. This will reset all
* values and clears everything.
*/
public function emptyZip(){
$this->resetProperties();
}
}
?>
Met enig commentaar, gebruik hoe je wilt (andere ook), maar zonder enige garantie ;-)
Valt wel mee. Vaak is PECL al geïnstalleerd. Dus je zou even moeten kijken (script uitproberen) of dat bij jou ook het geval is. Verder is de functie klaar voor gebruik en kun je het natuurlijk wel aanpassen naar jouw wens.
Code (php)
1
2
3
4
5
6
7
2
3
4
5
6
7
<?php
/*
* Bron, bestemming (incl bestandsnaam), eventueel tekst (Komt in de zip als README.txt)
* Voorbeeld: Alle bestanden in / opslaan in archive.zip zonder README
*/
Zip('/', '/archive.zip', '');
?>
/*
* Bron, bestemming (incl bestandsnaam), eventueel tekst (Komt in de zip als README.txt)
* Voorbeeld: Alle bestanden in / opslaan in archive.zip zonder README
*/
Zip('/', '/archive.zip', '');
?>
Erwin +1. Ziet er netjes uit.
groeten Wouter
Toevoeging op 24/04/2014 13:07:41:
Hallo Michael,
Het script lijkt te werken, maar ik krijg deze melding:
Warning: ZipArchive::open() [ziparchive.open]: SAFE MODE Restriction in effect. The script whose uid is 1236 is not allowed to access / owned by uid 0 in /public/sites/mijnsite/index.php on line 11
Wat kan ik hieraan doen? In het googlen vond ik dat de veiligheid in gedrang komt, wanneer ik dat uitzet.
groeten Wouter