Completed
Push — stable10 ( 5c61a6...9071fd )
by Morris
17:09 queued 14:42
created

LargeFileHelper::getFileSizeViaCOM()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 8
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 Lukas Reschke <[email protected]>
7
 * @author Michael Roitzsch <[email protected]>
8
 * @author Morris Jobke <[email protected]>
9
 * @author Thomas Müller <[email protected]>
10
 *
11
 * @license AGPL-3.0
12
 *
13
 * This code is free software: you can redistribute it and/or modify
14
 * it under the terms of the GNU Affero General Public License, version 3,
15
 * as published by the Free Software Foundation.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
 * GNU Affero General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License, version 3,
23
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
24
 *
25
 */
26
27
namespace OC;
28
29
/**
30
 * Helper class for large files on 32-bit platforms.
31
 */
32
class LargeFileHelper {
33
	/**
34
	* pow(2, 53) as a base-10 string.
35
	* @var string
36
	*/
37
	const POW_2_53 = '9007199254740992';
38
39
	/**
40
	* pow(2, 53) - 1 as a base-10 string.
41
	* @var string
42
	*/
43
	const POW_2_53_MINUS_1 = '9007199254740991';
44
45
	/**
46
	* @brief Checks whether our assumptions hold on the PHP platform we are on.
47
	*
48
	* @throws \RunTimeException if our assumptions do not hold on the current
49
	*                           PHP platform.
50
	*/
51
	public function __construct() {
52
		$pow_2_53 = floatval(self::POW_2_53_MINUS_1) + 1.0;
53
		if ($this->formatUnsignedInteger($pow_2_53) !== self::POW_2_53) {
54
			throw new \RunTimeException(
55
				'This class assumes floats to be double precision or "better".'
56
			);
57
		}
58
	}
59
60
	/**
61
	* @brief Formats a signed integer or float as an unsigned integer base-10
62
	*        string. Passed strings will be checked for being base-10.
63
	*
64
	* @param int|float|string $number Number containing unsigned integer data
65
	*
66
	* @throws \UnexpectedValueException if $number is not a float, not an int
67
	*                                   and not a base-10 string.
68
	*
69
	* @return string Unsigned integer base-10 string
70
	*/
71
	public function formatUnsignedInteger($number) {
72
		if (is_float($number)) {
73
			// Undo the effect of the php.ini setting 'precision'.
74
			return number_format($number, 0, '', '');
75
		} else if (is_string($number) && ctype_digit($number)) {
76
			return $number;
77
		} else if (is_int($number)) {
78
			// Interpret signed integer as unsigned integer.
79
			return sprintf('%u', $number);
80
		} else {
81
			throw new \UnexpectedValueException(
82
				'Expected int, float or base-10 string'
83
			);
84
		}
85
	}
86
87
	/**
88
	* @brief Tries to get the size of a file via various workarounds that
89
	*        even work for large files on 32-bit platforms.
90
	*
91
	* @param string $filename Path to the file.
92
	*
93
	* @return null|int|float Number of bytes as number (float or int) or
94
	*                        null on failure.
95
	*/
96
	public function getFileSize($filename) {
97
		$fileSize = $this->getFileSizeViaCurl($filename);
98
		if (!is_null($fileSize)) {
99
			return $fileSize;
100
		}
101
		$fileSize = $this->getFileSizeViaCOM($filename);
0 ignored issues
show
Bug introduced by
The method getFileSizeViaCOM() does not exist on OC\LargeFileHelper. Did you maybe mean getFileSize()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
102
		if (!is_null($fileSize)) {
103
			return $fileSize;
104
		}
105
		$fileSize = $this->getFileSizeViaExec($filename);
106
		if (!is_null($fileSize)) {
107
			return $fileSize;
108
		}
109
		return $this->getFileSizeNative($filename);
110
	}
111
112
	/**
113
	* @brief Tries to get the size of a file via a CURL HEAD request.
114
	*
115
	* @param string $fileName Path to the file.
116
	*
117
	* @return null|int|float Number of bytes as number (float or int) or
118
	*                        null on failure.
119
	*/
120
	public function getFileSizeViaCurl($fileName) {
121
		if (\OC::$server->getIniWrapper()->getString('open_basedir') === '') {
122
			$encodedFileName = rawurlencode($fileName);
123
			$ch = curl_init("file://$encodedFileName");
124
			curl_setopt($ch, CURLOPT_NOBODY, true);
125
			curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
126
			curl_setopt($ch, CURLOPT_HEADER, true);
127
			$data = curl_exec($ch);
128
			curl_close($ch);
129
			if ($data !== false) {
130
				$matches = array();
131
				preg_match('/Content-Length: (\d+)/', $data, $matches);
132
				if (isset($matches[1])) {
133
					return 0 + $matches[1];
134
				}
135
			}
136
		}
137
		return null;
138
	}
139
140
	/**
141
	* @brief Tries to get the size of a file via an exec() call.
142
	*
143
	* @param string $filename Path to the file.
144
	*
145
	* @return null|int|float Number of bytes as number (float or int) or
146
	*                        null on failure.
147
	*/
148
	public function getFileSizeViaExec($filename) {
149
		if (\OC_Helper::is_function_enabled('exec')) {
150
			$os = strtolower(php_uname('s'));
151
			$arg = escapeshellarg($filename);
152
			$result = null;
153
			if (strpos($os, 'linux') !== false) {
154
				$result = $this->exec("stat -c %s $arg");
155
			} else if (strpos($os, 'bsd') !== false || strpos($os, 'darwin') !== false) {
156
				$result = $this->exec("stat -f %z $arg");
157
			} else if (strpos($os, 'win') !== false) {
158
				$result = $this->exec("for %F in ($arg) do @echo %~zF");
159
				if (is_null($result)) {
160
					// PowerShell
161
					$result = $this->exec("(Get-Item $arg).length");
162
				}
163
			}
164
			return $result;
165
		}
166
		return null;
167
	}
168
169
	/**
170
	* @brief Gets the size of a file via a filesize() call and converts
171
	*        negative signed int to positive float. As the result of filesize()
172
	*        will wrap around after a file size of 2^32 bytes = 4 GiB, this
173
	*        should only be used as a last resort.
174
	*
175
	* @param string $filename Path to the file.
176
	*
177
	* @return int|float Number of bytes as number (float or int).
178
	*/
179
	public function getFileSizeNative($filename) {
180
		$result = filesize($filename);
181
		if ($result < 0) {
182
			// For file sizes between 2 GiB and 4 GiB, filesize() will return a
183
			// negative int, as the PHP data type int is signed. Interpret the
184
			// returned int as an unsigned integer and put it into a float.
185
			return (float) sprintf('%u', $result);
186
		}
187
		return $result;
188
	}
189
190
	protected function exec($cmd) {
191
		$result = trim(exec($cmd));
192
		return ctype_digit($result) ? 0 + $result : null;
193
	}
194
}
195