diff --git a/3rdparty/Archive/Tar.php b/3rdparty/Archive/Tar.php new file mode 100644 index 0000000000..d8eae851bd --- /dev/null +++ b/3rdparty/Archive/Tar.php @@ -0,0 +1,1954 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category File_Formats + * @package Archive_Tar + * @author Vincent Blavet + * @copyright 1997-2010 The Authors + * @license http://www.opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Tar.php 323476 2012-02-24 15:27:26Z mrook $ + * @link http://pear.php.net/package/Archive_Tar + */ + +require_once 'PEAR.php'; + +define('ARCHIVE_TAR_ATT_SEPARATOR', 90001); +define('ARCHIVE_TAR_END_BLOCK', pack("a512", '')); + +/** +* Creates a (compressed) Tar archive +* +* @package Archive_Tar +* @author Vincent Blavet +* @license http://www.opensource.org/licenses/bsd-license.php New BSD License +* @version $Revision: 323476 $ +*/ +class Archive_Tar extends PEAR +{ + /** + * @var string Name of the Tar + */ + var $_tarname=''; + + /** + * @var boolean if true, the Tar file will be gzipped + */ + var $_compress=false; + + /** + * @var string Type of compression : 'none', 'gz' or 'bz2' + */ + var $_compress_type='none'; + + /** + * @var string Explode separator + */ + var $_separator=' '; + + /** + * @var file descriptor + */ + var $_file=0; + + /** + * @var string Local Tar name of a remote Tar (http:// or ftp://) + */ + var $_temp_tarname=''; + + /** + * @var string regular expression for ignoring files or directories + */ + var $_ignore_regexp=''; + + /** + * @var object PEAR_Error object + */ + var $error_object=null; + + // {{{ constructor + /** + * Archive_Tar Class constructor. This flavour of the constructor only + * declare a new Archive_Tar object, identifying it by the name of the + * tar file. + * If the compress argument is set the tar will be read or created as a + * gzip or bz2 compressed TAR file. + * + * @param string $p_tarname The name of the tar archive to create + * @param string $p_compress can be null, 'gz' or 'bz2'. This + * parameter indicates if gzip or bz2 compression + * is required. For compatibility reason the + * boolean value 'true' means 'gz'. + * + * @access public + */ + function Archive_Tar($p_tarname, $p_compress = null) + { + $this->PEAR(); + $this->_compress = false; + $this->_compress_type = 'none'; + if (($p_compress === null) || ($p_compress == '')) { + if (@file_exists($p_tarname)) { + if ($fp = @fopen($p_tarname, "rb")) { + // look for gzip magic cookie + $data = fread($fp, 2); + fclose($fp); + if ($data == "\37\213") { + $this->_compress = true; + $this->_compress_type = 'gz'; + // No sure it's enought for a magic code .... + } elseif ($data == "BZ") { + $this->_compress = true; + $this->_compress_type = 'bz2'; + } + } + } else { + // probably a remote file or some file accessible + // through a stream interface + if (substr($p_tarname, -2) == 'gz') { + $this->_compress = true; + $this->_compress_type = 'gz'; + } elseif ((substr($p_tarname, -3) == 'bz2') || + (substr($p_tarname, -2) == 'bz')) { + $this->_compress = true; + $this->_compress_type = 'bz2'; + } + } + } else { + if (($p_compress === true) || ($p_compress == 'gz')) { + $this->_compress = true; + $this->_compress_type = 'gz'; + } else if ($p_compress == 'bz2') { + $this->_compress = true; + $this->_compress_type = 'bz2'; + } else { + $this->_error("Unsupported compression type '$p_compress'\n". + "Supported types are 'gz' and 'bz2'.\n"); + return false; + } + } + $this->_tarname = $p_tarname; + if ($this->_compress) { // assert zlib or bz2 extension support + if ($this->_compress_type == 'gz') + $extname = 'zlib'; + else if ($this->_compress_type == 'bz2') + $extname = 'bz2'; + + if (!extension_loaded($extname)) { + PEAR::loadExtension($extname); + } + if (!extension_loaded($extname)) { + $this->_error("The extension '$extname' couldn't be found.\n". + "Please make sure your version of PHP was built ". + "with '$extname' support.\n"); + return false; + } + } + } + // }}} + + // {{{ destructor + function _Archive_Tar() + { + $this->_close(); + // ----- Look for a local copy to delete + if ($this->_temp_tarname != '') + @unlink($this->_temp_tarname); + $this->_PEAR(); + } + // }}} + + // {{{ create() + /** + * This method creates the archive file and add the files / directories + * that are listed in $p_filelist. + * If a file with the same name exist and is writable, it is replaced + * by the new tar. + * The method return false and a PEAR error text. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * For each directory added in the archive, the files and + * sub-directories are also added. + * See also createModify() method for more details. + * + * @param array $p_filelist An array of filenames and directory names, or a + * single string with names separated by a single + * blank space. + * + * @return true on success, false on error. + * @see createModify() + * @access public + */ + function create($p_filelist) + { + return $this->createModify($p_filelist, '', ''); + } + // }}} + + // {{{ add() + /** + * This method add the files / directories that are listed in $p_filelist in + * the archive. If the archive does not exist it is created. + * The method return false and a PEAR error text. + * The files and directories listed are only added at the end of the archive, + * even if a file with the same name is already archived. + * See also createModify() method for more details. + * + * @param array $p_filelist An array of filenames and directory names, or a + * single string with names separated by a single + * blank space. + * + * @return true on success, false on error. + * @see createModify() + * @access public + */ + function add($p_filelist) + { + return $this->addModify($p_filelist, '', ''); + } + // }}} + + // {{{ extract() + function extract($p_path='', $p_preserve=false) + { + return $this->extractModify($p_path, '', $p_preserve); + } + // }}} + + // {{{ listContent() + function listContent() + { + $v_list_detail = array(); + + if ($this->_openRead()) { + if (!$this->_extractList('', $v_list_detail, "list", '', '')) { + unset($v_list_detail); + $v_list_detail = 0; + } + $this->_close(); + } + + return $v_list_detail; + } + // }}} + + // {{{ createModify() + /** + * This method creates the archive file and add the files / directories + * that are listed in $p_filelist. + * If the file already exists and is writable, it is replaced by the + * new tar. It is a create and not an add. If the file exists and is + * read-only or is a directory it is not replaced. The method return + * false and a PEAR error text. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * The path indicated in $p_remove_dir will be removed from the + * memorized path of each file / directory listed when this path + * exists. By default nothing is removed (empty path '') + * The path indicated in $p_add_dir will be added at the beginning of + * the memorized path of each file / directory listed. However it can + * be set to empty ''. The adding of a path is done after the removing + * of path. + * The path add/remove ability enables the user to prepare an archive + * for extraction in a different path than the origin files are. + * See also addModify() method for file adding properties. + * + * @param array $p_filelist An array of filenames and directory names, + * or a single string with names separated by + * a single blank space. + * @param string $p_add_dir A string which contains a path to be added + * to the memorized path of each element in + * the list. + * @param string $p_remove_dir A string which contains a path to be + * removed from the memorized path of each + * element in the list, when relevant. + * + * @return boolean true on success, false on error. + * @access public + * @see addModify() + */ + function createModify($p_filelist, $p_add_dir, $p_remove_dir='') + { + $v_result = true; + + if (!$this->_openWrite()) + return false; + + if ($p_filelist != '') { + if (is_array($p_filelist)) + $v_list = $p_filelist; + elseif (is_string($p_filelist)) + $v_list = explode($this->_separator, $p_filelist); + else { + $this->_cleanFile(); + $this->_error('Invalid file list'); + return false; + } + + $v_result = $this->_addList($v_list, $p_add_dir, $p_remove_dir); + } + + if ($v_result) { + $this->_writeFooter(); + $this->_close(); + } else + $this->_cleanFile(); + + return $v_result; + } + // }}} + + // {{{ addModify() + /** + * This method add the files / directories listed in $p_filelist at the + * end of the existing archive. If the archive does not yet exists it + * is created. + * The $p_filelist parameter can be an array of string, each string + * representing a filename or a directory name with their path if + * needed. It can also be a single string with names separated by a + * single blank. + * The path indicated in $p_remove_dir will be removed from the + * memorized path of each file / directory listed when this path + * exists. By default nothing is removed (empty path '') + * The path indicated in $p_add_dir will be added at the beginning of + * the memorized path of each file / directory listed. However it can + * be set to empty ''. The adding of a path is done after the removing + * of path. + * The path add/remove ability enables the user to prepare an archive + * for extraction in a different path than the origin files are. + * If a file/dir is already in the archive it will only be added at the + * end of the archive. There is no update of the existing archived + * file/dir. However while extracting the archive, the last file will + * replace the first one. This results in a none optimization of the + * archive size. + * If a file/dir does not exist the file/dir is ignored. However an + * error text is send to PEAR error. + * If a file/dir is not readable the file/dir is ignored. However an + * error text is send to PEAR error. + * + * @param array $p_filelist An array of filenames and directory + * names, or a single string with names + * separated by a single blank space. + * @param string $p_add_dir A string which contains a path to be + * added to the memorized path of each + * element in the list. + * @param string $p_remove_dir A string which contains a path to be + * removed from the memorized path of + * each element in the list, when + * relevant. + * + * @return true on success, false on error. + * @access public + */ + function addModify($p_filelist, $p_add_dir, $p_remove_dir='') + { + $v_result = true; + + if (!$this->_isArchive()) + $v_result = $this->createModify($p_filelist, $p_add_dir, + $p_remove_dir); + else { + if (is_array($p_filelist)) + $v_list = $p_filelist; + elseif (is_string($p_filelist)) + $v_list = explode($this->_separator, $p_filelist); + else { + $this->_error('Invalid file list'); + return false; + } + + $v_result = $this->_append($v_list, $p_add_dir, $p_remove_dir); + } + + return $v_result; + } + // }}} + + // {{{ addString() + /** + * This method add a single string as a file at the + * end of the existing archive. If the archive does not yet exists it + * is created. + * + * @param string $p_filename A string which contains the full + * filename path that will be associated + * with the string. + * @param string $p_string The content of the file added in + * the archive. + * + * @return true on success, false on error. + * @access public + */ + function addString($p_filename, $p_string) + { + $v_result = true; + + if (!$this->_isArchive()) { + if (!$this->_openWrite()) { + return false; + } + $this->_close(); + } + + if (!$this->_openAppend()) + return false; + + // Need to check the get back to the temporary file ? .... + $v_result = $this->_addString($p_filename, $p_string); + + $this->_writeFooter(); + + $this->_close(); + + return $v_result; + } + // }}} + + // {{{ extractModify() + /** + * This method extract all the content of the archive in the directory + * indicated by $p_path. When relevant the memorized path of the + * files/dir can be modified by removing the $p_remove_path path at the + * beginning of the file/dir path. + * While extracting a file, if the directory path does not exists it is + * created. + * While extracting a file, if the file already exists it is replaced + * without looking for last modification date. + * While extracting a file, if the file already exists and is write + * protected, the extraction is aborted. + * While extracting a file, if a directory with the same name already + * exists, the extraction is aborted. + * While extracting a directory, if a file with the same name already + * exists, the extraction is aborted. + * While extracting a file/directory if the destination directory exist + * and is write protected, or does not exist but can not be created, + * the extraction is aborted. + * If after extraction an extracted file does not show the correct + * stored file size, the extraction is aborted. + * When the extraction is aborted, a PEAR error text is set and false + * is returned. However the result can be a partial extraction that may + * need to be manually cleaned. + * + * @param string $p_path The path of the directory where the + * files/dir need to by extracted. + * @param string $p_remove_path Part of the memorized path that can be + * removed if present at the beginning of + * the file/dir path. + * @param boolean $p_preserve Preserve user/group ownership of files + * + * @return boolean true on success, false on error. + * @access public + * @see extractList() + */ + function extractModify($p_path, $p_remove_path, $p_preserve=false) + { + $v_result = true; + $v_list_detail = array(); + + if ($v_result = $this->_openRead()) { + $v_result = $this->_extractList($p_path, $v_list_detail, + "complete", 0, $p_remove_path, $p_preserve); + $this->_close(); + } + + return $v_result; + } + // }}} + + // {{{ extractInString() + /** + * This method extract from the archive one file identified by $p_filename. + * The return value is a string with the file content, or NULL on error. + * + * @param string $p_filename The path of the file to extract in a string. + * + * @return a string with the file content or NULL. + * @access public + */ + function extractInString($p_filename) + { + if ($this->_openRead()) { + $v_result = $this->_extractInString($p_filename); + $this->_close(); + } else { + $v_result = null; + } + + return $v_result; + } + // }}} + + // {{{ extractList() + /** + * This method extract from the archive only the files indicated in the + * $p_filelist. These files are extracted in the current directory or + * in the directory indicated by the optional $p_path parameter. + * If indicated the $p_remove_path can be used in the same way as it is + * used in extractModify() method. + * + * @param array $p_filelist An array of filenames and directory names, + * or a single string with names separated + * by a single blank space. + * @param string $p_path The path of the directory where the + * files/dir need to by extracted. + * @param string $p_remove_path Part of the memorized path that can be + * removed if present at the beginning of + * the file/dir path. + * @param boolean $p_preserve Preserve user/group ownership of files + * + * @return true on success, false on error. + * @access public + * @see extractModify() + */ + function extractList($p_filelist, $p_path='', $p_remove_path='', $p_preserve=false) + { + $v_result = true; + $v_list_detail = array(); + + if (is_array($p_filelist)) + $v_list = $p_filelist; + elseif (is_string($p_filelist)) + $v_list = explode($this->_separator, $p_filelist); + else { + $this->_error('Invalid string list'); + return false; + } + + if ($v_result = $this->_openRead()) { + $v_result = $this->_extractList($p_path, $v_list_detail, "partial", + $v_list, $p_remove_path, $p_preserve); + $this->_close(); + } + + return $v_result; + } + // }}} + + // {{{ setAttribute() + /** + * This method set specific attributes of the archive. It uses a variable + * list of parameters, in the format attribute code + attribute values : + * $arch->setAttribute(ARCHIVE_TAR_ATT_SEPARATOR, ','); + * + * @param mixed $argv variable list of attributes and values + * + * @return true on success, false on error. + * @access public + */ + function setAttribute() + { + $v_result = true; + + // ----- Get the number of variable list of arguments + if (($v_size = func_num_args()) == 0) { + return true; + } + + // ----- Get the arguments + $v_att_list = &func_get_args(); + + // ----- Read the attributes + $i=0; + while ($i<$v_size) { + + // ----- Look for next option + switch ($v_att_list[$i]) { + // ----- Look for options that request a string value + case ARCHIVE_TAR_ATT_SEPARATOR : + // ----- Check the number of parameters + if (($i+1) >= $v_size) { + $this->_error('Invalid number of parameters for ' + .'attribute ARCHIVE_TAR_ATT_SEPARATOR'); + return false; + } + + // ----- Get the value + $this->_separator = $v_att_list[$i+1]; + $i++; + break; + + default : + $this->_error('Unknow attribute code '.$v_att_list[$i].''); + return false; + } + + // ----- Next attribute + $i++; + } + + return $v_result; + } + // }}} + + // {{{ setIgnoreRegexp() + /** + * This method sets the regular expression for ignoring files and directories + * at import, for example: + * $arch->setIgnoreRegexp("#CVS|\.svn#"); + * + * @param string $regexp regular expression defining which files or directories to ignore + * + * @access public + */ + function setIgnoreRegexp($regexp) + { + $this->_ignore_regexp = $regexp; + } + // }}} + + // {{{ setIgnoreList() + /** + * This method sets the regular expression for ignoring all files and directories + * matching the filenames in the array list at import, for example: + * $arch->setIgnoreList(array('CVS', '.svn', 'bin/tool')); + * + * @param array $list a list of file or directory names to ignore + * + * @access public + */ + function setIgnoreList($list) + { + $regexp = str_replace(array('#', '.', '^', '$'), array('\#', '\.', '\^', '\$'), $list); + $regexp = '#/'.join('$|/', $list).'#'; + $this->setIgnoreRegexp($regexp); + } + // }}} + + // {{{ _error() + function _error($p_message) + { + $this->error_object = $this->raiseError($p_message); + } + // }}} + + // {{{ _warning() + function _warning($p_message) + { + $this->error_object = $this->raiseError($p_message); + } + // }}} + + // {{{ _isArchive() + function _isArchive($p_filename=null) + { + if ($p_filename == null) { + $p_filename = $this->_tarname; + } + clearstatcache(); + return @is_file($p_filename) && !@is_link($p_filename); + } + // }}} + + // {{{ _openWrite() + function _openWrite() + { + if ($this->_compress_type == 'gz' && function_exists('gzopen')) + $this->_file = @gzopen($this->_tarname, "wb9"); + else if ($this->_compress_type == 'bz2' && function_exists('bzopen')) + $this->_file = @bzopen($this->_tarname, "w"); + else if ($this->_compress_type == 'none') + $this->_file = @fopen($this->_tarname, "wb"); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + if ($this->_file == 0) { + $this->_error('Unable to open in write mode \'' + .$this->_tarname.'\''); + return false; + } + + return true; + } + // }}} + + // {{{ _openRead() + function _openRead() + { + if (strtolower(substr($this->_tarname, 0, 7)) == 'http://') { + + // ----- Look if a local copy need to be done + if ($this->_temp_tarname == '') { + $this->_temp_tarname = uniqid('tar').'.tmp'; + if (!$v_file_from = @fopen($this->_tarname, 'rb')) { + $this->_error('Unable to open in read mode \'' + .$this->_tarname.'\''); + $this->_temp_tarname = ''; + return false; + } + if (!$v_file_to = @fopen($this->_temp_tarname, 'wb')) { + $this->_error('Unable to open in write mode \'' + .$this->_temp_tarname.'\''); + $this->_temp_tarname = ''; + return false; + } + while ($v_data = @fread($v_file_from, 1024)) + @fwrite($v_file_to, $v_data); + @fclose($v_file_from); + @fclose($v_file_to); + } + + // ----- File to open if the local copy + $v_filename = $this->_temp_tarname; + + } else + // ----- File to open if the normal Tar file + $v_filename = $this->_tarname; + + if ($this->_compress_type == 'gz') + $this->_file = @gzopen($v_filename, "rb"); + else if ($this->_compress_type == 'bz2') + $this->_file = @bzopen($v_filename, "r"); + else if ($this->_compress_type == 'none') + $this->_file = @fopen($v_filename, "rb"); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + if ($this->_file == 0) { + $this->_error('Unable to open in read mode \''.$v_filename.'\''); + return false; + } + + return true; + } + // }}} + + // {{{ _openReadWrite() + function _openReadWrite() + { + if ($this->_compress_type == 'gz') + $this->_file = @gzopen($this->_tarname, "r+b"); + else if ($this->_compress_type == 'bz2') { + $this->_error('Unable to open bz2 in read/write mode \'' + .$this->_tarname.'\' (limitation of bz2 extension)'); + return false; + } else if ($this->_compress_type == 'none') + $this->_file = @fopen($this->_tarname, "r+b"); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + if ($this->_file == 0) { + $this->_error('Unable to open in read/write mode \'' + .$this->_tarname.'\''); + return false; + } + + return true; + } + // }}} + + // {{{ _close() + function _close() + { + //if (isset($this->_file)) { + if (is_resource($this->_file)) { + if ($this->_compress_type == 'gz') + @gzclose($this->_file); + else if ($this->_compress_type == 'bz2') + @bzclose($this->_file); + else if ($this->_compress_type == 'none') + @fclose($this->_file); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + $this->_file = 0; + } + + // ----- Look if a local copy need to be erase + // Note that it might be interesting to keep the url for a time : ToDo + if ($this->_temp_tarname != '') { + @unlink($this->_temp_tarname); + $this->_temp_tarname = ''; + } + + return true; + } + // }}} + + // {{{ _cleanFile() + function _cleanFile() + { + $this->_close(); + + // ----- Look for a local copy + if ($this->_temp_tarname != '') { + // ----- Remove the local copy but not the remote tarname + @unlink($this->_temp_tarname); + $this->_temp_tarname = ''; + } else { + // ----- Remove the local tarname file + @unlink($this->_tarname); + } + $this->_tarname = ''; + + return true; + } + // }}} + + // {{{ _writeBlock() + function _writeBlock($p_binary_data, $p_len=null) + { + if (is_resource($this->_file)) { + if ($p_len === null) { + if ($this->_compress_type == 'gz') + @gzputs($this->_file, $p_binary_data); + else if ($this->_compress_type == 'bz2') + @bzwrite($this->_file, $p_binary_data); + else if ($this->_compress_type == 'none') + @fputs($this->_file, $p_binary_data); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + } else { + if ($this->_compress_type == 'gz') + @gzputs($this->_file, $p_binary_data, $p_len); + else if ($this->_compress_type == 'bz2') + @bzwrite($this->_file, $p_binary_data, $p_len); + else if ($this->_compress_type == 'none') + @fputs($this->_file, $p_binary_data, $p_len); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + } + } + return true; + } + // }}} + + // {{{ _readBlock() + function _readBlock() + { + $v_block = null; + if (is_resource($this->_file)) { + if ($this->_compress_type == 'gz') + $v_block = @gzread($this->_file, 512); + else if ($this->_compress_type == 'bz2') + $v_block = @bzread($this->_file, 512); + else if ($this->_compress_type == 'none') + $v_block = @fread($this->_file, 512); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + } + return $v_block; + } + // }}} + + // {{{ _jumpBlock() + function _jumpBlock($p_len=null) + { + if (is_resource($this->_file)) { + if ($p_len === null) + $p_len = 1; + + if ($this->_compress_type == 'gz') { + @gzseek($this->_file, gztell($this->_file)+($p_len*512)); + } + else if ($this->_compress_type == 'bz2') { + // ----- Replace missing bztell() and bzseek() + for ($i=0; $i<$p_len; $i++) + $this->_readBlock(); + } else if ($this->_compress_type == 'none') + @fseek($this->_file, $p_len*512, SEEK_CUR); + else + $this->_error('Unknown or missing compression type (' + .$this->_compress_type.')'); + + } + return true; + } + // }}} + + // {{{ _writeFooter() + function _writeFooter() + { + if (is_resource($this->_file)) { + // ----- Write the last 0 filled block for end of archive + $v_binary_data = pack('a1024', ''); + $this->_writeBlock($v_binary_data); + } + return true; + } + // }}} + + // {{{ _addList() + function _addList($p_list, $p_add_dir, $p_remove_dir) + { + $v_result=true; + $v_header = array(); + + // ----- Remove potential windows directory separator + $p_add_dir = $this->_translateWinPath($p_add_dir); + $p_remove_dir = $this->_translateWinPath($p_remove_dir, false); + + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } + + if (sizeof($p_list) == 0) + return true; + + foreach ($p_list as $v_filename) { + if (!$v_result) { + break; + } + + // ----- Skip the current tar name + if ($v_filename == $this->_tarname) + continue; + + if ($v_filename == '') + continue; + + // ----- ignore files and directories matching the ignore regular expression + if ($this->_ignore_regexp && preg_match($this->_ignore_regexp, '/'.$v_filename)) { + $this->_warning("File '$v_filename' ignored"); + continue; + } + + if (!file_exists($v_filename) && !is_link($v_filename)) { + $this->_warning("File '$v_filename' does not exist"); + continue; + } + + // ----- Add the file or directory header + if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir)) + return false; + + if (@is_dir($v_filename) && !@is_link($v_filename)) { + if (!($p_hdir = opendir($v_filename))) { + $this->_warning("Directory '$v_filename' can not be read"); + continue; + } + while (false !== ($p_hitem = readdir($p_hdir))) { + if (($p_hitem != '.') && ($p_hitem != '..')) { + if ($v_filename != ".") + $p_temp_list[0] = $v_filename.'/'.$p_hitem; + else + $p_temp_list[0] = $p_hitem; + + $v_result = $this->_addList($p_temp_list, + $p_add_dir, + $p_remove_dir); + } + } + + unset($p_temp_list); + unset($p_hdir); + unset($p_hitem); + } + } + + return $v_result; + } + // }}} + + // {{{ _addFile() + function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir,$v_stored_filename=null) + { + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } + + if ($p_filename == '') { + $this->_error('Invalid file name'); + return false; + } + if(is_null($v_stored_filename)){ + + // ----- Calculate the stored filename + $p_filename = $this->_translateWinPath($p_filename, false); + $v_stored_filename = $p_filename; + if (strcmp($p_filename, $p_remove_dir) == 0) { + return true; + } + if ($p_remove_dir != '') { + if (substr($p_remove_dir, -1) != '/') + $p_remove_dir .= '/'; + + if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir) + $v_stored_filename = substr($p_filename, strlen($p_remove_dir)); + } + $v_stored_filename = $this->_translateWinPath($v_stored_filename); + if ($p_add_dir != '') { + if (substr($p_add_dir, -1) == '/') + $v_stored_filename = $p_add_dir.$v_stored_filename; + else + $v_stored_filename = $p_add_dir.'/'.$v_stored_filename; + } + + $v_stored_filename = $this->_pathReduction($v_stored_filename); + } + + if ($this->_isArchive($p_filename)) { + if (($v_file = @fopen($p_filename, "rb")) == 0) { + $this->_warning("Unable to open file '".$p_filename + ."' in binary read mode"); + return true; + } + + if (!$this->_writeHeader($p_filename, $v_stored_filename)) + return false; + + while (($v_buffer = fread($v_file, 512)) != '') { + $v_binary_data = pack("a512", "$v_buffer"); + $this->_writeBlock($v_binary_data); + } + + fclose($v_file); + + } else { + // ----- Only header for dir + if (!$this->_writeHeader($p_filename, $v_stored_filename)) + return false; + } + + return true; + } + // }}} + + // {{{ _addString() + function _addString($p_filename, $p_string) + { + if (!$this->_file) { + $this->_error('Invalid file descriptor'); + return false; + } + + if ($p_filename == '') { + $this->_error('Invalid file name'); + return false; + } + + // ----- Calculate the stored filename + $p_filename = $this->_translateWinPath($p_filename, false);; + + if (!$this->_writeHeaderBlock($p_filename, strlen($p_string), + time(), 384, "", 0, 0)) + return false; + + $i=0; + while (($v_buffer = substr($p_string, (($i++)*512), 512)) != '') { + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } + + return true; + } + // }}} + + // {{{ _writeHeader() + function _writeHeader($p_filename, $p_stored_filename) + { + if ($p_stored_filename == '') + $p_stored_filename = $p_filename; + $v_reduce_filename = $this->_pathReduction($p_stored_filename); + + if (strlen($v_reduce_filename) > 99) { + if (!$this->_writeLongHeader($v_reduce_filename)) + return false; + } + + $v_info = lstat($p_filename); + $v_uid = sprintf("%07s", DecOct($v_info[4])); + $v_gid = sprintf("%07s", DecOct($v_info[5])); + $v_perms = sprintf("%07s", DecOct($v_info['mode'] & 000777)); + + $v_mtime = sprintf("%011s", DecOct($v_info['mtime'])); + + $v_linkname = ''; + + if (@is_link($p_filename)) { + $v_typeflag = '2'; + $v_linkname = readlink($p_filename); + $v_size = sprintf("%011s", DecOct(0)); + } elseif (@is_dir($p_filename)) { + $v_typeflag = "5"; + $v_size = sprintf("%011s", DecOct(0)); + } else { + $v_typeflag = '0'; + clearstatcache(); + $v_size = sprintf("%011s", DecOct($v_info['size'])); + } + + $v_magic = 'ustar '; + + $v_version = ' '; + + if (function_exists('posix_getpwuid')) + { + $userinfo = posix_getpwuid($v_info[4]); + $groupinfo = posix_getgrgid($v_info[5]); + + $v_uname = $userinfo['name']; + $v_gname = $groupinfo['name']; + } + else + { + $v_uname = ''; + $v_gname = ''; + } + + $v_devmajor = ''; + + $v_devminor = ''; + + $v_prefix = ''; + + $v_binary_data_first = pack("a100a8a8a8a12a12", + $v_reduce_filename, $v_perms, $v_uid, + $v_gid, $v_size, $v_mtime); + $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", + $v_typeflag, $v_linkname, $v_magic, + $v_version, $v_uname, $v_gname, + $v_devmajor, $v_devminor, $v_prefix, ''); + + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum += ord(substr($v_binary_data_first,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156, $j=0; $i<512; $i++, $j++) + $v_checksum += ord(substr($v_binary_data_last,$j,1)); + + // ----- Write the first 148 bytes of the header in the archive + $this->_writeBlock($v_binary_data_first, 148); + + // ----- Write the calculated checksum + $v_checksum = sprintf("%06s ", DecOct($v_checksum)); + $v_binary_data = pack("a8", $v_checksum); + $this->_writeBlock($v_binary_data, 8); + + // ----- Write the last 356 bytes of the header in the archive + $this->_writeBlock($v_binary_data_last, 356); + + return true; + } + // }}} + + // {{{ _writeHeaderBlock() + function _writeHeaderBlock($p_filename, $p_size, $p_mtime=0, $p_perms=0, + $p_type='', $p_uid=0, $p_gid=0) + { + $p_filename = $this->_pathReduction($p_filename); + + if (strlen($p_filename) > 99) { + if (!$this->_writeLongHeader($p_filename)) + return false; + } + + if ($p_type == "5") { + $v_size = sprintf("%011s", DecOct(0)); + } else { + $v_size = sprintf("%011s", DecOct($p_size)); + } + + $v_uid = sprintf("%07s", DecOct($p_uid)); + $v_gid = sprintf("%07s", DecOct($p_gid)); + $v_perms = sprintf("%07s", DecOct($p_perms & 000777)); + + $v_mtime = sprintf("%11s", DecOct($p_mtime)); + + $v_linkname = ''; + + $v_magic = 'ustar '; + + $v_version = ' '; + + if (function_exists('posix_getpwuid')) + { + $userinfo = posix_getpwuid($p_uid); + $groupinfo = posix_getgrgid($p_gid); + + $v_uname = $userinfo['name']; + $v_gname = $groupinfo['name']; + } + else + { + $v_uname = ''; + $v_gname = ''; + } + + $v_devmajor = ''; + + $v_devminor = ''; + + $v_prefix = ''; + + $v_binary_data_first = pack("a100a8a8a8a12A12", + $p_filename, $v_perms, $v_uid, $v_gid, + $v_size, $v_mtime); + $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", + $p_type, $v_linkname, $v_magic, + $v_version, $v_uname, $v_gname, + $v_devmajor, $v_devminor, $v_prefix, ''); + + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum += ord(substr($v_binary_data_first,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156, $j=0; $i<512; $i++, $j++) + $v_checksum += ord(substr($v_binary_data_last,$j,1)); + + // ----- Write the first 148 bytes of the header in the archive + $this->_writeBlock($v_binary_data_first, 148); + + // ----- Write the calculated checksum + $v_checksum = sprintf("%06s ", DecOct($v_checksum)); + $v_binary_data = pack("a8", $v_checksum); + $this->_writeBlock($v_binary_data, 8); + + // ----- Write the last 356 bytes of the header in the archive + $this->_writeBlock($v_binary_data_last, 356); + + return true; + } + // }}} + + // {{{ _writeLongHeader() + function _writeLongHeader($p_filename) + { + $v_size = sprintf("%11s ", DecOct(strlen($p_filename))); + + $v_typeflag = 'L'; + + $v_linkname = ''; + + $v_magic = ''; + + $v_version = ''; + + $v_uname = ''; + + $v_gname = ''; + + $v_devmajor = ''; + + $v_devminor = ''; + + $v_prefix = ''; + + $v_binary_data_first = pack("a100a8a8a8a12a12", + '././@LongLink', 0, 0, 0, $v_size, 0); + $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", + $v_typeflag, $v_linkname, $v_magic, + $v_version, $v_uname, $v_gname, + $v_devmajor, $v_devminor, $v_prefix, ''); + + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum += ord(substr($v_binary_data_first,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156, $j=0; $i<512; $i++, $j++) + $v_checksum += ord(substr($v_binary_data_last,$j,1)); + + // ----- Write the first 148 bytes of the header in the archive + $this->_writeBlock($v_binary_data_first, 148); + + // ----- Write the calculated checksum + $v_checksum = sprintf("%06s ", DecOct($v_checksum)); + $v_binary_data = pack("a8", $v_checksum); + $this->_writeBlock($v_binary_data, 8); + + // ----- Write the last 356 bytes of the header in the archive + $this->_writeBlock($v_binary_data_last, 356); + + // ----- Write the filename as content of the block + $i=0; + while (($v_buffer = substr($p_filename, (($i++)*512), 512)) != '') { + $v_binary_data = pack("a512", "$v_buffer"); + $this->_writeBlock($v_binary_data); + } + + return true; + } + // }}} + + // {{{ _readHeader() + function _readHeader($v_binary_data, &$v_header) + { + if (strlen($v_binary_data)==0) { + $v_header['filename'] = ''; + return true; + } + + if (strlen($v_binary_data) != 512) { + $v_header['filename'] = ''; + $this->_error('Invalid block size : '.strlen($v_binary_data)); + return false; + } + + if (!is_array($v_header)) { + $v_header = array(); + } + // ----- Calculate the checksum + $v_checksum = 0; + // ..... First part of the header + for ($i=0; $i<148; $i++) + $v_checksum+=ord(substr($v_binary_data,$i,1)); + // ..... Ignore the checksum value and replace it by ' ' (space) + for ($i=148; $i<156; $i++) + $v_checksum += ord(' '); + // ..... Last part of the header + for ($i=156; $i<512; $i++) + $v_checksum+=ord(substr($v_binary_data,$i,1)); + + $v_data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/" . + "a8checksum/a1typeflag/a100link/a6magic/a2version/" . + "a32uname/a32gname/a8devmajor/a8devminor/a131prefix", + $v_binary_data); + + if (strlen($v_data["prefix"]) > 0) { + $v_data["filename"] = "$v_data[prefix]/$v_data[filename]"; + } + + // ----- Extract the checksum + $v_header['checksum'] = OctDec(trim($v_data['checksum'])); + if ($v_header['checksum'] != $v_checksum) { + $v_header['filename'] = ''; + + // ----- Look for last block (empty block) + if (($v_checksum == 256) && ($v_header['checksum'] == 0)) + return true; + + $this->_error('Invalid checksum for file "'.$v_data['filename'] + .'" : '.$v_checksum.' calculated, ' + .$v_header['checksum'].' expected'); + return false; + } + + // ----- Extract the properties + $v_header['filename'] = $v_data['filename']; + if ($this->_maliciousFilename($v_header['filename'])) { + $this->_error('Malicious .tar detected, file "' . $v_header['filename'] . + '" will not install in desired directory tree'); + return false; + } + $v_header['mode'] = OctDec(trim($v_data['mode'])); + $v_header['uid'] = OctDec(trim($v_data['uid'])); + $v_header['gid'] = OctDec(trim($v_data['gid'])); + $v_header['size'] = OctDec(trim($v_data['size'])); + $v_header['mtime'] = OctDec(trim($v_data['mtime'])); + if (($v_header['typeflag'] = $v_data['typeflag']) == "5") { + $v_header['size'] = 0; + } + $v_header['link'] = trim($v_data['link']); + /* ----- All these fields are removed form the header because + they do not carry interesting info + $v_header[magic] = trim($v_data[magic]); + $v_header[version] = trim($v_data[version]); + $v_header[uname] = trim($v_data[uname]); + $v_header[gname] = trim($v_data[gname]); + $v_header[devmajor] = trim($v_data[devmajor]); + $v_header[devminor] = trim($v_data[devminor]); + */ + + return true; + } + // }}} + + // {{{ _maliciousFilename() + /** + * Detect and report a malicious file name + * + * @param string $file + * + * @return bool + * @access private + */ + function _maliciousFilename($file) + { + if (strpos($file, '/../') !== false) { + return true; + } + if (strpos($file, '../') === 0) { + return true; + } + return false; + } + // }}} + + // {{{ _readLongHeader() + function _readLongHeader(&$v_header) + { + $v_filename = ''; + $n = floor($v_header['size']/512); + for ($i=0; $i<$n; $i++) { + $v_content = $this->_readBlock(); + $v_filename .= $v_content; + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + $v_filename .= trim($v_content); + } + + // ----- Read the next header + $v_binary_data = $this->_readBlock(); + + if (!$this->_readHeader($v_binary_data, $v_header)) + return false; + + $v_filename = trim($v_filename); + $v_header['filename'] = $v_filename; + if ($this->_maliciousFilename($v_filename)) { + $this->_error('Malicious .tar detected, file "' . $v_filename . + '" will not install in desired directory tree'); + return false; + } + + return true; + } + // }}} + + // {{{ _extractInString() + /** + * This method extract from the archive one file identified by $p_filename. + * The return value is a string with the file content, or null on error. + * + * @param string $p_filename The path of the file to extract in a string. + * + * @return a string with the file content or null. + * @access private + */ + function _extractInString($p_filename) + { + $v_result_str = ""; + + While (strlen($v_binary_data = $this->_readBlock()) != 0) + { + if (!$this->_readHeader($v_binary_data, $v_header)) + return null; + + if ($v_header['filename'] == '') + continue; + + // ----- Look for long filename + if ($v_header['typeflag'] == 'L') { + if (!$this->_readLongHeader($v_header)) + return null; + } + + if ($v_header['filename'] == $p_filename) { + if ($v_header['typeflag'] == "5") { + $this->_error('Unable to extract in string a directory ' + .'entry {'.$v_header['filename'].'}'); + return null; + } else { + $n = floor($v_header['size']/512); + for ($i=0; $i<$n; $i++) { + $v_result_str .= $this->_readBlock(); + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + $v_result_str .= substr($v_content, 0, + ($v_header['size'] % 512)); + } + return $v_result_str; + } + } else { + $this->_jumpBlock(ceil(($v_header['size']/512))); + } + } + + return null; + } + // }}} + + // {{{ _extractList() + function _extractList($p_path, &$p_list_detail, $p_mode, + $p_file_list, $p_remove_path, $p_preserve=false) + { + $v_result=true; + $v_nb = 0; + $v_extract_all = true; + $v_listing = false; + + $p_path = $this->_translateWinPath($p_path, false); + if ($p_path == '' || (substr($p_path, 0, 1) != '/' + && substr($p_path, 0, 3) != "../" && !strpos($p_path, ':'))) { + $p_path = "./".$p_path; + } + $p_remove_path = $this->_translateWinPath($p_remove_path); + + // ----- Look for path to remove format (should end by /) + if (($p_remove_path != '') && (substr($p_remove_path, -1) != '/')) + $p_remove_path .= '/'; + $p_remove_path_size = strlen($p_remove_path); + + switch ($p_mode) { + case "complete" : + $v_extract_all = true; + $v_listing = false; + break; + case "partial" : + $v_extract_all = false; + $v_listing = false; + break; + case "list" : + $v_extract_all = false; + $v_listing = true; + break; + default : + $this->_error('Invalid extract mode ('.$p_mode.')'); + return false; + } + + clearstatcache(); + + while (strlen($v_binary_data = $this->_readBlock()) != 0) + { + $v_extract_file = FALSE; + $v_extraction_stopped = 0; + + if (!$this->_readHeader($v_binary_data, $v_header)) + return false; + + if ($v_header['filename'] == '') { + continue; + } + + // ----- Look for long filename + if ($v_header['typeflag'] == 'L') { + if (!$this->_readLongHeader($v_header)) + return false; + } + + if ((!$v_extract_all) && (is_array($p_file_list))) { + // ----- By default no unzip if the file is not found + $v_extract_file = false; + + for ($i=0; $i strlen($p_file_list[$i])) + && (substr($v_header['filename'], 0, strlen($p_file_list[$i])) + == $p_file_list[$i])) { + $v_extract_file = true; + break; + } + } + + // ----- It is a file, so compare the file names + elseif ($p_file_list[$i] == $v_header['filename']) { + $v_extract_file = true; + break; + } + } + } else { + $v_extract_file = true; + } + + // ----- Look if this file need to be extracted + if (($v_extract_file) && (!$v_listing)) + { + if (($p_remove_path != '') + && (substr($v_header['filename'], 0, $p_remove_path_size) + == $p_remove_path)) + $v_header['filename'] = substr($v_header['filename'], + $p_remove_path_size); + if (($p_path != './') && ($p_path != '/')) { + while (substr($p_path, -1) == '/') + $p_path = substr($p_path, 0, strlen($p_path)-1); + + if (substr($v_header['filename'], 0, 1) == '/') + $v_header['filename'] = $p_path.$v_header['filename']; + else + $v_header['filename'] = $p_path.'/'.$v_header['filename']; + } + if (file_exists($v_header['filename'])) { + if ( (@is_dir($v_header['filename'])) + && ($v_header['typeflag'] == '')) { + $this->_error('File '.$v_header['filename'] + .' already exists as a directory'); + return false; + } + if ( ($this->_isArchive($v_header['filename'])) + && ($v_header['typeflag'] == "5")) { + $this->_error('Directory '.$v_header['filename'] + .' already exists as a file'); + return false; + } + if (!is_writeable($v_header['filename'])) { + $this->_error('File '.$v_header['filename'] + .' already exists and is write protected'); + return false; + } + if (filemtime($v_header['filename']) > $v_header['mtime']) { + // To be completed : An error or silent no replace ? + } + } + + // ----- Check the directory availability and create it if necessary + elseif (($v_result + = $this->_dirCheck(($v_header['typeflag'] == "5" + ?$v_header['filename'] + :dirname($v_header['filename'])))) != 1) { + $this->_error('Unable to create path for '.$v_header['filename']); + return false; + } + + if ($v_extract_file) { + if ($v_header['typeflag'] == "5") { + if (!@file_exists($v_header['filename'])) { + if (!@mkdir($v_header['filename'], 0777)) { + $this->_error('Unable to create directory {' + .$v_header['filename'].'}'); + return false; + } + } + } elseif ($v_header['typeflag'] == "2") { + if (@file_exists($v_header['filename'])) { + @unlink($v_header['filename']); + } + if (!@symlink($v_header['link'], $v_header['filename'])) { + $this->_error('Unable to extract symbolic link {' + .$v_header['filename'].'}'); + return false; + } + } else { + if (($v_dest_file = @fopen($v_header['filename'], "wb")) == 0) { + $this->_error('Error while opening {'.$v_header['filename'] + .'} in write binary mode'); + return false; + } else { + $n = floor($v_header['size']/512); + for ($i=0; $i<$n; $i++) { + $v_content = $this->_readBlock(); + fwrite($v_dest_file, $v_content, 512); + } + if (($v_header['size'] % 512) != 0) { + $v_content = $this->_readBlock(); + fwrite($v_dest_file, $v_content, ($v_header['size'] % 512)); + } + + @fclose($v_dest_file); + + if ($p_preserve) { + @chown($v_header['filename'], $v_header['uid']); + @chgrp($v_header['filename'], $v_header['gid']); + } + + // ----- Change the file mode, mtime + @touch($v_header['filename'], $v_header['mtime']); + if ($v_header['mode'] & 0111) { + // make file executable, obey umask + $mode = fileperms($v_header['filename']) | (~umask() & 0111); + @chmod($v_header['filename'], $mode); + } + } + + // ----- Check the file size + clearstatcache(); + if (!is_file($v_header['filename'])) { + $this->_error('Extracted file '.$v_header['filename'] + .'does not exist. Archive may be corrupted.'); + return false; + } + + $filesize = filesize($v_header['filename']); + if ($filesize != $v_header['size']) { + $this->_error('Extracted file '.$v_header['filename'] + .' does not have the correct file size \'' + .$filesize + .'\' ('.$v_header['size'] + .' expected). Archive may be corrupted.'); + return false; + } + } + } else { + $this->_jumpBlock(ceil(($v_header['size']/512))); + } + } else { + $this->_jumpBlock(ceil(($v_header['size']/512))); + } + + /* TBC : Seems to be unused ... + if ($this->_compress) + $v_end_of_file = @gzeof($this->_file); + else + $v_end_of_file = @feof($this->_file); + */ + + if ($v_listing || $v_extract_file || $v_extraction_stopped) { + // ----- Log extracted files + if (($v_file_dir = dirname($v_header['filename'])) + == $v_header['filename']) + $v_file_dir = ''; + if ((substr($v_header['filename'], 0, 1) == '/') && ($v_file_dir == '')) + $v_file_dir = '/'; + + $p_list_detail[$v_nb++] = $v_header; + if (is_array($p_file_list) && (count($p_list_detail) == count($p_file_list))) { + return true; + } + } + } + + return true; + } + // }}} + + // {{{ _openAppend() + function _openAppend() + { + if (filesize($this->_tarname) == 0) + return $this->_openWrite(); + + if ($this->_compress) { + $this->_close(); + + if (!@rename($this->_tarname, $this->_tarname.".tmp")) { + $this->_error('Error while renaming \''.$this->_tarname + .'\' to temporary file \''.$this->_tarname + .'.tmp\''); + return false; + } + + if ($this->_compress_type == 'gz') + $v_temp_tar = @gzopen($this->_tarname.".tmp", "rb"); + elseif ($this->_compress_type == 'bz2') + $v_temp_tar = @bzopen($this->_tarname.".tmp", "r"); + + if ($v_temp_tar == 0) { + $this->_error('Unable to open file \''.$this->_tarname + .'.tmp\' in binary read mode'); + @rename($this->_tarname.".tmp", $this->_tarname); + return false; + } + + if (!$this->_openWrite()) { + @rename($this->_tarname.".tmp", $this->_tarname); + return false; + } + + if ($this->_compress_type == 'gz') { + while (!@gzeof($v_temp_tar)) { + $v_buffer = @gzread($v_temp_tar, 512); + if ($v_buffer == ARCHIVE_TAR_END_BLOCK || strlen($v_buffer) == 0) { + // do not copy end blocks, we will re-make them + // after appending + continue; + } + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } + + @gzclose($v_temp_tar); + } + elseif ($this->_compress_type == 'bz2') { + while (strlen($v_buffer = @bzread($v_temp_tar, 512)) > 0) { + if ($v_buffer == ARCHIVE_TAR_END_BLOCK) { + continue; + } + $v_binary_data = pack("a512", $v_buffer); + $this->_writeBlock($v_binary_data); + } + + @bzclose($v_temp_tar); + } + + if (!@unlink($this->_tarname.".tmp")) { + $this->_error('Error while deleting temporary file \'' + .$this->_tarname.'.tmp\''); + } + + } else { + // ----- For not compressed tar, just add files before the last + // one or two 512 bytes block + if (!$this->_openReadWrite()) + return false; + + clearstatcache(); + $v_size = filesize($this->_tarname); + + // We might have zero, one or two end blocks. + // The standard is two, but we should try to handle + // other cases. + fseek($this->_file, $v_size - 1024); + if (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { + fseek($this->_file, $v_size - 1024); + } + elseif (fread($this->_file, 512) == ARCHIVE_TAR_END_BLOCK) { + fseek($this->_file, $v_size - 512); + } + } + + return true; + } + // }}} + + // {{{ _append() + function _append($p_filelist, $p_add_dir='', $p_remove_dir='') + { + if (!$this->_openAppend()) + return false; + + if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir)) + $this->_writeFooter(); + + $this->_close(); + + return true; + } + // }}} + + // {{{ _dirCheck() + + /** + * Check if a directory exists and create it (including parent + * dirs) if not. + * + * @param string $p_dir directory to check + * + * @return bool true if the directory exists or was created + */ + function _dirCheck($p_dir) + { + clearstatcache(); + if ((@is_dir($p_dir)) || ($p_dir == '')) + return true; + + $p_parent_dir = dirname($p_dir); + + if (($p_parent_dir != $p_dir) && + ($p_parent_dir != '') && + (!$this->_dirCheck($p_parent_dir))) + return false; + + if (!@mkdir($p_dir, 0777)) { + $this->_error("Unable to create directory '$p_dir'"); + return false; + } + + return true; + } + + // }}} + + // {{{ _pathReduction() + + /** + * Compress path by changing for example "/dir/foo/../bar" to "/dir/bar", + * rand emove double slashes. + * + * @param string $p_dir path to reduce + * + * @return string reduced path + * + * @access private + * + */ + function _pathReduction($p_dir) + { + $v_result = ''; + + // ----- Look for not empty path + if ($p_dir != '') { + // ----- Explode path by directory names + $v_list = explode('/', $p_dir); + + // ----- Study directories from last to first + for ($i=sizeof($v_list)-1; $i>=0; $i--) { + // ----- Look for current path + if ($v_list[$i] == ".") { + // ----- Ignore this directory + // Should be the first $i=0, but no check is done + } + else if ($v_list[$i] == "..") { + // ----- Ignore it and ignore the $i-1 + $i--; + } + else if ( ($v_list[$i] == '') + && ($i!=(sizeof($v_list)-1)) + && ($i!=0)) { + // ----- Ignore only the double '//' in path, + // but not the first and last / + } else { + $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?'/' + .$v_result:''); + } + } + } + + if (defined('OS_WINDOWS') && OS_WINDOWS) { + $v_result = strtr($v_result, '\\', '/'); + } + + return $v_result; + } + + // }}} + + // {{{ _translateWinPath() + function _translateWinPath($p_path, $p_remove_disk_letter=true) + { + if (defined('OS_WINDOWS') && OS_WINDOWS) { + // ----- Look for potential disk letter + if ( ($p_remove_disk_letter) + && (($v_position = strpos($p_path, ':')) != false)) { + $p_path = substr($p_path, $v_position+1); + } + // ----- Change potential windows directory separator + if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) { + $p_path = strtr($p_path, '\\', '/'); + } + } + return $p_path; + } + // }}} + +} +?> diff --git a/apps/calendar/ajax/import/import.php b/apps/calendar/ajax/import/import.php index c0797f6e42..d0bdab4f0d 100644 --- a/apps/calendar/ajax/import/import.php +++ b/apps/calendar/ajax/import/import.php @@ -10,7 +10,7 @@ ob_start(); require_once('../../../../lib/base.php'); OC_JSON::checkLoggedIn(); OC_Util::checkAppEnabled('calendar'); -$nl = "\n"; +$nl = "\n\r"; $progressfile = OC::$APPSROOT . '/apps/calendar/import_tmp/' . md5(session_id()) . '.txt'; if(is_writable('import_tmp/')){ $progressfopen = fopen($progressfile, 'w'); diff --git a/apps/calendar/export.php b/apps/calendar/export.php index 9886ad8e8c..2736eec96c 100644 --- a/apps/calendar/export.php +++ b/apps/calendar/export.php @@ -11,7 +11,7 @@ OC_Util::checkLoggedIn(); OC_Util::checkAppEnabled('calendar'); $cal = isset($_GET['calid']) ? $_GET['calid'] : NULL; $event = isset($_GET['eventid']) ? $_GET['eventid'] : NULL; -$nl = "\n"; +$nl = "\n\r"; if(isset($cal)){ $calendar = OC_Calendar_App::getCalendar($cal); $calobjects = OC_Calendar_Object::all($cal); diff --git a/apps/contacts/ajax/deletecard.php b/apps/contacts/ajax/deletecard.php index e26dfd6ebf..5675aef5f1 100644 --- a/apps/contacts/ajax/deletecard.php +++ b/apps/contacts/ajax/deletecard.php @@ -19,6 +19,11 @@ * License along with this library. If not, see . * */ +function bailOut($msg) { + OC_JSON::error(array('data' => array('message' => $msg))); + OC_Log::write('contacts','ajax/saveproperty.php: '.$msg, OC_Log::DEBUG); + exit(); +} // Init owncloud require_once('../../../lib/base.php'); @@ -27,7 +32,10 @@ require_once('../../../lib/base.php'); OC_JSON::checkLoggedIn(); OC_JSON::checkAppEnabled('contacts'); -$id = $_GET['id']; +$id = isset($_GET['id'])?$_GET['id']:null; +if(!$id) { + bailOut(OC_Contacts_App::$l10n->t('id is not set.')); +} $card = OC_Contacts_App::getContactObject( $id ); OC_Contacts_VCard::delete($id); diff --git a/apps/contacts/css/contacts.css b/apps/contacts/css/contacts.css index 7c36a511d6..76b5972ba3 100644 --- a/apps/contacts/css/contacts.css +++ b/apps/contacts/css/contacts.css @@ -10,8 +10,6 @@ #contacts_propertymenu_button { position:absolute;top:15px;right:150px; background:url('../../../core/img/actions/add.svg') no-repeat center; } #contacts_propertymenu { background-color: #fff; position:absolute;top:40px;right:150px; overflow:hidden; text-overflow:ellipsis; /*border: thin solid #1d2d44;*/ -moz-box-shadow:0 0 10px #000; -webkit-box-shadow:0 0 10px #000; box-shadow:0 0 10px #000; -moz-border-radius:0.5em; -webkit-border-radius:0.5em; border-radius:0.5em; -moz-border-radius:0.5em; -webkit-border-radius:0.5em; border-radius:0.5em; } #contacts_propertymenu li { display: block; font-weight: bold; height: 20px; width: 100px; } -/*#contacts_propertymenu li:first-child { border-top: thin solid #1d2d44; -moz-border-radius-topleft:0.5em; -webkit-border-top-left-radius:0.5em; border-top-left-radius:0.5em; -moz-border-radius-topright:0.5em; -webkit-border-top-right-radius:0.5em; border-top-right-radius:0.5em; } -#contacts_propertymenu li:last-child { border-bottom: thin solid #1d2d44; -moz-border-radius-bottomleft:0.5em; -webkit-border-bottom-left-radius:0.5em; border-bottom-left-radius:0.5em; -moz-border-radius-bottomright:0.5em; -webkit-border-bottom-right-radius:0.5em; border-bottom-right-radius:0.5em; }*/ #contacts_propertymenu li a { padding: 3px; display: block } #contacts_propertymenu li:hover { background-color: #1d2d44; } #contacts_propertymenu li a:hover { color: #fff } @@ -25,54 +23,12 @@ #card input[type="text"],input[type="email"],input[type="tel"],input[type="date"], select { background-color: #f8f8f8; border: 0 !important; -webkit-appearance:none !important; -moz-appearance:none !important; -webkit-box-sizing:none !important; -moz-box-sizing:none !important; box-sizing:none !important; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; -moz-border-radius: 0px; -webkit-border-radius: 0px; border-radius: 0px; float: left; } #card input[type="text"]:hover, input[type="text"]:focus, input[type="text"]:active,input[type="email"]:hover,input[type="tel"]:hover,input[type="date"]:hover,input[type="date"],input[type="date"]:hover,input[type="date"]:active,input[type="date"]:active,input[type="date"]:active,input[type="email"]:active,input[type="tel"]:active, select:hover, select:focus, select:active { border: 0 !important; -webkit-appearance:textfield; -moz-appearance:textfield; -webkit-box-sizing:content-box; -moz-box-sizing:content-box; box-sizing:content-box; background:#fff; color:#333; border:1px solid #ddd; -moz-box-shadow:0 1px 1px #fff, 0 2px 0 #bbb inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; box-shadow:0 1px 1px #fff, 0 1px 0 #bbb inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; outline:none; float: left; } input[type="text"]:invalid,input[type="email"]:invalid,input[type="tel"]:invalid,input[type="date"]:invalid { background-color: #ffc0c0 !important; } -/*input[type="text"]:valid,input[type="email"]:valid,input[type="tel"]:valid,input[type="date"]:valid { background-color: #b1d28f !important; }*/ -dl.form -{ - width: 100%; - float: left; - clear: right; - margin: 0; - padding: 0; -} - -.form dt -{ - display: table-cell; - clear: left; - float: left; - width: 7em; - /*overflow: hidden;*/ - margin: 0; - padding: 0.8em 0.5em 0 0; - font-weight: bold; - text-align:right; - text-overflow:ellipsis; - o-text-overflow: ellipsis; - vertical-align: text-bottom; - /* - white-space: pre-wrap; - white-space: -moz-pre-wrap !important; - white-space: -pre-wrap; - white-space: -o-pre-wrap;*/ -} - -.form dd -{ - display: table-cell; - clear: right; - float: left; - margin: 0; - padding: 0px; - white-space: nowrap; - vertical-align: text-bottom; - /*min-width: 20em;*/ - /*background-color: yellow;*/ -} +dl.form { width: 100%; float: left; clear: right; margin: 0; padding: 0; } +.form dt { display: table-cell; clear: left; float: left; width: 7em; margin: 0; padding: 0.8em 0.5em 0 0; font-weight: bold; text-align:right; text-overflow:ellipsis; o-text-overflow: ellipsis; vertical-align: text-bottom;/* white-space: pre-wrap; white-space: -moz-pre-wrap !important; white-space: -pre-wrap; white-space: -o-pre-wrap;*/ } +.form dd { display: table-cell; clear: right; float: left; margin: 0; padding: 0px; white-space: nowrap; vertical-align: text-bottom; } .loading { background: url('../../../core/img/loading.gif') no-repeat center !important; /*cursor: progress; */ cursor: wait; } -/*.add { cursor: pointer; width: 25px; height: 25px; margin: 0px; float: right; position:relative; content: "\+"; font-weight: bold; color: #666; font-size: large; bottom: 0px; right: 0px; clear: both; text-align: center; vertical-align: bottom; display: none; }*/ - .listactions { height: 1em; width:60px; float: left; clear: right; } .add,.edit,.delete,.mail, .globe { cursor: pointer; width: 20px; height: 20px; margin: 0; float: left; position:relative; display: none; } .add { background:url('../../../core/img/actions/add.svg') no-repeat center; clear: both; } @@ -82,75 +38,21 @@ dl.form /*.globe { background:url('../img/globe.svg') no-repeat center; }*/ .globe { background:url('../../../core/img/actions/public.svg') no-repeat center; } -#messagebox_msg { font-weight: bold; font-size: 1.2em; } - -/* Name editor */ -#edit_name_dialog { - /*width: 25em;*/ - padding:0; -} -#edit_name_dialog > input { - width: 15em; -} -/* Address editor */ -#edit_address_dialog { - /*width: 30em;*/ -} -#edit_address_dialog > input { - width: 15em; -} +#edit_name_dialog { padding:0; } +#edit_name_dialog > input { width: 15em; } +#edit_address_dialog { /*width: 30em;*/ } +#edit_address_dialog > input { width: 15em; } #edit_photo_dialog_img { display: block; width: 150; height: 200; border: thin solid black; } #fn { float: left; } /** * Create classes form, floateven and floatodd which flows left and right respectively. */ -.contactsection { - float: left; - min-width: 30em; - max-width: 40em; - margin: 0.5em; - border: thin solid lightgray; - -webkit-border-radius: 0.5em; - -moz-border-radius: 0.5em; - border-radius: 0.5em; - background-color: #f8f8f8; -} +.contactsection { float: left; min-width: 30em; max-width: 40em; margin: 0.5em; border: thin solid lightgray; -webkit-border-radius: 0.5em; -moz-border-radius: 0.5em; border-radius: 0.5em; background-color: #f8f8f8; } -.contactpart legend { - /*background: #fff; - font-weight: bold; - left: 1em; - border: thin solid gray; - -webkit-border-radius: 0.5em; - -moz-border-radius: 0.5em; - border-radius: 0.5em; - padding: 3px;*/ -width:auto; padding:.3em; border:1px solid #ddd; font-weight:bold; cursor:pointer; background:#f8f8f8; color:#555; text-shadow:#fff 0 1px 0; -moz-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; -} -/*#contacts_details_photo { - cursor: pointer; - z-index:1; - margin: auto; -} -*/ -#cropbox { - margin: auto; -} +.contactpart legend { width:auto; padding:.3em; border:1px solid #ddd; font-weight:bold; cursor:pointer; background:#f8f8f8; color:#555; text-shadow:#fff 0 1px 0; -moz-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; } +#cropbox { margin: auto; } -/* Photo editor */ -/*#contacts_details_photo_wrapper { - z-index: 1000; -}*/ -#contacts_details_photo { - border-radius: 0.5em; - border: thin solid #bbb; - padding: 0.5em; - margin: 1em 1em 1em 7em; - cursor: pointer; - /*background: #f8f8f8;*/ - background: url(../../../core/img/loading.gif) no-repeat center center; - clear: right; -} +#contacts_details_photo { border-radius: 0.5em; border: thin solid #bbb; padding: 0.5em; margin: 1em 1em 1em 7em; cursor: pointer; background: url(../../../core/img/loading.gif) no-repeat center center; clear: right; } #contacts_details_photo:hover { background: #fff; } #contacts_details_photo_progress { margin: 0.3em 0.3em 0.3em 7em; clear: left; } /* Address editor */ @@ -168,13 +70,6 @@ dl.addresscard dd > ul { margin: 0.3em; padding: 0.3em; } #adr_zipcode {} #adr_country {} -.delimiter { - height: 10px; - clear: both; -} - -/*input[type="text"] { float: left; max-width: 15em; } -input[type="radio"] { float: left; -khtml-appearance: none; width: 20px; height: 20px; vertical-align: middle; }*/ #file_upload_target, #crop_target { display:none; } #file_upload_start { opacity:0; filter:alpha(opacity=0); z-index:1; position:absolute; left:0; top:0; cursor:pointer; width:0; height:0;} diff --git a/apps/contacts/js/contacts.js b/apps/contacts/js/contacts.js index e182702745..d314878cc0 100644 --- a/apps/contacts/js/contacts.js +++ b/apps/contacts/js/contacts.js @@ -213,19 +213,27 @@ Contacts={ honpre:'', honsuf:'', data:undefined, - update:function() { + update:function(id) { // Make sure proper DOM is loaded. - console.log('Card.update(), #n: ' + $('#n').length); + var newid; + console.log('Card.update(), id: ' + id); console.log('Card.update(), #contacts: ' + $('#contacts li').length); - if($('#n').length == 0 && $('#contacts li').length > 0) { + if(id == undefined) { + newid = $('#contacts li:first-child').data('id'); + } else { + newid = id; + } + if($('#contacts li').length > 0) { $.getJSON(OC.filePath('contacts', 'ajax', 'loadcard.php'),{},function(jsondata){ if(jsondata.status == 'success'){ $('#rightcontent').html(jsondata.data.page); Contacts.UI.loadHandlers(); if($('#contacts li').length > 0) { - var firstid = $('#contacts li:first-child').data('id'); - console.log('trying to load: ' + firstid); - $.getJSON(OC.filePath('contacts', 'ajax', 'contactdetails.php'),{'id':firstid},function(jsondata){ + //var newid = $('#contacts li:first-child').data('id'); + //$('#contacts li:first-child').addClass('active'); + $('#leftcontent li[data-id="'+newid+'"]').addClass('active'); + console.log('trying to load: ' + newid); + $.getJSON(OC.filePath('contacts', 'ajax', 'contactdetails.php'),{'id':newid},function(jsondata){ if(jsondata.status == 'success'){ Contacts.UI.Card.loadContact(jsondata.data); } else{ @@ -300,35 +308,49 @@ Contacts={ } }); }, - delete: function() { + delete:function() { $('#contacts_deletecard').tipsy('hide'); - $.getJSON('ajax/deletecard.php',{'id':this.id},function(jsondata){ - if(jsondata.status == 'success'){ - $('#leftcontent [data-id="'+jsondata.data.id+'"]').remove(); - $('#rightcontent').data('id',''); - //$('#rightcontent').empty(); - this.id = this.fn = this.fullname = this.shortname = this.famname = this.givname = this.addname = this.honpre = this.honsuf = ''; - this.data = undefined; - // Load first in list. - if($('#contacts li').length > 0) { - Contacts.UI.Card.update(); - } else { - // load intro page - $.getJSON('ajax/loadintro.php',{},function(jsondata){ - if(jsondata.status == 'success'){ - id = ''; - $('#rightcontent').data('id',''); - $('#rightcontent').html(jsondata.data.page); + OC.dialogs.confirm(t('contacts', 'Are you sure you want to delete this contact?'), t('contacts', 'Warning'), function(answer) { + if(answer == true) { + $.getJSON('ajax/deletecard.php',{'id':Contacts.UI.Card.id},function(jsondata){ + if(jsondata.status == 'success'){ + var newid = ''; + var curlistitem = $('#leftcontent [data-id="'+jsondata.data.id+'"]'); + var newlistitem = curlistitem.prev(); + console.log('Previous: ' + newlistitem); + if(newlistitem == undefined) { + newlistitem = curlistitem.next(); } - else{ - OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); + curlistitem.remove(); + if(newlistitem != undefined) { + newid = newlistitem.data('id'); } - }); - } - } - else{ - OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); - //alert(jsondata.data.message); + $('#rightcontent').data('id',newid); + //$('#rightcontent').empty(); + this.id = this.fn = this.fullname = this.shortname = this.famname = this.givname = this.addname = this.honpre = this.honsuf = ''; + this.data = undefined; + // Load first in list. + if($('#contacts li').length > 0) { + Contacts.UI.Card.update(newid); + } else { + // load intro page + $.getJSON('ajax/loadintro.php',{},function(jsondata){ + if(jsondata.status == 'success'){ + id = ''; + $('#rightcontent').data('id',''); + $('#rightcontent').html(jsondata.data.page); + } + else{ + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); + } + }); + } + } + else{ + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); + //alert(jsondata.data.message); + } + }); } }); return false; @@ -1232,6 +1254,7 @@ $(document).ready(function(){ */ $('#leftcontent li').live('click',function(){ var id = $(this).data('id'); + $(this).addClass('active'); var oldid = $('#rightcontent').data('id'); if(oldid != 0){ $('#leftcontent li[data-id="'+oldid+'"]').removeClass('active'); diff --git a/apps/contacts/templates/part.contact.php b/apps/contacts/templates/part.contact.php index a93069fa72..d243c2b5e1 100644 --- a/apps/contacts/templates/part.contact.php +++ b/apps/contacts/templates/part.contact.php @@ -56,13 +56,6 @@ $id = isset($_['id']) ? $_['id'] : ''; - @@ -131,6 +124,7 @@ $(document).ready(function(){ if(''!='') { $.getJSON(OC.filePath('contacts', 'ajax', 'contactdetails.php'),{'id':''},function(jsondata){ if(jsondata.status == 'success'){ + $('#leftcontent li[data-id=""]').addClass('active'); Contacts.UI.Card.loadContact(jsondata.data); } else{ diff --git a/apps/files_archive/appinfo/app.php b/apps/files_archive/appinfo/app.php index 693c28d98a..67809ec980 100644 --- a/apps/files_archive/appinfo/app.php +++ b/apps/files_archive/appinfo/app.php @@ -7,7 +7,8 @@ */ OC::$CLASSPATH['OC_Archive'] = 'apps/files_archive/lib/archive.php'; -foreach(array('ZIP') as $type){ +OC::$CLASSPATH['Archive_Tar'] = '3rdparty/Archive/Tar.php'; +foreach(array('ZIP','TAR') as $type){ OC::$CLASSPATH['OC_Archive_'.$type] = 'apps/files_archive/lib/'.strtolower($type).'.php'; } diff --git a/apps/files_archive/js/archive.js b/apps/files_archive/js/archive.js index ec316c7bf2..531eb61c01 100644 --- a/apps/files_archive/js/archive.js +++ b/apps/files_archive/js/archive.js @@ -11,5 +11,9 @@ $(document).ready(function() { window.location='index.php?dir='+encodeURIComponent($('#dir').val()).replace(/%2F/g, '/')+'/'+encodeURIComponent(filename); }); FileActions.setDefault('application/zip','Open'); + FileActions.register('application/x-gzip','Open','',function(filename){ + window.location='index.php?dir='+encodeURIComponent($('#dir').val()).replace(/%2F/g, '/')+'/'+encodeURIComponent(filename); + }); + FileActions.setDefault('application/x-gzip','Open'); } }); diff --git a/apps/files_archive/lib/archive.php b/apps/files_archive/lib/archive.php index be89f894fb..113f92e960 100644 --- a/apps/files_archive/lib/archive.php +++ b/apps/files_archive/lib/archive.php @@ -17,6 +17,15 @@ abstract class OC_Archive{ switch($ext){ case '.zip': return new OC_Archive_ZIP($path); + case '.gz': + case '.bz': + case '.bz2': + if(strpos($path,'.tar.')){ + return new OC_Archive_TAR($path); + } + break; + case '.tgz': + return new OC_Archive_TAR($path); } } @@ -77,6 +86,13 @@ abstract class OC_Archive{ * @return bool */ abstract function extractFile($path,$dest); + /** + * extract the archive + * @param string path + * @param string dest + * @return bool + */ + abstract function extract($dest); /** * check if a file or folder exists in the archive * @param string path diff --git a/apps/files_archive/lib/storage.php b/apps/files_archive/lib/storage.php index 72a96ca5a5..700d963304 100644 --- a/apps/files_archive/lib/storage.php +++ b/apps/files_archive/lib/storage.php @@ -125,7 +125,7 @@ class OC_Filestorage_Archive extends OC_Filestorage_Common{ self::$rootView=new OC_FilesystemView(''); } self::$enableAutomount=false;//prevent recursion - $supported=array('zip'); + $supported=array('zip','tar.gz','tar.bz2','tgz'); foreach($supported as $type){ $ext='.'.$type.'/'; if(($pos=strpos(strtolower($path),$ext))!==false){ @@ -139,4 +139,8 @@ class OC_Filestorage_Archive extends OC_Filestorage_Common{ } self::$enableAutomount=true; } + + public function rename($path1,$path2){ + return $this->archive->rename($path1,$path2); + } } diff --git a/apps/files_archive/lib/tar.php b/apps/files_archive/lib/tar.php new file mode 100644 index 0000000000..a5d5400478 --- /dev/null +++ b/apps/files_archive/lib/tar.php @@ -0,0 +1,277 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +class OC_Archive_TAR extends OC_Archive{ + const PLAIN=0; + const GZIP=1; + const BZIP=2; + + /** + * @var Archive_Tar tar + */ + private $tar=null; + private $path; + + function __construct($source){ + $types=array(null,'gz','bz'); + $this->path=$source; + $this->tar=new Archive_Tar($source,$types[self::getTarType($source)]); + } + + /** + * try to detect the type of tar compression + * @param string file + * @return str + */ + static public function getTarType($file){ + if(strpos($file,'.')){ + $extention=substr($file,strrpos($file,'.')); + switch($extention){ + case 'gz': + case 'tgz': + return self::GZIP; + case 'bz': + case 'bz2': + return self::BZIP; + default: + return self::PLAIN; + } + }else{ + return self::PLAIN; + } + } + + /** + * add an empty folder to the archive + * @param string path + * @return bool + */ + function addFolder($path){ + $tmpBase=get_temp_dir().'/'; + if(substr($path,-1,1)!='/'){ + $path.='/'; + } + if($this->fileExists($path)){ + return false; + } + mkdir($tmpBase.$path); + $result=$this->tar->addModify(array($tmpBase.$path),'',$tmpBase); + rmdir($tmpBase.$path); + return $result; + } + /** + * add a file to the archive + * @param string path + * @param string source either a local file or string data + * @return bool + */ + function addFile($path,$source=''){ + if($this->fileExists($path)){ + $this->remove($path); + } + if(file_exists($source)){ + $header=array(); + $dummy=''; + $this->tar->_openAppend(); + $result=$this->tar->_addfile($source,$header,$dummy,$dummy,$path); + }else{ + $result=$this->tar->addString($path,$source); + } + return $result; + } + + /** + * rename a file or folder in the archive + * @param string source + * @param string dest + * @return bool + */ + function rename($source,$dest){ + //no proper way to delete, rename entire archive, rename file and remake archive + $tmp=OC_Helper::tmpFolder(); + $this->tar->extract($tmp); + rename($tmp.$source,$tmp.$dest); + $this->tar=null; + unlink($this->path); + $types=array(null,'gz','bz'); + $this->tar=new Archive_Tar($this->path,$types[self::getTarType($this->path)]); + $this->tar->createModify(array($tmp),'',$tmp.'/'); + } + + private function getHeader($file){ + $headers=$this->tar->listContent(); + foreach($headers as $header){ + if($file==$header['filename'] or $file.'/'==$header['filename']){ + return $header; + } + } + return null; + } + + /** + * get the uncompressed size of a file in the archive + * @param string path + * @return int + */ + function filesize($path){ + $stat=$this->getHeader($path); + return $stat['size']; + } + /** + * get the last modified time of a file in the archive + * @param string path + * @return int + */ + function mtime($path){ + $stat=$this->getHeader($path); + return $stat['mtime']; + } + + /** + * get the files in a folder + * @param path + * @return array + */ + function getFolder($path){ + $files=$this->getFiles(); + $folderContent=array(); + $pathLength=strlen($path); + foreach($files as $file){ + if(substr($file,0,$pathLength)==$path and $file!=$path){ + if(strrpos(substr($file,0,-1),'/')<=$pathLength){ + $folderContent[]=substr($file,$pathLength); + } + } + } + return $folderContent; + } + /** + *get all files in the archive + * @return array + */ + function getFiles(){ + $headers=$this->tar->listContent(); + $files=array(); + foreach($headers as $header){ + $files[]=$header['filename']; + } + return $files; + } + /** + * get the content of a file + * @param string path + * @return string + */ + function getFile($path){ + return $this->tar->extractInString($path); + } + /** + * extract a single file from the archive + * @param string path + * @param string dest + * @return bool + */ + function extractFile($path,$dest){ + $tmp=OC_Helper::tmpFolder(); + if(!$this->fileExists($path)){ + return false; + } + $success=$this->tar->extractList(array($path),$tmp); + if($success){ + rename($tmp.$path,$dest); + } + OC_Helper::rmdirr($tmp); + return $success; + } + /** + * extract the archive + * @param string path + * @param string dest + * @return bool + */ + function extract($dest){ + return $this->tar->extract($dest); + } + /** + * check if a file or folder exists in the archive + * @param string path + * @return bool + */ + function fileExists($path){ + return $this->getHeader($path)!==null; + } + + /** + * remove a file or folder from the archive + * @param string path + * @return bool + */ + function remove($path){ + if(!$this->fileExists($path)){ + return false; + } + //no proper way to delete, extract entire archive, delete file and remake archive + $tmp=OC_Helper::tmpFolder(); + $this->tar->extract($tmp); + OC_Helper::rmdirr($tmp.$path); + $this->tar=null; + unlink($this->path); + $this->reopen(); + $this->tar->createModify(array($tmp),'',$tmp); + return true; + } + /** + * get a file handler + * @param string path + * @param string mode + * @return resource + */ + function getStream($path,$mode){ + if(strrpos($path,'.')!==false){ + $ext=substr($path,strrpos($path,'.')); + }else{ + $ext=''; + } + $tmpFile=OC_Helper::tmpFile($ext); + if($this->fileExists($path)){ + $this->extractFile($path,$tmpFile); + }elseif($mode=='r' or $mode=='rb'){ + return false; + } + if($mode=='r' or $mode=='rb'){ + return fopen($tmpFile,$mode); + }else{ + OC_CloseStreamWrapper::$callBacks[$tmpFile]=array($this,'writeBack'); + self::$tempFiles[$tmpFile]=$path; + return fopen('close://'.$tmpFile,$mode); + } + } + + private static $tempFiles=array(); + /** + * write back temporary files + */ + function writeBack($tmpFile){ + if(isset(self::$tempFiles[$tmpFile])){ + $this->addFile(self::$tempFiles[$tmpFile],$tmpFile); + unlink($tmpFile); + } + } + + /** + * reopen the archive to ensure everything is written + */ + private function reopen(){ + if($this->tar){ + $this->tar->_close(); + $this->tar=null; + } + $types=array(null,'gz','bz'); + $this->tar=new Archive_Tar($this->path,$types[self::getTarType($this->path)]); + } +} diff --git a/apps/files_archive/lib/zip.php b/apps/files_archive/lib/zip.php index eab101b3a5..5a5bc76687 100644 --- a/apps/files_archive/lib/zip.php +++ b/apps/files_archive/lib/zip.php @@ -11,7 +11,6 @@ class OC_Archive_ZIP extends OC_Archive{ * @var ZipArchive zip */ private $zip=null; - private $contents=array(); private $success=false; private $path; @@ -56,7 +55,9 @@ class OC_Archive_ZIP extends OC_Archive{ * @return bool */ function rename($source,$dest){ - return $this->zip->renameName($source,$dest); + $source=$this->stripPath($source); + $dest=$this->stripPath($dest); + $this->zip->renameName($source,$dest); } /** * get the uncompressed size of a file in the archive @@ -99,15 +100,11 @@ class OC_Archive_ZIP extends OC_Archive{ * @return array */ function getFiles(){ - if(count($this->contents)){ - return $this->contents; - } $fileCount=$this->zip->numFiles; $files=array(); for($i=0;$i<$fileCount;$i++){ $files[]=$this->zip->getNameIndex($i); } - $this->contents=$files; return $files; } /** @@ -128,13 +125,22 @@ class OC_Archive_ZIP extends OC_Archive{ $fp = $this->zip->getStream($path); file_put_contents($dest,$fp); } + /** + * extract the archive + * @param string path + * @param string dest + * @return bool + */ + function extract($dest){ + return $this->zip->extractTo($dest); + } /** * check if a file or folder exists in the archive * @param string path * @return bool */ function fileExists($path){ - return $this->zip->locateName($path)!==false; + return ($this->zip->locateName($path)!==false) or ($this->zip->locateName($path.'/')!==false); } /** * remove a file or folder from the archive @@ -142,7 +148,11 @@ class OC_Archive_ZIP extends OC_Archive{ * @return bool */ function remove($path){ - return $this->zip->deleteName($path); + if($this->fileExists($path.'/')){ + return $this->zip->deleteName($path.'/'); + }else{ + return $this->zip->deleteName($path); + } } /** * get a file handler @@ -179,4 +189,12 @@ class OC_Archive_ZIP extends OC_Archive{ unlink($tmpFile); } } + + private function stripPath($path){ + if(substr($path,0,1)=='/'){ + return substr($path,1); + }else{ + return $path; + } + } } diff --git a/apps/files_archive/tests/archive.php b/apps/files_archive/tests/archive.php index 2e26b5e03b..9e99466a52 100644 --- a/apps/files_archive/tests/archive.php +++ b/apps/files_archive/tests/archive.php @@ -27,10 +27,10 @@ abstract class Test_Archive extends UnitTestCase { $this->instance=$this->getExisting(); $allFiles=$this->instance->getFiles(); $expected=array('lorem.txt','logo-wide.png','dir/','dir/lorem.txt'); - $this->assertEqual(4,count($allFiles)); + $this->assertEqual(4,count($allFiles),'only found '.count($allFiles).' out of 4 expected files'); foreach($expected as $file){ $this->assertNotIdentical(false,array_search($file,$allFiles),'cant find '.$file.' in archive'); - $this->assertTrue($this->instance->fileExists($file)); + $this->assertTrue($this->instance->fileExists($file),'file '.$file.' does not exist in archive'); } $this->assertFalse($this->instance->fileExists('non/existing/file')); @@ -68,6 +68,7 @@ abstract class Test_Archive extends UnitTestCase { $this->instance->addFile('lorem.txt',$textFile); $this->assertEqual(1,count($this->instance->getFiles())); $this->assertTrue($this->instance->fileExists('lorem.txt')); + $this->assertFalse($this->instance->fileExists('lorem.txt/')); $this->assertEqual(file_get_contents($textFile),$this->instance->getFile('lorem.txt')); $this->instance->addFile('lorem.txt','foobar'); @@ -94,4 +95,39 @@ abstract class Test_Archive extends UnitTestCase { $this->assertTrue($this->instance->fileExists('lorem.txt')); $this->assertEqual(file_get_contents($dir.'/lorem.txt'),$this->instance->getFile('lorem.txt')); } + public function testFolder(){ + $this->instance=$this->getNew(); + $this->assertFalse($this->instance->fileExists('/test')); + $this->assertFalse($this->instance->fileExists('/test/')); + $this->instance->addFolder('/test'); + $this->assertTrue($this->instance->fileExists('/test')); + $this->assertTrue($this->instance->fileExists('/test/')); + $this->instance->remove('/test'); + $this->assertFalse($this->instance->fileExists('/test')); + $this->assertFalse($this->instance->fileExists('/test/')); + } + public function testExtract(){ + $dir=OC::$SERVERROOT.'/apps/files_archive/tests/data'; + $this->instance=$this->getExisting(); + $tmpDir=OC_Helper::tmpFolder(); + $this->instance->extract($tmpDir); + $this->assertEqual(true,file_exists($tmpDir.'lorem.txt')); + $this->assertEqual(true,file_exists($tmpDir.'dir/lorem.txt')); + $this->assertEqual(true,file_exists($tmpDir.'logo-wide.png')); + $this->assertEqual(file_get_contents($dir.'/lorem.txt'),file_get_contents($tmpDir.'lorem.txt')); + OC_Helper::rmdirr($tmpDir); + } + public function testMoveRemove(){ + $dir=OC::$SERVERROOT.'/apps/files_archive/tests/data'; + $textFile=$dir.'/lorem.txt'; + $this->instance=$this->getNew(); + $this->instance->addFile('lorem.txt',$textFile); + $this->assertFalse($this->instance->fileExists('target.txt')); + $this->instance->rename('lorem.txt','target.txt'); + $this->assertTrue($this->instance->fileExists('target.txt')); + $this->assertFalse($this->instance->fileExists('lorem.txt')); + $this->assertEqual(file_get_contents($textFile),$this->instance->getFile('target.txt')); + $this->instance->remove('target.txt'); + $this->assertFalse($this->instance->fileExists('target.txt')); + } } diff --git a/apps/files_archive/tests/tar.php b/apps/files_archive/tests/tar.php new file mode 100644 index 0000000000..193a65b550 --- /dev/null +++ b/apps/files_archive/tests/tar.php @@ -0,0 +1,20 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +require_once('archive.php'); + +class Test_Archive_TAR extends Test_Archive{ + protected function getExisting(){ + $dir=OC::$SERVERROOT.'/apps/files_archive/tests/data'; + return new OC_Archive_TAR($dir.'/data.tar.gz'); + } + + protected function getNew(){ + return new OC_Archive_TAR(OC_Helper::tmpFile('.tar.gz')); + } +} diff --git a/apps/files_sharing/ajax/userautocomplete.php b/apps/files_sharing/ajax/userautocomplete.php index 9d971fb62a..38b673ee51 100644 --- a/apps/files_sharing/ajax/userautocomplete.php +++ b/apps/files_sharing/ajax/userautocomplete.php @@ -7,21 +7,23 @@ OC_JSON::checkLoggedIn(); OC_JSON::checkAppEnabled('files_sharing'); $users = array(); -$ocusers = OC_User::getUsers(); +$groups = array(); $self = OC_User::getUser(); -$groups = OC_Group::getUserGroups($self); +$userGroups = OC_Group::getUserGroups($self); $users[] = ""; -foreach ($ocusers as $user) { - if ($user != $self) { - $users[] = ""; +$groups[] = ""; +foreach ($userGroups as $group) { + $groupUsers = OC_Group::usersInGroup($group); + foreach ($groupUsers as $user) { + if ($user != $self) { + $users[] = ""; + } } + $groups[] = ""; } $users[] = ""; -$users[] = ""; -foreach ($groups as $group) { - $users[] = ""; -} -$users[] = ""; +$groups[] = ""; +$users = array_merge($users, $groups); OC_JSON::encodedPrint($users); ?> diff --git a/apps/files_sharing/lib_share.php b/apps/files_sharing/lib_share.php index 42739bdfba..673984f393 100644 --- a/apps/files_sharing/lib_share.php +++ b/apps/files_sharing/lib_share.php @@ -52,8 +52,18 @@ class OC_Share { // Remove the owner from the list of users in the group $uid_shared_with = array_diff($uid_shared_with, array($uid_owner)); } else if (OC_User::userExists($uid_shared_with)) { - $gid = null; - $uid_shared_with = array($uid_shared_with); + $userGroups = OC_Group::getUserGroups($uid_owner); + // Check if the user is in one of the owner's groups + foreach ($userGroups as $group) { + if ($inGroup = OC_Group::inGroup($uid_shared_with, $group)) { + $gid = null; + $uid_shared_with = array($uid_shared_with); + break; + } + } + if (!$inGroup) { + throw new Exception("You can't share with ".$uid_shared_with); + } } else { throw new Exception($uid_shared_with." is not a user"); } diff --git a/apps/gallery/ajax/galleryOp.php b/apps/gallery/ajax/galleryOp.php index b0433898cd..1b3ad48f56 100644 --- a/apps/gallery/ajax/galleryOp.php +++ b/apps/gallery/ajax/galleryOp.php @@ -127,6 +127,9 @@ function handleGetGallery($path) { function handleShare($path, $share, $recursive) { $recursive = $recursive == 'true' ? 1 : 0; $owner = OC_User::getUser(); + $root = OC_Preferences::getValue(OC_User::getUser(),'gallery', 'root', '/'); + $path = utf8_decode(rtrim($root.$path,'/')); + if($path == '') $path = '/'; $r = OC_Gallery_Album::find($owner, null, $path); if ($row = $r->fetchRow()) { $albumId = $row['album_id']; diff --git a/apps/media/css/music.css b/apps/media/css/music.css index 0717362401..164a6c62ae 100644 --- a/apps/media/css/music.css +++ b/apps/media/css/music.css @@ -22,6 +22,7 @@ div.jp-volume-bar-value { background:#ccc; width:0; height:0.4em; } #leftcontent img.remove { display:none; float:right; cursor:pointer; opacity: 0; } #leftcontent li:hover img.remove { display:inline; opacity: .3; } #leftcontent li div.label { float: left; width: 200px; overflow: hidden; text-overflow: ellipsis; } +#rightcontent { overflow: auto; } #playlist li { list-style-type:none; } .template { display:none; } .collection_playing { background:#eee; font-weight: bold; } diff --git a/core/templates/login.php b/core/templates/login.php index 6c0a7a1222..82222c8212 100644 --- a/core/templates/login.php +++ b/core/templates/login.php @@ -1,3 +1,4 @@ +
'; } ?> diff --git a/files/css/files.css b/files/css/files.css index 512e462cb6..9e950517b8 100644 --- a/files/css/files.css +++ b/files/css/files.css @@ -64,8 +64,8 @@ table td.filename .nametext { width:60%; } table td.filename form { float:left; font-size:.85em; } table thead.fixed tr{ position:fixed; top:6.5em; z-index:49; -moz-box-shadow:0 -3px 7px #ddd; -webkit-box-shadow:0 -3px 7px #ddd; box-shadow:0 -3px 7px #ddd; } table thead.fixed { height:2em; } -#fileList tr td.filename>input[type=checkbox]:first-child { opacity:0; float:left; margin:.7em 0 0 1em; /* bigger clickable area doesn’t work in FF width:2.8em; height:2.4em;*/ -webkit-transition:opacity 500ms; -moz-transition:opacity 500ms; -o-transition:opacity 500ms; transition:opacity 500ms; } -#fileList tr td.filename>input[type="checkbox"]:hover:first-child { opacity:.8; } +#fileList tr td.filename>input[type=checkbox]:first-child { opacity:0; -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; float:left; margin:.7em 0 0 1em; /* bigger clickable area doesn’t work in FF width:2.8em; height:2.4em;*/ -webkit-transition:opacity 500ms; -moz-transition:opacity 500ms; -o-transition:opacity 500ms; transition:opacity 500ms; } +#fileList tr td.filename>input[type="checkbox"]:hover:first-child { opacity:.8; -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=80)"; } #fileList tr td.filename>input[type="checkbox"]:checked:first-child { opacity:1; } #fileList tr td.filename { -webkit-transition:background-image 500ms; -moz-transition:background-image 500ms; -o-transition:background-image 500ms; transition:background-image 500ms; } #select_all { float:left; margin:.3em 0.6em 0 .5em; } diff --git a/files/js/files.js b/files/js/files.js index 539d559889..df9f45a7af 100644 --- a/files/js/files.js +++ b/files/js/files.js @@ -98,7 +98,7 @@ $(document).ready(function() { procesSelection(); }); - $('td.filename input:checkbox').live('click',function(event) { + $('td.filename input:checkbox').live('change',function(event) { if (event.shiftKey) { var last = $(lastChecked).parent().parent().prevAll().length; var first = $(this).parent().parent().prevAll().length; diff --git a/files/templates/index.php b/files/templates/index.php index da1e58ce13..418a170fec 100644 --- a/files/templates/index.php +++ b/files/templates/index.php @@ -1,3 +1,4 @@ +
diff --git a/lib/db.php b/lib/db.php index 9fab51edfc..a0fb6c385d 100644 --- a/lib/db.php +++ b/lib/db.php @@ -318,9 +318,6 @@ class OC_DB { // Make changes and save them to an in-memory file $file2 = 'static://db_scheme'; - if($file2 == ''){ - die('could not create tempfile in get_temp_dir() - aborting'); - } $content = str_replace( '*dbname*', $CONFIG_DBNAME, $content ); $content = str_replace( '*dbprefix*', $CONFIG_DBTABLEPREFIX, $content ); if( $CONFIG_DBTYPE == 'pgsql' ){ //mysql support it too but sqlite doesn't diff --git a/lib/helper.php b/lib/helper.php index 66f31d929b..efff00c2fe 100755 --- a/lib/helper.php +++ b/lib/helper.php @@ -432,6 +432,19 @@ class OC_Helper { self::$tmpFiles[]=$file; return $file; } + + /** + * create a temporary folder with an unique filename + * @return string + * + * temporary files are automatically cleaned up after the script is finished + */ + public static function tmpFolder(){ + $path=get_temp_dir().'/'.md5(time().rand()); + mkdir($path); + self::$tmpFiles[]=$path; + return $path.'/'; + } /** * remove all files created by self::tmpFile @@ -439,7 +452,7 @@ class OC_Helper { public static function cleanTmp(){ foreach(self::$tmpFiles as $file){ if(file_exists($file)){ - unlink($file); + self::rmdirr($file); } } } diff --git a/lib/installer.php b/lib/installer.php index 2a9676998f..c5ecacae54 100644 --- a/lib/installer.php +++ b/lib/installer.php @@ -62,7 +62,7 @@ class OC_Installer{ //download the file if necesary if($data['source']=='http'){ - $path=OC_Helper::tmpFile('.zip'); + $path=OC_Helper::tmpFile(); if(!isset($data['href'])){ OC_Log::write('core','No href specified when installing app from http',OC_Log::ERROR); return false; @@ -76,14 +76,24 @@ class OC_Installer{ $path=$data['path']; } + //detect the archive type + $mime=OC_Helper::getMimeType($path); + if($mime=='application/zip'){ + rename($path,$path.'.zip'); + $path.='.zip'; + }elseif($mime=='application/x-gzip'){ + rename($path,$path.'.tgz'); + $path.='.tgz'; + }else{ + OC_Log::write('core','Archives of type '.$mime.' are not supported',OC_Log::ERROR); + return false; + } + //extract the archive in a temporary folder - $extractDir=tempnam(get_temp_dir(),'oc_installer_uncompressed_'); - unlink($extractDir); + $extractDir=OC_Helper::tmpFolder(); mkdir($extractDir); - $zip = new ZipArchive; - if($zip->open($path)===true){ - $zip->extractTo($extractDir); - $zip->close(); + if($archive=OC_Archive::open($path)){ + $archive->extract($extractDir); } else { OC_Log::write('core','Failed to open archive when installing app',OC_Log::ERROR); OC_Helper::rmdirr($extractDir); @@ -94,6 +104,17 @@ class OC_Installer{ } //load the info.xml file of the app + if(!is_file($extractDir.'/appinfo/info.xml')){ + //try to find it in a subdir + $dh=opendir($extractDir); + while($folder=readdir($dh)){ + if(substr($folder,0,1)!='.' and is_dir($extractDir.'/'.$folder)){ + if(is_file($extractDir.'/'.$folder.'/appinfo/info.xml')){ + $extractDir.='/'.$folder; + } + } + } + } if(!is_file($extractDir.'/appinfo/info.xml')){ OC_Log::write('core','App does not provide an info.xml file',OC_Log::ERROR); OC_Helper::rmdirr($extractDir); diff --git a/tests/index.php b/tests/index.php index 2e86366740..a6f678b3bc 100644 --- a/tests/index.php +++ b/tests/index.php @@ -38,6 +38,7 @@ foreach($apps as $app){ } function loadTests($dir=''){ + $test=isset($_GET['test'])?$_GET['test']:false; if($dh=opendir($dir)){ while($name=readdir($dh)){ if(substr($name,0,1)!='.'){//no hidden files, '.' or '..' @@ -45,10 +46,13 @@ function loadTests($dir=''){ if(is_dir($file)){ loadTests($file); }elseif(substr($file,-4)=='.php' and $file!=__FILE__){ - $testCase=new TestSuite(getTestName($file)); - $testCase->addFile($file); - if($testCase->getSize()>0){ - $testCase->run(new HtmlReporter()); + $name=getTestName($file); + if($test===false or $test==$name or substr($name,0,strlen($test))==$test){ + $testCase=new TestSuite($name); + $testCase->addFile($file); + if($testCase->getSize()>0){ + $testCase->run(new HtmlReporter()); + } } } }