TempManager.php 6.5 KB

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