ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
FilePathSanitizer.php
Go to the documentation of this file.
1 <?php
2 
3 namespace ILIAS\File\Sanitation;
4 
6 use Exception;
7 use ilFileUtils;
10 use ilObjFile;
11 
18 {
19 
23  private $file_object;
27  private $relative_path;
31  private $fs;
35  private $absolute_path;
39  private $version = 1;
40 
41 
48  {
49  $this->version = (int) $file_object->getVersion();
50  $this->file_object = $file_object;
51  $this->absolute_path = $this->file_object->getDirectory($this->version) . "/" . $this->file_object->getFileName();
52  $this->relative_path = LegacyPathHelper::createRelativePath($this->absolute_path);
53  $this->fs = LegacyPathHelper::deriveFilesystemFrom($this->absolute_path);
54  }
55 
56 
60  public function needsSanitation() /* : bool*/
61  {
62  try {
63  $fs_relative_path_existing = $this->fs->has($this->relative_path);
64  $fs_valid_relative_path_existing = $this->fs->has(ilFileUtils::getValidFilename($this->relative_path));
65  $native_absolute_path_exists = file_exists($this->absolute_path);
66  $native_valid_absolute_path_existing = file_exists(ilFileUtils::getValidFilename($this->absolute_path));
67 
68  return (
69  !$fs_relative_path_existing
70  || !$fs_valid_relative_path_existing
71  || !$native_absolute_path_exists
72  || !$native_valid_absolute_path_existing
73  );
74  } catch (Exception $e) {
75  return false;
76  }
77  }
78 
79 
83  private function log(/*string*/ $message)
84  {
85  global $DIC;
86  $DIC->logger()->root()->debug("FilePathSanitizer: " . $message);
87  }
88 
89 
93  public function sanitizeIfNeeded() /* : void */
94  {
95  $versions = count($this->file_object->getVersions());
96  if ($versions === 1) {
97  $version_one = $this->file_object->getDirectory(1) . "/" . $this->file_object->getFileName();
98  $relative_version_one = LegacyPathHelper::createRelativePath($version_one);
99  $version_two = $this->file_object->getDirectory(2) . "/" . $this->file_object->getFileName();
100  $relative_version_two = LegacyPathHelper::createRelativePath($version_two);
101  if (!$this->fs->has($relative_version_one) && $this->fs->has($relative_version_two)) {
102  // $this->fs->copy($relative_version_two, $relative_version_one);
103  // $this->fs->delete($relative_version_two);
104  }
105  }
106 
107  if ($this->needsSanitation()) {
108  // First Try: using FileSystemService
109  $dirname = dirname($this->relative_path);
110  if (!$this->fs->has($dirname)) {
111  $this->log("FAILED: Sanitizing File Path: {$this->file_object->getFile()}. Message: Directory not found");
112 
113  return false;
114  }
115  try {
116  $first_file = reset($this->fs->listContents($dirname));
117  } catch (DirectoryNotFoundException $e) {
118  $this->log("FAILED AGAIN and AGAIN: Sanitizing File Path: {$this->file_object->getFile()}. Message: {$e->getMessage()}");
119 
120  return false;
121  }
122  if ($first_file instanceof \ILIAS\Filesystem\DTO\Metadata) {
123  try {
124  $valid_filename = $this->santitizeFilename($first_file->getPath());
125  // rename file in filesystem
126  if (!$this->fs->has($valid_filename)) {
127  $this->fs->rename($first_file->getPath(), $valid_filename);
128  // rename file object
129 
130  $this->log("Sanitized File Path: {$valid_filename}");
131  }
132  $this->saveNewNameForFileObject($valid_filename);
133 
134  return true;
135  } catch (Exception $e) {
136  $this->log("FAILED: Sanitizing File Path: {$this->file_object->getFile()}. Message: {$e->getMessage()}. Will try using native PHP");
137 
138  try {
139  // Second try: use native php
140  $scandir = scandir(dirname($this->absolute_path));
141  if (isset($scandir[2])) {
142  $first_file = $scandir[2];
143  if (is_file($first_file)) {
144  $valid_filename = $this->santitizeFilename($first_file);
145  if (rename($first_file, $valid_filename)) {
146  $this->saveNewNameForFileObject($valid_filename);
147  $this->log("Sanitized File Path: {$valid_filename}");
148  }
149  } else {
150  throw new Exception("is not a file: " . $first_file);
151  }
152  } else {
153  throw new Exception("no File found in " . dirname($this->absolute_path));
154  }
155  } catch (Exception $e) {
156  $this->log("FAILED AGAIN: Sanitizing File Path: {$this->file_object->getFile()}. Message: {$e->getMessage()}");
157 
158  try {
159  foreach (new DirectoryIterator(dirname($this->absolute_path)) as $item) {
160  if ($item->isDot()) {
161  continue;
162  }
163  if ($item->isFile()) {
164  $valid_filename = $this->santitizeFilename($item->getPathname());
165  if (rename($item->getPathname(), $valid_filename)) {
166  $this->saveNewNameForFileObject($valid_filename);
167  $this->log("Sanitized File Path: {$valid_filename}");
168  }
169  break;
170  }
171  }
172  } catch (Exception $e) {
173  $this->log("FAILED AGAIN and AGAIN: Sanitizing File Path: {$this->file_object->getFile()}. Message: {$e->getMessage()}");
174  }
175  }
176 
177  return false;
178  }
179  }
180 
181  return false;
182  }
183 
184  return true;
185  }
186 
187 
194  private function santitizeFilename($first_file)
195  {
196  $valid_filename = $first_file;
197 
198  while (preg_match('#\p{C}+|^\./#u', $valid_filename)) {
199  $valid_filename = preg_replace('#\p{C}+|^\./#u', '', $valid_filename);
200  }
201 
202  $valid_filename = preg_replace('/[\x00-\x1F\x7F-\xFF]/', '', $valid_filename); // removes all non printable characters (ASCII 7 Bit)
203 
204  // $valid_filename = \League\Flysystem\Util::normalizeRelativePath($valid_filename);
205  // $valid_filename = preg_replace('/[\x00-\x1F\x7F-\xA0\xAD]/u', '', $valid_filename);
206  // $valid_filename = iconv(mb_detect_encoding($valid_filename, mb_detect_order(), true), "UTF-8", $valid_filename);
207  // $valid_filename = utf8_encode($valid_filename);
208 
209  $valid_filename = ilFileUtils::getValidFilename($valid_filename);
210 
211  return $valid_filename;
212  }
213 
214 
218  private function saveNewNameForFileObject($valid_filename)
219  {
220  $sanitized_filename = basename($valid_filename);
221  $this->file_object->setFileName($sanitized_filename);
222  $this->file_object->update();
223  }
224 }
static createRelativePath(string $absolute_path)
Creates a relative path from an absolute path which starts with a valid storage location.
__construct(ilObjFile $file_object)
FilePathSanitizer constructor.
global $DIC
Definition: saml.php:7
Class BaseForm.
catch(Exception $e) $message
static deriveFilesystemFrom(string $absolute_path)
Tries to fetch the filesystem responsible for the absolute path.
Class FlySystemFileAccessTest.
static getValidFilename($a_filename)
Get valid filename.