home *** CD-ROM | disk | FTP | other *** search
/ PC World 2005 April / PCWorld_2005-04_cd.bin / akce / web / phptriad / phptriad2-2-1.exe / php / pear / Archive / Tar.php
PHP Script  |  2001-10-11  |  37KB  |  1,045 lines

  1. <?php
  2. /* vim: set ts=4 sw=4: */
  3. // +----------------------------------------------------------------------+
  4. // | PHP version 4.0                                                      |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2001 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.02 of the PHP license,      |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available at through the world-wide-web at                           |
  11. // | http://www.php.net/license/2_02.txt.                                 |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Vincent Blavet <vincent@blavet.net>                          |
  17. // +----------------------------------------------------------------------+
  18. //
  19. // $Id: Tar.php,v 1.1.2.2 2001/10/11 15:00:48 ssb Exp $
  20.  
  21. require_once 'PEAR.php';
  22.  
  23. /**
  24. * Creates a (compressed) Tar archive
  25. *
  26. * @author   Vincent Blavet <vincent@blavet.net>
  27. * @version  $Revision: 1.1.2.2 $
  28. * @package  Archive
  29. */
  30. class Archive_Tar extends PEAR
  31. {
  32.     /**
  33.     * @var string Name of the Tar
  34.     */
  35.     var $_tarname;
  36.  
  37.     /**
  38.     * @var boolean if true, the Tar file will be gzipped
  39.     */
  40.     var $_compress;
  41.  
  42.     /**
  43.     * @var file descriptor
  44.     */
  45.     var $_file;
  46.  
  47.     // {{{ constructor
  48.     /**
  49.     * Archive_Tar Class constructor. This flavour of the constructor only
  50.     * declare a new Archive_Tar object, identifying it by the name of the
  51.     * tar file.
  52.     * If the compress argument is set the tar will be read or created as a
  53.     * gzip compressed TAR file.
  54.     *
  55.     * @param    string  $p_tarname  The name of the tar archive to create
  56.     * @param    boolean $p_compress if true, the archive will be gezip(ped)
  57.     * @access public
  58.     */
  59.     function Archive_Tar($p_tarname, $p_compress = false)
  60.     {
  61.         $this->PEAR();
  62.         $this->_tarname = $p_tarname;
  63.         if ($p_compress) { // assert zlib extension support
  64.             $extname = 'zlib';
  65.             if (!extension_loaded($extname)) {
  66.                 $dlext = (substr(PHP_OS, 0, 3) == 'WIN') ? '.dll' : '.so';
  67.                 @dl($extname . $dlext);
  68.             }
  69.             if (!extension_loaded($extname)) {
  70.                 die("The extension '$extname' couldn't be loaded. ".
  71.                     'Probably you don\'t have support in your PHP '.
  72.                     'to this extension');
  73.                 return false;
  74.             }
  75.         }
  76.         $this->_compress = $p_compress;
  77.     }
  78.     // }}}
  79.  
  80.     // {{{ destructor
  81.     function _Archive_Tar()
  82.     {
  83.         $this->_close();
  84.         $this->_PEAR();
  85.     }
  86.     // }}}
  87.  
  88.     // {{{ create()
  89.     /**
  90.     * This method creates the archive file and add the files / directories
  91.     * that are listed in $p_filelist.
  92.     * If the file already exists and is writable, it is replaced by the
  93.     * new tar. It is a create and not an add. If the file exists and is
  94.     * read-only or is a directory it is not replaced. The method return
  95.     * false and a PEAR error text.
  96.     * The $p_filelist parameter can be an array of string, each string
  97.     * representing a filename or a directory name with their path if
  98.     * needed. It can also be a single string with names separated by a
  99.     * single blank.
  100.     * See also createModify() method for more details.
  101.     *
  102.     * @param array  $p_filelist An array of filenames and directory names, or a single
  103.     *                           string with names separated by a single blank space.
  104.     * @return                   true on success, false on error.
  105.     * @see createModify()
  106.     * @access public
  107.     */
  108.     function create($p_filelist)
  109.     {
  110.         return $this->createModify($p_filelist, "", "");
  111.     }
  112.     // }}}
  113.  
  114.     // {{{ add()
  115.     function add($p_filelist)
  116.     {
  117.         return $this->addModify($p_filelist, "", "");
  118.     }
  119.     // }}}
  120.  
  121.     // {{{ extract()
  122.     function extract($p_path="")
  123.     {
  124.         return $this->extractModify($p_path, "");
  125.     }
  126.     // }}}
  127.  
  128.     // {{{ listContent()
  129.     function listContent()
  130.     {
  131.         $v_list_detail = array();
  132.  
  133.         if ($this->_openRead()) {
  134.             if (!$this->_extractList("", $v_list_detail, "list", "", "")) {
  135.                 unset($v_list_detail);
  136.                 return(0);
  137.             }
  138.             $this->_close();
  139.         }
  140.  
  141.         return $v_list_detail;
  142.     }
  143.     // }}}
  144.  
  145.     // {{{ createModify()
  146.     /**
  147.     * This method creates the archive file and add the files / directories
  148.     * that are listed in $p_filelist.
  149.     * If the file already exists and is writable, it is replaced by the
  150.     * new tar. It is a create and not an add. If the file exists and is
  151.     * read-only or is a directory it is not replaced. The method return
  152.     * false and a PEAR error text.
  153.     * The $p_filelist parameter can be an array of string, each string
  154.     * representing a filename or a directory name with their path if
  155.     * needed. It can also be a single string with names separated by a
  156.     * single blank.
  157.     * The path indicated in $p_remove_dir will be removed from the
  158.     * memorized path of each file / directory listed when this path
  159.     * exists. By default nothing is removed (empty path "")
  160.     * The path indicated in $p_add_dir will be added at the beginning of
  161.     * the memorized path of each file / directory listed. However it can
  162.     * be set to empty "". The adding of a path is done after the removing
  163.     * of path.
  164.     * The path add/remove ability enables the user to prepare an archive
  165.     * for extraction in a different path than the origin files are.
  166.     * See also addModify() method for file adding properties.
  167.     *
  168.     * @param array  $p_filelist     An array of filenames and directory names, or a single
  169.     *                               string with names separated by a single blank space.
  170.     * @param string $p_add_dir      A string which contains a path to be added to the
  171.     *                               memorized path of each element in the list.
  172.     * @param string $p_remove_dir   A string which contains a path to be removed from
  173.     *                               the memorized path of each element in the list, when
  174.     *                               relevant.
  175.     * @return boolean               true on success, false on error.
  176.     * @access public
  177.     * @see addModify()
  178.     */
  179.     function createModify($p_filelist, $p_add_dir, $p_remove_dir="")
  180.     {
  181.         $v_result = true;
  182.  
  183.         if (!$this->_openWrite())
  184.             return false;
  185.  
  186.         if ($p_filelist != "") {
  187.             if (is_array($p_filelist))
  188.                 $v_list = $p_filelist;
  189.             elseif (is_string($p_filelist))
  190.                 $v_list = explode(" ", $p_filelist);
  191.             else {
  192.                 $this->_cleanFile();
  193.                 $this->_error("Invalid file list");
  194.                 return false;
  195.             }
  196.  
  197.             $v_result = $this->_addList($v_list, "", "");
  198.         }
  199.  
  200.         if ($v_result) {
  201.             $this->_writeFooter();
  202.             $this->_close();
  203.         } else
  204.             $this->_cleanFile();
  205.  
  206.         return $v_result;
  207.     }
  208.     // }}}
  209.  
  210.     // {{{ addModify()
  211.     /**
  212.     * This method add the files / directories listed in $p_filelist at the
  213.     * end of the existing archive. If the archive does not yet exists it
  214.     * is created.
  215.     * The $p_filelist parameter can be an array of string, each string
  216.     * representing a filename or a directory name with their path if
  217.     * needed. It can also be a single string with names separated by a
  218.     * single blank.
  219.     * The path indicated in $p_remove_dir will be removed from the
  220.     * memorized path of each file / directory listed when this path
  221.     * exists. By default nothing is removed (empty path "")
  222.     * The path indicated in $p_add_dir will be added at the beginning of
  223.     * the memorized path of each file / directory listed. However it can
  224.     * be set to empty "". The adding of a path is done after the removing
  225.     * of path.
  226.     * The path add/remove ability enables the user to prepare an archive
  227.     * for extraction in a different path than the origin files are.
  228.     * If a file/dir is already in the archive it will only be added at the
  229.     * end of the archive. There is no update of the existing archived
  230.     * file/dir. However while extracting the archive, the last file will
  231.     * replace the first one. This results in a none optimization of the
  232.     * archive size.
  233.     * If a file/dir does not exist the file/dir is ignored. However an
  234.     * error text is send to PEAR error.
  235.     * If a file/dir is not readable the file/dir is ignored. However an
  236.     * error text is send to PEAR error.
  237.     * If the resulting filename/dirname (after the add/remove option or
  238.     * not) string is greater than 99 char, the file/dir is
  239.     * ignored. However an error text is send to PEAR error.
  240.     *
  241.     * @param array      $p_filelist     An array of filenames and directory names, or a single
  242.     *                                   string with names separated by a single blank space.
  243.     * @param string     $p_add_dir      A string which contains a path to be added to the
  244.     *                                   memorized path of each element in the list.
  245.     * @param string     $p_remove_dir   A string which contains a path to be removed from
  246.     *                                   the memorized path of each element in the list, when
  247.     *                                   relevant.
  248.     * @return                           true on success, false on error.
  249.     * @access public
  250.     */
  251.     function addModify($p_filelist, $p_add_dir, $p_remove_dir="")
  252.     {
  253.         $v_result = true;
  254.  
  255.         if (!@is_file($this->_tarname))
  256.             $v_result = $this->createModify($p_filelist, $p_add_dir, $p_remove_dir);
  257.         else {
  258.             if (is_array($p_filelist))
  259.                 $v_list = $p_filelist;
  260.             elseif (is_string($p_filelist))
  261.                 $v_list = explode(" ", $p_filelist);
  262.             else {
  263.                 $this->_error("Invalid file list");
  264.                 return false;
  265.             }
  266.  
  267.             $v_result = $this->_append($v_list, $p_add_dir, $p_remove_dir);
  268.         }
  269.  
  270.         return $v_result;
  271.     }
  272.     // }}}
  273.  
  274.     // {{{ extractModify()
  275.     /**
  276.     * This method extract all the content of the archive in the directory
  277.     * indicated by $p_path. When relevant the memorized path of the
  278.     * files/dir can be modified by removing the $p_remove_path path at the
  279.     * beginning of the file/dir path.
  280.     * While extracting a file, if the directory path does not exists it is
  281.     * created.
  282.     * While extracting a file, if the file already exists it is replaced
  283.     * without looking for last modification date.
  284.     * While extracting a file, if the file already exists and is write
  285.     * protected, the extraction is aborted.
  286.     * While extracting a file, if a directory with the same name already
  287.     * exists, the extraction is aborted.
  288.     * While extracting a directory, if a file with the same name already
  289.     * exists, the extraction is aborted.
  290.     * While extracting a file/directory if the destination directory exist
  291.     * and is write protected, or does not exist but can not be created,
  292.     * the extraction is aborted.
  293.     * If after extraction an extracted file does not show the correct
  294.     * stored file size, the extraction is aborted.
  295.     * When the extraction is aborted, a PEAR error text is set and false
  296.     * is returned. However the result can be a partial extraction that may
  297.     * need to be manually cleaned.
  298.     *
  299.     * @param string $p_path         The path of the directory where the files/dir need to by
  300.     *                               extracted.
  301.     * @param string $p_remove_path  Part of the memorized path that can be removed if
  302.     *                               present at the beginning of the file/dir path.
  303.     * @return boolean               true on success, false on error.
  304.     * @access public
  305.     * @see extractList()
  306.     */
  307.     function extractModify($p_path, $p_remove_path)
  308.     {
  309.         $v_result = true;
  310.         $v_list_detail = array();
  311.  
  312.         if ($v_result = $this->_openRead()) {
  313.             $v_result = $this->_extractList($p_path, $v_list_detail, "complete", 0, $p_remove_path);
  314.             $this->_close();
  315.         }
  316.  
  317.         return $v_result;
  318.     }
  319.     // }}}
  320.  
  321.     // {{{ extractList()
  322.     /**
  323.     * This method extract from the archive only the files indicated in the
  324.     * $p_filelist. These files are extracted in the current directory or
  325.     * in the directory indicated by the optional $p_path parameter.
  326.     * If indicated the $p_remove_path can be used in the same way as it is
  327.     * used in extractModify() method.
  328.     * @param array  $p_filelist     An array of filenames and directory names, or a single
  329.     *                               string with names separated by a single blank space.
  330.     * @param string $p_path         The path of the directory where the files/dir need to by
  331.     *                               extracted.
  332.     * @param string $p_remove_path  Part of the memorized path that can be removed if
  333.     *                               present at the beginning of the file/dir path.
  334.     * @return                       true on success, false on error.
  335.     * @access public
  336.     * @see extractModify()
  337.     */
  338.     function extractList($p_filelist, $p_path="", $p_remove_path="")
  339.     {
  340.         $v_result = true;
  341.         $v_list_detail = array();
  342.  
  343.         if (is_array($p_filelist))
  344.             $v_list = $p_filelist;
  345.         elseif (is_string($p_filelist))
  346.             $v_list = explode(" ", $p_filelist);
  347.         else {
  348.             $this->_error("Invalid string list");
  349.             return false;
  350.         }
  351.  
  352.         if ($v_result = $this->_openRead()) {
  353.             $v_result = $this->_extractList($p_path, $v_list_detail, "complete", $v_list, $p_remove_path);
  354.             $this->_close();
  355.         }
  356.  
  357.         return $v_result;
  358.     }
  359.     // }}}
  360.  
  361.     // {{{ _error()
  362.     function _error($p_message)
  363.     {
  364.         // ----- To be completed
  365.         $this->raiseError($p_message);
  366.     }
  367.     // }}}
  368.  
  369.     // {{{ _warning()
  370.     function _warning($p_message)
  371.     {
  372.         // ----- To be completed
  373.         $this->raiseError($p_message);
  374.     }
  375.     // }}}
  376.  
  377.     // {{{ _openWrite()
  378.     function _openWrite()
  379.     {
  380.         if ($this->_compress)
  381.             $this->_file = @gzopen($this->_tarname, "w");
  382.         else
  383.             $this->_file = @fopen($this->_tarname, "w");
  384.  
  385.         if ($this->_file == 0) {
  386.             $this->_error("Unable to open in write mode '".$this->_tarname."'");
  387.             return false;
  388.         }
  389.  
  390.         return true;
  391.     }
  392.     // }}}
  393.  
  394.     // {{{ _openRead()
  395.     function _openRead()
  396.     {
  397.         if ($this->_compress)
  398.             $this->_file = @gzopen($this->_tarname, "rb");
  399.         else
  400.             $this->_file = @fopen($this->_tarname, "rb");
  401.  
  402.         if ($this->_file == 0) {
  403.             $this->_error("Unable to open in read mode '".$this->_tarname."'");
  404.             return false;
  405.         }
  406.  
  407.         return true;
  408.     }
  409.     // }}}
  410.  
  411.     // {{{ _openReadWrite()
  412.     function _openReadWrite()
  413.     {
  414.         if ($this->_compress)
  415.             $this->_file = @gzopen($this->_tarname, "r+b");
  416.         else
  417.             $this->_file = @fopen($this->_tarname, "r+b");
  418.  
  419.         if ($this->_file == 0) {
  420.             $this->_error("Unable to open in read/write mode '".$this->_tarname."'");
  421.             return false;
  422.         }
  423.  
  424.         return true;
  425.     }
  426.     // }}}
  427.  
  428.     // {{{ _close()
  429.     function _close()
  430.     {
  431.         if ($this->_file) {
  432.             if ($this->_compress)
  433.                 @gzclose($this->_file);
  434.             else
  435.                 @fclose($this->_file);
  436.  
  437.             $this->_file = 0;
  438.         }
  439.  
  440.         return true;
  441.     }
  442.     // }}}
  443.  
  444.     // {{{ _cleanFile()
  445.     function _cleanFile()
  446.     {
  447.         _close();
  448.         @unlink($this->tarname);
  449.  
  450.         return true;
  451.     }
  452.     // }}}
  453.  
  454.     // {{{ _writeFooter()
  455.     function _writeFooter()
  456.     {
  457.       if ($this->_file) {
  458.           // ----- Write the last 0 filled block for end of archive
  459.           $v_binary_data = pack("a512", "");
  460.           if ($this->_compress)
  461.             @gzputs($this->_file, $v_binary_data);
  462.           else
  463.             @fputs($this->_file, $v_binary_data);
  464.       }
  465.       return true;
  466.     }
  467.     // }}}
  468.  
  469.     // {{{ _addList()
  470.     function _addList($p_list, $p_add_dir, $p_remove_dir)
  471.     {
  472.       $v_result=true;
  473.       $v_header = array();
  474.  
  475.       if (!$this->_file) {
  476.           $this->_error("Invalid file descriptor");
  477.           return false;
  478.       }
  479.  
  480.       if (sizeof($p_list) == 0)
  481.           return true;
  482.  
  483.       for ($j=0; ($j<count($p_list)) && ($v_result); $j++) {
  484.         $v_filename = $p_list[$j];
  485.  
  486.         // ----- Skip the current tar name
  487.         if ($v_filename == $this->_tarname)
  488.             continue;
  489.  
  490.         if ($v_filename == "")
  491.             continue;
  492.  
  493.         if (!file_exists($v_filename)) {
  494.             $this->_warning("File '$v_filename' does not exist");
  495.             continue;
  496.         }
  497.  
  498.         // ----- Add the file or directory header
  499.         if (!$this->_addFile($v_filename, $v_header, $p_add_dir, $p_remove_dir))
  500.             return false;
  501.  
  502.         if (@is_dir($v_filename)) {
  503.             if (!($p_hdir = opendir($v_filename))) {
  504.                 $this->_warning("Directory '$v_filename' can not be read");
  505.                 continue;
  506.             }
  507.             $p_hitem = readdir($p_hdir); // '.' directory
  508.             $p_hitem = readdir($p_hdir); // '..' directory
  509.             while ($p_hitem = readdir($p_hdir)) {
  510.                 if ($v_filename != ".")
  511.                     $p_temp_list[0] = $v_filename."/".$p_hitem;
  512.                 else
  513.                     $p_temp_list[0] = $p_hitem;
  514.  
  515.                 $v_result = $this->_addList($p_temp_list, $p_add_dir, $p_remove_dir);
  516.             }
  517.  
  518.             unset($p_temp_list);
  519.             unset($p_hdir);
  520.             unset($p_hitem);
  521.         }
  522.       }
  523.  
  524.       return $v_result;
  525.     }
  526.     // }}}
  527.  
  528.     // {{{ _addFile()
  529.     function _addFile($p_filename, &$p_header, $p_add_dir, $p_remove_dir)
  530.     {
  531.       if (!$this->_file) {
  532.           $this->_error("Invalid file descriptor");
  533.           return false;
  534.       }
  535.  
  536.       if ($p_filename == "") {
  537.           $this->_error("Invalid file name");
  538.           return false;
  539.       }
  540.  
  541.       // ----- Calculate the stored filename
  542.       $v_stored_filename = $p_filename;
  543.       if ($p_remove_dir != "") {
  544.           if (substr($p_remove_dir, -1) != '/')
  545.               $p_remove_dir .= "/";
  546.  
  547.           if (substr($p_filename, 0, strlen($p_remove_dir)) == $p_remove_dir)
  548.               $v_stored_filename = substr($p_filename, strlen($p_remove_dir));
  549.       }
  550.       if ($p_add_dir != "") {
  551.           if (substr($p_add_dir, -1) == "/")
  552.               $v_stored_filename = $p_add_dir.$v_stored_filename;
  553.           else
  554.               $v_stored_filename = $p_add_dir."/".$v_stored_filename;
  555.       }
  556.  
  557.       if (strlen($v_stored_filename) > 99) {
  558.           $this->_warning("Stored file name is too long (max. 99) : '$v_stored_filename'");
  559.           fclose($v_file);
  560.           return true;
  561.       }
  562.  
  563.       if (is_file($p_filename)) {
  564.           if (($v_file = @fopen($p_filename, "rb")) == 0) {
  565.               $this->_warning("Unable to open file '$p_filename' in binary read mode");
  566.               return true;
  567.           }
  568.  
  569.           if (!$this->_writeHeader($p_filename, $v_stored_filename))
  570.               return false;
  571.  
  572.           while (($v_buffer = fread($v_file, 512)) != "") {
  573.               $v_binary_data = pack("a512", "$v_buffer");
  574.               if ($this->_compress)
  575.                   @gzputs($this->_file, $v_binary_data);
  576.               else
  577.                   @fputs($this->_file, $v_binary_data);
  578.           }
  579.  
  580.           fclose($v_file);
  581.  
  582.       } else {
  583.           // ----- Only header for dir
  584.           if (!$this->_writeHeader($p_filename, $v_stored_filename))
  585.               return false;
  586.       }
  587.  
  588.       return true;
  589.     }
  590.     // }}}
  591.  
  592.     // {{{ _writeHeader()
  593.     function _writeHeader($p_filename, $p_stored_filename)
  594.     {
  595.         if ($p_stored_filename == "")
  596.             $p_stored_filename = $p_filename;
  597.         $v_reduce_filename = $this->_pathReduction($p_stored_filename);
  598.  
  599.         $v_info = stat($p_filename);
  600.         $v_uid = sprintf("%6s ", DecOct($v_info[4]));
  601.         $v_gid = sprintf("%6s ", DecOct($v_info[5]));
  602.         $v_perms = sprintf("%6s ", DecOct(fileperms($p_filename)));
  603.  
  604.         clearstatcache();
  605.         $v_size = sprintf("%11s ", DecOct(filesize($p_filename)));
  606.  
  607.         $v_mtime = sprintf("%11s", DecOct(filemtime($p_filename)));
  608.  
  609.         if (@is_dir($p_filename))
  610.           $v_typeflag = "5";
  611.         else
  612.           $v_typeflag = "";
  613.  
  614.         $v_linkname = "";
  615.  
  616.         $v_magic = "";
  617.  
  618.         $v_version = "";
  619.  
  620.         $v_uname = "";
  621.  
  622.         $v_gname = "";
  623.  
  624.         $v_devmajor = "";
  625.  
  626.         $v_devminor = "";
  627.  
  628.         $v_prefix = "";
  629.  
  630.         $v_binary_data_first = pack("a100a8a8a8a12A12", $v_reduce_filename, $v_perms, $v_uid, $v_gid, $v_size, $v_mtime);
  631.         $v_binary_data_last = pack("a1a100a6a2a32a32a8a8a155a12", $v_typeflag, $v_linkname, $v_magic, $v_version, $v_uname, $v_gname, $v_devmajor, $v_devminor, $v_prefix, "");
  632.  
  633.         // ----- Calculate the checksum
  634.         $v_checksum = 0;
  635.         // ..... First part of the header
  636.         for ($i=0; $i<148; $i++)
  637.             $v_checksum += ord(substr($v_binary_data_first,$i,1));
  638.         // ..... Ignore the checksum value and replace it by ' ' (space)
  639.         for ($i=148; $i<156; $i++)
  640.             $v_checksum += ord(' ');
  641.         // ..... Last part of the header
  642.         for ($i=156, $j=0; $i<512; $i++, $j++)
  643.             $v_checksum += ord(substr($v_binary_data_last,$j,1));
  644.  
  645.         // ----- Write the first 148 bytes of the header in the archive
  646.         if ($this->_compress)
  647.             @gzputs($this->_file, $v_binary_data_first, 148);
  648.         else
  649.             @fputs($this->_file, $v_binary_data_first, 148);
  650.  
  651.         // ----- Write the calculated checksum
  652.         $v_checksum = sprintf("%6s ", DecOct($v_checksum));
  653.         $v_binary_data = pack("a8", $v_checksum);
  654.         if ($this->_compress)
  655.           @gzputs($this->_file, $v_binary_data, 8);
  656.         else
  657.           @fputs($this->_file, $v_binary_data, 8);
  658.  
  659.         // ----- Write the last 356 bytes of the header in the archive
  660.         if ($this->_compress)
  661.             @gzputs($this->_file, $v_binary_data_last, 356);
  662.         else
  663.             @fputs($this->_file, $v_binary_data_last, 356);
  664.  
  665.         return true;
  666.     }
  667.     // }}}
  668.  
  669.     // {{{ _readHeader()
  670.     function _readHeader($v_binary_data, &$v_header)
  671.     {
  672.         if (strlen($v_binary_data)==0) {
  673.             $v_header[filename] = "";
  674.             return true;
  675.         }
  676.  
  677.         if (strlen($v_binary_data) != 512) {
  678.             $v_header[filename] = "";
  679.             $this->_error("Invalid block size : ".strlen($v_binary_data));
  680.             return false;
  681.         }
  682.  
  683.         // ----- Calculate the checksum
  684.         $v_checksum = 0;
  685.         // ..... First part of the header
  686.         for ($i=0; $i<148; $i++)
  687.             $v_checksum+=ord(substr($v_binary_data,$i,1));
  688.         // ..... Ignore the checksum value and replace it by ' ' (space)
  689.         for ($i=148; $i<156; $i++)
  690.             $v_checksum += ord(' ');
  691.         // ..... Last part of the header
  692.         for ($i=156; $i<512; $i++)
  693.            $v_checksum+=ord(substr($v_binary_data,$i,1));
  694.  
  695.         $v_data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor", $v_binary_data);
  696.  
  697.         // ----- Extract the checksum
  698.         $v_header[checksum] = OctDec(trim($v_data[checksum]));
  699.         if ($v_header[checksum] != $v_checksum) {
  700.             $v_header[filename] = "";
  701.  
  702.             // ----- Look for last block (empty block)
  703.             if (($v_checksum == 256) && ($v_header[checksum] == 0))
  704.                 return true;
  705.  
  706.             $this->_error("Invalid checksum : $v_checksum calculated, ".$v_header[checksum]." expected");
  707.             return false;
  708.         }
  709.  
  710.         // ----- Extract the properties
  711.         $v_header[filename] = trim($v_data[filename]);
  712.         $v_header[mode] = OctDec(trim($v_data[mode]));
  713.         $v_header[uid] = OctDec(trim($v_data[uid]));
  714.         $v_header[gid] = OctDec(trim($v_data[gid]));
  715.         $v_header[size] = OctDec(trim($v_data[size]));
  716.         $v_header[mtime] = OctDec(trim($v_data[mtime]));
  717.         $v_header[typeflag] = $v_data[typeflag];
  718.         /* ----- All these fields are removed form the header because they do not carry interesting info
  719.         $v_header[link] = trim($v_data[link]);
  720.         $v_header[magic] = trim($v_data[magic]);
  721.         $v_header[version] = trim($v_data[version]);
  722.         $v_header[uname] = trim($v_data[uname]);
  723.         $v_header[gname] = trim($v_data[gname]);
  724.         $v_header[devmajor] = trim($v_data[devmajor]);
  725.         $v_header[devminor] = trim($v_data[devminor]);
  726.         */
  727.  
  728.         return true;
  729.     }
  730.     // }}}
  731.  
  732.     // {{{ _extractList()
  733.     function _extractList($p_path, &$p_list_detail, $p_mode, $p_file_list, $p_remove_path)
  734.     {
  735.     $v_result=true;
  736.     $v_nb = 0;
  737.     $v_extract_all = true;
  738.     $v_listing = false;        
  739.  
  740.     if ($p_path == "" || (substr($p_path, 0, 1) != "/" && substr($p_path, 0, 3) != "../" && substr($p_path, 1, 3) != ":\\")) {
  741.       $p_path = "./".$p_path;
  742.     }
  743.  
  744.     // ----- Look for path to remove format (should end by /)
  745.     if (($p_remove_path != "") && (substr($p_remove_path, -1) != '/'))
  746.       $p_remove_path .= '/';
  747.     $p_remove_path_size = strlen($p_remove_path);
  748.  
  749.     switch ($p_mode) {
  750.       case "complete" :
  751.         $v_extract_all = TRUE;
  752.         $v_listing = FALSE;
  753.       break;
  754.       case "partial" :
  755.           $v_extract_all = FALSE;
  756.           $v_listing = FALSE;
  757.       break;
  758.       case "list" :
  759.           $v_extract_all = FALSE;
  760.           $v_listing = TRUE;
  761.       break;
  762.       default :
  763.         $this->_error("Invalid extract mode ($p_mode)");
  764.         return false;
  765.     }
  766.  
  767.     clearstatcache();
  768.  
  769.     While (!($v_end_of_file = ($this->_compress?@gzeof($this->_file):@feof($this->_file))))
  770.     {
  771.       $v_extract_file = FALSE;
  772.       $v_extraction_stopped = 0;
  773.  
  774.       if ($this->_compress)
  775.         $v_binary_data = @gzread($this->_file, 512);
  776.       else
  777.         $v_binary_data = @fread($this->_file, 512);
  778.  
  779.       if (!$this->_readHeader($v_binary_data, $v_header))
  780.         return false;
  781.  
  782.       if ($v_header[filename] == "")
  783.         continue;
  784.  
  785.       if ((!$v_extract_all) && (is_array($p_file_list))) {
  786.         // ----- By default no unzip if the file is not found
  787.         $v_extract_file = false;
  788.  
  789.         for ($i=0; $i<sizeof($p_file_list); $i++) {
  790.           // ----- Look if it is a directory
  791.           if (substr($p_file_list[$i], -1) == "/") {
  792.             // ----- Look if the directory is in the filename path
  793.             if ((strlen($v_header[filename]) > strlen($p_file_list[$i])) && (substr($v_header[filename], 0, strlen($p_file_list[$i])) == $p_file_list[$i])) {
  794.               $v_extract_file = TRUE;
  795.               break;
  796.             }
  797.           }
  798.  
  799.           // ----- It is a file, so compare the file names
  800.           elseif ($p_file_list[$i] == $v_header[filename]) {
  801.             $v_extract_file = TRUE;
  802.             break;
  803.           }
  804.         }
  805.       } else {
  806.         $v_extract_file = TRUE;
  807.       }
  808.  
  809.       // ----- Look if this file need to be extracted
  810.       if (($v_extract_file) && (!$v_listing))
  811.       {              
  812.         if (($p_remove_path != "")
  813.             && (substr($v_header[filename], 0, $p_remove_path_size) == $p_remove_path))
  814.           $v_header[filename] = substr($v_header[filename], $p_remove_path_size);
  815.         if (($p_path != "./") && ($p_path != "/")) {
  816.           while (substr($p_path, -1) == "/")
  817.             $p_path = substr($p_path, 0, strlen($p_path)-1);
  818.  
  819.           if (substr($v_header[filename], 0, 1) == "/")
  820.               $v_header[filename] = $p_path.$v_header[filename];
  821.           else
  822.             $v_header[filename] = $p_path."/".$v_header[filename];
  823.         }
  824.         if (file_exists($v_header[filename])) {
  825.           if ((@is_dir($v_header[filename])) && ($v_header[typeflag] == "")) {
  826.             $this->_error("File $v_header[filename] already exists as a directory");
  827.             return false;
  828.           }
  829.           if ((is_file($v_header[filename])) && ($v_header[typeflag] == "5")) {
  830.             $this->_error("Directory $v_header[filename] already exists as a file");
  831.             return false;
  832.           }
  833.           if (!is_writeable($v_header[filename])) {
  834.             $this->_error("File $v_header[filename] already exists and is write protected");
  835.             return false;
  836.           }
  837.           if (filemtime($v_header[filename]) > $v_header[mtime]) {
  838.             // To be completed : An error or silent no replace ?
  839.           }
  840.         }
  841.  
  842.         // ----- Check the directory availability and create it if necessary
  843.         elseif (($v_result = $this->_dirCheck(($v_header[typeflag] == "5"?$v_header[filename]:dirname($v_header[filename])))) != 1) {
  844.             $this->_error("Unable to create path for $v_header[filename]");
  845.             return false;
  846.         }
  847.  
  848.         if ($v_extract_file) {
  849.           if ($v_header[typeflag] == "5") {
  850.             if (!@file_exists($v_header[filename])) {
  851.                 if (!@mkdir($v_header[filename], 0777)) {
  852.                     $this->_error("Unable to create directory $v_header[filename]");
  853.                     return false;
  854.                 }
  855.             }
  856.           } else {
  857.               if (($v_dest_file = @fopen($v_header[filename], "wb")) == 0) {
  858.                   $this->_error("Error while opening $v_header[filename] in write binary mode");
  859.                   return false;
  860.               } else {
  861.                   $n = floor($v_header[size]/512);
  862.                   for ($i=0; $i<$n; $i++) {
  863.                       if ($this->_compress)
  864.                           $v_content = @gzread($this->_file, 512);
  865.                       else
  866.                           $v_content = @fread($this->_file, 512);
  867.                       fwrite($v_dest_file, $v_content, 512);
  868.                   }
  869.             if (($v_header[size] % 512) != 0) {
  870.               if ($this->_compress)
  871.                 $v_content = @gzread($this->_file, 512);
  872.               else
  873.                 $v_content = @fread($this->_file, 512);
  874.               fwrite($v_dest_file, $v_content, ($v_header[size] % 512));
  875.             }
  876.  
  877.             @fclose($v_dest_file);
  878.  
  879.             // ----- Change the file mode, mtime
  880.             @touch($v_header[filename], $v_header[mtime]);
  881.             // To be completed
  882.             //chmod($v_header[filename], DecOct($v_header[mode]));
  883.           }
  884.  
  885.           // ----- Check the file size
  886.           if (filesize($v_header[filename]) != $v_header[size]) {
  887.               $this->_error("Extracted file $v_header[filename] does not have the correct file size '".filesize($v_filename)."' ($v_header[size] expected). Archive may be corrupted.");
  888.               return false;
  889.           }
  890.           }
  891.         } else {
  892.           // ----- Jump to next file
  893.           if ($this->_compress)
  894.               @gzseek($this->_file, @gztell($this->_file)+(ceil(($v_header[size]/512))*512));
  895.           else
  896.               @fseek($this->_file, @ftell($this->_file)+(ceil(($v_header[size]/512))*512));
  897.         }
  898.       } else {
  899.         // ----- Jump to next file
  900.         if ($this->_compress)
  901.           @gzseek($this->_file, @gztell($this->_file)+(ceil(($v_header[size]/512))*512));
  902.         else
  903.           @fseek($this->_file, @ftell($this->_file)+(ceil(($v_header[size]/512))*512));
  904.       }
  905.  
  906.       if ($this->_compress)
  907.         $v_end_of_file = @gzeof($this->_file);
  908.       else
  909.         $v_end_of_file = @feof($this->_file);
  910.  
  911.       if ($v_listing || $v_extract_file || $v_extraction_stopped) {
  912.         // ----- Log extracted files
  913.         if (($v_file_dir = dirname($v_header[filename])) == $v_header[filename])
  914.           $v_file_dir = "";
  915.         if ((substr($v_header[filename], 0, 1) == "/") && ($v_file_dir == ""))
  916.           $v_file_dir = "/";
  917.  
  918.         $p_list_detail[$v_nb++] = $v_header;
  919.       }
  920.     }
  921.  
  922.         return true;
  923.     }
  924.     // }}}
  925.  
  926.     // {{{ _append()
  927.     function _append($p_filelist, $p_add_dir="", $p_remove_dir="")
  928.     {
  929.         if ($this->_compress) {
  930.             $this->_close();
  931.  
  932.             if (!@rename($this->_tarname, $this->_tarname.".tmp")) {
  933.                 $this->_error("Error while renaming '".$this->_tarname."' to temporary file '".$this->_tarname.".tmp'");
  934.                 return false;
  935.             }
  936.  
  937.             if (($v_temp_tar = @gzopen($this->_tarname.".tmp", "rb")) == 0) {
  938.                 $this->_error("Unable to open file '".$this->_tarname.".tmp' in binary read mode");
  939.                 @rename($this->_tarname.".tmp", $this->_tarname);
  940.                 return false;
  941.             }
  942.  
  943.             if (!$this->_openWrite()) {
  944.                 @rename($this->_tarname.".tmp", $this->_tarname);
  945.                 return false;
  946.             }
  947.  
  948.             $v_buffer = @gzread($v_temp_tar, 512);
  949.  
  950.             // ----- Read the following blocks but not the last one
  951.             if (!@gzeof($v_temp_tar)) {
  952.                 do{
  953.                     $v_binary_data = pack("a512", "$v_buffer");
  954.                     @gzputs($this->_file, $v_binary_data);
  955.                     $v_buffer = @gzread($v_temp_tar, 512);
  956.  
  957.                 } while (!@gzeof($v_temp_tar));
  958.             }
  959.  
  960.             if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir))
  961.                 $this->_writeFooter();
  962.  
  963.             $this->_close();
  964.             @gzclose($v_temp_tar);
  965.  
  966.             if (!@unlink($this->_tarname.".tmp")) {
  967.                 $this->_error("Error while deleting temporary file '".$this->_tarname.".tmp'");
  968.             }
  969.  
  970.             return true;
  971.         }
  972.  
  973.         // ----- For not compressed tar, just add files before the last 512 bytes block
  974.         if (!$this->_openReadWrite())
  975.            return false;
  976.  
  977.         $v_size = filesize($this->_tarname);
  978.         fseek($this->_file, $v_size-512);
  979.  
  980.         if ($this->_addList($p_filelist, $p_add_dir, $p_remove_dir))
  981.            $this->_writeFooter();
  982.  
  983.         $this->_close();
  984.  
  985.         return true;
  986.     }
  987.     // }}}
  988.  
  989.     // {{{ _dirCheck()
  990.     function _dirCheck($p_dir)
  991.     {
  992.         if ((@is_dir($p_dir)) || ($p_dir == ""))
  993.             return true;
  994.  
  995.         $p_parent_dir = dirname($p_dir);
  996.  
  997.         if (($p_parent_dir != $p_dir) &&
  998.             ($p_parent_dir != "") &&
  999.             (!$this->_dirCheck($p_parent_dir)))
  1000.              return false;
  1001.  
  1002.         if (!@mkdir($p_dir, 0777)) {
  1003.             $this->_error("Unable to create directory '$p_dir'");
  1004.             return false;
  1005.         }
  1006.  
  1007.         return true;
  1008.     }
  1009.     // }}}
  1010.  
  1011.     // {{{ _pathReduction()
  1012.     function _pathReduction($p_dir)
  1013.     {
  1014.         $v_result = "";
  1015.  
  1016.         // ----- Look for not empty path
  1017.         if ($p_dir != "") {
  1018.             // ----- Explode path by directory names
  1019.             $v_list = explode("/", $p_dir);
  1020.  
  1021.             // ----- Study directories from last to first
  1022.             for ($i=sizeof($v_list)-1; $i>=0; $i--) {
  1023.                 // ----- Look for current path
  1024.                 if ($v_list[$i] == ".") {
  1025.                     // ----- Ignore this directory
  1026.                     // Should be the first $i=0, but no check is done
  1027.                 }
  1028.                 else if ($v_list[$i] == "..") {
  1029.                     // ----- Ignore it and ignore the $i-1
  1030.                     $i--;
  1031.                 }
  1032.                 else if (($v_list[$i] == "") && ($i!=(sizeof($v_list)-1)) && ($i!=0)) {
  1033.                     // ----- Ignore only the double '//' in path,
  1034.                     // but not the first and last '/'
  1035.                 } else {
  1036.                     $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?"/".$v_result:"");
  1037.                 }
  1038.             }
  1039.         }
  1040.         return $v_result;
  1041.     }
  1042.     // }}}
  1043.  
  1044. }
  1045. ?>