(Maxg) * @author Christopher Smith */ /** * Those constants represent the compression method to use. * COMPRESS_GZIP is used for the GZIP compression; COMPRESS_BZIP for * BZIP2 and COMPRESS_NONE for no compression. * * On the other hand, COMPRESS_AUTO is a bit harder. It will first check * if the zlib extensions are loaded. * If it is, GZIP will be used. Else it will check if the bz2 extensions * are loaded. If it is, BZIP2 will be used. Else no compression will be * performed. * * You can then use getCompression() to know the compression chosen. * * If you selected a compression which can't be used (i.e extension not * present), it will be just disabled, and won't produce any error ! * As a consequence, getCompression() will return COMPRESS_NONE * * ARCHIVE_DYNAMIC can be passed as the first argument of the constructor, to * create an archive in memory instead of a file. See also: MaxgTar(), * getDynamicArchive() and writeArchive() * * ARCHIVE_RENAMECOMP is a flag that can be multiplied by the compression method * (i.e COMPRESS_AUTO * ARCHIVE_RENAMECOMP). This will add the correct extension * to the archive name, which is useful with COMPRESS_AUTO, ie .bz2 if you gave * COMPRESS_BZIP. See also getCompression(TRUE) which does exactly the * same * * COMPRESS_DETECT does exactly the opposite and try to detect the * compression to use to read the archive depending on its extension. (i.e if * the archive ends with .tar.gz TarLib will try to decompress it with * GZIP). See also setCompression() * * FULL_ARCHIVE is a -1 constant that means "the complete archive" when * extracting. This is explained in Extract() */ #define('COMPRESS_GZIP',1); #define('COMPRESS_BZIP',2); #define('COMPRESS_AUTO',3); #define('COMPRESS_NONE',0); #define('TARLIB_VERSION','1.2'); #define('FULL_ARCHIVE',-1); #define('ARCHIVE_DYNAMIC',0); #define('ARCHIVE_RENAMECOMP',5); #define('COMPRESS_DETECT',-1); class TarLib { var $_comptype; var $_compzlevel; var $_fp; var $_memdat; var $_nomf; var $_result; var $_initerror; const COMPRESS_GZIP = 1; const COMPRESS_BZIP = 2; const COMPRESS_AUTO = 3; const COMPRESS_NONE = 0; const TARLIB_VERSION = '1.2'; const FULL_ARCHIVE = -1; const ARCHIVE_DYNAMIC = 0; const ARCHIVE_RENAMECOMP = 5; const COMPRESS_DETECT = -1; /** * constructor, initialize the class * * The constructor initialize the variables and prepare the class for the * archive, and return the object created. Note that you can use multiple * instances of the MaxgTar class, if you call this function another time and * store the object in an other variable. * * In fact, MaxgTar accepts the following arguments (all are optional) : * * filename can be either a file name (absolute or relative). In this * case, it can be used both for reading and writing. You can also open * remote archive if you add a protocole name at the beginning of the file * (ie https://host.dom/archive.tar.gz), but for reading only and if the * directive allow_url_fopen is enabled in PHP.INI (this can be checked with * TarInfo()). If you pass a file that doesn't exist, the script * will try to create it. If the archive already exists and contains files, * you can use Add() to append files.But by default this parameter * is ARCHIVE_DYNAMIC (write only) so the archive is created in memory and * can be sent to a file [writeArchive()] or to the client * [sendClient()] * * compression_type should be a constant that represents a type of * compression, or its integer value. The different values are described in * the constants. * * compression_level is an integer between 1 and 9 (by default) an * represent the GZIP or BZIP compression level. 1 produce fast compression, * and 9 produce smaller files. See the RFC 1952 for more infos. */ function tarlib($p_filen = TarLib::ARCHIVE_DYNAMIC , $p_comptype = TarLib::COMPRESS_AUTO, $p_complevel = 9) { $this->_initerror = 0; $this->_nomf = $p_filen; $flag=0; if($p_comptype && $p_comptype % 5 == 0){ $p_comptype /= TarLib::ARCHIVE_RENAMECOMP; $flag=1; } if($p_complevel > 0 && $p_complevel <= 9) $this->_compzlevel = $p_complevel; else $p_complevel = 9; if($p_comptype == TarLib::COMPRESS_DETECT) { if(strtolower(substr($p_filen,-3)) == '.gz') $p_comptype = TarLib::COMPRESS_GZIP; elseif(strtolower(substr($p_filen,-4)) == '.bz2') $p_comptype = TarLib::COMPRESS_BZIP; else $p_comptype = TarLib::COMPRESS_NONE; } switch($p_comptype) { case TarLib::COMPRESS_GZIP: if(!extension_loaded('zlib')) $this->_initerror = -1; $this->_comptype = TarLib::COMPRESS_GZIP; break; case TarLib::COMPRESS_BZIP: if(!extension_loaded('bz2')) $this->_initerror = -2; $this->_comptype = TarLib::COMPRESS_BZIP; break; case TarLib::COMPRESS_AUTO: if(extension_loaded('zlib')) $this->_comptype = TarLib::COMPRESS_GZIP; elseif(extension_loaded('bz2')) $this->_comptype = TarLib::COMPRESS_BZIP; else $this->_comptype = TarLib::COMPRESS_NONE; break; default: $this->_comptype = TarLib::COMPRESS_NONE; } if($this->_initerror < 0) $this->_comptype = TarLib::COMPRESS_NONE; if($flag) $this->_nomf.= '.'.$this->getCompression(1); $this->_result = true; } /** * Recycle a TAR object. * * This function does exactly the same as TarLib (constructor), except it * returns a status code. */ function setArchive($p_name='', $p_comp = TarLib::COMPRESS_AUTO, $p_level=9) { $this->_CompTar(); $this->TarLib($p_name, $p_comp, $p_level); return $this->_result; } /** * Get the compression used to generate the archive * * This is a very useful function when you're using dynamical archives. * Besides, if you let the script chose which compression to use, you'll have * a problem when you'll want to send it to the client if you don't know * which compression was used. * * There are two ways to call this function : if you call it without argument * or with FALSE, it will return the compression constants, explained on the * MaxgTar Constants. If you call it with GetExtension on TRUE it will * return the extension without starting dot (ie "tar" or "tar.bz2" or * "tar.gz") * * NOTE: This can be done with the flag ARCHIVE_RENAMECOMP, see the * MaxgTar Constants */ function getCompression($ext = false) { $exts = Array('tar','tar.gz','tar.bz2'); if($ext) return $exts[$this->_comptype]; return $this->_comptype; } /** * Change the compression mode. * * This function will change the compression methode to read or write * the archive. See the MaxgTar Constants to see which constants you can use. * It may look strange, but it returns the GZIP compression level. */ function setCompression($p_comp = TarLib::COMPRESS_AUTO) { $this->setArchive($this->_nomf, $p_comp, $this->_compzlevel); return $this->_compzlevel; } /** * Returns the compressed dynamic archive. * * When you're working with dynamic archives, use this function to grab * the final compressed archive in a string ready to be put in a SQL table or * in a file. */ function getDynamicArchive() { return $this->_encode($this->_memdat); } /** * Write a dynamical archive into a file * * This function attempts to write a dynamicaly-genrated archive into * TargetFile on the webserver. It returns a TarErrorStr() status * code. * * To know the extension to add to the file if you're using AUTO_DETECT * compression, you can use getCompression(). */ function writeArchive($p_archive) { if(!$this->_memdat) return -7; $fp = @fopen($p_archive, 'wb'); if(!$fp) return -6; fwrite($fp, $this->_memdat); fclose($fp); return true; } /** * Send a TAR archive to the client browser. * * This function will send an archive to the client, and return a status * code, but can behave differently depending on the arguments you give. All * arguments are optional. * * ClientName is used to specify the archive name to give to the browser. If * you don't give one, it will send the constructor filename or return an * error code in case of dynamical archive. * * FileName is optional and used to send a specific archive. Leave it blank * to send dynamical archives or the current working archive. * * If SendHeaders is enabled (by default), the library will send the HTTP * headers itself before it sends the contents. This headers are : * Content-Type, Content-Disposition, Content-Length and Accept-Range. * * Please note that this function DOES NOT stops the script so don't forget * to exit() to avoid your script sending other data and corrupt the archive. * Another note : for AUTO_DETECT dynamical archives you can know the * extension to add to the name with getCompression() */ function sendClient($name = '', $archive = '', $headers = true) { if(!$name && !$this->_nomf) return -9; if(!$archive && !$this->_memdat) return -10; if(!$name) $name = basename($this->_nomf); if($archive){ if(!file_exists($archive)) return -11; } else $decoded = $this->getDynamicArchive(); if($headers) { header('Content-Type: application/x-gtar'); header('Content-Disposition: attachment; filename='.basename($name)); header('Accept-Ranges: bytes'); header('Content-Length: '.($archive ? filesize($archive) : strlen($decoded))); } if($archive) { $fp = @fopen($archive,'rb'); if(!$fp) return -4; while(!feof($fp)) echo fread($fp,2048); } else { echo $decoded; } return true; } /** * Extract part or totality of the archive. * * This function can extract files from an archive, and returns then a * status codes that can be converted with TarErrorStr() into a * human readable message. * * Only the first argument is required, What and it can be either the * constant FULL_ARCHIVE or an indexed array containing each file you want to * extract. * * To contains the target folder to extract the archive. It is optional and * the default value is '.' which means the current folder. If the target * folder doesn't exist, the script attempts to create it and give it * permissions 0777 by default. * * RemovePath is very usefull when you want to extract files from a subfoler * in the archive to a root folder. For instance, if you have a file in the * archive called some/sub/folder/test.txt and you want to extract it to the * script folder, you can call Extract with To = '.' and RemovePath = * 'some/sub/folder/' * * FileMode is optional and its default value is 0755. It is in fact the UNIX * permission in octal mode (prefixed with a 0) that will be given on each * extracted file. */ function Extract($p_what = TarLib::FULL_ARCHIVE, $p_to = '.', $p_remdir='', $p_mode = 0755) { if(!$this->_OpenRead()) return -4; // if(!@is_dir($p_to)) if(!@mkdir($p_to, 0777)) return -8; --CS if(!@is_dir($p_to)) if(!$this->_dirApp($p_to)) return -8; //--CS (route through correct dir fn) $ok = $this->_extractList($p_to, $p_what, $p_remdir, $p_mode); $this->_CompTar(); return $ok; } /** * Create a new package with the given files * * This function will attempt to create a new archive with global headers * then add the given files into. If the archive is a real file, the * contents are written directly into the file, if it is a dynamic archive * contents are only stored in memory. This function should not be used to * add files to an existing archive, you should use Add() instead. * * The FileList actually supports three different modes : * * - You can pass a string containing filenames separated by pipes '|'. * In this case the file are read from the webserver filesystem and the * root folder is the folder where the script using the MaxgTar is called. * * - You can also give a unidimensional indexed array containing the * filenames. The behaviour for the content reading is the same that a * '|'ed string. * * - The more useful usage is to pass bidimensional arrays, where the * first element contains the filename and the second contains the file * contents. You can even add empty folders to the package if the filename * has a leading '/'. Once again, have a look at the exemples to understand * better. * * Note you can also give arrays with both dynamic contents and static files. * * The optional parameter RemovePath can be used to delete a part of the tree * of the filename you're adding, for instance if you're adding in the root * of a package a file that is stored somewhere in the server tree. * * On the contrary the parameter AddPath can be used to add a prefix folder * to the file you store. Note also that the RemovePath is applied before the * AddPath is added, so it HAS a sense to use both parameters together. */ function Create($p_filelist,$p_add='',$p_rem='') { if(!$fl = $this->_fetchFilelist($p_filelist)) return -5; if(!$this->_OpenWrite()) return -6; $ok = $this->_addFileList($fl,$p_add,$p_rem); if($ok){ $this->_writeFooter(); }else{ $this->_CompTar(); @unlink($this->_nomf); } return $ok; } /** * Add files to an existing package. * * This function does exactly the same than Create() exept it * will append the given files at the end of the archive. Please not the is * safe to call Add() on a newly created archive whereas the * contrary will fail ! * * This function returns a status code, you can use TarErrorStr() on * it to get the human-readable description of the error. */ function Add($p_filelist, $p_add = '', $p_rem = '') { if (($this->_nomf != TarLib::ARCHIVE_DYNAMIC && @is_file($this->_nomf)) || ($this->_nomf == TarLib::ARCHIVE_DYNAMIC && !$this->_memdat)){ return $this->Create($p_filelist, $p_add, $p_rem); } if(!$fl = $this->_fetchFilelist($p_filelist)) return -5; return $this->_append($fl, $p_add, $p_rem); } /** * Read the contents of a TAR archive * * This function attempts to get the list of the files stored in the * archive, and return either an error code or an indexed array of * associative array containing for each file the following information : * * checksum Tar Checksum of the file * filename The full name of the stored file (up to 100 c.) * mode UNIX permissions in DECIMAL, not octal * uid The Owner ID * gid The Group ID * size Uncompressed filesize * mtime Timestamp of last modification * typeflag Empty for files, set for folders * link For the links, did you guess it ? * uname Owner name * gname Group name */ function ListContents() { if(!$this->_nomf) return -3; if(!$this->_OpenRead()) return -4; $result = Array(); while ($dat = $this->_read(512)) { $dat = $this->_readHeader($dat); if(!is_array($dat)) continue; $this->_seek(ceil($dat['size']/512)*512,1); $result[] = $dat; } return $result; } /** * Convert a status code into a human readable message * * Some MaxgTar functions like Create(), Add() ... return numerical * status code. You can pass them to this function to grab their english * equivalent. */ function TarErrorStr($i) { $ecodes = Array( 1 => true, 0 => "Undocumented error", -1 => "Can't use COMPRESS_GZIP compression : ZLIB extensions are not loaded !", -2 => "Can't use COMPRESS_BZIP compression : BZ2 extensions are not loaded !", -3 => "You must set a archive file to read the contents !", -4 => "Can't open the archive file for read !", -5 => "Invalide file list !", -6 => "Can't open the archive in write mode !", -7 => "There is no ARCHIVE_DYNAMIC to write !", -8 => "Can't create the directory to extract files !", -9 => "Please pass a archive name to send if you made created an ARCHIVE_DYNAMIC !", -10 => "You didn't pass an archive filename and there is no stored ARCHIVE_DYNAMIC !", -11 => "Given archive doesn't exist !" ); return isset($ecodes[$i]) ? $ecodes[$i] : $ecodes[0]; } function _seek($p_flen, $tell=0) { if($this->_nomf === TarLib::ARCHIVE_DYNAMIC) $this->_memdat=substr($this->_memdat,0,($tell ? strlen($this->_memdat) : 0) + $p_flen); elseif($this->_comptype == TarLib::COMPRESS_GZIP) @gzseek($this->_fp, ($tell ? @gztell($this->_fp) : 0)+$p_flen); elseif($this->_comptype == TarLib::COMPRESS_BZIP) @fseek($this->_fp, ($tell ? @ftell($this->_fp) : 0)+$p_flen); else @fseek($this->_fp, ($tell ? @ftell($this->_fp) : 0)+$p_flen); } function _OpenRead() { if($this->_comptype == TarLib::COMPRESS_GZIP) $this->_fp = @gzopen($this->_nomf, 'rb'); elseif($this->_comptype == TarLib::COMPRESS_BZIP) $this->_fp = @bzopen($this->_nomf, 'rb'); else $this->_fp = @fopen($this->_nomf, 'rb'); return ($this->_fp ? true : false); } function _OpenWrite($add = 'w') { if($this->_nomf === TarLib::ARCHIVE_DYNAMIC) return true; if($this->_comptype == TarLib::COMPRESS_GZIP) $this->_fp = @gzopen($this->_nomf, $add.'b'.$this->_compzlevel); elseif($this->_comptype == TarLib::COMPRESS_BZIP) $this->_fp = @bzopen($this->_nomf, $add.'b'); else $this->_fp = @fopen($this->_nomf, $add.'b'); return ($this->_fp ? true : false); } function _CompTar() { if($this->_nomf === TarLib::ARCHIVE_DYNAMIC || !$this->_fp) return; if($this->_comptype == TarLib::COMPRESS_GZIP) @gzclose($this->_fp); elseif($this->_comptype == TarLib::COMPRESS_BZIP) @bzclose($this->_fp); else @fclose($this->_fp); } function _read($p_len) { if($this->_comptype == TarLib::COMPRESS_GZIP) return @gzread($this->_fp,$p_len); elseif($this->_comptype == TarLib::COMPRESS_BZIP) return @bzread($this->_fp,$p_len); else return @fread($this->_fp,$p_len); } function _write($p_data) { if($this->_nomf === TarLib::ARCHIVE_DYNAMIC) $this->_memdat .= $p_data; elseif($this->_comptype == TarLib::COMPRESS_GZIP) return @gzwrite($this->_fp,$p_data); elseif($this->_comptype == TarLib::COMPRESS_BZIP) return @bzwrite($this->_fp,$p_data); else return @fwrite($this->_fp,$p_data); } function _encode($p_dat) { if($this->_comptype == TarLib::COMPRESS_GZIP) return gzencode($p_dat, $this->_compzlevel); elseif($this->_comptype == TarLib::COMPRESS_BZIP) return bzcompress($p_dat, $this->_compzlevel); else return $p_dat; } function _readHeader($p_dat) { if (!$p_dat || strlen($p_dat) != 512) return false; for ($i=0, $chks=0; $i<148; $i++) $chks += ord($p_dat[$i]); for ($i=156,$chks+=256; $i<512; $i++) $chks += ord($p_dat[$i]); $headers = @unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor", $p_dat); if(!$headers) return false; $return['checksum'] = OctDec(trim($headers['checksum'])); if ($return['checksum'] != $chks) return false; $return['filename'] = trim($headers['filename']); $return['mode'] = OctDec(trim($headers['mode'])); $return['uid'] = OctDec(trim($headers['uid'])); $return['gid'] = OctDec(trim($headers['gid'])); $return['size'] = OctDec(trim($headers['size'])); $return['mtime'] = OctDec(trim($headers['mtime'])); $return['typeflag'] = $headers['typeflag']; $return['link'] = trim($headers['link']); $return['uname'] = trim($headers['uname']); $return['gname'] = trim($headers['gname']); return $return; } function _fetchFilelist($p_filelist) { if(!$p_filelist || (is_array($p_filelist) && !@count($p_filelist))) return false; if(is_string($p_filelist)) { $p_filelist = explode('|',$p_filelist); if(!is_array($p_filelist)) $p_filelist = Array($p_filelist); } return $p_filelist; } function _addFileList($p_fl, $p_addir, $p_remdir) { foreach($p_fl as $file) { if(($file == $this->_nomf && $this->_nomf != TarLib::ARCHIVE_DYNAMIC) || !$file || (!file_exists($file) && !is_array($file))) continue; if (!$this->_addFile($file, $p_addir, $p_remdir)) continue; if (@is_dir($file)) { $d = @opendir($file); if(!$d) continue; readdir($d); readdir($d); while($f = readdir($d)) { if($file != ".") $tmplist[0] = "$file/$f"; else $tmplist[0] = $d; $this->_addFileList($tmplist, $p_addir, $p_remdir); } closedir($d); unset($tmplist,$f); } } return true; } function _addFile($p_fn, $p_addir = '', $p_remdir = '') { if(is_array($p_fn)) list($p_fn, $data) = $p_fn; $sname = $p_fn; if($p_remdir) { if(substr($p_remdir,-1) != '/') $p_remdir .= "/"; if(substr($sname, 0, strlen($p_remdir)) == $p_remdir) $sname = substr($sname, strlen($p_remdir)); } if($p_addir) $sname = $p_addir.(substr($p_addir,-1) == '/' ? '' : "/").$sname; if(strlen($sname) > 99) return; if(@is_dir($p_fn)) { if(!$this->_writeFileHeader($p_fn, $sname)) return false; } else { if(!$data) { $fp = fopen($p_fn, 'rb'); if(!$fp) return false; } if(!$this->_writeFileHeader($p_fn, $sname, ($data ? strlen($data) : false))) return false; if(!$data) { while(!feof($fp)) { $packed = pack("a512", fread($fp,512)); $this->_write($packed); } fclose($fp); } else { $len = strlen($data); for($s = 0; $s < $len; $s += 512){ $this->_write(pack("a512",substr($data,$s,512))); } } } return true; } function _writeFileHeader($p_file, $p_sname, $p_data=false) { if(!$p_data) { if (!$p_sname) $p_sname = $p_file; $p_sname = $this->_pathTrans($p_sname); $h_info = stat($p_file); $h[0] = sprintf("%6s ", DecOct($h_info[4])); $h[] = sprintf("%6s ", DecOct($h_info[5])); $h[] = sprintf("%6s ", DecOct(fileperms($p_file))); clearstatcache(); $h[] = sprintf("%11s ", DecOct(filesize($p_file))); $h[] = sprintf("%11s", DecOct(filemtime($p_file))); $dir = @is_dir($p_file) ? '5' : ''; } else { $dir = ''; $p_data = sprintf("%11s ", DecOct($p_data)); $time = sprintf("%11s ", DecOct(time())); $h = Array(" 0 "," 0 "," 40777 ",$p_data,$time); } $data_first = pack("a100a8a8a8a12A12", $p_sname, $h[2], $h[0], $h[1], $h[3], $h[4]); $data_last = pack("a1a100a6a2a32a32a8a8a155a12", $dir, '', '', '', '', '', '', '', '', ""); for ($i=0,$chks=0; $i<148; $i++) $chks += ord($data_first[$i]); for ($i=156, $chks+=256, $j=0; $i<512; $i++, $j++) $chks += ord($data_last[$j]); $this->_write($data_first); $chks = pack("a8",sprintf("%6s ", DecOct($chks))); $this->_write($chks.$data_last); return true; } function _append($p_filelist, $p_addir="", $p_remdir="") { if(!$this->_fp) if(!$this->_OpenWrite('a')) return -6; if($this->_nomf == TarLib::ARCHIVE_DYNAMIC) { $s = strlen($this->_memdat); $this->_memdat = substr($this->_memdat,0,-512); } else { $s = filesize($this->_nomf); $this->_seek($s-512); } $ok = $this->_addFileList($p_filelist, $p_addir, $p_remdir); $this->_writeFooter(); return $ok; } function _pathTrans($p_dir) { if ($p_dir) { $subf = explode("/", $p_dir); $r=''; for ($i=count($subf)-1; $i>=0; $i--) { if ($subf[$i] == ".") { # do nothing } elseif ($subf[$i] == "..") { $i--; } elseif (!$subf[$i] && $i!=count($subf)-1 && $i) { # do nothing } else { $r = $subf[$i].($i!=(count($subf)-1) ? "/".$r : ""); } } } return $r; } function _writeFooter() { $this->_write(pack("a512", "")); } function _extractList($p_to, $p_files, $p_remdir, $p_mode = 0755) { if (!$p_to || ($p_to[0]!="/"&&substr($p_to,0,3)!="../"&&substr($p_to,1,3)!=":\\"&&substr($p_to,1,2)!=":/")) /*" // <- PHP Coder bug */ $p_to = "./$p_to"; if ($p_remdir && substr($p_remdir,-1)!='/') $p_remdir .= '/'; $p_remdirs = strlen($p_remdir); while($dat = $this->_read(512)) { $headers = $this->_readHeader($dat); if(!$headers['filename']) continue; if($p_files == -1 || $p_files[0] == -1){ $extract = true; } else { $extract = false; foreach($p_files as $f) { if(substr($f,-1) == "/") { if((strlen($headers['filename']) > strlen($f)) && (substr($headers['filename'],0,strlen($f))==$f)) { $extract = true; break; } } elseif($f == $headers['filename']) { $extract = true; break; } } } if ($extract) { $det[] = $headers; if ($p_remdir && substr($headers['filename'],0,$p_remdirs)==$p_remdir) $headers['filename'] = substr($headers['filename'],$p_remdirs); if($headers['filename'].'/' == $p_remdir && $headers['typeflag']=='5') continue; if ($p_to != "./" && $p_to != "/") { while($p_to{-1}=="/") $p_to = substr($p_to,0,-1); if($headers['filename']{0} == "/") $headers['filename'] = $p_to.$headers['filename']; else $headers['filename'] = $p_to."/".$headers['filename']; } $ok = $this->_dirApp($headers['typeflag']=="5" ? $headers['filename'] : dirname($headers['filename'])); if($ok < 0) return $ok; if (!$headers['typeflag']) { if (!$fp = @fopen($headers['filename'], "wb")) return -6; $n = floor($headers['size']/512); for ($i=0; $i<$n; $i++){ fwrite($fp, $this->_read(512),512); } if (($headers['size'] % 512) != 0) fwrite($fp, $this->_read(512), $headers['size'] % 512); fclose($fp); touch($headers['filename'], $headers['mtime']); chmod($headers['filename'], $p_mode); } else { $this->_seek(ceil($headers['size']/512)*512,1); } }else $this->_seek(ceil($headers['size']/512)*512,1); } return $det; } function _dirApp($d) { // map to dokuwiki function (its more robust) return io_mkdir_p($d); } }