TempManager.php 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Lars <winnetou+github@catolic.de>
  6. * @author Lukas Reschke <lukas@statuscode.ch>
  7. * @author Martin Mattel <martin.mattel@diemattels.at>
  8. * @author Morris Jobke <hey@morrisjobke.de>
  9. * @author Olivier Paroz <github@oparoz.com>
  10. * @author Robin Appelman <robin@icewind.nl>
  11. * @author Robin McCorkell <robin@mccorkell.me.uk>
  12. * @author Stefan Weil <sw@weilnetz.de>
  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. use OCP\ILogger;
  31. use OCP\IConfig;
  32. use OCP\ITempManager;
  33. class TempManager implements ITempManager {
  34. /** @var string[] Current temporary files and folders, used for cleanup */
  35. protected $current = [];
  36. /** @var string i.e. /tmp on linux systems */
  37. protected $tmpBaseDir;
  38. /** @var ILogger */
  39. protected $log;
  40. /** @var IConfig */
  41. protected $config;
  42. /** Prefix */
  43. const TMP_PREFIX = 'oc_tmp_';
  44. /**
  45. * @param \OCP\ILogger $logger
  46. * @param \OCP\IConfig $config
  47. */
  48. public function __construct(ILogger $logger, IConfig $config) {
  49. $this->log = $logger;
  50. $this->config = $config;
  51. $this->tmpBaseDir = $this->getTempBaseDir();
  52. }
  53. /**
  54. * Builds the filename with suffix and removes potential dangerous characters
  55. * such as directory separators.
  56. *
  57. * @param string $absolutePath Absolute path to the file / folder
  58. * @param string $postFix Postfix appended to the temporary file name, may be user controlled
  59. * @return string
  60. */
  61. private function buildFileNameWithSuffix($absolutePath, $postFix = '') {
  62. if($postFix !== '') {
  63. $postFix = '.' . ltrim($postFix, '.');
  64. $postFix = str_replace(['\\', '/'], '', $postFix);
  65. $absolutePath .= '-';
  66. }
  67. return $absolutePath . $postFix;
  68. }
  69. /**
  70. * Create a temporary file and return the path
  71. *
  72. * @param string $postFix Postfix appended to the temporary file name
  73. * @return string
  74. */
  75. public function getTemporaryFile($postFix = '') {
  76. if (is_writable($this->tmpBaseDir)) {
  77. // To create an unique file and prevent the risk of race conditions
  78. // or duplicated temporary files by other means such as collisions
  79. // we need to create the file using `tempnam` and append a possible
  80. // postfix to it later
  81. $file = tempnam($this->tmpBaseDir, self::TMP_PREFIX);
  82. $this->current[] = $file;
  83. // If a postfix got specified sanitize it and create a postfixed
  84. // temporary file
  85. if($postFix !== '') {
  86. $fileNameWithPostfix = $this->buildFileNameWithSuffix($file, $postFix);
  87. touch($fileNameWithPostfix);
  88. chmod($fileNameWithPostfix, 0600);
  89. $this->current[] = $fileNameWithPostfix;
  90. return $fileNameWithPostfix;
  91. }
  92. return $file;
  93. } else {
  94. $this->log->warning(
  95. 'Can not create a temporary file in directory {dir}. Check it exists and has correct permissions',
  96. [
  97. 'dir' => $this->tmpBaseDir,
  98. ]
  99. );
  100. return false;
  101. }
  102. }
  103. /**
  104. * Create a temporary folder and return the path
  105. *
  106. * @param string $postFix Postfix appended to the temporary folder name
  107. * @return string
  108. */
  109. public function getTemporaryFolder($postFix = '') {
  110. if (is_writable($this->tmpBaseDir)) {
  111. // To create an unique directory and prevent the risk of race conditions
  112. // or duplicated temporary files by other means such as collisions
  113. // we need to create the file using `tempnam` and append a possible
  114. // postfix to it later
  115. $uniqueFileName = tempnam($this->tmpBaseDir, self::TMP_PREFIX);
  116. $this->current[] = $uniqueFileName;
  117. // Build a name without postfix
  118. $path = $this->buildFileNameWithSuffix($uniqueFileName . '-folder', $postFix);
  119. mkdir($path, 0700);
  120. $this->current[] = $path;
  121. return $path . '/';
  122. } else {
  123. $this->log->warning(
  124. 'Can not create a temporary folder in directory {dir}. Check it exists and has correct permissions',
  125. [
  126. 'dir' => $this->tmpBaseDir,
  127. ]
  128. );
  129. return false;
  130. }
  131. }
  132. /**
  133. * Remove the temporary files and folders generated during this request
  134. */
  135. public function clean() {
  136. $this->cleanFiles($this->current);
  137. }
  138. /**
  139. * @param string[] $files
  140. */
  141. protected function cleanFiles($files) {
  142. foreach ($files as $file) {
  143. if (file_exists($file)) {
  144. try {
  145. \OC_Helper::rmdirr($file);
  146. } catch (\UnexpectedValueException $ex) {
  147. $this->log->warning(
  148. "Error deleting temporary file/folder: {file} - Reason: {error}",
  149. [
  150. 'file' => $file,
  151. 'error' => $ex->getMessage(),
  152. ]
  153. );
  154. }
  155. }
  156. }
  157. }
  158. /**
  159. * Remove old temporary files and folders that were failed to be cleaned
  160. */
  161. public function cleanOld() {
  162. $this->cleanFiles($this->getOldFiles());
  163. }
  164. /**
  165. * Get all temporary files and folders generated by oc older than an hour
  166. *
  167. * @return string[]
  168. */
  169. protected function getOldFiles() {
  170. $cutOfTime = time() - 3600;
  171. $files = [];
  172. $dh = opendir($this->tmpBaseDir);
  173. if ($dh) {
  174. while (($file = readdir($dh)) !== false) {
  175. if (substr($file, 0, 7) === self::TMP_PREFIX) {
  176. $path = $this->tmpBaseDir . '/' . $file;
  177. $mtime = filemtime($path);
  178. if ($mtime < $cutOfTime) {
  179. $files[] = $path;
  180. }
  181. }
  182. }
  183. }
  184. return $files;
  185. }
  186. /**
  187. * Get the temporary base directory configured on the server
  188. *
  189. * @return string Path to the temporary directory or null
  190. * @throws \UnexpectedValueException
  191. */
  192. public function getTempBaseDir() {
  193. if ($this->tmpBaseDir) {
  194. return $this->tmpBaseDir;
  195. }
  196. $directories = [];
  197. if ($temp = $this->config->getSystemValue('tempdirectory', null)) {
  198. $directories[] = $temp;
  199. }
  200. if ($temp = \OC::$server->getIniWrapper()->get('upload_tmp_dir')) {
  201. $directories[] = $temp;
  202. }
  203. if ($temp = getenv('TMP')) {
  204. $directories[] = $temp;
  205. }
  206. if ($temp = getenv('TEMP')) {
  207. $directories[] = $temp;
  208. }
  209. if ($temp = getenv('TMPDIR')) {
  210. $directories[] = $temp;
  211. }
  212. if ($temp = sys_get_temp_dir()) {
  213. $directories[] = $temp;
  214. }
  215. foreach ($directories as $dir) {
  216. if ($this->checkTemporaryDirectory($dir)) {
  217. return $dir;
  218. }
  219. }
  220. $temp = tempnam(dirname(__FILE__), '');
  221. if (file_exists($temp)) {
  222. unlink($temp);
  223. return dirname($temp);
  224. }
  225. throw new \UnexpectedValueException('Unable to detect system temporary directory');
  226. }
  227. /**
  228. * Check if a temporary directory is ready for use
  229. *
  230. * @param mixed $directory
  231. * @return bool
  232. */
  233. private function checkTemporaryDirectory($directory) {
  234. // suppress any possible errors caused by is_writable
  235. // checks missing or invalid path or characters, wrong permissions etc
  236. try {
  237. if (is_writeable($directory)) {
  238. return true;
  239. }
  240. } catch (\Exception $e) {
  241. }
  242. $this->log->warning('Temporary directory {dir} is not present or writable',
  243. ['dir' => $directory]
  244. );
  245. return false;
  246. }
  247. /**
  248. * Override the temporary base directory
  249. *
  250. * @param string $directory
  251. */
  252. public function overrideTempBaseDir($directory) {
  253. $this->tmpBaseDir = $directory;
  254. }
  255. }