Completed
Push — stable10 ( 8e5b2c...30a24b )
by Morris
10:27
created

Detection::registerType()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 3
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
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
		if (strpos($path, '.')) {
170
			//try to guess the type by the file extension
171
			$extension = strtolower(strrchr(basename($path), "."));
172
			$extension = substr($extension, 1); //remove leading .
173
			return (isset($this->mimetypes[$extension]) && isset($this->mimetypes[$extension][0]))
174
				? $this->mimetypes[$extension][0]
175
				: 'application/octet-stream';
176
		} else {
177
			return 'application/octet-stream';
178
		}
179
	}
180
181
	/**
182
	 * detect mimetype based on both filename and content
183
	 *
184
	 * @param string $path
185
	 * @return string
186
	 */
187
	public function detect($path) {
188
		$this->loadMappings();
189
190
		if (@is_dir($path)) {
191
			// directories are easy
192
			return "httpd/unix-directory";
193
		}
194
195
		$mimeType = $this->detectPath($path);
196
197
		if ($mimeType === 'application/octet-stream' and function_exists('finfo_open')
198
			and function_exists('finfo_file') and $finfo = finfo_open(FILEINFO_MIME)
199
		) {
200
			$info = @strtolower(finfo_file($finfo, $path));
201
			finfo_close($finfo);
202
			if ($info) {
203
				$mimeType = strpos($info, ';') !== false ? substr($info, 0, strpos($info, ';')) : $info;
204
				return empty($mimeType) ? 'application/octet-stream' : $mimeType;
205
			}
206
207
		}
208
		$isWrapped = (strpos($path, '://') !== false) and (substr($path, 0, 7) === 'file://');
209
		if (!$isWrapped and $mimeType === 'application/octet-stream' && function_exists("mime_content_type")) {
210
			// use mime magic extension if available
211
			$mimeType = mime_content_type($path);
212
		}
213
		if (!$isWrapped and $mimeType === 'application/octet-stream' && \OC_Helper::canExecute("file")) {
214
			// it looks like we have a 'file' command,
215
			// lets see if it does have mime support
216
			$path = escapeshellarg($path);
217
			$fp = popen("file -b --mime-type $path 2>/dev/null", "r");
218
			$reply = fgets($fp);
219
			pclose($fp);
220
221
			//trim the newline
222
			$mimeType = trim($reply);
223
224
			if (empty($mimeType)) {
225
				$mimeType = 'application/octet-stream';
226
			}
227
228
		}
229
		return $mimeType;
230
	}
231
232
	/**
233
	 * detect mimetype based on the content of a string
234
	 *
235
	 * @param string $data
236
	 * @return string
237
	 */
238
	public function detectString($data) {
239
		if (function_exists('finfo_open') and function_exists('finfo_file')) {
240
			$finfo = finfo_open(FILEINFO_MIME);
241
			$info = finfo_buffer($finfo, $data);
242
			return strpos($info, ';') !== false ? substr($info, 0, strpos($info, ';')) : $info;
243 View Code Duplication
		} else {
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...
244
			$tmpFile = \OC::$server->getTempManager()->getTemporaryFile();
245
			$fh = fopen($tmpFile, 'wb');
246
			fwrite($fh, $data, 8024);
247
			fclose($fh);
248
			$mime = $this->detect($tmpFile);
249
			unset($tmpFile);
250
			return $mime;
251
		}
252
	}
253
254
	/**
255
	 * Get a secure mimetype that won't expose potential XSS.
256
	 *
257
	 * @param string $mimeType
258
	 * @return string
259
	 */
260
	public function getSecureMimeType($mimeType) {
261
		$this->loadMappings();
262
263
		return isset($this->secureMimeTypes[$mimeType])
264
			? $this->secureMimeTypes[$mimeType]
265
			: 'application/octet-stream';
266
	}
267
268
	/**
269
	 * Get path to the icon of a file type
270
	 * @param string $mimetype the MIME type
271
	 * @return string the url
272
	 */
273
	public function mimeTypeIcon($mimetype) {
274
		$this->loadAliases();
275
276
		while (isset($this->mimeTypeAlias[$mimetype])) {
277
			$mimetype = $this->mimeTypeAlias[$mimetype];
278
		}
279
		if (isset($this->mimetypeIcons[$mimetype])) {
280
			return $this->mimetypeIcons[$mimetype];
281
		}
282
283
		// Replace slash and backslash with a minus
284
		$icon = str_replace('/', '-', $mimetype);
285
		$icon = str_replace('\\', '-', $icon);
286
287
		// Is it a dir?
288 View Code Duplication
		if ($mimetype === 'dir') {
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...
289
			$this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/folder.svg');
290
			return $this->mimetypeIcons[$mimetype];
291
		}
292 View Code Duplication
		if ($mimetype === 'dir-shared') {
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...
293
			$this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/folder-shared.svg');
294
			return $this->mimetypeIcons[$mimetype];
295
		}
296 View Code Duplication
		if ($mimetype === 'dir-external') {
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...
297
			$this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/folder-external.svg');
298
			return $this->mimetypeIcons[$mimetype];
299
		}
300
301
		// Icon exists?
302
		try {
303
			$this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/' . $icon . '.svg');
304
			return $this->mimetypeIcons[$mimetype];
305
		} catch (\RuntimeException $e) {
306
			// Specified image not found
307
		}
308
309
		// Try only the first part of the filetype
310
		$mimePart = substr($icon, 0, strpos($icon, '-'));
311
		try {
312
			$this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/' . $mimePart . '.svg');
313
			return $this->mimetypeIcons[$mimetype];
314
		} catch (\RuntimeException $e) {
315
			// Image for the first part of the mimetype not found
316
		}
317
318
		$this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/file.svg');
319
		return $this->mimetypeIcons[$mimetype];
320
	}
321
}
322