Passed
Push — master ( a74705...2ba34c )
by Morris
13:54 queued 10s
created

TAR::getError()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 3
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 5
rs 10
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bart Visscher <[email protected]>
6
 * @author Christopher Schäpers <[email protected]>
7
 * @author Christoph Wurst <[email protected]>
8
 * @author Joas Schilling <[email protected]>
9
 * @author Jörn Friedrich Dreyer <[email protected]>
10
 * @author Morris Jobke <[email protected]>
11
 * @author Remco Brenninkmeijer <[email protected]>
12
 * @author Robin Appelman <[email protected]>
13
 * @author Robin McCorkell <[email protected]>
14
 * @author Roeland Jago Douma <[email protected]>
15
 * @author Thomas Müller <[email protected]>
16
 *
17
 * @license AGPL-3.0
18
 *
19
 * This code is free software: you can redistribute it and/or modify
20
 * it under the terms of the GNU Affero General Public License, version 3,
21
 * as published by the Free Software Foundation.
22
 *
23
 * This program is distributed in the hope that it will be useful,
24
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26
 * GNU Affero General Public License for more details.
27
 *
28
 * You should have received a copy of the GNU Affero General Public License, version 3,
29
 * along with this program. If not, see <http://www.gnu.org/licenses/>
30
 *
31
 */
32
33
namespace OC\Archive;
34
35
use Icewind\Streams\CallbackWrapper;
36
37
class TAR extends Archive {
38
	public const PLAIN = 0;
39
	public const GZIP = 1;
40
	public const BZIP = 2;
41
42
	private $fileList;
43
	private $cachedHeaders;
44
45
	/**
46
	 * @var \Archive_Tar tar
47
	 */
48
	private $tar = null;
49
	private $path;
50
51
	/**
52
	 * @param string $source
53
	 */
54
	public function __construct($source) {
55
		$types = [null, 'gz', 'bz2'];
56
		$this->path = $source;
57
		$this->tar = new \Archive_Tar($source, $types[self::getTarType($source)]);
58
	}
59
60
	/**
61
	 * try to detect the type of tar compression
62
	 *
63
	 * @param string $file
64
	 * @return integer
65
	 */
66
	public static function getTarType($file) {
67
		if (strpos($file, '.')) {
68
			$extension = substr($file, strrpos($file, '.'));
69
			switch ($extension) {
70
				case '.gz':
71
				case '.tgz':
72
					return self::GZIP;
73
				case '.bz':
74
				case '.bz2':
75
					return self::BZIP;
76
				case '.tar':
77
					return self::PLAIN;
78
				default:
79
					return self::PLAIN;
80
			}
81
		} else {
82
			return self::PLAIN;
83
		}
84
	}
85
86
	/**
87
	 * add an empty folder to the archive
88
	 *
89
	 * @param string $path
90
	 * @return bool
91
	 */
92
	public function addFolder($path) {
93
		$tmpBase = \OC::$server->getTempManager()->getTemporaryFolder();
94
		$path = rtrim($path, '/') . '/';
95
		if ($this->fileExists($path)) {
96
			return false;
97
		}
98
		$parts = explode('/', $path);
99
		$folder = $tmpBase;
100
		foreach ($parts as $part) {
101
			$folder .= '/' . $part;
102
			if (!is_dir($folder)) {
103
				mkdir($folder);
104
			}
105
		}
106
		$result = $this->tar->addModify([$tmpBase . $path], '', $tmpBase);
107
		rmdir($tmpBase . $path);
108
		$this->fileList = false;
109
		$this->cachedHeaders = false;
110
		return $result;
111
	}
112
113
	/**
114
	 * add a file to the archive
115
	 *
116
	 * @param string $path
117
	 * @param string $source either a local file or string data
118
	 * @return bool
119
	 */
120
	public function addFile($path, $source = '') {
121
		if ($this->fileExists($path)) {
122
			$this->remove($path);
123
		}
124
		if ($source and $source[0] == '/' and file_exists($source)) {
125
			$source = file_get_contents($source);
126
		}
127
		$result = $this->tar->addString($path, $source);
128
		$this->fileList = false;
129
		$this->cachedHeaders = false;
130
		return $result;
131
	}
132
133
	/**
134
	 * rename a file or folder in the archive
135
	 *
136
	 * @param string $source
137
	 * @param string $dest
138
	 * @return bool
139
	 */
140
	public function rename($source, $dest) {
141
		//no proper way to delete, rename entire archive, rename file and remake archive
142
		$tmp = \OC::$server->getTempManager()->getTemporaryFolder();
143
		$this->tar->extract($tmp);
144
		rename($tmp . $source, $tmp . $dest);
145
		$this->tar = null;
146
		unlink($this->path);
147
		$types = [null, 'gz', 'bz'];
148
		$this->tar = new \Archive_Tar($this->path, $types[self::getTarType($this->path)]);
149
		$this->tar->createModify([$tmp], '', $tmp . '/');
150
		$this->fileList = false;
151
		$this->cachedHeaders = false;
152
		return true;
153
	}
154
155
	/**
156
	 * @param string $file
157
	 */
158
	private function getHeader($file) {
159
		if (!$this->cachedHeaders) {
160
			$this->cachedHeaders = $this->tar->listContent();
161
		}
162
		foreach ($this->cachedHeaders as $header) {
163
			if ($file == $header['filename']
164
				or $file . '/' == $header['filename']
165
				or '/' . $file . '/' == $header['filename']
166
				or '/' . $file == $header['filename']
167
			) {
168
				return $header;
169
			}
170
		}
171
		return null;
172
	}
173
174
	/**
175
	 * get the uncompressed size of a file in the archive
176
	 *
177
	 * @param string $path
178
	 * @return int
179
	 */
180
	public function filesize($path) {
181
		$stat = $this->getHeader($path);
182
		return $stat['size'];
183
	}
184
185
	/**
186
	 * get the last modified time of a file in the archive
187
	 *
188
	 * @param string $path
189
	 * @return int
190
	 */
191
	public function mtime($path) {
192
		$stat = $this->getHeader($path);
193
		return $stat['mtime'];
194
	}
195
196
	/**
197
	 * get the files in a folder
198
	 *
199
	 * @param string $path
200
	 * @return array
201
	 */
202
	public function getFolder($path) {
203
		$files = $this->getFiles();
204
		$folderContent = [];
205
		$pathLength = strlen($path);
206
		foreach ($files as $file) {
207
			if ($file[0] == '/') {
208
				$file = substr($file, 1);
209
			}
210
			if (substr($file, 0, $pathLength) == $path and $file != $path) {
211
				$result = substr($file, $pathLength);
212
				if ($pos = strpos($result, '/')) {
213
					$result = substr($result, 0, $pos + 1);
214
				}
215
				if (array_search($result, $folderContent) === false) {
216
					$folderContent[] = $result;
217
				}
218
			}
219
		}
220
		return $folderContent;
221
	}
222
223
	/**
224
	 * get all files in the archive
225
	 *
226
	 * @return array
227
	 */
228
	public function getFiles() {
229
		if ($this->fileList) {
230
			return $this->fileList;
231
		}
232
		if (!$this->cachedHeaders) {
233
			$this->cachedHeaders = $this->tar->listContent();
234
		}
235
		$files = [];
236
		foreach ($this->cachedHeaders as $header) {
237
			$files[] = $header['filename'];
238
		}
239
		$this->fileList = $files;
240
		return $files;
241
	}
242
243
	/**
244
	 * get the content of a file
245
	 *
246
	 * @param string $path
247
	 * @return string
248
	 */
249
	public function getFile($path) {
250
		return $this->tar->extractInString($path);
251
	}
252
253
	/**
254
	 * extract a single file from the archive
255
	 *
256
	 * @param string $path
257
	 * @param string $dest
258
	 * @return bool
259
	 */
260
	public function extractFile($path, $dest) {
261
		$tmp = \OC::$server->getTempManager()->getTemporaryFolder();
262
		if (!$this->fileExists($path)) {
263
			return false;
264
		}
265
		if ($this->fileExists('/' . $path)) {
266
			$success = $this->tar->extractList(['/' . $path], $tmp);
267
		} else {
268
			$success = $this->tar->extractList([$path], $tmp);
269
		}
270
		if ($success) {
0 ignored issues
show
introduced by
The condition $success is always true.
Loading history...
271
			rename($tmp . $path, $dest);
272
		}
273
		\OCP\Files::rmdirr($tmp);
274
		return $success;
275
	}
276
277
	/**
278
	 * extract the archive
279
	 *
280
	 * @param string $dest
281
	 * @return bool
282
	 */
283
	public function extract($dest) {
284
		return $this->tar->extract($dest);
285
	}
286
287
	/**
288
	 * check if a file or folder exists in the archive
289
	 *
290
	 * @param string $path
291
	 * @return bool
292
	 */
293
	public function fileExists($path) {
294
		$files = $this->getFiles();
295
		if ((array_search($path, $files) !== false) or (array_search($path . '/', $files) !== false)) {
296
			return true;
297
		} else {
298
			$folderPath = rtrim($path, '/') . '/';
299
			$pathLength = strlen($folderPath);
300
			foreach ($files as $file) {
301
				if (strlen($file) > $pathLength and substr($file, 0, $pathLength) == $folderPath) {
302
					return true;
303
				}
304
			}
305
		}
306
		if ($path[0] != '/') { //not all programs agree on the use of a leading /
307
			return $this->fileExists('/' . $path);
308
		} else {
309
			return false;
310
		}
311
	}
312
313
	/**
314
	 * remove a file or folder from the archive
315
	 *
316
	 * @param string $path
317
	 * @return bool
318
	 */
319
	public function remove($path) {
320
		if (!$this->fileExists($path)) {
321
			return false;
322
		}
323
		$this->fileList = false;
324
		$this->cachedHeaders = false;
325
		//no proper way to delete, extract entire archive, delete file and remake archive
326
		$tmp = \OC::$server->getTempManager()->getTemporaryFolder();
327
		$this->tar->extract($tmp);
328
		\OCP\Files::rmdirr($tmp . $path);
329
		$this->tar = null;
330
		unlink($this->path);
331
		$this->reopen();
332
		$this->tar->createModify([$tmp], '', $tmp);
333
		return true;
334
	}
335
336
	/**
337
	 * get a file handler
338
	 *
339
	 * @param string $path
340
	 * @param string $mode
341
	 * @return resource
342
	 */
343
	public function getStream($path, $mode) {
344
		if (strrpos($path, '.') !== false) {
345
			$ext = substr($path, strrpos($path, '.'));
346
		} else {
347
			$ext = '';
348
		}
349
		$tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext);
350
		if ($this->fileExists($path)) {
351
			$this->extractFile($path, $tmpFile);
352
		} elseif ($mode == 'r' or $mode == 'rb') {
353
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type resource.
Loading history...
354
		}
355
		if ($mode == 'r' or $mode == 'rb') {
356
			return fopen($tmpFile, $mode);
0 ignored issues
show
Bug Best Practice introduced by
The expression return fopen($tmpFile, $mode) could also return false which is incompatible with the documented return type resource. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
357
		} else {
358
			$handle = fopen($tmpFile, $mode);
359
			return CallbackWrapper::wrap($handle, null, null, function () use ($path, $tmpFile) {
360
				$this->writeBack($tmpFile, $path);
361
			});
362
		}
363
	}
364
365
	/**
366
	 * write back temporary files
367
	 */
368
	public function writeBack($tmpFile, $path) {
369
		$this->addFile($path, $tmpFile);
370
		unlink($tmpFile);
371
	}
372
373
	/**
374
	 * reopen the archive to ensure everything is written
375
	 */
376
	private function reopen() {
377
		if ($this->tar) {
378
			$this->tar->_close();
379
			$this->tar = null;
380
		}
381
		$types = [null, 'gz', 'bz'];
382
		$this->tar = new \Archive_Tar($this->path, $types[self::getTarType($this->path)]);
383
	}
384
385
	/**
386
	 * Get error object from archive_tar.
387
	 */
388
	public function getError(): ?\PEAR_Error {
389
		if ($this->tar instanceof \Archive_Tar && $this->tar->error_object instanceof \PEAR_Error) {
390
			return $this->tar->error_object;
391
		}
392
		return null;
393
	}
394
}
395