Subversion-Projekte lars-tiefland.ci

Revision

Revision 2107 | Revision 2254 | Zur aktuellen Revision | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
68 lars 1
<?php
2
/**
3
 * CodeIgniter
4
 *
5
 * An open source application development framework for PHP
6
 *
7
 * This content is released under the MIT License (MIT)
8
 *
2242 lars 9
 * Copyright (c) 2014 - 2018, British Columbia Institute of Technology
68 lars 10
 *
11
 * Permission is hereby granted, free of charge, to any person obtaining a copy
12
 * of this software and associated documentation files (the "Software"), to deal
13
 * in the Software without restriction, including without limitation the rights
14
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
 * copies of the Software, and to permit persons to whom the Software is
16
 * furnished to do so, subject to the following conditions:
17
 *
18
 * The above copyright notice and this permission notice shall be included in
19
 * all copies or substantial portions of the Software.
20
 *
21
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
 * THE SOFTWARE.
28
 *
29
 * @package	CodeIgniter
30
 * @author	EllisLab Dev Team
31
 * @copyright	Copyright (c) 2008 - 2014, EllisLab, Inc. (https://ellislab.com/)
2242 lars 32
 * @copyright	Copyright (c) 2014 - 2018, British Columbia Institute of Technology (http://bcit.ca/)
68 lars 33
 * @license	http://opensource.org/licenses/MIT	MIT License
34
 * @link	https://codeigniter.com
35
 * @since	Version 1.0.0
36
 * @filesource
37
 */
38
defined('BASEPATH') OR exit('No direct script access allowed');
39
 
40
/**
41
 * Zip Compression Class
42
 *
43
 * This class is based on a library I found at Zend:
44
 * http://www.zend.com/codex.php?id=696&single=1
45
 *
46
 * The original library is a little rough around the edges so I
47
 * refactored it and added several additional methods -- Rick Ellis
48
 *
49
 * @package		CodeIgniter
50
 * @subpackage	Libraries
51
 * @category	Encryption
52
 * @author		EllisLab Dev Team
53
 * @link		https://codeigniter.com/user_guide/libraries/zip.html
54
 */
55
class CI_Zip {
56
 
57
	/**
58
	 * Zip data in string form
59
	 *
60
	 * @var string
61
	 */
62
	public $zipdata = '';
63
 
64
	/**
65
	 * Zip data for a directory in string form
66
	 *
67
	 * @var string
68
	 */
69
	public $directory = '';
70
 
71
	/**
72
	 * Number of files/folder in zip file
73
	 *
74
	 * @var int
75
	 */
76
	public $entries = 0;
77
 
78
	/**
79
	 * Number of files in zip
80
	 *
81
	 * @var int
82
	 */
83
	public $file_num = 0;
84
 
85
	/**
86
	 * relative offset of local header
87
	 *
88
	 * @var int
89
	 */
90
	public $offset = 0;
91
 
92
	/**
93
	 * Reference to time at init
94
	 *
95
	 * @var int
96
	 */
97
	public $now;
98
 
99
	/**
100
	 * The level of compression
101
	 *
102
	 * Ranges from 0 to 9, with 9 being the highest level.
103
	 *
104
	 * @var	int
105
	 */
106
	public $compression_level = 2;
107
 
108
	/**
2107 lars 109
	 * mbstring.func_overload flag
1257 lars 110
	 *
111
	 * @var	bool
112
	 */
2107 lars 113
	protected static $func_overload;
1257 lars 114
 
115
	/**
68 lars 116
	 * Initialize zip compression class
117
	 *
118
	 * @return	void
119
	 */
120
	public function __construct()
121
	{
2107 lars 122
		isset(self::$func_overload) OR self::$func_overload = (extension_loaded('mbstring') && ini_get('mbstring.func_overload'));
1257 lars 123
 
68 lars 124
		$this->now = time();
125
		log_message('info', 'Zip Compression Class Initialized');
126
	}
127
 
128
	// --------------------------------------------------------------------
129
 
130
	/**
131
	 * Add Directory
132
	 *
133
	 * Lets you add a virtual directory into which you can place files.
134
	 *
135
	 * @param	mixed	$directory	the directory name. Can be string or array
136
	 * @return	void
137
	 */
138
	public function add_dir($directory)
139
	{
140
		foreach ((array) $directory as $dir)
141
		{
142
			if ( ! preg_match('|.+/$|', $dir))
143
			{
144
				$dir .= '/';
145
			}
146
 
147
			$dir_time = $this->_get_mod_time($dir);
148
			$this->_add_dir($dir, $dir_time['file_mtime'], $dir_time['file_mdate']);
149
		}
150
	}
151
 
152
	// --------------------------------------------------------------------
153
 
154
	/**
155
	 * Get file/directory modification time
156
	 *
157
	 * If this is a newly created file/dir, we will set the time to 'now'
158
	 *
159
	 * @param	string	$dir	path to file
160
	 * @return	array	filemtime/filemdate
161
	 */
162
	protected function _get_mod_time($dir)
163
	{
164
		// filemtime() may return false, but raises an error for non-existing files
165
		$date = file_exists($dir) ? getdate(filemtime($dir)) : getdate($this->now);
166
 
167
		return array(
168
			'file_mtime' => ($date['hours'] << 11) + ($date['minutes'] << 5) + $date['seconds'] / 2,
169
			'file_mdate' => (($date['year'] - 1980) << 9) + ($date['mon'] << 5) + $date['mday']
170
		);
171
	}
172
 
173
	// --------------------------------------------------------------------
174
 
175
	/**
176
	 * Add Directory
177
	 *
178
	 * @param	string	$dir	the directory name
179
	 * @param	int	$file_mtime
180
	 * @param	int	$file_mdate
181
	 * @return	void
182
	 */
183
	protected function _add_dir($dir, $file_mtime, $file_mdate)
184
	{
185
		$dir = str_replace('\\', '/', $dir);
186
 
187
		$this->zipdata .=
188
			"\x50\x4b\x03\x04\x0a\x00\x00\x00\x00\x00"
189
			.pack('v', $file_mtime)
190
			.pack('v', $file_mdate)
191
			.pack('V', 0) // crc32
192
			.pack('V', 0) // compressed filesize
193
			.pack('V', 0) // uncompressed filesize
1257 lars 194
			.pack('v', self::strlen($dir)) // length of pathname
68 lars 195
			.pack('v', 0) // extra field length
196
			.$dir
197
			// below is "data descriptor" segment
198
			.pack('V', 0) // crc32
199
			.pack('V', 0) // compressed filesize
200
			.pack('V', 0); // uncompressed filesize
201
 
202
		$this->directory .=
203
			"\x50\x4b\x01\x02\x00\x00\x0a\x00\x00\x00\x00\x00"
204
			.pack('v', $file_mtime)
205
			.pack('v', $file_mdate)
206
			.pack('V',0) // crc32
207
			.pack('V',0) // compressed filesize
208
			.pack('V',0) // uncompressed filesize
1257 lars 209
			.pack('v', self::strlen($dir)) // length of pathname
68 lars 210
			.pack('v', 0) // extra field length
211
			.pack('v', 0) // file comment length
212
			.pack('v', 0) // disk number start
213
			.pack('v', 0) // internal file attributes
214
			.pack('V', 16) // external file attributes - 'directory' bit set
215
			.pack('V', $this->offset) // relative offset of local header
216
			.$dir;
217
 
1257 lars 218
		$this->offset = self::strlen($this->zipdata);
68 lars 219
		$this->entries++;
220
	}
221
 
222
	// --------------------------------------------------------------------
223
 
224
	/**
225
	 * Add Data to Zip
226
	 *
227
	 * Lets you add files to the archive. If the path is included
228
	 * in the filename it will be placed within a directory. Make
229
	 * sure you use add_dir() first to create the folder.
230
	 *
231
	 * @param	mixed	$filepath	A single filepath or an array of file => data pairs
232
	 * @param	string	$data		Single file contents
233
	 * @return	void
234
	 */
235
	public function add_data($filepath, $data = NULL)
236
	{
237
		if (is_array($filepath))
238
		{
239
			foreach ($filepath as $path => $data)
240
			{
241
				$file_data = $this->_get_mod_time($path);
242
				$this->_add_data($path, $data, $file_data['file_mtime'], $file_data['file_mdate']);
243
			}
244
		}
245
		else
246
		{
247
			$file_data = $this->_get_mod_time($filepath);
248
			$this->_add_data($filepath, $data, $file_data['file_mtime'], $file_data['file_mdate']);
249
		}
250
	}
251
 
252
	// --------------------------------------------------------------------
253
 
254
	/**
255
	 * Add Data to Zip
256
	 *
257
	 * @param	string	$filepath	the file name/path
258
	 * @param	string	$data	the data to be encoded
259
	 * @param	int	$file_mtime
260
	 * @param	int	$file_mdate
261
	 * @return	void
262
	 */
263
	protected function _add_data($filepath, $data, $file_mtime, $file_mdate)
264
	{
265
		$filepath = str_replace('\\', '/', $filepath);
266
 
1257 lars 267
		$uncompressed_size = self::strlen($data);
68 lars 268
		$crc32  = crc32($data);
1257 lars 269
		$gzdata = self::substr(gzcompress($data, $this->compression_level), 2, -4);
270
		$compressed_size = self::strlen($gzdata);
68 lars 271
 
272
		$this->zipdata .=
273
			"\x50\x4b\x03\x04\x14\x00\x00\x00\x08\x00"
274
			.pack('v', $file_mtime)
275
			.pack('v', $file_mdate)
276
			.pack('V', $crc32)
277
			.pack('V', $compressed_size)
278
			.pack('V', $uncompressed_size)
1257 lars 279
			.pack('v', self::strlen($filepath)) // length of filename
68 lars 280
			.pack('v', 0) // extra field length
281
			.$filepath
282
			.$gzdata; // "file data" segment
283
 
284
		$this->directory .=
285
			"\x50\x4b\x01\x02\x00\x00\x14\x00\x00\x00\x08\x00"
286
			.pack('v', $file_mtime)
287
			.pack('v', $file_mdate)
288
			.pack('V', $crc32)
289
			.pack('V', $compressed_size)
290
			.pack('V', $uncompressed_size)
1257 lars 291
			.pack('v', self::strlen($filepath)) // length of filename
68 lars 292
			.pack('v', 0) // extra field length
293
			.pack('v', 0) // file comment length
294
			.pack('v', 0) // disk number start
295
			.pack('v', 0) // internal file attributes
296
			.pack('V', 32) // external file attributes - 'archive' bit set
297
			.pack('V', $this->offset) // relative offset of local header
298
			.$filepath;
299
 
1257 lars 300
		$this->offset = self::strlen($this->zipdata);
68 lars 301
		$this->entries++;
302
		$this->file_num++;
303
	}
304
 
305
	// --------------------------------------------------------------------
306
 
307
	/**
308
	 * Read the contents of a file and add it to the zip
309
	 *
310
	 * @param	string	$path
311
	 * @param	bool	$archive_filepath
312
	 * @return	bool
313
	 */
314
	public function read_file($path, $archive_filepath = FALSE)
315
	{
316
		if (file_exists($path) && FALSE !== ($data = file_get_contents($path)))
317
		{
318
			if (is_string($archive_filepath))
319
			{
320
				$name = str_replace('\\', '/', $archive_filepath);
321
			}
322
			else
323
			{
324
				$name = str_replace('\\', '/', $path);
325
 
326
				if ($archive_filepath === FALSE)
327
				{
328
					$name = preg_replace('|.*/(.+)|', '\\1', $name);
329
				}
330
			}
331
 
332
			$this->add_data($name, $data);
333
			return TRUE;
334
		}
335
 
336
		return FALSE;
337
	}
338
 
339
	// ------------------------------------------------------------------------
340
 
341
	/**
342
	 * Read a directory and add it to the zip.
343
	 *
344
	 * This function recursively reads a folder and everything it contains (including
345
	 * sub-folders) and creates a zip based on it. Whatever directory structure
346
	 * is in the original file path will be recreated in the zip file.
347
	 *
348
	 * @param	string	$path	path to source directory
349
	 * @param	bool	$preserve_filepath
350
	 * @param	string	$root_path
351
	 * @return	bool
352
	 */
353
	public function read_dir($path, $preserve_filepath = TRUE, $root_path = NULL)
354
	{
355
		$path = rtrim($path, '/\\').DIRECTORY_SEPARATOR;
356
		if ( ! $fp = @opendir($path))
357
		{
358
			return FALSE;
359
		}
360
 
361
		// Set the original directory root for child dir's to use as relative
362
		if ($root_path === NULL)
363
		{
364
			$root_path = str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, dirname($path)).DIRECTORY_SEPARATOR;
365
		}
366
 
367
		while (FALSE !== ($file = readdir($fp)))
368
		{
369
			if ($file[0] === '.')
370
			{
371
				continue;
372
			}
373
 
374
			if (is_dir($path.$file))
375
			{
376
				$this->read_dir($path.$file.DIRECTORY_SEPARATOR, $preserve_filepath, $root_path);
377
			}
378
			elseif (FALSE !== ($data = file_get_contents($path.$file)))
379
			{
380
				$name = str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, $path);
381
				if ($preserve_filepath === FALSE)
382
				{
383
					$name = str_replace($root_path, '', $name);
384
				}
385
 
386
				$this->add_data($name.$file, $data);
387
			}
388
		}
389
 
390
		closedir($fp);
391
		return TRUE;
392
	}
393
 
394
	// --------------------------------------------------------------------
395
 
396
	/**
397
	 * Get the Zip file
398
	 *
399
	 * @return	string	(binary encoded)
400
	 */
401
	public function get_zip()
402
	{
403
		// Is there any data to return?
404
		if ($this->entries === 0)
405
		{
406
			return FALSE;
407
		}
408
 
409
		return $this->zipdata
410
			.$this->directory."\x50\x4b\x05\x06\x00\x00\x00\x00"
411
			.pack('v', $this->entries) // total # of entries "on this disk"
412
			.pack('v', $this->entries) // total # of entries overall
1257 lars 413
			.pack('V', self::strlen($this->directory)) // size of central dir
414
			.pack('V', self::strlen($this->zipdata)) // offset to start of central dir
68 lars 415
			."\x00\x00"; // .zip file comment length
416
	}
417
 
418
	// --------------------------------------------------------------------
419
 
420
	/**
421
	 * Write File to the specified directory
422
	 *
423
	 * Lets you write a file
424
	 *
425
	 * @param	string	$filepath	the file name
426
	 * @return	bool
427
	 */
428
	public function archive($filepath)
429
	{
430
		if ( ! ($fp = @fopen($filepath, 'w+b')))
431
		{
432
			return FALSE;
433
		}
434
 
435
		flock($fp, LOCK_EX);
436
 
1257 lars 437
		for ($result = $written = 0, $data = $this->get_zip(), $length = self::strlen($data); $written < $length; $written += $result)
68 lars 438
		{
1257 lars 439
			if (($result = fwrite($fp, self::substr($data, $written))) === FALSE)
68 lars 440
			{
441
				break;
442
			}
443
		}
444
 
445
		flock($fp, LOCK_UN);
446
		fclose($fp);
447
 
448
		return is_int($result);
449
	}
450
 
451
	// --------------------------------------------------------------------
452
 
453
	/**
454
	 * Download
455
	 *
456
	 * @param	string	$filename	the file name
457
	 * @return	void
458
	 */
459
	public function download($filename = 'backup.zip')
460
	{
461
		if ( ! preg_match('|.+?\.zip$|', $filename))
462
		{
463
			$filename .= '.zip';
464
		}
465
 
466
		get_instance()->load->helper('download');
467
		$get_zip = $this->get_zip();
468
		$zip_content =& $get_zip;
469
 
470
		force_download($filename, $zip_content);
471
	}
472
 
473
	// --------------------------------------------------------------------
474
 
475
	/**
476
	 * Initialize Data
477
	 *
478
	 * Lets you clear current zip data. Useful if you need to create
479
	 * multiple zips with different data.
480
	 *
481
	 * @return	CI_Zip
482
	 */
483
	public function clear_data()
484
	{
485
		$this->zipdata = '';
486
		$this->directory = '';
487
		$this->entries = 0;
488
		$this->file_num = 0;
489
		$this->offset = 0;
490
		return $this;
491
	}
492
 
1257 lars 493
	// --------------------------------------------------------------------
494
 
495
	/**
496
	 * Byte-safe strlen()
497
	 *
498
	 * @param	string	$str
499
	 * @return	int
500
	 */
501
	protected static function strlen($str)
502
	{
2107 lars 503
		return (self::$func_overload)
1257 lars 504
			? mb_strlen($str, '8bit')
505
			: strlen($str);
506
	}
507
 
508
	// --------------------------------------------------------------------
509
 
510
	/**
511
	 * Byte-safe substr()
512
	 *
513
	 * @param	string	$str
514
	 * @param	int	$start
515
	 * @param	int	$length
516
	 * @return	string
517
	 */
518
	protected static function substr($str, $start, $length = NULL)
519
	{
2107 lars 520
		if (self::$func_overload)
1257 lars 521
		{
522
			// mb_substr($str, $start, null, '8bit') returns an empty
523
			// string on PHP 5.3
524
			isset($length) OR $length = ($start >= 0 ? self::strlen($str) - $start : -$start);
525
			return mb_substr($str, $start, $length, '8bit');
526
		}
527
 
528
		return isset($length)
529
			? substr($str, $start, $length)
530
			: substr($str, $start);
531
	}
68 lars 532
}