TempManager.php 7.4 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 Roeland Jago Douma <roeland@famdouma.nl>
  13. * @author Stefan Weil <sw@weilnetz.de>
  14. *
  15. * @license AGPL-3.0
  16. *
  17. * This code is free software: you can redistribute it and/or modify
  18. * it under the terms of the GNU Affero General Public License, version 3,
  19. * as published by the Free Software Foundation.
  20. *
  21. * This program is distributed in the hope that it will be useful,
  22. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  23. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  24. * GNU Affero General Public License for more details.
  25. *
  26. * You should have received a copy of the GNU Affero General Public License, version 3,
  27. * along with this program. If not, see <http://www.gnu.org/licenses/>
  28. *
  29. */
  30. namespace OC;
  31. use OCP\ILogger;
  32. use OCP\IConfig;
  33. use OCP\ITempManager;
  34. class TempManager implements ITempManager {
  35. /** @var string[] Current temporary files and folders, used for cleanup */
  36. protected $current = [];
  37. /** @var string i.e. /tmp on linux systems */
  38. protected $tmpBaseDir;
  39. /** @var ILogger */
  40. protected $log;
  41. /** @var IConfig */
  42. protected $config;
  43. /** Prefix */
  44. const TMP_PREFIX = 'oc_tmp_';
  45. /**
  46. * @param \OCP\ILogger $logger
  47. * @param \OCP\IConfig $config
  48. */
  49. public function __construct(ILogger $logger, IConfig $config) {
  50. $this->log = $logger;
  51. $this->config = $config;
  52. $this->tmpBaseDir = $this->getTempBaseDir();
  53. }
  54. /**
  55. * Builds the filename with suffix and removes potential dangerous characters
  56. * such as directory separators.
  57. *
  58. * @param string $absolutePath Absolute path to the file / folder
  59. * @param string $postFix Postfix appended to the temporary file name, may be user controlled
  60. * @return string
  61. */
  62. private function buildFileNameWithSuffix($absolutePath, $postFix = '') {
  63. if($postFix !== '') {
  64. $postFix = '.' . ltrim($postFix, '.');
  65. $postFix = str_replace(['\\', '/'], '', $postFix);
  66. $absolutePath .= '-';
  67. }
  68. return $absolutePath . $postFix;
  69. }
  70. /**
  71. * Create a temporary file and return the path
  72. *
  73. * @param string $postFix Postfix appended to the temporary file name
  74. * @return string
  75. */
  76. public function getTemporaryFile($postFix = '') {
  77. if (is_writable($this->tmpBaseDir)) {
  78. // To create an unique file and prevent the risk of race conditions
  79. // or duplicated temporary files by other means such as collisions
  80. // we need to create the file using `tempnam` and append a possible
  81. // postfix to it later
  82. $file = tempnam($this->tmpBaseDir, self::TMP_PREFIX);
  83. $this->current[] = $file;
  84. // If a postfix got specified sanitize it and create a postfixed
  85. // temporary file
  86. if($postFix !== '') {
  87. $fileNameWithPostfix = $this->buildFileNameWithSuffix($file, $postFix);
  88. touch($fileNameWithPostfix);
  89. chmod($fileNameWithPostfix, 0600);
  90. $this->current[] = $fileNameWithPostfix;
  91. return $fileNameWithPostfix;
  92. }
  93. return $file;
  94. } else {
  95. $this->log->warning(
  96. 'Can not create a temporary file in directory {dir}. Check it exists and has correct permissions',
  97. [
  98. 'dir' => $this->tmpBaseDir,
  99. ]
  100. );
  101. return false;
  102. }
  103. }
  104. /**
  105. * Create a temporary folder and return the path
  106. *
  107. * @param string $postFix Postfix appended to the temporary folder name
  108. * @return string
  109. */
  110. public function getTemporaryFolder($postFix = '') {
  111. if (is_writable($this->tmpBaseDir)) {
  112. // To create an unique directory and prevent the risk of race conditions
  113. // or duplicated temporary files by other means such as collisions
  114. // we need to create the file using `tempnam` and append a possible
  115. // postfix to it later
  116. $uniqueFileName = tempnam($this->tmpBaseDir, self::TMP_PREFIX);
  117. $this->current[] = $uniqueFileName;
  118. // Build a name without postfix
  119. $path = $this->buildFileNameWithSuffix($uniqueFileName . '-folder', $postFix);
  120. mkdir($path, 0700);
  121. $this->current[] = $path;
  122. return $path . '/';
  123. } else {
  124. $this->log->warning(
  125. 'Can not create a temporary folder in directory {dir}. Check it exists and has correct permissions',
  126. [
  127. 'dir' => $this->tmpBaseDir,
  128. ]
  129. );
  130. return false;
  131. }
  132. }
  133. /**
  134. * Remove the temporary files and folders generated during this request
  135. */
  136. public function clean() {
  137. $this->cleanFiles($this->current);
  138. }
  139. /**
  140. * @param string[] $files
  141. */
  142. protected function cleanFiles($files) {
  143. foreach ($files as $file) {
  144. if (file_exists($file)) {
  145. try {
  146. \OC_Helper::rmdirr($file);
  147. } catch (\UnexpectedValueException $ex) {
  148. $this->log->warning(
  149. "Error deleting temporary file/folder: {file} - Reason: {error}",
  150. [
  151. 'file' => $file,
  152. 'error' => $ex->getMessage(),
  153. ]
  154. );
  155. }
  156. }
  157. }
  158. }
  159. /**
  160. * Remove old temporary files and folders that were failed to be cleaned
  161. */
  162. public function cleanOld() {
  163. $this->cleanFiles($this->getOldFiles());
  164. }
  165. /**
  166. * Get all temporary files and folders generated by oc older than an hour
  167. *
  168. * @return string[]
  169. */
  170. protected function getOldFiles() {
  171. $cutOfTime = time() - 3600;
  172. $files = [];
  173. $dh = opendir($this->tmpBaseDir);
  174. if ($dh) {
  175. while (($file = readdir($dh)) !== false) {
  176. if (substr($file, 0, 7) === self::TMP_PREFIX) {
  177. $path = $this->tmpBaseDir . '/' . $file;
  178. $mtime = filemtime($path);
  179. if ($mtime < $cutOfTime) {
  180. $files[] = $path;
  181. }
  182. }
  183. }
  184. }
  185. return $files;
  186. }
  187. /**
  188. * Get the temporary base directory configured on the server
  189. *
  190. * @return string Path to the temporary directory or null
  191. * @throws \UnexpectedValueException
  192. */
  193. public function getTempBaseDir() {
  194. if ($this->tmpBaseDir) {
  195. return $this->tmpBaseDir;
  196. }
  197. $directories = [];
  198. if ($temp = $this->config->getSystemValue('tempdirectory', null)) {
  199. $directories[] = $temp;
  200. }
  201. if ($temp = \OC::$server->getIniWrapper()->get('upload_tmp_dir')) {
  202. $directories[] = $temp;
  203. }
  204. if ($temp = getenv('TMP')) {
  205. $directories[] = $temp;
  206. }
  207. if ($temp = getenv('TEMP')) {
  208. $directories[] = $temp;
  209. }
  210. if ($temp = getenv('TMPDIR')) {
  211. $directories[] = $temp;
  212. }
  213. if ($temp = sys_get_temp_dir()) {
  214. $directories[] = $temp;
  215. }
  216. foreach ($directories as $dir) {
  217. if ($this->checkTemporaryDirectory($dir)) {
  218. return $dir;
  219. }
  220. }
  221. $temp = tempnam(dirname(__FILE__), '');
  222. if (file_exists($temp)) {
  223. unlink($temp);
  224. return dirname($temp);
  225. }
  226. throw new \UnexpectedValueException('Unable to detect system temporary directory');
  227. }
  228. /**
  229. * Check if a temporary directory is ready for use
  230. *
  231. * @param mixed $directory
  232. * @return bool
  233. */
  234. private function checkTemporaryDirectory($directory) {
  235. // suppress any possible errors caused by is_writable
  236. // checks missing or invalid path or characters, wrong permissions etc
  237. try {
  238. if (is_writable($directory)) {
  239. return true;
  240. }
  241. } catch (\Exception $e) {
  242. }
  243. $this->log->warning('Temporary directory {dir} is not present or writable',
  244. ['dir' => $directory]
  245. );
  246. return false;
  247. }
  248. /**
  249. * Override the temporary base directory
  250. *
  251. * @param string $directory
  252. */
  253. public function overrideTempBaseDir($directory) {
  254. $this->tmpBaseDir = $directory;
  255. }
  256. }