LargeFileHelper.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Andreas Fischer <bantu@owncloud.com>
  6. * @author Lukas Reschke <lukas@statuscode.ch>
  7. * @author Michael Roitzsch <reactorcontrol@icloud.com>
  8. * @author Morris Jobke <hey@morrisjobke.de>
  9. * @author Thomas Müller <thomas.mueller@tmit.eu>
  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. namespace OC;
  27. /**
  28. * Helper class for large files on 32-bit platforms.
  29. */
  30. class LargeFileHelper {
  31. /**
  32. * pow(2, 53) as a base-10 string.
  33. * @var string
  34. */
  35. const POW_2_53 = '9007199254740992';
  36. /**
  37. * pow(2, 53) - 1 as a base-10 string.
  38. * @var string
  39. */
  40. const POW_2_53_MINUS_1 = '9007199254740991';
  41. /**
  42. * @brief Checks whether our assumptions hold on the PHP platform we are on.
  43. *
  44. * @throws \RunTimeException if our assumptions do not hold on the current
  45. * PHP platform.
  46. */
  47. public function __construct() {
  48. $pow_2_53 = floatval(self::POW_2_53_MINUS_1) + 1.0;
  49. if ($this->formatUnsignedInteger($pow_2_53) !== self::POW_2_53) {
  50. throw new \RunTimeException(
  51. 'This class assumes floats to be double precision or "better".'
  52. );
  53. }
  54. }
  55. /**
  56. * @brief Formats a signed integer or float as an unsigned integer base-10
  57. * string. Passed strings will be checked for being base-10.
  58. *
  59. * @param int|float|string $number Number containing unsigned integer data
  60. *
  61. * @throws \UnexpectedValueException if $number is not a float, not an int
  62. * and not a base-10 string.
  63. *
  64. * @return string Unsigned integer base-10 string
  65. */
  66. public function formatUnsignedInteger($number) {
  67. if (is_float($number)) {
  68. // Undo the effect of the php.ini setting 'precision'.
  69. return number_format($number, 0, '', '');
  70. } else if (is_string($number) && ctype_digit($number)) {
  71. return $number;
  72. } else if (is_int($number)) {
  73. // Interpret signed integer as unsigned integer.
  74. return sprintf('%u', $number);
  75. } else {
  76. throw new \UnexpectedValueException(
  77. 'Expected int, float or base-10 string'
  78. );
  79. }
  80. }
  81. /**
  82. * @brief Tries to get the size of a file via various workarounds that
  83. * even work for large files on 32-bit platforms.
  84. *
  85. * @param string $filename Path to the file.
  86. *
  87. * @return null|int|float Number of bytes as number (float or int) or
  88. * null on failure.
  89. */
  90. public function getFileSize($filename) {
  91. $fileSize = $this->getFileSizeViaCurl($filename);
  92. if (!is_null($fileSize)) {
  93. return $fileSize;
  94. }
  95. $fileSize = $this->getFileSizeViaCOM($filename);
  96. if (!is_null($fileSize)) {
  97. return $fileSize;
  98. }
  99. $fileSize = $this->getFileSizeViaExec($filename);
  100. if (!is_null($fileSize)) {
  101. return $fileSize;
  102. }
  103. return $this->getFileSizeNative($filename);
  104. }
  105. /**
  106. * @brief Tries to get the size of a file via a CURL HEAD request.
  107. *
  108. * @param string $fileName Path to the file.
  109. *
  110. * @return null|int|float Number of bytes as number (float or int) or
  111. * null on failure.
  112. */
  113. public function getFileSizeViaCurl($fileName) {
  114. if (\OC::$server->getIniWrapper()->getString('open_basedir') === '') {
  115. $encodedFileName = rawurlencode($fileName);
  116. $ch = curl_init("file://$encodedFileName");
  117. curl_setopt($ch, CURLOPT_NOBODY, true);
  118. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  119. curl_setopt($ch, CURLOPT_HEADER, true);
  120. $data = curl_exec($ch);
  121. curl_close($ch);
  122. if ($data !== false) {
  123. $matches = array();
  124. preg_match('/Content-Length: (\d+)/', $data, $matches);
  125. if (isset($matches[1])) {
  126. return 0 + $matches[1];
  127. }
  128. }
  129. }
  130. return null;
  131. }
  132. /**
  133. * @brief Tries to get the size of a file via an exec() call.
  134. *
  135. * @param string $filename Path to the file.
  136. *
  137. * @return null|int|float Number of bytes as number (float or int) or
  138. * null on failure.
  139. */
  140. public function getFileSizeViaExec($filename) {
  141. if (\OC_Helper::is_function_enabled('exec')) {
  142. $os = strtolower(php_uname('s'));
  143. $arg = escapeshellarg($filename);
  144. $result = null;
  145. if (strpos($os, 'linux') !== false) {
  146. $result = $this->exec("stat -c %s $arg");
  147. } else if (strpos($os, 'bsd') !== false || strpos($os, 'darwin') !== false) {
  148. $result = $this->exec("stat -f %z $arg");
  149. } else if (strpos($os, 'win') !== false) {
  150. $result = $this->exec("for %F in ($arg) do @echo %~zF");
  151. if (is_null($result)) {
  152. // PowerShell
  153. $result = $this->exec("(Get-Item $arg).length");
  154. }
  155. }
  156. return $result;
  157. }
  158. return null;
  159. }
  160. /**
  161. * @brief Gets the size of a file via a filesize() call and converts
  162. * negative signed int to positive float. As the result of filesize()
  163. * will wrap around after a file size of 2^32 bytes = 4 GiB, this
  164. * should only be used as a last resort.
  165. *
  166. * @param string $filename Path to the file.
  167. *
  168. * @return int|float Number of bytes as number (float or int).
  169. */
  170. public function getFileSizeNative($filename) {
  171. $result = filesize($filename);
  172. if ($result < 0) {
  173. // For file sizes between 2 GiB and 4 GiB, filesize() will return a
  174. // negative int, as the PHP data type int is signed. Interpret the
  175. // returned int as an unsigned integer and put it into a float.
  176. return (float) sprintf('%u', $result);
  177. }
  178. return $result;
  179. }
  180. protected function exec($cmd) {
  181. $result = trim(exec($cmd));
  182. return ctype_digit($result) ? 0 + $result : null;
  183. }
  184. }