You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

210 lines
6.2 KiB

10 years ago
10 years ago
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. * @copyright Copyright (c) 2016, Lukas Reschke <lukas@statuscode.ch>
  5. *
  6. * @author Andreas Fischer <bantu@owncloud.com>
  7. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  8. * @author Lukas Reschke <lukas@statuscode.ch>
  9. * @author marco44 <cousinmarc@gmail.com>
  10. * @author Michael Roitzsch <reactorcontrol@icloud.com>
  11. * @author Morris Jobke <hey@morrisjobke.de>
  12. * @author Thomas Müller <thomas.mueller@tmit.eu>
  13. *
  14. * @license AGPL-3.0
  15. *
  16. * This code is free software: you can redistribute it and/or modify
  17. * it under the terms of the GNU Affero General Public License, version 3,
  18. * as published by the Free Software Foundation.
  19. *
  20. * This program is distributed in the hope that it will be useful,
  21. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  22. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  23. * GNU Affero General Public License for more details.
  24. *
  25. * You should have received a copy of the GNU Affero General Public License, version 3,
  26. * along with this program. If not, see <http://www.gnu.org/licenses/>
  27. *
  28. */
  29. namespace OC;
  30. /**
  31. * Helper class for large files on 32-bit platforms.
  32. */
  33. class LargeFileHelper {
  34. /**
  35. * pow(2, 53) as a base-10 string.
  36. * @var string
  37. */
  38. public const POW_2_53 = '9007199254740992';
  39. /**
  40. * pow(2, 53) - 1 as a base-10 string.
  41. * @var string
  42. */
  43. public const POW_2_53_MINUS_1 = '9007199254740991';
  44. /**
  45. * @brief Checks whether our assumptions hold on the PHP platform we are on.
  46. *
  47. * @throws \RunTimeException if our assumptions do not hold on the current
  48. * PHP platform.
  49. */
  50. public function __construct() {
  51. $pow_2_53 = (float)self::POW_2_53_MINUS_1 + 1.0;
  52. if ($this->formatUnsignedInteger($pow_2_53) !== self::POW_2_53) {
  53. throw new \RuntimeException(
  54. 'This class assumes floats to be double precision or "better".'
  55. );
  56. }
  57. }
  58. /**
  59. * @brief Formats a signed integer or float as an unsigned integer base-10
  60. * string. Passed strings will be checked for being base-10.
  61. *
  62. * @param int|float|string $number Number containing unsigned integer data
  63. *
  64. * @throws \UnexpectedValueException if $number is not a float, not an int
  65. * and not a base-10 string.
  66. *
  67. * @return string Unsigned integer base-10 string
  68. */
  69. public function formatUnsignedInteger($number) {
  70. if (is_float($number)) {
  71. // Undo the effect of the php.ini setting 'precision'.
  72. return number_format($number, 0, '', '');
  73. } elseif (is_string($number) && ctype_digit($number)) {
  74. return $number;
  75. } elseif (is_int($number)) {
  76. // Interpret signed integer as unsigned integer.
  77. return sprintf('%u', $number);
  78. } else {
  79. throw new \UnexpectedValueException(
  80. 'Expected int, float or base-10 string'
  81. );
  82. }
  83. }
  84. /**
  85. * @brief Tries to get the size of a file via various workarounds that
  86. * even work for large files on 32-bit platforms.
  87. *
  88. * @param string $filename Path to the file.
  89. *
  90. * @return null|int|float Number of bytes as number (float or int) or
  91. * null on failure.
  92. */
  93. public function getFileSize($filename) {
  94. $fileSize = $this->getFileSizeViaCurl($filename);
  95. if (!is_null($fileSize)) {
  96. return $fileSize;
  97. }
  98. $fileSize = $this->getFileSizeViaExec($filename);
  99. if (!is_null($fileSize)) {
  100. return $fileSize;
  101. }
  102. return $this->getFileSizeNative($filename);
  103. }
  104. /**
  105. * @brief Tries to get the size of a file via a CURL HEAD request.
  106. *
  107. * @param string $fileName Path to the file.
  108. *
  109. * @return null|int|float Number of bytes as number (float or int) or
  110. * null on failure.
  111. */
  112. public function getFileSizeViaCurl($fileName) {
  113. if (\OC::$server->getIniWrapper()->getString('open_basedir') === '') {
  114. $encodedFileName = rawurlencode($fileName);
  115. $ch = curl_init("file:///$encodedFileName");
  116. curl_setopt($ch, CURLOPT_NOBODY, true);
  117. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  118. curl_setopt($ch, CURLOPT_HEADER, true);
  119. $data = curl_exec($ch);
  120. curl_close($ch);
  121. if ($data !== false) {
  122. $matches = [];
  123. preg_match('/Content-Length: (\d+)/', $data, $matches);
  124. if (isset($matches[1])) {
  125. return 0 + $matches[1];
  126. }
  127. }
  128. }
  129. return null;
  130. }
  131. /**
  132. * @brief Tries to get the size of a file via an exec() call.
  133. *
  134. * @param string $filename Path to the file.
  135. *
  136. * @return null|int|float Number of bytes as number (float or int) or
  137. * null on failure.
  138. */
  139. public function getFileSizeViaExec($filename) {
  140. if (\OC_Helper::is_function_enabled('exec')) {
  141. $os = strtolower(php_uname('s'));
  142. $arg = escapeshellarg($filename);
  143. $result = null;
  144. if (strpos($os, 'linux') !== false) {
  145. $result = $this->exec("stat -c %s $arg");
  146. } elseif (strpos($os, 'bsd') !== false || strpos($os, 'darwin') !== false) {
  147. $result = $this->exec("stat -f %z $arg");
  148. }
  149. return $result;
  150. }
  151. return null;
  152. }
  153. /**
  154. * @brief Gets the size of a file via a filesize() call and converts
  155. * negative signed int to positive float. As the result of filesize()
  156. * will wrap around after a file size of 2^32 bytes = 4 GiB, this
  157. * should only be used as a last resort.
  158. *
  159. * @param string $filename Path to the file.
  160. *
  161. * @return int|float Number of bytes as number (float or int).
  162. */
  163. public function getFileSizeNative($filename) {
  164. $result = filesize($filename);
  165. if ($result < 0) {
  166. // For file sizes between 2 GiB and 4 GiB, filesize() will return a
  167. // negative int, as the PHP data type int is signed. Interpret the
  168. // returned int as an unsigned integer and put it into a float.
  169. return (float) sprintf('%u', $result);
  170. }
  171. return $result;
  172. }
  173. /**
  174. * Returns the current mtime for $fullPath
  175. *
  176. * @param string $fullPath
  177. * @return int
  178. */
  179. public function getFileMtime($fullPath) {
  180. try {
  181. $result = filemtime($fullPath);
  182. } catch (\Exception $e) {
  183. $result =- 1;
  184. }
  185. if ($result < 0) {
  186. if (\OC_Helper::is_function_enabled('exec')) {
  187. $os = strtolower(php_uname('s'));
  188. if (strpos($os, 'linux') !== false) {
  189. return $this->exec('stat -c %Y ' . escapeshellarg($fullPath));
  190. }
  191. }
  192. }
  193. return $result;
  194. }
  195. protected function exec($cmd) {
  196. $result = trim(exec($cmd));
  197. return ctype_digit($result) ? 0 + $result : null;
  198. }
  199. }