1 <?php
2 /**
3 * @package Joomla.Platform
4 * @subpackage Archive
5 *
6 * @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 */
9
10 defined('JPATH_PLATFORM') or die;
11
12 jimport('joomla.filesystem.file');
13 jimport('joomla.filesystem.folder');
14 jimport('joomla.filesystem.path');
15
16 /**
17 * Tar format adapter for the JArchive class
18 *
19 * This class is inspired from and draws heavily in code and concept from the Compress package of
20 * The Horde Project <https://www.horde.org>
21 *
22 * @contributor Michael Slusarz <slusarz@horde.org>
23 * @contributor Michael Cochrane <mike@graftonhall.co.nz>
24 *
25 * @since 11.1
26 */
27 class JArchiveTar implements JArchiveExtractable
28 {
29 /**
30 * Tar file types.
31 *
32 * @var array
33 * @since 11.1
34 */
35 private $_types = array(
36 0x0 => 'Unix file',
37 0x30 => 'File',
38 0x31 => 'Link',
39 0x32 => 'Symbolic link',
40 0x33 => 'Character special file',
41 0x34 => 'Block special file',
42 0x35 => 'Directory',
43 0x36 => 'FIFO special file',
44 0x37 => 'Contiguous file',
45 );
46
47 /**
48 * Tar file data buffer
49 *
50 * @var string
51 * @since 11.1
52 */
53 private $_data = null;
54
55 /**
56 * Tar file metadata array
57 *
58 * @var array
59 * @since 11.1
60 */
61 private $_metadata = null;
62
63 /**
64 * Extract a ZIP compressed file to a given path
65 *
66 * @param string $archive Path to ZIP archive to extract
67 * @param string $destination Path to extract archive into
68 * @param array $options Extraction options [unused]
69 *
70 * @return boolean|JException True on success, JException instance on failure if JError class exists
71 *
72 * @since 11.1
73 * @throws RuntimeException if JError class does not exist
74 */
75 public function extract($archive, $destination, array $options = array())
76 {
77 $this->_data = null;
78 $this->_metadata = null;
79
80 $this->_data = file_get_contents($archive);
81
82 if (!$this->_data)
83 {
84 if (class_exists('JError'))
85 {
86 return JError::raiseWarning(100, 'Unable to read archive');
87 }
88 else
89 {
90 throw new RuntimeException('Unable to read archive');
91 }
92 }
93
94 $this->_getTarInfo($this->_data);
95
96 for ($i = 0, $n = count($this->_metadata); $i < $n; $i++)
97 {
98 $type = strtolower($this->_metadata[$i]['type']);
99
100 if ($type == 'file' || $type == 'unix file')
101 {
102 $buffer = $this->_metadata[$i]['data'];
103 $path = JPath::clean($destination . '/' . $this->_metadata[$i]['name']);
104
105 // Make sure the destination folder exists
106 if (!JFolder::create(dirname($path)))
107 {
108 if (class_exists('JError'))
109 {
110 return JError::raiseWarning(100, 'Unable to create destination');
111 }
112 else
113 {
114 throw new RuntimeException('Unable to create destination');
115 }
116 }
117
118 if (JFile::write($path, $buffer) === false)
119 {
120 if (class_exists('JError'))
121 {
122 return JError::raiseWarning(100, 'Unable to write entry');
123 }
124 else
125 {
126 throw new RuntimeException('Unable to write entry');
127 }
128 }
129 }
130 }
131
132 return true;
133 }
134
135 /**
136 * Tests whether this adapter can unpack files on this computer.
137 *
138 * @return boolean True if supported
139 *
140 * @since 11.3
141 */
142 public static function isSupported()
143 {
144 return true;
145 }
146
147 /**
148 * Get the list of files/data from a Tar archive buffer.
149 *
150 * @param string &$data The Tar archive buffer.
151 *
152 * @return boolean|JException True on success, JException instance on failure if JError class exists
153 *
154 * @since 11.1
155 * @throws RuntimeException if JError class does not exist
156 */
157 protected function _getTarInfo(& $data)
158 {
159 $position = 0;
160 $return_array = array();
161
162 while ($position < strlen($data))
163 {
164 if (version_compare(PHP_VERSION, '5.5', '>='))
165 {
166 $info = @unpack(
167 'Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/Z8checksum/Ctypeflag/Z100link/Z6magic/Z2version/Z32uname/Z32gname/Z8devmajor/Z8devminor',
168 substr($data, $position)
169 );
170 }
171 else
172 {
173 $info = @unpack(
174 'a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/Ctypeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor',
175 substr($data, $position)
176 );
177 }
178
179 /**
180 * This variable has been set in the previous loop,
181 * meaning that the filename was present in the previous block
182 * to allow more than 100 characters - see below
183 */
184 if (isset($longlinkfilename))
185 {
186 $info['filename'] = $longlinkfilename;
187 unset($longlinkfilename);
188 }
189
190 if (!$info)
191 {
192 if (class_exists('JError'))
193 {
194 return JError::raiseWarning(100, 'Unable to decompress data');
195 }
196 else
197 {
198 throw new RuntimeException('Unable to decompress data');
199 }
200 }
201
202 $position += 512;
203 $contents = substr($data, $position, octdec($info['size']));
204 $position += ceil(octdec($info['size']) / 512) * 512;
205
206 if ($info['filename'])
207 {
208 $file = array(
209 'attr' => null,
210 'data' => null,
211 'date' => octdec($info['mtime']),
212 'name' => trim($info['filename']),
213 'size' => octdec($info['size']),
214 'type' => isset($this->_types[$info['typeflag']]) ? $this->_types[$info['typeflag']] : null,
215 );
216
217 if (($info['typeflag'] == 0) || ($info['typeflag'] == 0x30) || ($info['typeflag'] == 0x35))
218 {
219 // File or folder.
220 $file['data'] = $contents;
221
222 $mode = hexdec(substr($info['mode'], 4, 3));
223 $file['attr'] = (($info['typeflag'] == 0x35) ? 'd' : '-') . (($mode & 0x400) ? 'r' : '-') . (($mode & 0x200) ? 'w' : '-') .
224 (($mode & 0x100) ? 'x' : '-') . (($mode & 0x040) ? 'r' : '-') . (($mode & 0x020) ? 'w' : '-') . (($mode & 0x010) ? 'x' : '-') .
225 (($mode & 0x004) ? 'r' : '-') . (($mode & 0x002) ? 'w' : '-') . (($mode & 0x001) ? 'x' : '-');
226 }
227 elseif (chr($info['typeflag']) == 'L' && $info['filename'] == '././@LongLink')
228 {
229 // GNU tar ././@LongLink support - the filename is actually in the contents,
230 // setting a variable here so we can test in the next loop
231 $longlinkfilename = $contents;
232
233 // And the file contents are in the next block so we'll need to skip this
234 continue;
235 }
236 else
237 {
238 // Some other type.
239 }
240
241 $return_array[] = $file;
242 }
243 }
244
245 $this->_metadata = $return_array;
246
247 return true;
248 }
249 }
250