Completed
Push — master ( defc4b...3e0789 )
by Joas
14:23
created

Detection   B

Complexity

Total Complexity 49

Size/Duplication

Total Lines 289
Duplicated Lines 7.27 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
dl 21
loc 289
rs 8.5454
c 0
b 0
f 0
wmc 49
lcom 1
cbo 5

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A registerType() 0 6 2
A registerTypeArray() 0 8 3
A loadAliases() 0 12 3
A getAllAliases() 0 4 1
A loadMappings() 0 15 3
A getAllMappings() 0 4 1
B detectPath() 0 24 4
D detect() 0 44 17
A detectString() 9 15 4
A getSecureMimeType() 0 7 2
C mimeTypeIcon() 12 48 8

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Detection often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Detection, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Andreas Fischer <[email protected]>
6
 * @author Hendrik Leppelsack <[email protected]>
7
 * @author Jens-Christian Fischer <[email protected]>
8
 * @author Lukas Reschke <[email protected]>
9
 * @author Morris Jobke <[email protected]>
10
 * @author Robin Appelman <[email protected]>
11
 * @author Robin McCorkell <[email protected]>
12
 * @author Roeland Jago Douma <[email protected]>
13
 * @author Thomas Tanghus <[email protected]>
14
 *
15
 * @license AGPL-3.0
16
 *
17
 * This code is free software: you can redistribute it and/or modify
18
 * it under the terms of the GNU Affero General Public License, version 3,
19
 * as published by the Free Software Foundation.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License, version 3,
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
28
 *
29
 */
30
31
namespace OC\Files\Type;
32
33
use OCP\Files\IMimeTypeDetector;
34
use OCP\IURLGenerator;
35
36
/**
37
 * Class Detection
38
 *
39
 * Mimetype detection
40
 *
41
 * @package OC\Files\Type
42
 */
43
class Detection implements IMimeTypeDetector {
44
	protected $mimetypes = [];
45
	protected $secureMimeTypes = [];
46
47
	protected $mimetypeIcons = [];
48
	/** @var string[] */
49
	protected $mimeTypeAlias = [];
50
51
	/** @var IURLGenerator */
52
	private $urlGenerator;
53
54
	/** @var string */
55
	private $customConfigDir;
56
57
	/** @var string */
58
	private $defaultConfigDir;
59
60
	/**
61
	 * @param IURLGenerator $urlGenerator
62
	 * @param string $customConfigDir
63
	 * @param string $defaultConfigDir
64
	 */
65
	public function __construct(IURLGenerator $urlGenerator,
66
								$customConfigDir,
67
								$defaultConfigDir) {
68
		$this->urlGenerator = $urlGenerator;
69
		$this->customConfigDir = $customConfigDir;
70
		$this->defaultConfigDir = $defaultConfigDir;
71
	}
72
73
	/**
74
	 * Add an extension -> mimetype mapping
75
	 *
76
	 * $mimetype is the assumed correct mime type
77
	 * The optional $secureMimeType is an alternative to send to send
78
	 * to avoid potential XSS.
79
	 *
80
	 * @param string $extension
81
	 * @param string $mimetype
82
	 * @param string|null $secureMimeType
83
	 */
84
	public function registerType($extension,
85
								 $mimetype,
86
								 $secureMimeType = null) {
87
		$this->mimetypes[$extension] = array($mimetype, $secureMimeType);
88
		$this->secureMimeTypes[$mimetype] = $secureMimeType ?: $mimetype;
89
	}
90
91
	/**
92
	 * Add an array of extension -> mimetype mappings
93
	 *
94
	 * The mimetype value is in itself an array where the first index is
95
	 * the assumed correct mimetype and the second is either a secure alternative
96
	 * or null if the correct is considered secure.
97
	 *
98
	 * @param array $types
99
	 */
100
	public function registerTypeArray($types) {
101
		$this->mimetypes = array_merge($this->mimetypes, $types);
102
103
		// Update the alternative mimetypes to avoid having to look them up each time.
104
		foreach ($this->mimetypes as $mimeType) {
105
			$this->secureMimeTypes[$mimeType[0]] = isset($mimeType[1]) ? $mimeType[1]: $mimeType[0];
106
		}
107
	}
108
109
	/**
110
	 * Add the mimetype aliases if they are not yet present
111
	 */
112
	private function loadAliases() {
113
		if (!empty($this->mimeTypeAlias)) {
114
			return;
115
		}
116
117
		$this->mimeTypeAlias = json_decode(file_get_contents($this->defaultConfigDir . '/mimetypealiases.dist.json'), true);
0 ignored issues
show
Documentation Bug introduced by
It seems like json_decode(file_get_con...ases.dist.json'), true) of type * is incompatible with the declared type array<integer,string> of property $mimeTypeAlias.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
118
119
		if (file_exists($this->customConfigDir . '/mimetypealiases.json')) {
120
			$custom = json_decode(file_get_contents($this->customConfigDir . '/mimetypealiases.json'), true);
121
			$this->mimeTypeAlias = array_merge($this->mimeTypeAlias, $custom);
122
		}
123
	}
124
125
	/**
126
	 * @return string[]
127
	 */
128
	public function getAllAliases() {
129
		$this->loadAliases();
130
		return $this->mimeTypeAlias;
131
	}
132
133
	/**
134
	 * Add mimetype mappings if they are not yet present
135
	 */
136
	private function loadMappings() {
137
		if (!empty($this->mimetypes)) {
138
			return;
139
		}
140
141
		$mimetypeMapping = json_decode(file_get_contents($this->defaultConfigDir . '/mimetypemapping.dist.json'), true);
142
143
		//Check if need to load custom mappings
144
		if (file_exists($this->customConfigDir . '/mimetypemapping.json')) {
145
			$custom = json_decode(file_get_contents($this->customConfigDir . '/mimetypemapping.json'), true);
146
			$mimetypeMapping = array_merge($mimetypeMapping, $custom);
147
		}
148
149
		$this->registerTypeArray($mimetypeMapping);
150
	}
151
152
	/**
153
	 * @return array
154
	 */
155
	public function getAllMappings() {
156
		$this->loadMappings();
157
		return $this->mimetypes;
158
	}
159
160
	/**
161
	 * detect mimetype only based on filename, content of file is not used
162
	 *
163
	 * @param string $path
164
	 * @return string
165
	 */
166
	public function detectPath($path) {
167
		$this->loadMappings();
168
169
		$fileName = basename($path);
170
171
		// remove leading dot on hidden files with a file extension
172
		$fileName = ltrim($fileName, '.');
173
174
		// note: leading dot doesn't qualify as extension
175
		if (strpos($fileName, '.') > 0) {
176
177
			// remove versioning extension: name.v1508946057 and transfer extension: name.ocTransferId2057600214.part
178
			$fileName = preg_replace('!((\.v\d+)|((.ocTransferId\d+)?.part))$!', '', $fileName);
179
180
			//try to guess the type by the file extension
181
			$extension = strtolower(strrchr($fileName, '.'));
182
			$extension = substr($extension, 1); //remove leading .
183
			return (isset($this->mimetypes[$extension]) && isset($this->mimetypes[$extension][0]))
184
				? $this->mimetypes[$extension][0]
185
				: 'application/octet-stream';
186
		} else {
187
			return 'application/octet-stream';
188
		}
189
	}
190
191
	/**
192
	 * detect mimetype based on both filename and content
193
	 *
194
	 * @param string $path
195
	 * @return string
196
	 */
197
	public function detect($path) {
198
		$this->loadMappings();
199
200
		if (@is_dir($path)) {
201
			// directories are easy
202
			return "httpd/unix-directory";
203
		}
204
205
		$mimeType = $this->detectPath($path);
206
207
		if ($mimeType === 'application/octet-stream' and function_exists('finfo_open')
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...
208
			and function_exists('finfo_file') and $finfo = finfo_open(FILEINFO_MIME)
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...
209
		) {
210
			$info = @strtolower(finfo_file($finfo, $path));
211
			finfo_close($finfo);
212
			if ($info) {
213
				$mimeType = strpos($info, ';') !== false ? substr($info, 0, strpos($info, ';')) : $info;
214
				return empty($mimeType) ? 'application/octet-stream' : $mimeType;
215
			}
216
217
		}
218
		$isWrapped = (strpos($path, '://') !== false) and (substr($path, 0, 7) === 'file://');
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...
219
		if (!$isWrapped and $mimeType === 'application/octet-stream' && function_exists("mime_content_type")) {
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...
220
			// use mime magic extension if available
221
			$mimeType = mime_content_type($path);
222
		}
223
		if (!$isWrapped and $mimeType === 'application/octet-stream' && \OC_Helper::canExecute("file")) {
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...
224
			// it looks like we have a 'file' command,
225
			// lets see if it does have mime support
226
			$path = escapeshellarg($path);
227
			$fp = popen("file -b --mime-type $path 2>/dev/null", "r");
228
			$reply = fgets($fp);
229
			pclose($fp);
230
231
			//trim the newline
232
			$mimeType = trim($reply);
233
234
			if (empty($mimeType)) {
235
				$mimeType = 'application/octet-stream';
236
			}
237
238
		}
239
		return $mimeType;
240
	}
241
242
	/**
243
	 * detect mimetype based on the content of a string
244
	 *
245
	 * @param string $data
246
	 * @return string
247
	 */
248
	public function detectString($data) {
249
		if (function_exists('finfo_open') and function_exists('finfo_file')) {
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...
250
			$finfo = finfo_open(FILEINFO_MIME);
251
			$info = finfo_buffer($finfo, $data);
252
			return strpos($info, ';') !== false ? substr($info, 0, strpos($info, ';')) : $info;
253 View Code Duplication
		} else {
254
			$tmpFile = \OC::$server->getTempManager()->getTemporaryFile();
255
			$fh = fopen($tmpFile, 'wb');
256
			fwrite($fh, $data, 8024);
257
			fclose($fh);
258
			$mime = $this->detect($tmpFile);
259
			unset($tmpFile);
260
			return $mime;
261
		}
262
	}
263
264
	/**
265
	 * Get a secure mimetype that won't expose potential XSS.
266
	 *
267
	 * @param string $mimeType
268
	 * @return string
269
	 */
270
	public function getSecureMimeType($mimeType) {
271
		$this->loadMappings();
272
273
		return isset($this->secureMimeTypes[$mimeType])
274
			? $this->secureMimeTypes[$mimeType]
275
			: 'application/octet-stream';
276
	}
277
278
	/**
279
	 * Get path to the icon of a file type
280
	 * @param string $mimetype the MIME type
281
	 * @return string the url
282
	 */
283
	public function mimeTypeIcon($mimetype) {
284
		$this->loadAliases();
285
286
		while (isset($this->mimeTypeAlias[$mimetype])) {
287
			$mimetype = $this->mimeTypeAlias[$mimetype];
288
		}
289
		if (isset($this->mimetypeIcons[$mimetype])) {
290
			return $this->mimetypeIcons[$mimetype];
291
		}
292
293
		// Replace slash and backslash with a minus
294
		$icon = str_replace('/', '-', $mimetype);
295
		$icon = str_replace('\\', '-', $icon);
296
297
		// Is it a dir?
298 View Code Duplication
		if ($mimetype === 'dir') {
299
			$this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/folder.svg');
300
			return $this->mimetypeIcons[$mimetype];
301
		}
302 View Code Duplication
		if ($mimetype === 'dir-shared') {
303
			$this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/folder-shared.svg');
304
			return $this->mimetypeIcons[$mimetype];
305
		}
306 View Code Duplication
		if ($mimetype === 'dir-external') {
307
			$this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/folder-external.svg');
308
			return $this->mimetypeIcons[$mimetype];
309
		}
310
311
		// Icon exists?
312
		try {
313
			$this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/' . $icon . '.svg');
314
			return $this->mimetypeIcons[$mimetype];
315
		} catch (\RuntimeException $e) {
316
			// Specified image not found
317
		}
318
319
		// Try only the first part of the filetype
320
		$mimePart = substr($icon, 0, strpos($icon, '-'));
321
		try {
322
			$this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/' . $mimePart . '.svg');
323
			return $this->mimetypeIcons[$mimetype];
324
		} catch (\RuntimeException $e) {
325
			// Image for the first part of the mimetype not found
326
		}
327
328
		$this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/file.svg');
329
		return $this->mimetypeIcons[$mimetype];
330
	}
331
}
332