1 <?php 2 3 declare(strict_types=1); 4 5 namespace OpenSpout\Common\Helper; 6 7 use OpenSpout\Common\Exception\IOException; 8 use RecursiveDirectoryIterator; 9 use RecursiveIteratorIterator; 10 11 /** 12 * @internal 13 */ 14 final class FileSystemHelper implements FileSystemHelperInterface 15 { 16 /** @var string Real path of the base folder where all the I/O can occur */ 17 private string $baseFolderRealPath; 18 19 /** 20 * @param string $baseFolderPath The path of the base folder where all the I/O can occur 21 */ 22 public function __construct(string $baseFolderPath) 23 { 24 $realpath = realpath($baseFolderPath); 25 \assert(false !== $realpath); 26 $this->baseFolderRealPath = $realpath; 27 } 28 29 public function getBaseFolderRealPath(): string 30 { 31 return $this->baseFolderRealPath; 32 } 33 34 /** 35 * Creates an empty folder with the given name under the given parent folder. 36 * 37 * @param string $parentFolderPath The parent folder path under which the folder is going to be created 38 * @param string $folderName The name of the folder to create 39 * 40 * @return string Path of the created folder 41 * 42 * @throws \OpenSpout\Common\Exception\IOException If unable to create the folder or if the folder path is not inside of the base folder 43 */ 44 public function createFolder(string $parentFolderPath, string $folderName): string 45 { 46 $this->throwIfOperationNotInBaseFolder($parentFolderPath); 47 48 $folderPath = $parentFolderPath.\DIRECTORY_SEPARATOR.$folderName; 49 50 $errorMessage = ''; 51 set_error_handler(static function ($nr, $message) use (&$errorMessage): bool { 52 $errorMessage = $message; 53 54 return true; 55 }); 56 $wasCreationSuccessful = mkdir($folderPath, 0777, true); 57 restore_error_handler(); 58 59 if (!$wasCreationSuccessful) { 60 throw new IOException("Unable to create folder: {$folderPath} - {$errorMessage}"); 61 } 62 63 return $folderPath; 64 } 65 66 /** 67 * Creates a file with the given name and content in the given folder. 68 * The parent folder must exist. 69 * 70 * @param string $parentFolderPath The parent folder path where the file is going to be created 71 * @param string $fileName The name of the file to create 72 * @param string $fileContents The contents of the file to create 73 * 74 * @return string Path of the created file 75 * 76 * @throws \OpenSpout\Common\Exception\IOException If unable to create the file or if the file path is not inside of the base folder 77 */ 78 public function createFileWithContents(string $parentFolderPath, string $fileName, string $fileContents): string 79 { 80 $this->throwIfOperationNotInBaseFolder($parentFolderPath); 81 82 $filePath = $parentFolderPath.\DIRECTORY_SEPARATOR.$fileName; 83 84 $errorMessage = ''; 85 set_error_handler(static function ($nr, $message) use (&$errorMessage): bool { 86 $errorMessage = $message; 87 88 return true; 89 }); 90 $wasCreationSuccessful = file_put_contents($filePath, $fileContents); 91 restore_error_handler(); 92 93 if (false === $wasCreationSuccessful) { 94 throw new IOException("Unable to create file: {$filePath} - {$errorMessage}"); 95 } 96 97 return $filePath; 98 } 99 100 /** 101 * Delete the file at the given path. 102 * 103 * @param string $filePath Path of the file to delete 104 * 105 * @throws \OpenSpout\Common\Exception\IOException If the file path is not inside of the base folder 106 */ 107 public function deleteFile(string $filePath): void 108 { 109 $this->throwIfOperationNotInBaseFolder($filePath); 110 111 if (file_exists($filePath) && is_file($filePath)) { 112 unlink($filePath); 113 } 114 } 115 116 /** 117 * Delete the folder at the given path as well as all its contents. 118 * 119 * @param string $folderPath Path of the folder to delete 120 * 121 * @throws \OpenSpout\Common\Exception\IOException If the folder path is not inside of the base folder 122 */ 123 public function deleteFolderRecursively(string $folderPath): void 124 { 125 $this->throwIfOperationNotInBaseFolder($folderPath); 126 127 $itemIterator = new RecursiveIteratorIterator( 128 new RecursiveDirectoryIterator($folderPath, RecursiveDirectoryIterator::SKIP_DOTS), 129 RecursiveIteratorIterator::CHILD_FIRST 130 ); 131 132 foreach ($itemIterator as $item) { 133 if ($item->isDir()) { 134 rmdir($item->getPathname()); 135 } else { 136 unlink($item->getPathname()); 137 } 138 } 139 140 rmdir($folderPath); 141 } 142 143 /** 144 * All I/O operations must occur inside the base folder, for security reasons. 145 * This function will throw an exception if the folder where the I/O operation 146 * should occur is not inside the base folder. 147 * 148 * @param string $operationFolderPath The path of the folder where the I/O operation should occur 149 * 150 * @throws \OpenSpout\Common\Exception\IOException If the folder where the I/O operation should occur 151 * is not inside the base folder or the base folder does not exist 152 */ 153 private function throwIfOperationNotInBaseFolder(string $operationFolderPath): void 154 { 155 $operationFolderRealPath = realpath($operationFolderPath); 156 if (false === $operationFolderRealPath) { 157 throw new IOException("Folder not found: {$operationFolderRealPath}"); 158 } 159 $isInBaseFolder = str_starts_with($operationFolderRealPath, $this->baseFolderRealPath); 160 if (!$isInBaseFolder) { 161 throw new IOException("Cannot perform I/O operation outside of the base folder: {$this->baseFolderRealPath}"); 162 } 163 } 164 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body