Passed
Push — master ( 1f4312...5a0318 )
by John
27:53 queued 17:39
created

Detection::loadMappings()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 0
dl 0
loc 14
rs 10
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 Joas Schilling <[email protected]>
9
 * @author Lukas Reschke <[email protected]>
10
 * @author Magnus Walbeck <[email protected]>
11
 * @author Morris Jobke <[email protected]>
12
 * @author Robin Appelman <[email protected]>
13
 * @author Robin McCorkell <[email protected]>
14
 * @author Roeland Jago Douma <[email protected]>
15
 * @author Thomas Tanghus <[email protected]>
16
 * @author Vincent Petry <[email protected]>
17
 *
18
 * @license AGPL-3.0
19
 *
20
 * This code is free software: you can redistribute it and/or modify
21
 * it under the terms of the GNU Affero General Public License, version 3,
22
 * as published by the Free Software Foundation.
23
 *
24
 * This program is distributed in the hope that it will be useful,
25
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27
 * GNU Affero General Public License for more details.
28
 *
29
 * You should have received a copy of the GNU Affero General Public License, version 3,
30
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
31
 *
32
 */
33
34
namespace OC\Files\Type;
35
36
use OCP\Files\IMimeTypeDetector;
37
use OCP\IURLGenerator;
38
39
/**
40
 * Class Detection
41
 *
42
 * Mimetype detection
43
 *
44
 * @package OC\Files\Type
45
 */
46
class Detection implements IMimeTypeDetector {
47
	protected $mimetypes = [];
48
	protected $secureMimeTypes = [];
49
50
	protected $mimetypeIcons = [];
51
	/** @var string[] */
52
	protected $mimeTypeAlias = [];
53
54
	/** @var IURLGenerator */
55
	private $urlGenerator;
56
57
	/** @var string */
58
	private $customConfigDir;
59
60
	/** @var string */
61
	private $defaultConfigDir;
62
63
	/**
64
	 * @param IURLGenerator $urlGenerator
65
	 * @param string $customConfigDir
66
	 * @param string $defaultConfigDir
67
	 */
68
	public function __construct(IURLGenerator $urlGenerator,
69
								$customConfigDir,
70
								$defaultConfigDir) {
71
		$this->urlGenerator = $urlGenerator;
72
		$this->customConfigDir = $customConfigDir;
73
		$this->defaultConfigDir = $defaultConfigDir;
74
	}
75
76
	/**
77
	 * Add an extension -> mimetype mapping
78
	 *
79
	 * $mimetype is the assumed correct mime type
80
	 * The optional $secureMimeType is an alternative to send to send
81
	 * to avoid potential XSS.
82
	 *
83
	 * @param string $extension
84
	 * @param string $mimetype
85
	 * @param string|null $secureMimeType
86
	 */
87
	public function registerType($extension,
88
								 $mimetype,
89
								 $secureMimeType = null) {
90
		$this->mimetypes[$extension] = array($mimetype, $secureMimeType);
91
		$this->secureMimeTypes[$mimetype] = $secureMimeType ?: $mimetype;
92
	}
93
94
	/**
95
	 * Add an array of extension -> mimetype mappings
96
	 *
97
	 * The mimetype value is in itself an array where the first index is
98
	 * the assumed correct mimetype and the second is either a secure alternative
99
	 * or null if the correct is considered secure.
100
	 *
101
	 * @param array $types
102
	 */
103
	public function registerTypeArray($types) {
104
		$this->mimetypes = array_merge($this->mimetypes, $types);
105
106
		// Update the alternative mimetypes to avoid having to look them up each time.
107
		foreach ($this->mimetypes as $mimeType) {
108
			$this->secureMimeTypes[$mimeType[0]] = isset($mimeType[1]) ? $mimeType[1]: $mimeType[0];
109
		}
110
	}
111
112
	/**
113
	 * Add the mimetype aliases if they are not yet present
114
	 */
115
	private function loadAliases() {
116
		if (!empty($this->mimeTypeAlias)) {
117
			return;
118
		}
119
120
		$this->mimeTypeAlias = json_decode(file_get_contents($this->defaultConfigDir . '/mimetypealiases.dist.json'), true);
121
122
		if (file_exists($this->customConfigDir . '/mimetypealiases.json')) {
123
			$custom = json_decode(file_get_contents($this->customConfigDir . '/mimetypealiases.json'), true);
124
			$this->mimeTypeAlias = array_merge($this->mimeTypeAlias, $custom);
125
		}
126
	}
127
128
	/**
129
	 * @return string[]
130
	 */
131
	public function getAllAliases() {
132
		$this->loadAliases();
133
		return $this->mimeTypeAlias;
134
	}
135
136
	public function getOnlyDefaultAliases() {
137
		$this->loadMappings();
138
		$this->mimeTypeAlias = json_decode(file_get_contents($this->defaultConfigDir . '/mimetypealiases.dist.json'), true);
139
		return $this->mimeTypeAlias;
140
	}
141
142
	/**
143
	 * Add mimetype mappings if they are not yet present
144
	 */
145
	private function loadMappings() {
146
		if (!empty($this->mimetypes)) {
147
			return;
148
		}
149
150
		$mimetypeMapping = json_decode(file_get_contents($this->defaultConfigDir . '/mimetypemapping.dist.json'), true);
151
152
		//Check if need to load custom mappings
153
		if (file_exists($this->customConfigDir . '/mimetypemapping.json')) {
154
			$custom = json_decode(file_get_contents($this->customConfigDir . '/mimetypemapping.json'), true);
155
			$mimetypeMapping = array_merge($mimetypeMapping, $custom);
156
		}
157
158
		$this->registerTypeArray($mimetypeMapping);
159
	}
160
161
	/**
162
	 * @return array
163
	 */
164
	public function getAllMappings() {
165
		$this->loadMappings();
166
		return $this->mimetypes;
167
	}
168
169
	/**
170
	 * detect mimetype only based on filename, content of file is not used
171
	 *
172
	 * @param string $path
173
	 * @return string
174
	 */
175
	public function detectPath($path) {
176
		$this->loadMappings();
177
178
		$fileName = basename($path);
179
180
		// remove leading dot on hidden files with a file extension
181
		$fileName = ltrim($fileName, '.');
182
183
		// note: leading dot doesn't qualify as extension
184
		if (strpos($fileName, '.') > 0) {
185
186
			// remove versioning extension: name.v1508946057 and transfer extension: name.ocTransferId2057600214.part
187
			$fileName = preg_replace('!((\.v\d+)|((.ocTransferId\d+)?.part))$!', '', $fileName);
188
189
			//try to guess the type by the file extension
190
			$extension = strtolower(strrchr($fileName, '.'));
191
			$extension = substr($extension, 1); //remove leading .
192
			return (isset($this->mimetypes[$extension]) && isset($this->mimetypes[$extension][0]))
193
				? $this->mimetypes[$extension][0]
194
				: 'application/octet-stream';
195
		} else {
196
			return 'application/octet-stream';
197
		}
198
	}
199
200
	/**
201
	 * detect mimetype based on both filename and content
202
	 *
203
	 * @param string $path
204
	 * @return string
205
	 */
206
	public function detect($path) {
207
		$this->loadMappings();
208
209
		if (@is_dir($path)) {
210
			// directories are easy
211
			return "httpd/unix-directory";
212
		}
213
214
		$mimeType = $this->detectPath($path);
215
216
		if ($mimeType === 'application/octet-stream' and function_exists('finfo_open')
217
			and function_exists('finfo_file') and $finfo = finfo_open(FILEINFO_MIME)
218
		) {
219
			$info = @strtolower(finfo_file($finfo, $path));
220
			finfo_close($finfo);
221
			if ($info) {
222
				$mimeType = strpos($info, ';') !== false ? substr($info, 0, strpos($info, ';')) : $info;
223
				return empty($mimeType) ? 'application/octet-stream' : $mimeType;
224
			}
225
226
		}
227
		$isWrapped = (strpos($path, '://') !== false) and (substr($path, 0, 7) === 'file://');
228
		if (!$isWrapped and $mimeType === 'application/octet-stream' && function_exists("mime_content_type")) {
229
			// use mime magic extension if available
230
			$mimeType = mime_content_type($path);
231
		}
232
		if (!$isWrapped and $mimeType === 'application/octet-stream' && \OC_Helper::canExecute("file")) {
233
			// it looks like we have a 'file' command,
234
			// lets see if it does have mime support
235
			$path = escapeshellarg($path);
236
			$fp = popen("file -b --mime-type $path 2>/dev/null", "r");
237
			$reply = fgets($fp);
238
			pclose($fp);
239
240
			//trim the newline
241
			$mimeType = trim($reply);
242
243
			if (empty($mimeType)) {
244
				$mimeType = 'application/octet-stream';
245
			}
246
247
		}
248
		return $mimeType;
249
	}
250
251
	/**
252
	 * detect mimetype based on the content of a string
253
	 *
254
	 * @param string $data
255
	 * @return string
256
	 */
257
	public function detectString($data) {
258
		if (function_exists('finfo_open') and function_exists('finfo_file')) {
259
			$finfo = finfo_open(FILEINFO_MIME);
260
			$info = finfo_buffer($finfo, $data);
261
			return strpos($info, ';') !== false ? substr($info, 0, strpos($info, ';')) : $info;
262
		} else {
263
			$tmpFile = \OC::$server->getTempManager()->getTemporaryFile();
264
			$fh = fopen($tmpFile, 'wb');
265
			fwrite($fh, $data, 8024);
266
			fclose($fh);
267
			$mime = $this->detect($tmpFile);
268
			unset($tmpFile);
269
			return $mime;
270
		}
271
	}
272
273
	/**
274
	 * Get a secure mimetype that won't expose potential XSS.
275
	 *
276
	 * @param string $mimeType
277
	 * @return string
278
	 */
279
	public function getSecureMimeType($mimeType) {
280
		$this->loadMappings();
281
282
		return isset($this->secureMimeTypes[$mimeType])
283
			? $this->secureMimeTypes[$mimeType]
284
			: 'application/octet-stream';
285
	}
286
287
	/**
288
	 * Get path to the icon of a file type
289
	 * @param string $mimetype the MIME type
290
	 * @return string the url
291
	 */
292
	public function mimeTypeIcon($mimetype) {
293
		$this->loadAliases();
294
295
		while (isset($this->mimeTypeAlias[$mimetype])) {
296
			$mimetype = $this->mimeTypeAlias[$mimetype];
297
		}
298
		if (isset($this->mimetypeIcons[$mimetype])) {
299
			return $this->mimetypeIcons[$mimetype];
300
		}
301
302
		// Replace slash and backslash with a minus
303
		$icon = str_replace('/', '-', $mimetype);
304
		$icon = str_replace('\\', '-', $icon);
305
306
		// Is it a dir?
307
		if ($mimetype === 'dir') {
308
			$this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/folder.svg');
309
			return $this->mimetypeIcons[$mimetype];
310
		}
311
		if ($mimetype === 'dir-shared') {
312
			$this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/folder-shared.svg');
313
			return $this->mimetypeIcons[$mimetype];
314
		}
315
		if ($mimetype === 'dir-external') {
316
			$this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/folder-external.svg');
317
			return $this->mimetypeIcons[$mimetype];
318
		}
319
320
		// Icon exists?
321
		try {
322
			$this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/' . $icon . '.svg');
323
			return $this->mimetypeIcons[$mimetype];
324
		} catch (\RuntimeException $e) {
325
			// Specified image not found
326
		}
327
328
		// Try only the first part of the filetype
329
		$mimePart = substr($icon, 0, strpos($icon, '-'));
330
		try {
331
			$this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/' . $mimePart . '.svg');
332
			return $this->mimetypeIcons[$mimetype];
333
		} catch (\RuntimeException $e) {
334
			// Image for the first part of the mimetype not found
335
		}
336
337
		$this->mimetypeIcons[$mimetype] = $this->urlGenerator->imagePath('core', 'filetypes/file.svg');
338
		return $this->mimetypeIcons[$mimetype];
339
	}
340
}
341