<?php
# ***** BEGIN LICENSE BLOCK *****
# This file is part of Clearbricks.
# Copyright (c) 2003-2011 Olivier Meunier & Association Dotclear
# All rights reserved.
#
# Clearbricks is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Clearbricks is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Clearbricks; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# ***** END LICENSE BLOCK *****
class fileZip
{
protected $entries = array();
protected $root_dir = null;
protected $ctrl_dir = array();
protected $eof_ctrl_dir = "\x50\x4b\x05\x06\x00\x00\x00\x00";
protected $old_offset = 0;
protected $fp;
protected $memory_limit = null;
protected $exclusions = array();
public function __construct($out_fp)
{
if (!is_resource($out_fp)) {
throw new Exception('Output file descriptor is not a resource');
}
if (!in_array(get_resource_type($out_fp),array('stream','file'))) {
throw new Exception('Output file descriptor is not a valid resource');
}
$this->fp = $out_fp;
}
public function __destruct()
{
$this->close();
}
public function close()
{
if ($this->memory_limit) {
ini_set('memory_limit',$this->memory_limit);
}
}
public function addExclusion($reg)
{
$this->exclusions[] = $reg;
}
public function addFile($file,$name=null)
{
$file = preg_replace('#[\\\/]+#','/',$file);
if (!$name) {
$name = $file;
}
$name = $this->formatName($name);
if ($this->isExcluded($name)) {
return;
}
if (!file_exists($file) || !is_file($file)) {
throw new Exception(__('File does not exist'));
}
if (!is_readable($file)) {
throw new Exception(__('Cannot read file'));
}
$info = stat($file);
$this->entries[$name] = array(
'file' => $file,
'is_dir' => false,
'mtime' => $info['mtime'],
'size' => $info['size']
);
}
public function addDirectory($dir,$name=null,$recursive=false)
{
$dir = preg_replace('#[\\\/]+#','/',$dir);
if (substr($dir,-1-1) != '/') {
$dir .= '/';
}
if (!$name && $name !== '') {
$name = $dir;
}
if ($this->isExcluded($name)) {
return;
}
if ($name !== '')
{
if (substr($name,-1,1) != '/') {
$name .= '/';
}
$name = $this->formatName($name);
if ($name !== '')
{
$this->entries[$name] = array(
'file' => null,
'is_dir' => true,
'mtime' => time(),
'size' => 0
);
}
}
if ($recursive)
{
if (!is_dir($dir)) {
throw new Exception(__('Directory does not exist'));
}
if (!is_readable($dir)) {
throw new Exception(__('Cannot read directory'));
}
$D = dir($dir);
while (($e = $D->read()) !== false) {
if ($e == '.' || $e == '..') {
continue;
}
if (is_dir($dir.'/'.$e)) {
$this->addDirectory($dir.$e,$name.$e,true);
} elseif (is_file($dir.'/'.$e)) {
$this->addFile($dir.$e,$name.$e);
}
}
}
}
public function write()
{
foreach ($this->entries as $name => $v)
{
if ($v['is_dir']) {
$this->writeDirectory($name);
} else {
$this->writeFile($name,$v['file'],$v['size'],$v['mtime']);
}
}
$ctrldir = implode('',$this->ctrl_dir);
fwrite($this->fp,
$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',$this->old_offset). # offset to start of central dir
"\x00\x00" # .zip file comment length
);
}
protected function writeDirectory($name)
{
if (!isset($this->entries[$name])) {
return;
}
$mdate = $this->makeDate(time());
$mtime = $this->makeTime(time());
# Data descriptor
$data_desc =
"\x50\x4b\x03\x04".
"\x0a\x00". # ver needed to extract
"\x00\x00". # gen purpose bit flag
"\x00\x00". # compression method
pack('v',$mtime). # last mod time
pack('v',$mdate). # last mod date
pack('V',0). # crc32
pack('V',0). # compressed filesize
pack('V',0). # uncompressed filesize
pack('v',strlen($name)). # length of pathname
pack('v',0). # extra field length
$name. # end of "local file header" segment
pack('V',0). # crc32
pack('V',0). # compressed filesize
pack('V',0); # uncompressed filesize
$new_offset = $this->old_offset + strlen($data_desc);
fwrite($this->fp,$data_desc);
# Add to central record
$cdrec =
"\x50\x4b\x01\x02".
"\x00\x00". # version made by
"\x0a\x00". # version needed to extract
"\x00\x00". # gen purpose bit flag
"\x00\x00". # compression method
pack('v',$mtime). # last mod time
pack('v',$mdate). # last mod date
pack('V',0). # crc32
pack('V',0). # compressed filesize
pack('V',0). # uncompressed filesize
pack('v',strlen($name)). # length of filename
pack('v',0). # extra field length
pack('v',0). # file comment length
pack('v',0). # disk number start
pack('v',0). # internal file attributes
pack('V',16). # external file attributes - 'directory' bit set
pack('V',$this->old_offset). # relative offset of local header
$name;
$this->old_offset = $new_offset;
$this->ctrl_dir[] = $cdrec;
}
protected function writeFile($name,$file,$size,$mtime)
{
if (!isset($this->entries[$name])) {
return;
}
$size = filesize($file);
$this->memoryAllocate($size*3);
$content = file_get_contents($file);
$unc_len = strlen($content);
$crc = crc32($content);
$zdata = gzdeflate($content);
$c_len = strlen($zdata);
unset($content);
$mdate = $this->makeDate($mtime);
$mtime = $this->makeTime($mtime);
# Data descriptor
$data_desc =
"\x50\x4b\x03\x04".
"\x14\x00". # ver needed to extract
"\x00\x00". # gen purpose bit flag
"\x08\x00". # compression method
pack('v',$mtime). # last mod time
pack('v',$mdate). # last mod date
pack('V',$crc). # crc32
pack('V',$c_len). # compressed filesize
pack('V',$unc_len). # uncompressed filesize
pack('v',strlen($name)). # length of filename
pack('v',0). # extra field length
$name. # end of "local file header" segment
$zdata. # "file data" segment
pack('V',$crc). # crc32
pack('V',$c_len). # compressed filesize
pack('V',$unc_len); # uncompressed filesize
fwrite($this->fp,$data_desc);
unset($zdata);
$new_offset = $this->old_offset + strlen($data_desc);
# Add to central directory record
$cdrec =
"\x50\x4b\x01\x02".
"\x00\x00". # version made by
"\x14\x00". # version needed to extract
"\x00\x00". # gen purpose bit flag
"\x08\x00". # compression method
pack('v',$mtime). # last mod time
pack('v',$mdate). # last mod date
pack('V',$crc). # crc32
pack('V',$c_len). # compressed filesize
pack('V',$unc_len). # uncompressed filesize
pack('v',strlen($name)). # length of filename
pack('v',0). # extra field length
pack('v',0). # file comment length
pack('v',0). # disk number start
pack('v',0). # internal file attributes
pack('V',32). # external file attributes - 'archive' bit set
pack('V',$this->old_offset). # relative offset of local header
$name;
$this->old_offset = $new_offset;
$this->ctrl_dir[] = $cdrec;
}
protected function formatName($name)
{
if (substr($name,0,1) == '/') {
$name = substr($name,1);
}
return $name;
}
protected function isExcluded($name)
{
foreach ($this->exclusions as $reg) {
if (preg_match($reg,$name)) {
return true;
}
}
return false;
}
protected function makeDate($ts)
{
$year = date('Y',$ts)-1980;
if ($year < 0) {
$year = 0;
}
$year = sprintf('%07b',$year);
$month = sprintf('%04b',date('n',$ts));
$day = sprintf('%05b',date('j',$ts));
return bindec($year.$month.$day);
}
protected function makeTime($ts)
{
$hour = sprintf('%05b',date('G',$ts));
$minute = sprintf('%06b',date('i',$ts));
$second = sprintf('%05b',ceil(date('s',$ts)/2));
return bindec($hour.$minute.$second);
}
protected function memoryAllocate($size)
{
$mem_used = function_exists('memory_get_usage') ? @memory_get_usage() : 4000000;
$mem_limit = @ini_get('memory_limit');
if ($mem_used && $mem_limit)
{
$mem_limit = files::str2bytes($mem_limit);
$mem_avail = $mem_limit-$mem_used-(512*1024);
$mem_needed = $size;
if ($mem_needed > $mem_avail)
{
if (@ini_set('memory_limit',$mem_limit+$mem_needed+$mem_used) === false) {
throw new Exception(__('Not enough memory to open file.'));
}
if (!$this->memory_limit) {
$this->memory_limit = $mem_limit;
}
}
}
}
}
?>