1<?php
2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
4/**
5 * Recursively uncompress every file it finds
6 *
7 * PHP versions 4 and 5
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330,Boston,MA 02111-1307 USA
22 *
23 * @category   File Formats
24 * @package    File_Archive
25 * @author     Vincent Lascaux <vincentlascaux@php.net>
26 * @copyright  1997-2005 The PHP Group
27 * @license    http://www.gnu.org/copyleft/lesser.html  LGPL
28 * @version    CVS: $Id: Uncompress.php,v 1.32 2005/07/09 12:54:35 vincentlascaux Exp $
29 * @link       http://pear.php.net/package/File_Archive
30 */
31
32require_once "File/Archive/Reader.php";
33require_once "File/Archive/Reader/ChangeName.php";
34
35/**
36 * Recursively uncompress every file it finds
37 */
38class File_Archive_Reader_Uncompress extends File_Archive_Reader_Relay
39{
40    /**
41     * @var Array Stack of readers
42     * @access private
43     */
44    var $readers = array();
45
46    /**
47     * @var array of readers to close when closing $this
48     * @access private
49     */
50    var $toClose = array();
51
52    /**
53     * @var File_Archive_Reader Reader from which all started (usefull to be
54     *      able to close)
55     * @access private
56     */
57    var $startReader;
58
59    /**
60     * @var Int Maximum depth of uncompression after the basicDir
61     *          (that may contain some uncompression also)
62     *          -1 means no limit
63     * @access private
64     */
65    var $uncompressionLevel;
66
67    /**
68     * @var array Only files starting with $baseDir will be reported
69     *      This array contains explode('/', $directoryName)
70     * @access private
71     */
72    var $baseDir = '';
73
74    /**
75     * @var int Compression level required to go to reach the baseDir
76     *          or null if it is currently being computed
77     * @access private
78     */
79    var $baseDirCompressionLevel = null;
80
81    /**
82     * @var int We are selecting substr($baseDir, 0, $baseDirProgression)
83     */
84    var $baseDirProgression = 0;
85
86    /**
87     * @var boolean Flag set to indicate that the current file has not been
88     *              displayed
89     */
90    var $currentFileNotDisplayed = false;
91
92    function File_Archive_Reader_Uncompress(
93                        &$innerReader, $uncompressionLevel = -1)
94    {
95        parent::File_Archive_Reader_Relay($innerReader);
96        $this->startReader =& $innerReader;
97        $this->uncompressionLevel = $uncompressionLevel;
98    }
99
100    /**
101     * Attempt to change the current source (if the current file is an archive)
102     * If this is the case, push the current source onto the stack and make the
103     * good archive reader the current source. A file is considered as an
104     * archive if its extension is one of tar, gz, zip, tgz
105     *
106     * @return bool whether the source has been pushed or not
107     * @access private
108     */
109    function push()
110    {
111        if ($this->uncompressionLevel >= 0 &&
112            $this->baseDirCompressionLevel !== null &&
113            count($this->readers) >= $this->uncompressionLevel
114           ) {
115           return false;
116        }
117
118        // Check the extension of the file (maybe we need to uncompress it?)
119        $filename  = $this->source->getFilename();
120
121        $extensions = explode('.', strtolower($filename));
122
123        $reader =& $this->source;
124        $nbUncompressions = 0;
125
126        while (($extension = array_pop($extensions)) !== null) {
127            $nbUncompressions++;
128            unset($next);
129            $next = File_Archive::readArchive($extension, $reader, $nbUncompressions == 1);
130            if ($next === false) {
131                $extensions = array();
132            } else {
133                unset($reader);
134                $reader =& $next;
135            }
136        }
137        if ($nbUncompressions == 1) {
138            return false;
139        } else {
140            $this->readers[count($this->readers)] =& $this->source;
141            unset($this->source);
142            $this->source = new File_Archive_Reader_AddBaseName(
143                $filename, $reader
144            );
145            return true;
146        }
147    }
148    /**
149     * @see File_Archive_Reader::close()
150     */
151    function next()
152    {
153        if ($this->currentFileNotDisplayed) {
154            $this->currentFileNotDisplayed = false;
155            return true;
156        }
157
158        do {
159            do {
160                $selection = substr($this->baseDir, 0, $this->baseDirProgression);
161                if ($selection === false) {
162                    $selection = '';
163                }
164
165                $error = $this->source->select($selection, false);
166                if (PEAR::isError($error)) {
167                    return $error;
168                }
169                if (!$error) {
170                    if (empty($this->readers)) {
171                        return false;
172                    }
173                    $this->source->close();
174                    unset($this->source);
175                    $this->source =& $this->readers[count($this->readers)-1];
176                    unset($this->readers[count($this->readers)-1]);
177                }
178            } while (!$error);
179
180            $filename = $this->source->getFilename();
181            if (strlen($filename) < strlen($this->baseDir)) {
182                $goodFile = (strncmp($filename, $this->baseDir, strlen($filename)) == 0 &&
183                             $this->baseDir{strlen($filename)} == '/');
184                if ($goodFile) {
185                    if (strlen($filename) + 2 < strlen($this->baseDirProgression)) {
186                        $this->baseDirProgression = strpos($this->baseDir, '/', strlen($filename)+2);
187                        if ($this->baseDirProgression === false) {
188                            $this->baseDirProgression = strlen($this->baseDir);
189                        }
190                    } else {
191                        $this->baseDirProgression = strlen($this->baseDir);
192                    }
193                }
194            } else {
195                $goodFile = (strncmp($filename, $this->baseDir, strlen($this->baseDir)) == 0);
196                if ($goodFile) {
197                    $this->baseDirProgression = strlen($this->baseDir);
198                }
199            }
200        } while ($goodFile && $this->push());
201
202        return true;
203    }
204
205    /**
206     * Efficiently filter out the files which URL does not start with $baseDir
207     * Throws an error if the $baseDir can't be found
208     * @return bool Whether baseDir was a directory or a file
209     */
210    function setBaseDir($baseDir)
211    {
212        $this->baseDir = $baseDir;
213        $this->baseDirProgression = strpos($baseDir, '/');
214        if ($this->baseDirProgression === false) {
215            $this->baseDirProgression = strlen($baseDir);
216        }
217
218        $error = $this->next();
219        if ($error === false) {
220            return PEAR::raiseError("No directory $baseDir in inner reader");
221        } else if (PEAR::isError($error)) {
222            return $error;
223        }
224
225        $this->currentFileNotDisplayed = true;
226        return strlen($this->getFilename())>strlen($baseDir);
227    }
228    /**
229     * @see File_Archive_Reader::select()
230     */
231    function select($filename, $close = true)
232    {
233        if ($close) {
234            $error = $this->close();
235            if (PEAR::isError($close)) {
236                return $error;
237            }
238        }
239
240        $oldBaseDir = $this->baseDir;
241        $oldProgression = $this->baseDirProgression;
242
243        $this->baseDir = $filename;
244        $this->baseDirProgression = 0;
245
246        $res = $this->next();
247
248        $this->baseDir = $oldBaseDir;
249        $this->baseDirProgression = $oldProgression;
250
251        return $res;
252    }
253
254    /**
255     * @see File_Archive_Reader::close()
256     */
257    function close()
258    {
259        for ($i=0; $i<count($this->readers); ++$i) {
260            $this->readers[$i]->close();
261        }
262        //var_dump($this->toClose);
263        for ($i=0; $i<count($this->toClose); ++$i) {
264            if ($this->toClose[$i] !== null) {
265                $this->toClose[$i]->close();
266            }
267        }
268
269        $this->readers = array();
270        $this->toClose = array();
271        $error = parent::close();
272        $this->baseDirCompressionLevel = null;
273        $this->baseDirProgression = 0;
274
275        unset($this->source);
276        $this->source =& $this->startReader;
277        $this->source->close();
278        $this->currentFileNotDisplayed = false;
279
280        return $error;
281    }
282
283    /**
284     * @see File_Archive_Reader::makeAppendWriter()
285     */
286    function makeAppendWriter()
287    {
288        //The reader needs to be open so that the base dir is found
289        $error = $this->next();
290        if (PEAR::isError($error)) {
291            return $error;
292        }
293
294        return parent::makeAppendWriter();
295    }
296
297    /**
298     * @see File_Archive_Reader::makeWriterRemoveFiles()
299     */
300    function makeWriterRemoveFiles($pred)
301    {
302        //The reader needs to be open so that the base dir is found
303        $error = $this->next();
304        if (PEAR::isError($error)) {
305            return $error;
306        }
307
308        return parent::makeWriterRemoveFiles($pred);
309    }
310}
311
312?>