Completed
Push — master ( 0327ef...0fa796 )
by Morris
52:54 queued 37:36
created

OC_Files::getNumberOfFiles()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 1
dl 0
loc 15
rs 9.4285
c 0
b 0
f 0
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\Lock\ILockingProvider;
45
46
/**
47
 * Class for file server access
48
 *
49
 */
50
class OC_Files {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
51
	const FILE = 1;
52
	const ZIP_FILES = 2;
53
	const ZIP_DIR = 3;
54
55
	const UPLOAD_MIN_LIMIT_BYTES = 1048576; // 1 MiB
56
57
58
	private static $multipartBoundary = '';
59
60
	/**
61
	 * @return string
62
	 */
63
	private static function getBoundary() {
64
		if (empty(self::$multipartBoundary)) {
65
			self::$multipartBoundary = md5(mt_rand());
66
		}
67
		return self::$multipartBoundary;
68
	}
69
70
	/**
71
	 * @param string $filename
72
	 * @param string $name
73
	 * @param array $rangeArray ('from'=>int,'to'=>int), ...
74
	 */
75
	private static function sendHeaders($filename, $name, array $rangeArray) {
76
		OC_Response::setContentDispositionHeader($name, 'attachment');
77
		header('Content-Transfer-Encoding: binary', true);
78
		header('Pragma: public');// enable caching in IE
79
		header('Expires: 0');
80
		header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
81
		$fileSize = \OC\Files\Filesystem::filesize($filename);
82
		$type = \OC::$server->getMimeTypeDetector()->getSecureMimeType(\OC\Files\Filesystem::getMimeType($filename));
83
		if ($fileSize > -1) {
84
			if (!empty($rangeArray)) {
85
			    header('HTTP/1.1 206 Partial Content', true);
86
			    header('Accept-Ranges: bytes', true);
87
			    if (count($rangeArray) > 1) {
88
				$type = 'multipart/byteranges; boundary='.self::getBoundary();
89
				// no Content-Length header here
90
			    }
91
			    else {
92
				header(sprintf('Content-Range: bytes %d-%d/%d', $rangeArray[0]['from'], $rangeArray[0]['to'], $fileSize), true);
93
				OC_Response::setContentLengthHeader($rangeArray[0]['to'] - $rangeArray[0]['from'] + 1);
94
			    }
95
			}
96
			else {
97
			    OC_Response::setContentLengthHeader($fileSize);
98
			}
99
		}
100
		header('Content-Type: '.$type, true);
101
	}
102
103
	/**
104
	 * return the content of a file or return a zip file containing multiple files
105
	 *
106
	 * @param string $dir
107
	 * @param string $files ; separated list of files to download
108
	 * @param array $params ; 'head' boolean to only send header of the request ; 'range' http range header
109
	 */
110
	public static function get($dir, $files, $params = null) {
111
112
		$view = \OC\Files\Filesystem::getView();
113
		$getType = self::FILE;
114
		$filename = $dir;
115
		try {
116
117
			if (is_array($files) && count($files) === 1) {
118
				$files = $files[0];
119
			}
120
121
			if (!is_array($files)) {
122
				$filename = $dir . '/' . $files;
123
				if (!$view->is_dir($filename)) {
124
					self::getSingleFile($view, $dir, $files, is_null($params) ? array() : $params);
125
					return;
126
				}
127
			}
128
129
			$name = 'download';
130
			if (is_array($files)) {
131
				$getType = self::ZIP_FILES;
132
				$basename = basename($dir);
133
				if ($basename) {
134
					$name = $basename;
135
				}
136
137
				$filename = $dir . '/' . $name;
138
			} else {
139
				$filename = $dir . '/' . $files;
140
				$getType = self::ZIP_DIR;
141
				// downloading root ?
142
				if ($files !== '') {
143
					$name = $files;
144
				}
145
			}
146
147
			self::lockFiles($view, $dir, $files);
148
149
			/* Calculate filesize and number of files */
150
			if ($getType === self::ZIP_FILES) {
151
				$fileInfos = array();
152
				$fileSize = 0;
153
				foreach ($files as $file) {
154
					$fileInfo = \OC\Files\Filesystem::getFileInfo($dir . '/' . $file);
155
					$fileSize += $fileInfo->getSize();
156
					$fileInfos[] = $fileInfo;
157
				}
158
				$numberOfFiles = self::getNumberOfFiles($fileInfos);
159
			} elseif ($getType === self::ZIP_DIR) {
160
				$fileInfo = \OC\Files\Filesystem::getFileInfo($dir . '/' . $files);
161
				$fileSize = $fileInfo->getSize();
162
				$numberOfFiles = self::getNumberOfFiles(array($fileInfo));
0 ignored issues
show
Documentation introduced by
array($fileInfo) is of type array<integer,false|obje...CP\\Files\\FileInfo>"}>, but the function expects a array<integer,object<OCP\Files\FileInfo>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
163
			}
164
165
			$streamer = new Streamer(\OC::$server->getRequest(), $fileSize, $numberOfFiles);
0 ignored issues
show
Bug introduced by
The variable $fileSize does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $numberOfFiles does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
166
			OC_Util::obEnd();
167
168
			$streamer->sendHeaders($name);
169
			$executionTime = (int)OC::$server->getIniWrapper()->getNumeric('max_execution_time');
170
			if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
171
				@set_time_limit(0);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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...
172
			}
173
			ignore_user_abort(true);
174
175
			if ($getType === self::ZIP_FILES) {
176
				foreach ($files as $file) {
177
					$file = $dir . '/' . $file;
178 View Code Duplication
					if (\OC\Files\Filesystem::is_file($file)) {
179
						$fileSize = \OC\Files\Filesystem::filesize($file);
180
						$fileTime = \OC\Files\Filesystem::filemtime($file);
181
						$fh = \OC\Files\Filesystem::fopen($file, 'r');
182
						$streamer->addFileFromStream($fh, basename($file), $fileSize, $fileTime);
0 ignored issues
show
Documentation introduced by
$fh is of type resource, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
183
						fclose($fh);
184
					} elseif (\OC\Files\Filesystem::is_dir($file)) {
185
						$streamer->addDirRecursive($file);
186
					}
187
				}
188
			} elseif ($getType === self::ZIP_DIR) {
189
				$file = $dir . '/' . $files;
190
				$streamer->addDirRecursive($file);
191
			}
192
			$streamer->finalize();
193
			set_time_limit($executionTime);
194
			self::unlockAllTheFiles($dir, $files, $getType, $view, $filename);
195
		} catch (\OCP\Lock\LockedException $ex) {
196
			self::unlockAllTheFiles($dir, $files, $getType, $view, $filename);
197
			OC::$server->getLogger()->logException($ex);
0 ignored issues
show
Documentation introduced by
$ex is of type object<OCP\Lock\LockedException>, but the function expects a object<Throwable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
198
			$l = \OC::$server->getL10N('core');
199
			$hint = method_exists($ex, 'getHint') ? $ex->getHint() : '';
0 ignored issues
show
Bug introduced by
The method getHint() does not seem to exist on object<OCP\Lock\LockedException>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
200
			\OC_Template::printErrorPage($l->t('File is currently busy, please try again later'), $hint);
201
		} catch (\OCP\Files\ForbiddenException $ex) {
202
			self::unlockAllTheFiles($dir, $files, $getType, $view, $filename);
203
			OC::$server->getLogger()->logException($ex);
0 ignored issues
show
Documentation introduced by
$ex is of type object<OCP\Files\ForbiddenException>, but the function expects a object<Throwable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
204
			$l = \OC::$server->getL10N('core');
205
			\OC_Template::printErrorPage($l->t('Can\'t read file'), $ex->getMessage());
206
		} catch (\Exception $ex) {
207
			self::unlockAllTheFiles($dir, $files, $getType, $view, $filename);
208
			OC::$server->getLogger()->logException($ex);
0 ignored issues
show
Documentation introduced by
$ex is of type object<Exception>, but the function expects a object<Throwable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
209
			$l = \OC::$server->getL10N('core');
210
			$hint = method_exists($ex, 'getHint') ? $ex->getHint() : '';
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method getHint() does only exist in the following sub-classes of Exception: OCA\Encryption\Exceptions\MultiKeyDecryptException, OCA\Encryption\Exceptions\MultiKeyEncryptException, OCA\Encryption\Exception...vateKeyMissingException, OCA\Encryption\Exception...blicKeyMissingException, OCA\Files_External\Lib\I...aningfulAnswerException, OCP\Encryption\Exception...ericEncryptionException, OCP\Files\StorageAuthException, OCP\Files\StorageBadConfigException, OCP\Files\StorageConnectionException, OCP\Files\StorageNotAvailableException, OCP\Files\StorageTimeoutException, OCP\Share\Exceptions\GenericShareException, OCP\Share\Exceptions\IllegalIDChangeException, OCP\Share\Exceptions\ShareNotFound, OC\DatabaseSetupException, OC\Encryption\Exceptions\DecryptionFailedException, OC\Encryption\Exceptions...EncryptionDataException, OC\Encryption\Exceptions\EncryptionFailedException, OC\Encryption\Exceptions...eaderKeyExistsException, OC\Encryption\Exceptions...nHeaderToLargeException, OC\Encryption\Exceptions...eAlreadyExistsException, OC\Encryption\Exceptions...eDoesNotExistsException, OC\Encryption\Exceptions\UnknownCipherException, OC\HintException. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
211
			\OC_Template::printErrorPage($l->t('Can\'t read file'), $hint);
212
		}
213
	}
214
215
	/**
216
	 * @param string $rangeHeaderPos
217
	 * @param int $fileSize
218
	 * @return array $rangeArray ('from'=>int,'to'=>int), ...
219
	 */
220
	private static function parseHttpRangeHeader($rangeHeaderPos, $fileSize) {
221
		$rArray=explode(',', $rangeHeaderPos);
222
		$minOffset = 0;
223
		$ind = 0;
224
225
		$rangeArray = array();
226
227
		foreach ($rArray as $value) {
228
			$ranges = explode('-', $value);
229
			if (is_numeric($ranges[0])) {
230
				if ($ranges[0] < $minOffset) { // case: bytes=500-700,601-999
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
231
					$ranges[0] = $minOffset;
232
				}
233
				if ($ind > 0 && $rangeArray[$ind-1]['to']+1 == $ranges[0]) { // case: bytes=500-600,601-999
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
234
					$ind--;
235
					$ranges[0] = $rangeArray[$ind]['from'];
236
				}
237
			}
238
239
			if (is_numeric($ranges[0]) && is_numeric($ranges[1]) && $ranges[0] < $fileSize && $ranges[0] <= $ranges[1]) {
240
				// case: x-x
241
				if ($ranges[1] >= $fileSize) {
242
					$ranges[1] = $fileSize-1;
243
				}
244
				$rangeArray[$ind++] = array( 'from' => $ranges[0], 'to' => $ranges[1], 'size' => $fileSize );
245
				$minOffset = $ranges[1] + 1;
246
				if ($minOffset >= $fileSize) {
247
					break;
248
				}
249
			}
250
			elseif (is_numeric($ranges[0]) && $ranges[0] < $fileSize) {
251
				// case: x-
252
				$rangeArray[$ind++] = array( 'from' => $ranges[0], 'to' => $fileSize-1, 'size' => $fileSize );
253
				break;
254
			}
255
			elseif (is_numeric($ranges[1])) {
256
				// case: -x
257
				if ($ranges[1] > $fileSize) {
258
					$ranges[1] = $fileSize;
259
				}
260
				$rangeArray[$ind++] = array( 'from' => $fileSize-$ranges[1], 'to' => $fileSize-1, 'size' => $fileSize );
261
				break;
262
			}
263
		}
264
		return $rangeArray;
265
	}
266
267
	/**
268
	 * @param View $view
269
	 * @param string $name
270
	 * @param string $dir
271
	 * @param array $params ; 'head' boolean to only send header of the request ; 'range' http range header
272
	 */
273
	private static function getSingleFile($view, $dir, $name, $params) {
274
		$filename = $dir . '/' . $name;
275
		OC_Util::obEnd();
276
		$view->lockFile($filename, ILockingProvider::LOCK_SHARED);
277
		
278
		$rangeArray = array();
279
280
		if (isset($params['range']) && substr($params['range'], 0, 6) === 'bytes=') {
281
			$rangeArray = self::parseHttpRangeHeader(substr($params['range'], 6), 
282
								 \OC\Files\Filesystem::filesize($filename));
283
		}
284
		
285
		if (\OC\Files\Filesystem::isReadable($filename)) {
286
			self::sendHeaders($filename, $name, $rangeArray);
287
		} elseif (!\OC\Files\Filesystem::file_exists($filename)) {
288
			header("HTTP/1.1 404 Not Found");
289
			$tmpl = new OC_Template('', '404', 'guest');
290
			$tmpl->printPage();
291
			exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method getSingleFile() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
292
		} else {
293
			header("HTTP/1.1 403 Forbidden");
294
			die('403 Forbidden');
0 ignored issues
show
Coding Style Compatibility introduced by
The method getSingleFile() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
295
		}
296
		if (isset($params['head']) && $params['head']) {
297
			return;
298
		}
299
		if (!empty($rangeArray)) {
300
			try {
301
			    if (count($rangeArray) == 1) {
302
				$view->readfilePart($filename, $rangeArray[0]['from'], $rangeArray[0]['to']);
303
			    }
304
			    else {
305
				// check if file is seekable (if not throw UnseekableException)
306
				// we have to check it before body contents
307
				$view->readfilePart($filename, $rangeArray[0]['size'], $rangeArray[0]['size']);
308
309
				$type = \OC::$server->getMimeTypeDetector()->getSecureMimeType(\OC\Files\Filesystem::getMimeType($filename));
310
311
				foreach ($rangeArray as $range) {
312
				    echo "\r\n--".self::getBoundary()."\r\n".
313
				         "Content-type: ".$type."\r\n".
314
				         "Content-range: bytes ".$range['from']."-".$range['to']."/".$range['size']."\r\n\r\n";
315
				    $view->readfilePart($filename, $range['from'], $range['to']);
316
				}
317
				echo "\r\n--".self::getBoundary()."--\r\n";
318
			    }
319
			} catch (\OCP\Files\UnseekableException $ex) {
320
			    // file is unseekable
321
			    header_remove('Accept-Ranges');
322
			    header_remove('Content-Range');
323
			    header("HTTP/1.1 200 OK");
324
			    self::sendHeaders($filename, $name, array());
325
			    $view->readfile($filename);
326
			}
327
		}
328
		else {
329
		    $view->readfile($filename);
330
		}
331
	}
332
333
	/**
334
	 * Returns the total (recursive) number of files and folders in the given
335
	 * FileInfos.
336
	 *
337
	 * @param \OCP\Files\FileInfo[] $fileInfos the FileInfos to count
338
	 * @return int the total number of files and folders
339
	 */
340
	private static function getNumberOfFiles($fileInfos) {
341
		$numberOfFiles = 0;
342
343
		$view = new View();
344
345
		while ($fileInfo = array_pop($fileInfos)) {
346
			$numberOfFiles++;
347
348
			if ($fileInfo->getType() === \OCP\Files\FileInfo::TYPE_FOLDER) {
349
				$fileInfos = array_merge($fileInfos, $view->getDirectoryContent($fileInfo->getPath()));
350
			}
351
		}
352
353
		return $numberOfFiles;
354
	}
355
356
	/**
357
	 * @param View $view
358
	 * @param string $dir
359
	 * @param string[]|string $files
360
	 */
361
	public static function lockFiles($view, $dir, $files) {
362
		if (!is_array($files)) {
363
			$file = $dir . '/' . $files;
364
			$files = [$file];
365
		}
366
		foreach ($files as $file) {
367
			$file = $dir . '/' . $file;
368
			$view->lockFile($file, ILockingProvider::LOCK_SHARED);
369
			if ($view->is_dir($file)) {
370
				$contents = $view->getDirectoryContent($file);
371
				$contents = array_map(function($fileInfo) use ($file) {
372
					/** @var \OCP\Files\FileInfo $fileInfo */
373
					return $file . '/' . $fileInfo->getName();
374
				}, $contents);
375
				self::lockFiles($view, $dir, $contents);
376
			}
377
		}
378
	}
379
380
	/**
381
	 * set the maximum upload size limit for apache hosts using .htaccess
382
	 *
383
	 * @param int $size file size in bytes
384
	 * @param array $files override '.htaccess' and '.user.ini' locations
385
	 * @return bool|int false on failure, size on success
386
	 */
387
	public static function setUploadLimit($size, $files = []) {
388
		//don't allow user to break his config
389
		$size = (int)$size;
390
		if ($size < self::UPLOAD_MIN_LIMIT_BYTES) {
391
			return false;
392
		}
393
		$size = OC_Helper::phpFileSize($size);
394
395
		$phpValueKeys = array(
396
			'upload_max_filesize',
397
			'post_max_size'
398
		);
399
400
		// default locations if not overridden by $files
401
		$files = array_merge([
402
			'.htaccess' => OC::$SERVERROOT . '/.htaccess',
403
			'.user.ini' => OC::$SERVERROOT . '/.user.ini'
404
		], $files);
405
406
		$updateFiles = [
407
			$files['.htaccess'] => [
408
				'pattern' => '/php_value %1$s (\S)*/',
409
				'setting' => 'php_value %1$s %2$s'
410
			],
411
			$files['.user.ini'] => [
412
				'pattern' => '/%1$s=(\S)*/',
413
				'setting' => '%1$s=%2$s'
414
			]
415
		];
416
417
		$success = true;
418
419
		foreach ($updateFiles as $filename => $patternMap) {
420
			// suppress warnings from fopen()
421
			$handle = @fopen($filename, 'r+');
422
			if (!$handle) {
423
				\OCP\Util::writeLog('files',
424
					'Can\'t write upload limit to ' . $filename . '. Please check the file permissions',
425
					\OCP\Util::WARN);
426
				$success = false;
427
				continue; // try to update as many files as possible
428
			}
429
430
			$content = '';
431
			while (!feof($handle)) {
432
				$content .= fread($handle, 1000);
433
			}
434
435
			foreach ($phpValueKeys as $key) {
436
				$pattern = vsprintf($patternMap['pattern'], [$key]);
437
				$setting = vsprintf($patternMap['setting'], [$key, $size]);
438
				$hasReplaced = 0;
439
				$newContent = preg_replace($pattern, $setting, $content, 2, $hasReplaced);
440
				if ($newContent !== null) {
441
					$content = $newContent;
442
				}
443
				if ($hasReplaced === 0) {
444
					$content .= "\n" . $setting;
445
				}
446
			}
447
448
			// write file back
449
			ftruncate($handle, 0);
450
			rewind($handle);
451
			fwrite($handle, $content);
452
453
			fclose($handle);
454
		}
455
456
		if ($success) {
457
			return OC_Helper::computerFileSize($size);
458
		}
459
		return false;
460
	}
461
462
	/**
463
	 * @param string $dir
464
	 * @param $files
465
	 * @param integer $getType
466
	 * @param View $view
467
	 * @param string $filename
468
	 */
469
	private static function unlockAllTheFiles($dir, $files, $getType, $view, $filename) {
470
		if ($getType === self::FILE) {
471
			$view->unlockFile($filename, ILockingProvider::LOCK_SHARED);
472
		}
473 View Code Duplication
		if ($getType === self::ZIP_FILES) {
474
			foreach ($files as $file) {
475
				$file = $dir . '/' . $file;
476
				$view->unlockFile($file, ILockingProvider::LOCK_SHARED);
477
			}
478
		}
479 View Code Duplication
		if ($getType === self::ZIP_DIR) {
480
			$file = $dir . '/' . $files;
481
			$view->unlockFile($file, ILockingProvider::LOCK_SHARED);
482
		}
483
	}
484
485
}
486