Completed
Push — master ( ca493a...4c38d1 )
by Morris
132:28 queued 111:40
created

TAR::getFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bart Visscher <[email protected]>
6
 * @author Christian Weiske <[email protected]>
7
 * @author Christopher Schäpers <[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
	const PLAIN = 0;
39
	const GZIP = 1;
40
	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 = array(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
	static public function getTarType($file) {
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
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(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
	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)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
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 = \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']
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
165
				or '/' . $file . '/' == $header['filename']
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
166
				or '/' . $file == $header['filename']
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
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 = 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) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
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 = 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
	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 = \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
	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)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
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) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
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 = \OCP\Files::tmpFolder();
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(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...
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 = \OCP\Files::tmpFile($ext);
350
		if ($this->fileExists($path)) {
351
			$this->extractFile($path, $tmpFile);
352
		} elseif ($mode == 'r' or $mode == 'rb') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
353
			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...
354
		}
355
		if ($mode == 'r' or $mode == 'rb') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
356
			return fopen($tmpFile, $mode);
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 = array(null, 'gz', 'bz');
382
		$this->tar = new \Archive_Tar($this->path, $types[self::getTarType($this->path)]);
383
	}
384
}
385