ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
FilePathSanitizer.php
Go to the documentation of this file.
1<?php
2
4
5use DirectoryIterator;
6use Exception;
10use ilObjFile;
11
18{
19
23 private $file_object;
31 private $fs;
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}
An exception for terminatinating execution or to throw for unit testing.
__construct(ilObjFile $file_object)
FilePathSanitizer constructor.
static createRelativePath(string $absolute_path)
Creates a relative path from an absolute path which starts with a valid storage location.
static deriveFilesystemFrom(string $absolute_path)
Tries to fetch the filesystem responsible for the absolute path.
Class ilFileUtils.
static getValidFilename($a_filename)
Get valid filename.
Class ilObjFile.
catch(Exception $e) $message
Class FlySystemFileAccessTest.
Class BaseForm.
global $DIC
Definition: saml.php:7