Completed
Push — master ( a72e6a...adf7e7 )
by Lukas
08:39
created

TAR::getHeader()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 15
rs 8.2222
cc 7
eloc 10
nc 6
nop 1
1
<?php
2
/**
3
 * @author Bart Visscher <[email protected]>
4
 * @author Christian Weiske <[email protected]>
5
 * @author Christopher Schäpers <[email protected]>
6
 * @author Felix Moeller <[email protected]>
7
 * @author Frank Karlitschek <[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 Thomas Müller <[email protected]>
15
 *
16
 * @copyright Copyright (c) 2016, ownCloud, Inc.
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
class TAR extends Archive {
36
	const PLAIN = 0;
37
	const GZIP = 1;
38
	const BZIP = 2;
39
40
	private $fileList;
41
	private $cachedHeaders;
42
43
	/**
44
	 * @var \Archive_Tar tar
45
	 */
46
	private $tar = null;
47
	private $path;
48
49
	/**
50
	 * @param string $source
51
	 */
52
	function __construct($source) {
53
		$types = array(null, 'gz', 'bz2');
54
		$this->path = $source;
55
		$this->tar = new \Archive_Tar($source, $types[self::getTarType($source)]);
56
	}
57
58
	/**
59
	 * try to detect the type of tar compression
60
	 *
61
	 * @param string $file
62
	 * @return integer
63
	 */
64
	static public function getTarType($file) {
65
		if (strpos($file, '.')) {
66
			$extension = substr($file, strrpos($file, '.'));
67
			switch ($extension) {
68
				case '.gz':
69
				case '.tgz':
70
					return self::GZIP;
71
				case '.bz':
72
				case '.bz2':
73
					return self::BZIP;
74
				case '.tar':
75
					return self::PLAIN;
76
				default:
77
					return self::PLAIN;
78
			}
79
		} else {
80
			return self::PLAIN;
81
		}
82
	}
83
84
	/**
85
	 * add an empty folder to the archive
86
	 *
87
	 * @param string $path
88
	 * @return bool
89
	 */
90
	function addFolder($path) {
91
		$tmpBase = \OC::$server->getTempManager()->getTemporaryFolder();
92
		if (substr($path, -1, 1) != '/') {
93
			$path .= '/';
94
		}
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(array($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
	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
	function rename($source, $dest) {
141
		//no proper way to delete, rename entire archive, rename file and remake archive
142
		$tmp = \OCP\Files::tmpFolder();
143
		$this->tar->extract($tmp);
144
		rename($tmp . $source, $tmp . $dest);
145
		$this->tar = null;
146
		unlink($this->path);
147
		$types = array(null, 'gz', 'bz');
148
		$this->tar = new \Archive_Tar($this->path, $types[self::getTarType($this->path)]);
149
		$this->tar->createModify(array($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
	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
	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
	function getFolder($path) {
203
		$files = $this->getFiles();
204
		$folderContent = array();
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
	function getFiles() {
229
		if ($this->fileList) {
230
			return $this->fileList;
231
		}
232
		if (!$this->cachedHeaders) {
233
			$this->cachedHeaders = $this->tar->listContent();
234
		}
235
		$files = array();
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
	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
	function extractFile($path, $dest) {
261
		$tmp = \OCP\Files::tmpFolder();
262
		if (!$this->fileExists($path)) {
263
			return false;
264
		}
265
		if ($this->fileExists('/' . $path)) {
266
			$success = $this->tar->extractList(array('/' . $path), $tmp);
267
		} else {
268
			$success = $this->tar->extractList(array($path), $tmp);
269
		}
270
		if ($success) {
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
	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
	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 = $path;
299
			if (substr($folderPath, -1, 1) != '/') {
300
				$folderPath .= '/';
301
			}
302
			$pathLength = strlen($folderPath);
303
			foreach ($files as $file) {
304
				if (strlen($file) > $pathLength and substr($file, 0, $pathLength) == $folderPath) {
305
					return true;
306
				}
307
			}
308
		}
309
		if ($path[0] != '/') { //not all programs agree on the use of a leading /
310
			return $this->fileExists('/' . $path);
311
		} else {
312
			return false;
313
		}
314
	}
315
316
	/**
317
	 * remove a file or folder from the archive
318
	 *
319
	 * @param string $path
320
	 * @return bool
321
	 */
322
	function remove($path) {
323
		if (!$this->fileExists($path)) {
324
			return false;
325
		}
326
		$this->fileList = false;
327
		$this->cachedHeaders = false;
328
		//no proper way to delete, extract entire archive, delete file and remake archive
329
		$tmp = \OCP\Files::tmpFolder();
330
		$this->tar->extract($tmp);
331
		\OCP\Files::rmdirr($tmp . $path);
332
		$this->tar = null;
333
		unlink($this->path);
334
		$this->reopen();
335
		$this->tar->createModify(array($tmp), '', $tmp);
0 ignored issues
show
Bug introduced by
The method createModify cannot be called on $this->tar (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
336
		return true;
337
	}
338
339
	/**
340
	 * get a file handler
341
	 *
342
	 * @param string $path
343
	 * @param string $mode
344
	 * @return resource
345
	 */
346
	function getStream($path, $mode) {
347
		if (strrpos($path, '.') !== false) {
348
			$ext = substr($path, strrpos($path, '.'));
349
		} else {
350
			$ext = '';
351
		}
352
		$tmpFile = \OCP\Files::tmpFile($ext);
353
		if ($this->fileExists($path)) {
354
			$this->extractFile($path, $tmpFile);
355
		} elseif ($mode == 'r' or $mode == 'rb') {
356
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type declared by the abstract method OC\Archive\Archive::getStream of type resource.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
357
		}
358
		if ($mode == 'r' or $mode == 'rb') {
359
			return fopen($tmpFile, $mode);
360
		} else {
361
			\OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack'));
362
			self::$tempFiles[$tmpFile] = $path;
363
			return fopen('close://' . $tmpFile, $mode);
364
		}
365
	}
366
367
	private static $tempFiles = array();
368
369
	/**
370
	 * write back temporary files
371
	 */
372
	function writeBack($tmpFile) {
373 View Code Duplication
		if (isset(self::$tempFiles[$tmpFile])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
374
			$this->addFile(self::$tempFiles[$tmpFile], $tmpFile);
375
			unlink($tmpFile);
376
		}
377
	}
378
379
	/**
380
	 * reopen the archive to ensure everything is written
381
	 */
382
	private function reopen() {
383
		if ($this->tar) {
384
			$this->tar->_close();
385
			$this->tar = null;
386
		}
387
		$types = array(null, 'gz', 'bz');
388
		$this->tar = new \Archive_Tar($this->path, $types[self::getTarType($this->path)]);
389
	}
390
}
391