1<?php
2
3namespace splitbrain\PHPArchive;
4
5/**
6 * Class FileInfo
7 *
8 * stores meta data about a file in an Archive
9 *
10 * @author  Andreas Gohr <andi@splitbrain.org>
11 * @package splitbrain\PHPArchive
12 * @license MIT
13 */
14class FileInfo
15{
16
17    protected $isdir = false;
18    protected $path = '';
19    protected $size = 0;
20    protected $csize = 0;
21    protected $mtime = 0;
22    protected $mode = 0664;
23    protected $owner = '';
24    protected $group = '';
25    protected $uid = 0;
26    protected $gid = 0;
27    protected $comment = '';
28
29    /**
30     * initialize dynamic defaults
31     *
32     * @param string $path The path of the file, can also be set later through setPath()
33     */
34    public function __construct($path = '')
35    {
36        $this->mtime = time();
37        $this->setPath($path);
38    }
39
40    /**
41     * Handle calls to deprecated methods
42     *
43     * @param string $name
44     * @param array $arguments
45     * @return mixed
46     */
47    public function __call($name, $arguments)
48    {
49        if($name === 'match') {
50            trigger_error('FileInfo::match() is deprecated, use FileInfo::matchExpression() instead.', E_USER_NOTICE);
51            return call_user_func_array([$this, $name], $arguments);
52        }
53
54        trigger_error('Call to undefined method FileInfo::'.$name.'()', E_USER_ERROR);
55        return null;
56    }
57
58    /**
59     * Factory to build FileInfo from existing file or directory
60     *
61     * @param string $path path to a file on the local file system
62     * @param string $as   optional path to use inside the archive
63     * @throws FileInfoException
64     * @return FileInfo
65     */
66    public static function fromPath($path, $as = '')
67    {
68        clearstatcache(false, $path);
69
70        if (!file_exists($path)) {
71            throw new FileInfoException("$path does not exist");
72        }
73
74        $stat = stat($path);
75        $file = new FileInfo();
76
77        $file->setPath($path);
78        $file->setIsdir(is_dir($path));
79        $file->setMode(fileperms($path));
80        $file->setOwner(fileowner($path));
81        $file->setGroup(filegroup($path));
82        $file->setSize(filesize($path));
83        $file->setUid($stat['uid']);
84        $file->setGid($stat['gid']);
85        $file->setMtime($stat['mtime']);
86
87        if ($as) {
88            $file->setPath($as);
89        }
90
91        return $file;
92    }
93
94    /**
95     * @return int the filesize. always 0 for directories
96     */
97    public function getSize()
98    {
99        if($this->isdir) return 0;
100        return $this->size;
101    }
102
103    /**
104     * @param int $size
105     */
106    public function setSize($size)
107    {
108        $this->size = $size;
109    }
110
111    /**
112     * @return int
113     */
114    public function getCompressedSize()
115    {
116        return $this->csize;
117    }
118
119    /**
120     * @param int $csize
121     */
122    public function setCompressedSize($csize)
123    {
124        $this->csize = $csize;
125    }
126
127    /**
128     * @return int
129     */
130    public function getMtime()
131    {
132        return $this->mtime;
133    }
134
135    /**
136     * @param int $mtime
137     */
138    public function setMtime($mtime)
139    {
140        $this->mtime = $mtime;
141    }
142
143    /**
144     * @return int
145     */
146    public function getGid()
147    {
148        return $this->gid;
149    }
150
151    /**
152     * @param int $gid
153     */
154    public function setGid($gid)
155    {
156        $this->gid = $gid;
157    }
158
159    /**
160     * @return int
161     */
162    public function getUid()
163    {
164        return $this->uid;
165    }
166
167    /**
168     * @param int $uid
169     */
170    public function setUid($uid)
171    {
172        $this->uid = $uid;
173    }
174
175    /**
176     * @return string
177     */
178    public function getComment()
179    {
180        return $this->comment;
181    }
182
183    /**
184     * @param string $comment
185     */
186    public function setComment($comment)
187    {
188        $this->comment = $comment;
189    }
190
191    /**
192     * @return string
193     */
194    public function getGroup()
195    {
196        return $this->group;
197    }
198
199    /**
200     * @param string $group
201     */
202    public function setGroup($group)
203    {
204        $this->group = $group;
205    }
206
207    /**
208     * @return boolean
209     */
210    public function getIsdir()
211    {
212        return $this->isdir;
213    }
214
215    /**
216     * @param boolean $isdir
217     */
218    public function setIsdir($isdir)
219    {
220        // default mode for directories
221        if ($isdir && $this->mode === 0664) {
222            $this->mode = 0775;
223        }
224        $this->isdir = $isdir;
225    }
226
227    /**
228     * @return int
229     */
230    public function getMode()
231    {
232        return $this->mode;
233    }
234
235    /**
236     * @param int $mode
237     */
238    public function setMode($mode)
239    {
240        $this->mode = $mode;
241    }
242
243    /**
244     * @return string
245     */
246    public function getOwner()
247    {
248        return $this->owner;
249    }
250
251    /**
252     * @param string $owner
253     */
254    public function setOwner($owner)
255    {
256        $this->owner = $owner;
257    }
258
259    /**
260     * @return string
261     */
262    public function getPath()
263    {
264        return $this->path;
265    }
266
267    /**
268     * @param string $path
269     */
270    public function setPath($path)
271    {
272        $this->path = $this->cleanPath($path);
273    }
274
275    /**
276     * Cleans up a path and removes relative parts, also strips leading slashes
277     *
278     * @param string $path
279     * @return string
280     */
281    protected function cleanPath($path)
282    {
283        $path    = str_replace('\\', '/', $path);
284        $path    = explode('/', $path);
285        $newpath = array();
286        foreach ($path as $p) {
287            if ($p === '' || $p === '.') {
288                continue;
289            }
290            if ($p === '..') {
291                array_pop($newpath);
292                continue;
293            }
294            array_push($newpath, $p);
295        }
296        return trim(implode('/', $newpath), '/');
297    }
298
299    /**
300     * Strip given prefix or number of path segments from the filename
301     *
302     * The $strip parameter allows you to strip a certain number of path components from the filenames
303     * found in the tar file, similar to the --strip-components feature of GNU tar. This is triggered when
304     * an integer is passed as $strip.
305     * Alternatively a fixed string prefix may be passed in $strip. If the filename matches this prefix,
306     * the prefix will be stripped. It is recommended to give prefixes with a trailing slash.
307     *
308     * @param  int|string $strip
309     */
310    public function strip($strip)
311    {
312        $filename = $this->getPath();
313        $striplen = strlen($strip);
314        if (is_int($strip)) {
315            // if $strip is an integer we strip this many path components
316            $parts = explode('/', $filename);
317            if (!$this->getIsdir()) {
318                $base = array_pop($parts); // keep filename itself
319            } else {
320                $base = '';
321            }
322            $filename = join('/', array_slice($parts, $strip));
323            if ($base) {
324                $filename .= "/$base";
325            }
326        } else {
327            // if strip is a string, we strip a prefix here
328            if (substr($filename, 0, $striplen) == $strip) {
329                $filename = substr($filename, $striplen);
330            }
331        }
332
333        $this->setPath($filename);
334    }
335
336    /**
337     * Does the file match the given include and exclude expressions?
338     *
339     * Exclude rules take precedence over include rules
340     *
341     * @param string $include Regular expression of files to include
342     * @param string $exclude Regular expression of files to exclude
343     * @return bool
344     */
345    public function matchExpression($include = '', $exclude = '')
346    {
347        $extract = true;
348        if ($include && !preg_match($include, $this->getPath())) {
349            $extract = false;
350        }
351        if ($exclude && preg_match($exclude, $this->getPath())) {
352            $extract = false;
353        }
354
355        return $extract;
356    }
357}
358
359