2 /****************************************************************/
4 /****************************************************************/
5 /* Copyright (c) 2002-2008 by Greg Gay & Joel Kronenberg */
6 /* Adaptive Technology Resource Centre / University of Toronto */
9 /* This program is free software. You can redistribute it and/or*/
10 /* modify it under the terms of the GNU General Public License */
11 /* as published by the Free Software Foundation. */
12 /****************************************************************/
13 // $Id: zipfile.class.php 7208 2008-01-09 16:07:24Z greg $
17 * Class for creating and accessing an archive zip file
19 * @link http://www.pkware.com/products/enterprise/white_papers/appnote.html for the specs
20 * @author Joel Kronenberg
25 * string $files_data - stores file information like the header and description
31 * string $central_directory_headers - headers necessary for including file in central record
34 var $central_directory_headers;
37 * int $num_entries - a counter for the number of entries in the archive
43 * string $zip_file - complete contents of file
49 * boolean $is_closed - flag set to true if file is closed, false if still open
56 * Constructor method. Initialises variables.
58 * @author Joel Kronenberg
61 $this->files_data = '';
62 $this->central_directory_headers = '';
63 $this->num_entries = 0;
64 $this->is_closed = false;
68 * Public interface for adding a dir and its contents recursively to zip file
70 * @param string $dir the real system directory that contains the files to add to the zip
71 * @param string $zip_prefix_dir the zip dir where the contents of $dir will be put in
72 * @param string $pre_pend_dir used during the recursion to keep track of the path, default=''
73 * @see $_base_path in include/vitals.inc.php
74 * @see priv_add_dir() in include/classes/zipfile.class.php
75 * @see add_file() in include/classes/zipfile.class.php
76 * @author Joel Kronenberg
78 function add_dir($dir, $zip_prefix_dir, $pre_pend_dir='') {
79 if (!($dh = @opendir($dir.$pre_pend_dir))) {
80 echo 'cant open dir: '.$dir.$pre_pend_dir;
84 while (($file = readdir($dh)) !== false) {
85 /* skip directories */
86 if ($file == '.' || $file == '..') {
89 /* skip potential harmful files/directories */
90 if ( (strpos($file, '..') !== false) || (strpos($file, '/') !== false)) {
94 $file_info = stat( $dir . $pre_pend_dir . $file );
96 if (is_dir( $dir . $pre_pend_dir . $file )) {
97 /* create this dir in the zip */
98 $this->priv_add_dir( $zip_prefix_dir . $pre_pend_dir . $file . '/',
99 $file_info['mtime'] );
101 /* continue recursion, going down this dir */
102 $this->add_dir( $dir,
104 $pre_pend_dir . $file . '/' );
107 /* add this file to the zip */
108 $this-> add_file( file_get_contents($dir . $pre_pend_dir . $file),
109 $zip_prefix_dir . $pre_pend_dir . $file,
110 $file_info['mtime'] );
117 * Adding a dir to the archive
119 * @param string $name directory name
120 * @param string $timestamp time, default=''
121 * @author Joel Kronenberg
123 function priv_add_dir($name, $timestamp = '') {
124 $name = str_replace("\\", "/", $name);
125 $old_offset = strlen($this->files_data);
127 $local_file_header = "\x50\x4b\x03\x04"; // local file header signature 4 bytes (0x04034b50)
128 $local_file_header .= "\x0a\x00"; // ver needed to extract // version needed to extract 2 bytes
129 $local_file_header .= "\x00\x00"; // gen purpose bit flag // general purpose bit flag 2 bytes
130 $local_file_header .= "\x00\x00"; // compression method // compression method 2 bytes
131 $local_file_header .= "\x00\x00\x00\x00"; // last mod time and date // last mod file time 2 bytes & last mod file date 2 bytes
132 $local_file_header .= pack("V",0); // crc32 // crc-32 4 bytes
133 $local_file_header .= pack("V",0); //compressed filesize // compressed size 4 bytes
134 $local_file_header .= pack("V",0); //uncompressed filesize // uncompressed size 4 bytes
135 $local_file_header .= pack("v", strlen($name) ); //length of pathname // file name length 2 bytes
136 $local_file_header .= pack("v", 0 ); //extra field length // extra field length 2 bytes
137 $local_file_header .= $name; // file name (variable size) & extra field (variable size)
138 // end of "local file header" segment
140 // no "file data" segment for path
142 // add this entry to array
143 $this->files_data .= $local_file_header;
145 // ext. file attributes mirrors MS-DOS directory attr byte, detailed
146 // at http://support.microsoft.com/support/kb/articles/Q125/0/19.asp
149 $v_date = getdate($timestamp);
153 $time = ($v_date['hours']<<11) + ($v_date['minutes']<<5) + $v_date['seconds']/2;
154 $date = (($v_date['year']-1980)<<9) + ($v_date['mon']<<5) + $v_date['mday'];
156 // now add to central record
157 $central_directory = "\x50\x4b\x01\x02"; // central file header signature 4 bytes (0x02014b50)
158 $central_directory .="\x14\x00"; // version made by // version made by 2 bytes
159 $central_directory .="\x14\x00"; // version needed to extract // version needed to extract 2 bytes
160 $central_directory .="\x00\x00"; // gen purpose bit flag // general purpose bit flag 2 bytes
161 $central_directory .="\x00\x00"; // compression method // compression method 2 bytes
162 $central_directory .= pack("v",$time); // time // last mod file time 2 bytes
163 $central_directory .= pack("v",$date); // date // last mod file date 2 bytes
164 $central_directory .= pack("V", 0); // crc32 // crc-32 4 bytes
165 $central_directory .= pack("V", 0); // compressed filesize // compressed size 4 bytes
166 $central_directory .= pack("V", 0); // uncompressed filesize // uncompressed size 4 bytes
167 $central_directory .= pack("v", strlen($name) ); //length of filename // file name length 2 bytes
168 $central_directory .= pack("v", 0); // extra field length // extra field length 2 bytes
169 $central_directory .= pack("v", 0); // file comment length // file comment length 2 bytes
170 $central_directory .= pack("v", 0); // disk number start // disk number start 2 bytes
171 $central_directory .= pack("v", 0); // internal file attributes // internal file attributes 2 bytes
172 $central_directory .= pack("V", 16+32); //external file attributes - 'directory' 'archive' bit set // external file attributes 4 bytes
173 $central_directory .= pack("V", $old_offset); //relative offset of local header // relative offset of local header 4 bytes
174 $central_directory .= $name; // file name (variable size)
176 $this->central_directory_headers .= $central_directory;
178 $this->num_entries++;
182 * Public interface to create a directory in the archive.
184 * @param string $name directory name
185 * @param string $timestamp time of creation, default=''
186 * @see $_base_path in include/vitals.inc.php
187 * @see priv_add_dir() in include/zipfile.class.php
188 * @author Joel Kronenberg
190 function create_dir($name, $timestamp='') {
193 if (substr($name, -1) != '/') {
194 /* add the trailing slash */
198 $this->priv_add_dir($name, $timestamp = '');
202 * Adds a file to the archive.
204 * @param string $file_data file contents
205 * @param string $name name of file in archive (add path if your want)
206 * @param string $timestamp time of creation, default=''
207 * @see $_base_path in include/vitals.inc.php
208 * @see priv_add_dir() in include/zipfile.class.php
209 * @author Joel Kronenberg
211 function add_file($file_data, $name, $timestamp = '') {
212 $name = str_replace("\\", "/", $name);
213 $crc = crc32($file_data);
214 $uncompressed_size = strlen($file_data);
215 $file_data = substr(gzcompress($file_data, 9), 2, -4);
216 $compressed_size = strlen($file_data);
217 $old_offset = strlen($this->files_data);
219 /* local file header */
220 $local_file_header = "\x50\x4b\x03\x04"; // local file header signature 4 bytes (0x04034b50)
221 $local_file_header .= "\x14\x00"; // ver needed to extract // version needed to extract 2 bytes
222 $local_file_header .= "\x00\x00"; // gen purpose bit flag // general purpose bit flag 2 bytes
223 $local_file_header .= "\x08\x00"; // compression method // compression method 2 bytes
224 $local_file_header .= "\x00\x00\x00\x00"; // last mod time and date // last mod file time 2 bytes & last mod file date 2 bytes
225 $local_file_header .= pack("V",$crc); // crc32 // crc-32 4 bytes
226 $local_file_header .= pack("V",$compressed_size); //compressed filesize // compressed size 4 bytes
227 $local_file_header .= pack("V",$uncompressed_size); //uncompressed filesize // uncompressed size 4 bytes
228 $local_file_header .= pack("v", strlen($name) ); //length of filename // file name length 2 bytes
229 $local_file_header .= "\x00\x00"; //extra field length // extra field length 2 bytes
230 $local_file_header .= $name; // file name (variable size) & extra field (variable size)
231 /* end of local file header */
233 $this->files_data .= $local_file_header . $file_data; // . $data_descriptor;;
235 /* create the central directory */
236 $central_directory = '';
238 $v_date = getdate($timestamp);
242 $time = ($v_date['hours']<<11) + ($v_date['minutes']<<5) + $v_date['seconds']/2;
243 $date = (($v_date['year']-1980)<<9) + ($v_date['mon']<<5) + $v_date['mday'];
245 // now add to central directory record
246 $central_directory = "\x50\x4b\x01\x02"; // central file header signature 4 bytes (0x02014b50)
247 $central_directory .="\x14\x00"; // version made by // version made by 2 bytes
248 $central_directory .="\x14\x00"; // version needed to extract // version needed to extract 2 bytes
249 $central_directory .="\x00\x00"; // gen purpose bit flag // general purpose bit flag 2 bytes
250 $central_directory .="\x08\x00"; // compression method // compression method 2 bytes
251 $central_directory .= pack("v",$time); // time // last mod file time 2 bytes
252 $central_directory .= pack("v",$date); // date // last mod file date 2 bytes
253 $central_directory .= pack("V",$crc); // crc32 // crc-32 4 bytes
254 $central_directory .= pack("V",$compressed_size); //compressed filesize // compressed size 4 bytes
255 $central_directory .= pack("V",$uncompressed_size); //uncompressed filesize // uncompressed size 4 bytes
256 $central_directory .= pack("v", strlen($name) ); //length of filename // file name length 2 bytes
257 $central_directory .= "\x00\x00"; //extra field length // extra field length 2 bytes
258 $central_directory .= "\x00\x00"; //file comment length // file comment length 2 bytes
259 $central_directory .= "\x00\x00"; //disk number start // disk number start 2 bytes
260 $central_directory .= "\x00\x00"; //internal file attributes // internal file attributes 2 bytes
261 $central_directory .= pack("V", 32); //external file attributes - 'archive' bit set // external file attributes 4 bytes
262 $central_directory .= pack("V", $old_offset);
264 $central_directory .= $name; // file name (variable size)
266 $this->central_directory_headers .= $central_directory;
268 $this->num_entries++;
272 * Closes archive, sets $is_closed to true
275 * @author Joel Kronenberg
278 $this->files_data .= $this->central_directory_headers . "\x50\x4b\x05\x06\x00\x00\x00\x00" .
279 pack("v", $this->num_entries). // total # of entries "on this disk"
280 pack("v", $this->num_entries). // total # of entries overall
281 pack("V", strlen($this->central_directory_headers)). // size of central dir
282 pack("V", strlen($this->files_data)). // offset to start of central dir
285 unset($this->central_directory_headers);
286 unset($this->num_entries);
288 $this->zip_file =& $this->files_data;
289 $this->is_closed = true;
293 * Gets size of new archive
294 * Only call this after calling close() - will return false if the zip wasn't close()d yet
296 * @return int size of file
297 * @author Joel Kronenberg
299 function get_size() {
300 if (!$this->is_closed) {
303 return strlen($this->zip_file);
308 * Returns binary file
310 * @see get_size() in include/classes/zipfile.class.php
311 * @author Joel Kronenberg
313 function get_file() {
314 if (!$this->is_closed) {
317 return $this->zip_file;
321 * Writes the file to disk.
322 * Similar to get_file(), but instead of returning the file, it saves it to disk.
324 * @author Joel Kronenberg
325 * @param $file The full path and file name of the destination file.
327 function write_file($file) {
328 if (!$this->is_closed) {
331 if (function_exists('file_put_contents')) {
332 file_put_contents($file, $this->zip_file);
334 $fp = fopen($file, 'wb+');
335 fwrite($fp, $this->zip_file);
342 * Outputs the file - sends headers to browser to force download
343 * Only call this after calling close() - will return false if the zip wasn't close()d yet
345 * @see get_size() in include/classes/zipfile.class.php
346 * @author Joel Kronenberg
348 function send_file($file_name) {
349 if (!$this->is_closed) {
352 $file_name = str_replace(array('"', '<', '>', '|', '?', '*', ':', '/', '\\'), '', $file_name);
354 header('Content-Type: application/x-zip');
355 header('Content-transfer-encoding: binary');
356 header('Content-Disposition: attachment; filename="'.htmlspecialchars($file_name).'.zip"');
357 header('Expires: 0');
358 header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
359 header('Pragma: public');
360 header('Content-Length: '.$this->get_size());
362 echo $this->get_file();