ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
FilePathSanitizer.php
Go to the documentation of this file.
1 <?php
2 
4 
6 use Exception;
7 use ilFileUtils;
9 use ilObjFile;
10 
17 {
18 
22  private $file_object;
26  private $relative_path;
30  private $fs;
34  private $absolute_path;
35 
36 
43  {
44  $this->file_object = $file_object;
45  $this->absolute_path = $this->file_object->getDirectory($this->file_object->getVersion()) . "/" . $this->file_object->getFileName();
46  $this->relative_path = LegacyPathHelper::createRelativePath($this->absolute_path);
47  $this->fs = LegacyPathHelper::deriveFilesystemFrom($this->absolute_path);
48  }
49 
50 
54  public function needsSanitation() /* : bool*/
55  {
56  try {
57  $fs_relative_path_existing = $this->fs->has($this->relative_path);
58  $fs_valid_relative_path_existing = $this->fs->has(ilFileUtils::getValidFilename($this->relative_path));
59  $native_absolute_path_exists = file_exists($this->absolute_path);
60  $native_valid_absolute_path_existing = file_exists(ilFileUtils::getValidFilename($this->absolute_path));
61 
62  return (
63  !$fs_relative_path_existing
64  || !$fs_valid_relative_path_existing
65  || !$native_absolute_path_exists
66  || !$native_valid_absolute_path_existing
67  );
68  } catch (Exception $e) {
69  return false;
70  }
71  }
72 
73 
77  private function log(/*string*/ $message)
78  {
79  global $DIC;
80  $DIC->logger()->root()->debug("FilePathSanitizer: " . $message);
81  }
82 
83 
88  public function sanitizeIfNeeded() /* : void */
89  {
90  if ($this->needsSanitation()) {
91  // First Try: using FileSystemService
92  $dirname = dirname($this->relative_path);
93  if (!$this->fs->has($dirname)) {
94  $this->log("FAILED: Sanitizing File Path: {$this->file_object->getFile()}. Message: Directory not found");
95 
96  return false;
97  }
98  $first_file = reset($this->fs->listContents($dirname));
99  if ($first_file instanceof \ILIAS\Filesystem\DTO\Metadata) {
100  try {
101  $valid_filename = $this->santitizeFilename($first_file->getPath());
102  // rename file in filesystem
103  if (!$this->fs->has($valid_filename)) {
104  $this->fs->rename($first_file->getPath(), $valid_filename);
105  // rename file object
106 
107  $this->log("Sanitized File Path: {$valid_filename}");
108  }
109  $this->saveNewNameForFileObject($valid_filename);
110 
111  return true;
112  } catch (Exception $e) {
113  $this->log("FAILED: Sanitizing File Path: {$this->file_object->getFile()}. Message: {$e->getMessage()}. Will try using native PHP");
114 
115  try {
116  // Second try: use native php
117  $scandir = scandir(dirname($this->absolute_path));
118  if (isset($scandir[2])) {
119  $first_file = $scandir[2];
120  if (is_file($first_file)) {
121  $valid_filename = $this->santitizeFilename($first_file);
122  if (rename($first_file, $valid_filename)) {
123  $this->saveNewNameForFileObject($valid_filename);
124  $this->log("Sanitized File Path: {$valid_filename}");
125  }
126  } else {
127  throw new Exception("is not a file: " . $first_file);
128  }
129  } else {
130  throw new Exception("no File found in " . dirname($this->absolute_path));
131  }
132  } catch (Exception $e) {
133  $this->log("FAILED AGAIN: Sanitizing File Path: {$this->file_object->getFile()}. Message: {$e->getMessage()}");
134 
135  try {
136  foreach (new DirectoryIterator(dirname($this->absolute_path)) as $item) {
137  if ($item->isDot()) {
138  continue;
139  }
140  if ($item->isFile()) {
141  $valid_filename = $this->santitizeFilename($item->getPathname());
142  if (rename($item->getPathname(), $valid_filename)) {
143  $this->saveNewNameForFileObject($valid_filename);
144  $this->log("Sanitized File Path: {$valid_filename}");
145  }
146  break;
147  }
148  }
149  } catch (Exception $e) {
150  $this->log("FAILED AGAIN and AGAIN: Sanitizing File Path: {$this->file_object->getFile()}. Message: {$e->getMessage()}");
151  }
152  }
153 
154  return false;
155  }
156  }
157 
158  return false;
159  }
160 
161  return true;
162  }
163 
164 
171  private function santitizeFilename($first_file)
172  {
173  $valid_filename = $first_file;
174 
175  while (preg_match('#\p{C}+|^\./#u', $valid_filename)) {
176  $valid_filename = preg_replace('#\p{C}+|^\./#u', '', $valid_filename);
177  }
178 
179  $valid_filename = preg_replace('/[\x00-\x1F\x7F-\xFF]/', '', $valid_filename); // removes all non printable characters (ASCII 7 Bit)
180 
181  // $valid_filename = \League\Flysystem\Util::normalizeRelativePath($valid_filename);
182  // $valid_filename = preg_replace('/[\x00-\x1F\x7F-\xA0\xAD]/u', '', $valid_filename);
183  // $valid_filename = iconv(mb_detect_encoding($valid_filename, mb_detect_order(), true), "UTF-8", $valid_filename);
184  // $valid_filename = utf8_encode($valid_filename);
185 
186  $valid_filename = ilFileUtils::getValidFilename($valid_filename);
187 
188  return $valid_filename;
189  }
190 
191 
195  private function saveNewNameForFileObject($valid_filename)
196  {
197  $sanitized_filename = basename($valid_filename);
198  $this->file_object->setFileName($sanitized_filename);
199  $this->file_object->update();
200  }
201 }
__construct(ilObjFile $file_object)
FilePathSanitizer constructor.
global $DIC
Definition: saml.php:7
Class BaseForm.
static createRelativePath($absolute_path)
Creates a relative path from an absolute path which starts with a valid storage location.
catch(Exception $e) $message
static deriveFilesystemFrom($absolute_path)
Tries to fetch the filesystem responsible for the absolute path.
Class FlySystemFileAccessTest.
static getValidFilename($a_filename)
Get valid filename.