files.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. <?php
  2. /**
  3. * @author Andreas Fischer <bantu@owncloud.com>
  4. * @author Arthur Schiwon <blizzz@owncloud.com>
  5. * @author Bart Visscher <bartv@thisnet.nl>
  6. * @author Björn Schießle <schiessle@owncloud.com>
  7. * @author dratini0 <dratini0@gmail.com>
  8. * @author Frank Karlitschek <frank@owncloud.org>
  9. * @author Jakob Sack <mail@jakobsack.de>
  10. * @author Joas Schilling <nickvergessen@owncloud.com>
  11. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  12. * @author Lukas Reschke <lukas@owncloud.com>
  13. * @author Michael Gapczynski <GapczynskiM@gmail.com>
  14. * @author Morris Jobke <hey@morrisjobke.de>
  15. * @author mvn23 <schopdiedwaas@gmail.com>
  16. * @author Nicolai Ehemann <en@enlightened.de>
  17. * @author Robin Appelman <icewind@owncloud.com>
  18. * @author Robin McCorkell <rmccorkell@karoshi.org.uk>
  19. * @author Scrutinizer Auto-Fixer <auto-fixer@scrutinizer-ci.com>
  20. * @author Thibaut GRIDEL <tgridel@free.fr>
  21. * @author Thomas Müller <thomas.mueller@tmit.eu>
  22. * @author Valerio Ponte <valerio.ponte@gmail.com>
  23. * @author Vincent Petry <pvince81@owncloud.com>
  24. * @author Robin McCorkell <rmccorkell@karoshi.org.uk>
  25. *
  26. * @copyright Copyright (c) 2015, ownCloud, Inc.
  27. * @license AGPL-3.0
  28. *
  29. * This code is free software: you can redistribute it and/or modify
  30. * it under the terms of the GNU Affero General Public License, version 3,
  31. * as published by the Free Software Foundation.
  32. *
  33. * This program is distributed in the hope that it will be useful,
  34. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  35. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  36. * GNU Affero General Public License for more details.
  37. *
  38. * You should have received a copy of the GNU Affero General Public License, version 3,
  39. * along with this program. If not, see <http://www.gnu.org/licenses/>
  40. *
  41. */
  42. // TODO: get rid of this using proper composer packages
  43. require_once 'mcnetic/phpzipstreamer/ZipStreamer.php';
  44. use OC\Lock\NoopLockingProvider;
  45. use OCP\Lock\ILockingProvider;
  46. /**
  47. * Class for file server access
  48. *
  49. */
  50. class OC_Files {
  51. const FILE = 1;
  52. const ZIP_FILES = 2;
  53. const ZIP_DIR = 3;
  54. const UPLOAD_MIN_LIMIT_BYTES = 1048576; // 1 MiB
  55. /**
  56. * @param string $filename
  57. * @param string $name
  58. * @param bool $zip
  59. */
  60. private static function sendHeaders($filename, $name, $zip = false) {
  61. OC_Response::setContentDispositionHeader($name, 'attachment');
  62. header('Content-Transfer-Encoding: binary');
  63. OC_Response::disableCaching();
  64. if ($zip) {
  65. header('Content-Type: application/zip');
  66. } else {
  67. $filesize = \OC\Files\Filesystem::filesize($filename);
  68. header('Content-Type: '.\OC_Helper::getSecureMimeType(\OC\Files\Filesystem::getMimeType($filename)));
  69. if ($filesize > -1) {
  70. OC_Response::setContentLengthHeader($filesize);
  71. }
  72. }
  73. }
  74. /**
  75. * return the content of a file or return a zip file containing multiple files
  76. *
  77. * @param string $dir
  78. * @param string $files ; separated list of files to download
  79. * @param boolean $only_header ; boolean to only send header of the request
  80. */
  81. public static function get($dir, $files, $only_header = false) {
  82. $view = \OC\Files\Filesystem::getView();
  83. $xsendfile = false;
  84. if (\OC::$server->getLockingProvider() instanceof NoopLockingProvider) {
  85. if (isset($_SERVER['MOD_X_SENDFILE_ENABLED']) ||
  86. isset($_SERVER['MOD_X_SENDFILE2_ENABLED']) ||
  87. isset($_SERVER['MOD_X_ACCEL_REDIRECT_ENABLED'])
  88. ) {
  89. $xsendfile = true;
  90. }
  91. }
  92. if (is_array($files) && count($files) === 1) {
  93. $files = $files[0];
  94. }
  95. if (is_array($files)) {
  96. $get_type = self::ZIP_FILES;
  97. $basename = basename($dir);
  98. if ($basename) {
  99. $name = $basename . '.zip';
  100. } else {
  101. $name = 'download.zip';
  102. }
  103. $filename = $dir . '/' . $name;
  104. } else {
  105. $filename = $dir . '/' . $files;
  106. if (\OC\Files\Filesystem::is_dir($dir . '/' . $files)) {
  107. $get_type = self::ZIP_DIR;
  108. // downloading root ?
  109. if ($files === '') {
  110. $name = 'download.zip';
  111. } else {
  112. $name = $files . '.zip';
  113. }
  114. } else {
  115. $get_type = self::FILE;
  116. $name = $files;
  117. }
  118. }
  119. if ($get_type === self::FILE) {
  120. $zip = false;
  121. if ($xsendfile && \OC::$server->getEncryptionManager()->isEnabled()) {
  122. $xsendfile = false;
  123. }
  124. } else {
  125. $zip = new ZipStreamer(false);
  126. }
  127. OC_Util::obEnd();
  128. try {
  129. if ($get_type === self::FILE) {
  130. $view->lockFile($filename, ILockingProvider::LOCK_SHARED);
  131. }
  132. if ($zip or \OC\Files\Filesystem::isReadable($filename)) {
  133. self::sendHeaders($filename, $name, $zip);
  134. } elseif (!\OC\Files\Filesystem::file_exists($filename)) {
  135. header("HTTP/1.0 404 Not Found");
  136. $tmpl = new OC_Template('', '404', 'guest');
  137. $tmpl->printPage();
  138. exit();
  139. } else {
  140. header("HTTP/1.0 403 Forbidden");
  141. die('403 Forbidden');
  142. }
  143. if ($only_header) {
  144. return;
  145. }
  146. if ($zip) {
  147. $executionTime = intval(ini_get('max_execution_time'));
  148. set_time_limit(0);
  149. if ($get_type === self::ZIP_FILES) {
  150. foreach ($files as $file) {
  151. $file = $dir . '/' . $file;
  152. if (\OC\Files\Filesystem::is_file($file)) {
  153. $fh = \OC\Files\Filesystem::fopen($file, 'r');
  154. $zip->addFileFromStream($fh, basename($file));
  155. fclose($fh);
  156. } elseif (\OC\Files\Filesystem::is_dir($file)) {
  157. self::zipAddDir($file, $zip);
  158. }
  159. }
  160. } elseif ($get_type === self::ZIP_DIR) {
  161. $file = $dir . '/' . $files;
  162. self::zipAddDir($file, $zip);
  163. }
  164. $zip->finalize();
  165. set_time_limit($executionTime);
  166. } else {
  167. if ($xsendfile) {
  168. /** @var $storage \OC\Files\Storage\Storage */
  169. list($storage) = $view->resolvePath($filename);
  170. if ($storage->isLocal()) {
  171. self::addSendfileHeader($filename);
  172. } else {
  173. \OC\Files\Filesystem::readfile($filename);
  174. }
  175. } else {
  176. \OC\Files\Filesystem::readfile($filename);
  177. }
  178. }
  179. if ($get_type === self::FILE) {
  180. $view->unlockFile($filename, ILockingProvider::LOCK_SHARED);
  181. }
  182. } catch (\OCP\Lock\LockedException $ex) {
  183. $l = \OC::$server->getL10N('core');
  184. $hint = method_exists($ex, 'getHint') ? $ex->getHint() : '';
  185. \OC_Template::printErrorPage($l->t('File is currently busy, please try again later'), $hint);
  186. } catch (\Exception $ex) {
  187. $l = \OC::$server->getL10N('core');
  188. $hint = method_exists($ex, 'getHint') ? $ex->getHint() : '';
  189. \OC_Template::printErrorPage($l->t('Can\'t read file'), $hint);
  190. }
  191. }
  192. /**
  193. * @param false|string $filename
  194. */
  195. private static function addSendfileHeader($filename) {
  196. if (isset($_SERVER['MOD_X_SENDFILE_ENABLED'])) {
  197. $filename = \OC\Files\Filesystem::getLocalFile($filename);
  198. header("X-Sendfile: " . $filename);
  199. }
  200. if (isset($_SERVER['MOD_X_SENDFILE2_ENABLED'])) {
  201. $filename = \OC\Files\Filesystem::getLocalFile($filename);
  202. if (isset($_SERVER['HTTP_RANGE']) &&
  203. preg_match("/^bytes=([0-9]+)-([0-9]*)$/", $_SERVER['HTTP_RANGE'], $range)) {
  204. $filelength = filesize($filename);
  205. if ($range[2] === "") {
  206. $range[2] = $filelength - 1;
  207. }
  208. header("Content-Range: bytes $range[1]-$range[2]/" . $filelength);
  209. header("HTTP/1.1 206 Partial content");
  210. header("X-Sendfile2: " . str_replace(",", "%2c", rawurlencode($filename)) . " $range[1]-$range[2]");
  211. } else {
  212. header("X-Sendfile: " . $filename);
  213. }
  214. }
  215. if (isset($_SERVER['MOD_X_ACCEL_REDIRECT_ENABLED'])) {
  216. if (isset($_SERVER['MOD_X_ACCEL_REDIRECT_PREFIX'])) {
  217. $filename = $_SERVER['MOD_X_ACCEL_REDIRECT_PREFIX'] . \OC\Files\Filesystem::getLocalFile($filename);
  218. } else {
  219. $filename = \OC::$WEBROOT . '/data' . \OC\Files\Filesystem::getRoot() . $filename;
  220. }
  221. header("X-Accel-Redirect: " . $filename);
  222. }
  223. }
  224. /**
  225. * @param string $dir
  226. * @param ZipStreamer $zip
  227. * @param string $internalDir
  228. */
  229. public static function zipAddDir($dir, ZipStreamer $zip, $internalDir='') {
  230. $dirname=basename($dir);
  231. $rootDir = $internalDir.$dirname;
  232. if (!empty($rootDir)) {
  233. $zip->addEmptyDir($rootDir);
  234. }
  235. $internalDir.=$dirname.='/';
  236. // prevent absolute dirs
  237. $internalDir = ltrim($internalDir, '/');
  238. $files=\OC\Files\Filesystem::getDirectoryContent($dir);
  239. foreach($files as $file) {
  240. $filename=$file['name'];
  241. $file=$dir.'/'.$filename;
  242. if(\OC\Files\Filesystem::is_file($file)) {
  243. $fh = \OC\Files\Filesystem::fopen($file, 'r');
  244. $zip->addFileFromStream($fh, $internalDir.$filename);
  245. fclose($fh);
  246. }elseif(\OC\Files\Filesystem::is_dir($file)) {
  247. self::zipAddDir($file, $zip, $internalDir);
  248. }
  249. }
  250. }
  251. /**
  252. * set the maximum upload size limit for apache hosts using .htaccess
  253. *
  254. * @param int $size file size in bytes
  255. * @param array $files override '.htaccess' and '.user.ini' locations
  256. * @return bool false on failure, size on success
  257. */
  258. public static function setUploadLimit($size, $files = []) {
  259. //don't allow user to break his config
  260. $size = intval($size);
  261. if ($size < self::UPLOAD_MIN_LIMIT_BYTES) {
  262. return false;
  263. }
  264. $size = OC_Helper::phpFileSize($size);
  265. $phpValueKeys = array(
  266. 'upload_max_filesize',
  267. 'post_max_size'
  268. );
  269. // default locations if not overridden by $files
  270. $files = array_merge([
  271. '.htaccess' => OC::$SERVERROOT . '/.htaccess',
  272. '.user.ini' => OC::$SERVERROOT . '/.user.ini'
  273. ], $files);
  274. $updateFiles = [
  275. $files['.htaccess'] => [
  276. 'pattern' => '/php_value %1$s (\S)*/',
  277. 'setting' => 'php_value %1$s %2$s'
  278. ],
  279. $files['.user.ini'] => [
  280. 'pattern' => '/%1$s=(\S)*/',
  281. 'setting' => '%1$s=%2$s'
  282. ]
  283. ];
  284. $success = true;
  285. foreach ($updateFiles as $filename => $patternMap) {
  286. // suppress warnings from fopen()
  287. $handle = @fopen($filename, 'r+');
  288. if (!$handle) {
  289. \OCP\Util::writeLog('files',
  290. 'Can\'t write upload limit to ' . $filename . '. Please check the file permissions',
  291. \OCP\Util::WARN);
  292. $success = false;
  293. continue; // try to update as many files as possible
  294. }
  295. $content = '';
  296. while (!feof($handle)) {
  297. $content .= fread($handle, 1000);
  298. }
  299. foreach ($phpValueKeys as $key) {
  300. $pattern = vsprintf($patternMap['pattern'], [$key]);
  301. $setting = vsprintf($patternMap['setting'], [$key, $size]);
  302. $hasReplaced = 0;
  303. $newContent = preg_replace($pattern, $setting, $content, 1, $hasReplaced);
  304. if ($newContent !== null) {
  305. $content = $newContent;
  306. }
  307. if ($hasReplaced === 0) {
  308. $content .= "\n" . $setting;
  309. }
  310. }
  311. // write file back
  312. ftruncate($handle, 0);
  313. rewind($handle);
  314. fwrite($handle, $content);
  315. fclose($handle);
  316. }
  317. if ($success) {
  318. return OC_Helper::computerFileSize($size);
  319. }
  320. return false;
  321. }
  322. }