Passed
Push — master ( daee22...bbb168 )
by Morris
13:11 queued 10s
created

OC_Files::setUploadLimit()   B

Complexity

Conditions 9
Paths 25

Size

Total Lines 73
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 46
nc 25
nop 2
dl 0
loc 73
rs 7.6226
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bart Visscher <[email protected]>
7
 * @author Björn Schießle <[email protected]>
8
 * @author Clark Tomlinson <[email protected]>
9
 * @author Frank Karlitschek <[email protected]>
10
 * @author Jakob Sack <[email protected]>
11
 * @author Joas Schilling <[email protected]>
12
 * @author Jörn Friedrich Dreyer <[email protected]>
13
 * @author Ko- <[email protected]>
14
 * @author Lukas Reschke <[email protected]>
15
 * @author Michael Gapczynski <[email protected]>
16
 * @author Nicolai Ehemann <[email protected]>
17
 * @author noveens <[email protected]>
18
 * @author Piotr Filiciak <[email protected]>
19
 * @author Robin Appelman <[email protected]>
20
 * @author Robin McCorkell <[email protected]>
21
 * @author Thibaut GRIDEL <[email protected]>
22
 * @author Thomas Müller <[email protected]>
23
 * @author Victor Dubiniuk <[email protected]>
24
 * @author Vincent Petry <[email protected]>
25
 *
26
 * @license AGPL-3.0
27
 *
28
 * This code is free software: you can redistribute it and/or modify
29
 * it under the terms of the GNU Affero General Public License, version 3,
30
 * as published by the Free Software Foundation.
31
 *
32
 * This program is distributed in the hope that it will be useful,
33
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
34
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35
 * GNU Affero General Public License for more details.
36
 *
37
 * You should have received a copy of the GNU Affero General Public License, version 3,
38
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
39
 *
40
 */
41
42
use OC\Files\View;
43
use OC\Streamer;
44
use OCP\ILogger;
45
use OCP\Lock\ILockingProvider;
46
47
/**
48
 * Class for file server access
49
 *
50
 */
51
class OC_Files {
52
	const FILE = 1;
53
	const ZIP_FILES = 2;
54
	const ZIP_DIR = 3;
55
56
	const UPLOAD_MIN_LIMIT_BYTES = 1048576; // 1 MiB
57
58
59
	private static $multipartBoundary = '';
60
61
	/**
62
	 * @return string
63
	 */
64
	private static function getBoundary() {
65
		if (empty(self::$multipartBoundary)) {
66
			self::$multipartBoundary = md5(mt_rand());
67
		}
68
		return self::$multipartBoundary;
69
	}
70
71
	/**
72
	 * @param string $filename
73
	 * @param string $name
74
	 * @param array $rangeArray ('from'=>int,'to'=>int), ...
75
	 */
76
	private static function sendHeaders($filename, $name, array $rangeArray) {
77
		OC_Response::setContentDispositionHeader($name, 'attachment');
78
		header('Content-Transfer-Encoding: binary', true);
79
		header('Pragma: public');// enable caching in IE
80
		header('Expires: 0');
81
		header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
82
		$fileSize = \OC\Files\Filesystem::filesize($filename);
83
		$type = \OC::$server->getMimeTypeDetector()->getSecureMimeType(\OC\Files\Filesystem::getMimeType($filename));
84
		if ($fileSize > -1) {
85
			if (!empty($rangeArray)) {
86
			    http_response_code(206);
87
			    header('Accept-Ranges: bytes', true);
88
			    if (count($rangeArray) > 1) {
89
				$type = 'multipart/byteranges; boundary='.self::getBoundary();
90
				// no Content-Length header here
91
			    }
92
			    else {
93
				header(sprintf('Content-Range: bytes %d-%d/%d', $rangeArray[0]['from'], $rangeArray[0]['to'], $fileSize), true);
94
				OC_Response::setContentLengthHeader($rangeArray[0]['to'] - $rangeArray[0]['from'] + 1);
95
			    }
96
			}
97
			else {
98
			    OC_Response::setContentLengthHeader($fileSize);
99
			}
100
		}
101
		header('Content-Type: '.$type, true);
102
	}
103
104
	/**
105
	 * return the content of a file or return a zip file containing multiple files
106
	 *
107
	 * @param string $dir
108
	 * @param string $files ; separated list of files to download
109
	 * @param array $params ; 'head' boolean to only send header of the request ; 'range' http range header
110
	 */
111
	public static function get($dir, $files, $params = null) {
112
113
		$view = \OC\Files\Filesystem::getView();
114
		$getType = self::FILE;
115
		$filename = $dir;
116
		try {
117
118
			if (is_array($files) && count($files) === 1) {
0 ignored issues
show
introduced by
The condition is_array($files) is always false.
Loading history...
119
				$files = $files[0];
120
			}
121
122
			if (!is_array($files)) {
0 ignored issues
show
introduced by
The condition is_array($files) is always false.
Loading history...
123
				$filename = $dir . '/' . $files;
124
				if (!$view->is_dir($filename)) {
125
					self::getSingleFile($view, $dir, $files, is_null($params) ? array() : $params);
126
					return;
127
				}
128
			}
129
130
			$name = 'download';
131
			if (is_array($files)) {
0 ignored issues
show
introduced by
The condition is_array($files) is always false.
Loading history...
132
				$getType = self::ZIP_FILES;
133
				$basename = basename($dir);
134
				if ($basename) {
135
					$name = $basename;
136
				}
137
138
				$filename = $dir . '/' . $name;
139
			} else {
140
				$filename = $dir . '/' . $files;
141
				$getType = self::ZIP_DIR;
142
				// downloading root ?
143
				if ($files !== '') {
144
					$name = $files;
145
				}
146
			}
147
148
			self::lockFiles($view, $dir, $files);
149
150
			/* Calculate filesize and number of files */
151
			if ($getType === self::ZIP_FILES) {
0 ignored issues
show
introduced by
The condition $getType === self::ZIP_FILES is always false.
Loading history...
152
				$fileInfos = array();
153
				$fileSize = 0;
154
				foreach ($files as $file) {
155
					$fileInfo = \OC\Files\Filesystem::getFileInfo($dir . '/' . $file);
156
					$fileSize += $fileInfo->getSize();
157
					$fileInfos[] = $fileInfo;
158
				}
159
				$numberOfFiles = self::getNumberOfFiles($fileInfos);
160
			} elseif ($getType === self::ZIP_DIR) {
0 ignored issues
show
introduced by
The condition $getType === self::ZIP_DIR is always true.
Loading history...
161
				$fileInfo = \OC\Files\Filesystem::getFileInfo($dir . '/' . $files);
162
				$fileSize = $fileInfo->getSize();
163
				$numberOfFiles = self::getNumberOfFiles(array($fileInfo));
0 ignored issues
show
Bug introduced by
array($fileInfo) of type array<integer,OC\Files\FileInfo|false> is incompatible with the type OCP\Files\FileInfo[] expected by parameter $fileInfos of OC_Files::getNumberOfFiles(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

163
				$numberOfFiles = self::getNumberOfFiles(/** @scrutinizer ignore-type */ array($fileInfo));
Loading history...
164
			}
165
166
			$streamer = new Streamer(\OC::$server->getRequest(), $fileSize, $numberOfFiles);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $numberOfFiles does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $fileSize does not seem to be defined for all execution paths leading up to this point.
Loading history...
167
			OC_Util::obEnd();
168
169
			$streamer->sendHeaders($name);
170
			$executionTime = (int)OC::$server->getIniWrapper()->getNumeric('max_execution_time');
171
			if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
172
				@set_time_limit(0);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for set_time_limit(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

172
				/** @scrutinizer ignore-unhandled */ @set_time_limit(0);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
173
			}
174
			ignore_user_abort(true);
175
176
			if ($getType === self::ZIP_FILES) {
0 ignored issues
show
introduced by
The condition $getType === self::ZIP_FILES is always false.
Loading history...
177
				foreach ($files as $file) {
178
					$file = $dir . '/' . $file;
179
					if (\OC\Files\Filesystem::is_file($file)) {
180
						$fileSize = \OC\Files\Filesystem::filesize($file);
181
						$fileTime = \OC\Files\Filesystem::filemtime($file);
182
						$fh = \OC\Files\Filesystem::fopen($file, 'r');
183
						$streamer->addFileFromStream($fh, basename($file), $fileSize, $fileTime);
184
						fclose($fh);
185
					} elseif (\OC\Files\Filesystem::is_dir($file)) {
186
						$streamer->addDirRecursive($file);
187
					}
188
				}
189
			} elseif ($getType === self::ZIP_DIR) {
0 ignored issues
show
introduced by
The condition $getType === self::ZIP_DIR is always true.
Loading history...
190
				$file = $dir . '/' . $files;
191
				$streamer->addDirRecursive($file);
192
			}
193
			$streamer->finalize();
194
			set_time_limit($executionTime);
195
			self::unlockAllTheFiles($dir, $files, $getType, $view, $filename);
196
		} catch (\OCP\Lock\LockedException $ex) {
197
			self::unlockAllTheFiles($dir, $files, $getType, $view, $filename);
198
			OC::$server->getLogger()->logException($ex);
199
			$l = \OC::$server->getL10N('core');
200
			$hint = method_exists($ex, 'getHint') ? $ex->getHint() : '';
201
			\OC_Template::printErrorPage($l->t('File is currently busy, please try again later'), $hint, 200);
202
		} catch (\OCP\Files\ForbiddenException $ex) {
203
			self::unlockAllTheFiles($dir, $files, $getType, $view, $filename);
204
			OC::$server->getLogger()->logException($ex);
205
			$l = \OC::$server->getL10N('core');
206
			\OC_Template::printErrorPage($l->t('Can\'t read file'), $ex->getMessage(), 200);
207
		} catch (\Exception $ex) {
208
			self::unlockAllTheFiles($dir, $files, $getType, $view, $filename);
209
			OC::$server->getLogger()->logException($ex);
210
			$l = \OC::$server->getL10N('core');
211
			$hint = method_exists($ex, 'getHint') ? $ex->getHint() : '';
212
			\OC_Template::printErrorPage($l->t('Can\'t read file'), $hint, 200);
213
		}
214
	}
215
216
	/**
217
	 * @param string $rangeHeaderPos
218
	 * @param int $fileSize
219
	 * @return array $rangeArray ('from'=>int,'to'=>int), ...
220
	 */
221
	private static function parseHttpRangeHeader($rangeHeaderPos, $fileSize) {
222
		$rArray=explode(',', $rangeHeaderPos);
223
		$minOffset = 0;
224
		$ind = 0;
225
226
		$rangeArray = array();
227
228
		foreach ($rArray as $value) {
229
			$ranges = explode('-', $value);
230
			if (is_numeric($ranges[0])) {
231
				if ($ranges[0] < $minOffset) { // case: bytes=500-700,601-999
232
					$ranges[0] = $minOffset;
233
				}
234
				if ($ind > 0 && $rangeArray[$ind-1]['to']+1 == $ranges[0]) { // case: bytes=500-600,601-999
235
					$ind--;
236
					$ranges[0] = $rangeArray[$ind]['from'];
237
				}
238
			}
239
240
			if (is_numeric($ranges[0]) && is_numeric($ranges[1]) && $ranges[0] < $fileSize && $ranges[0] <= $ranges[1]) {
241
				// case: x-x
242
				if ($ranges[1] >= $fileSize) {
243
					$ranges[1] = $fileSize-1;
244
				}
245
				$rangeArray[$ind++] = array( 'from' => $ranges[0], 'to' => $ranges[1], 'size' => $fileSize );
246
				$minOffset = $ranges[1] + 1;
247
				if ($minOffset >= $fileSize) {
248
					break;
249
				}
250
			}
251
			elseif (is_numeric($ranges[0]) && $ranges[0] < $fileSize) {
252
				// case: x-
253
				$rangeArray[$ind++] = array( 'from' => $ranges[0], 'to' => $fileSize-1, 'size' => $fileSize );
254
				break;
255
			}
256
			elseif (is_numeric($ranges[1])) {
257
				// case: -x
258
				if ($ranges[1] > $fileSize) {
259
					$ranges[1] = $fileSize;
260
				}
261
				$rangeArray[$ind++] = array( 'from' => $fileSize-$ranges[1], 'to' => $fileSize-1, 'size' => $fileSize );
262
				break;
263
			}
264
		}
265
		return $rangeArray;
266
	}
267
268
	/**
269
	 * @param View $view
270
	 * @param string $name
271
	 * @param string $dir
272
	 * @param array $params ; 'head' boolean to only send header of the request ; 'range' http range header
273
	 */
274
	private static function getSingleFile($view, $dir, $name, $params) {
275
		$filename = $dir . '/' . $name;
276
		OC_Util::obEnd();
277
		$view->lockFile($filename, ILockingProvider::LOCK_SHARED);
278
		
279
		$rangeArray = array();
280
281
		if (isset($params['range']) && substr($params['range'], 0, 6) === 'bytes=') {
282
			$rangeArray = self::parseHttpRangeHeader(substr($params['range'], 6), 
283
								 \OC\Files\Filesystem::filesize($filename));
284
		}
285
		
286
		if (\OC\Files\Filesystem::isReadable($filename)) {
287
			self::sendHeaders($filename, $name, $rangeArray);
288
		} elseif (!\OC\Files\Filesystem::file_exists($filename)) {
289
			http_response_code(404);
290
			$tmpl = new OC_Template('', '404', 'guest');
291
			$tmpl->printPage();
292
			exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
293
		} else {
294
			http_response_code(403);
295
			die('403 Forbidden');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
296
		}
297
		if (isset($params['head']) && $params['head']) {
298
			return;
299
		}
300
		if (!empty($rangeArray)) {
301
			try {
302
			    if (count($rangeArray) == 1) {
303
				$view->readfilePart($filename, $rangeArray[0]['from'], $rangeArray[0]['to']);
304
			    }
305
			    else {
306
				// check if file is seekable (if not throw UnseekableException)
307
				// we have to check it before body contents
308
				$view->readfilePart($filename, $rangeArray[0]['size'], $rangeArray[0]['size']);
309
310
				$type = \OC::$server->getMimeTypeDetector()->getSecureMimeType(\OC\Files\Filesystem::getMimeType($filename));
311
312
				foreach ($rangeArray as $range) {
313
				    echo "\r\n--".self::getBoundary()."\r\n".
314
				         "Content-type: ".$type."\r\n".
315
				         "Content-range: bytes ".$range['from']."-".$range['to']."/".$range['size']."\r\n\r\n";
316
				    $view->readfilePart($filename, $range['from'], $range['to']);
317
				}
318
				echo "\r\n--".self::getBoundary()."--\r\n";
319
			    }
320
			} catch (\OCP\Files\UnseekableException $ex) {
321
			    // file is unseekable
322
			    header_remove('Accept-Ranges');
323
			    header_remove('Content-Range');
324
			    http_response_code(200);
325
			    self::sendHeaders($filename, $name, array());
326
			    $view->readfile($filename);
327
			}
328
		}
329
		else {
330
		    $view->readfile($filename);
331
		}
332
	}
333
334
	/**
335
	 * Returns the total (recursive) number of files and folders in the given
336
	 * FileInfos.
337
	 *
338
	 * @param \OCP\Files\FileInfo[] $fileInfos the FileInfos to count
339
	 * @return int the total number of files and folders
340
	 */
341
	private static function getNumberOfFiles($fileInfos) {
342
		$numberOfFiles = 0;
343
344
		$view = new View();
345
346
		while ($fileInfo = array_pop($fileInfos)) {
347
			$numberOfFiles++;
348
349
			if ($fileInfo->getType() === \OCP\Files\FileInfo::TYPE_FOLDER) {
350
				$fileInfos = array_merge($fileInfos, $view->getDirectoryContent($fileInfo->getPath()));
351
			}
352
		}
353
354
		return $numberOfFiles;
355
	}
356
357
	/**
358
	 * @param View $view
359
	 * @param string $dir
360
	 * @param string[]|string $files
361
	 */
362
	public static function lockFiles($view, $dir, $files) {
363
		if (!is_array($files)) {
364
			$file = $dir . '/' . $files;
365
			$files = [$file];
366
		}
367
		foreach ($files as $file) {
368
			$file = $dir . '/' . $file;
369
			$view->lockFile($file, ILockingProvider::LOCK_SHARED);
370
			if ($view->is_dir($file)) {
371
				$contents = $view->getDirectoryContent($file);
372
				$contents = array_map(function($fileInfo) use ($file) {
373
					/** @var \OCP\Files\FileInfo $fileInfo */
374
					return $file . '/' . $fileInfo->getName();
375
				}, $contents);
376
				self::lockFiles($view, $dir, $contents);
377
			}
378
		}
379
	}
380
381
	/**
382
	 * @param string $dir
383
	 * @param $files
384
	 * @param integer $getType
385
	 * @param View $view
386
	 * @param string $filename
387
	 */
388
	private static function unlockAllTheFiles($dir, $files, $getType, $view, $filename) {
389
		if ($getType === self::FILE) {
390
			$view->unlockFile($filename, ILockingProvider::LOCK_SHARED);
391
		}
392
		if ($getType === self::ZIP_FILES) {
393
			foreach ($files as $file) {
394
				$file = $dir . '/' . $file;
395
				$view->unlockFile($file, ILockingProvider::LOCK_SHARED);
396
			}
397
		}
398
		if ($getType === self::ZIP_DIR) {
399
			$file = $dir . '/' . $files;
400
			$view->unlockFile($file, ILockingProvider::LOCK_SHARED);
401
		}
402
	}
403
404
}
405