Part-DB /
Part-DB-symfony
| 1 | <?php |
||
| 2 | /** |
||
| 3 | * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). |
||
| 4 | * |
||
| 5 | * Copyright (C) 2019 - 2022 Jan Böhmer (https://github.com/jbtronics) |
||
| 6 | * |
||
| 7 | * This program is free software: you can redistribute it and/or modify |
||
| 8 | * it under the terms of the GNU Affero General Public License as published |
||
| 9 | * by the Free Software Foundation, either version 3 of the License, or |
||
| 10 | * (at your option) any later version. |
||
| 11 | * |
||
| 12 | * This program is distributed in the hope that it will be useful, |
||
| 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
| 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
| 15 | * GNU Affero General Public License for more details. |
||
| 16 | * |
||
| 17 | * You should have received a copy of the GNU Affero General Public License |
||
| 18 | * along with this program. If not, see <https://www.gnu.org/licenses/>. |
||
| 19 | */ |
||
| 20 | |||
| 21 | declare(strict_types=1); |
||
| 22 | |||
| 23 | namespace App\Services\Attachments; |
||
| 24 | |||
| 25 | use FontLib\Table\Type\maxp; |
||
| 26 | use const DIRECTORY_SEPARATOR; |
||
| 27 | use Symfony\Component\Filesystem\Filesystem; |
||
| 28 | |||
| 29 | /** |
||
| 30 | * This service converts the relative pathes for attachments saved in database (like %MEDIA%/img.jpg) to real pathes |
||
| 31 | * an vice versa. |
||
| 32 | */ |
||
| 33 | class AttachmentPathResolver |
||
| 34 | { |
||
| 35 | protected string $project_dir; |
||
| 36 | |||
| 37 | protected ?string $media_path; |
||
| 38 | protected ?string $footprints_path; |
||
| 39 | protected ?string $models_path; |
||
| 40 | protected ?string $secure_path; |
||
| 41 | |||
| 42 | protected array $placeholders; |
||
| 43 | protected array $pathes; |
||
| 44 | protected array $placeholders_regex; |
||
| 45 | protected array $pathes_regex; |
||
| 46 | |||
| 47 | /** |
||
| 48 | * AttachmentPathResolver constructor. |
||
| 49 | * |
||
| 50 | * @param string $project_dir the kernel that should be used to resolve the project dir |
||
| 51 | * @param string $media_path the path where uploaded attachments should be stored |
||
| 52 | * @param string|null $footprints_path The path where builtin attachments are stored. |
||
| 53 | * Set to null if this ressource should be disabled. |
||
| 54 | * @param string|null $models_path set to null if this ressource should be disabled |
||
| 55 | */ |
||
| 56 | public function __construct(string $project_dir, string $media_path, string $secure_path, ?string $footprints_path, ?string $models_path) |
||
| 57 | { |
||
| 58 | $this->project_dir = $project_dir; |
||
| 59 | |||
| 60 | //Determine the path for our ressources |
||
| 61 | $this->media_path = $this->parameterToAbsolutePath($media_path); |
||
| 62 | $this->footprints_path = $this->parameterToAbsolutePath($footprints_path); |
||
| 63 | $this->models_path = $this->parameterToAbsolutePath($models_path); |
||
| 64 | $this->secure_path = $this->parameterToAbsolutePath($secure_path); |
||
| 65 | |||
| 66 | //Here we define the valid placeholders and their replacement values |
||
| 67 | $this->placeholders = ['%MEDIA%', '%BASE%/data/media', '%FOOTPRINTS%', '%FOOTPRINTS_3D%', '%SECURE%']; |
||
| 68 | $this->pathes = [$this->media_path, $this->media_path, $this->footprints_path, $this->models_path, $this->secure_path]; |
||
| 69 | |||
| 70 | //Remove all disabled placeholders |
||
| 71 | foreach ($this->pathes as $key => $path) { |
||
| 72 | if (null === $path) { |
||
| 73 | unset($this->placeholders[$key], $this->pathes[$key]); |
||
| 74 | } |
||
| 75 | } |
||
| 76 | |||
| 77 | //Create the regex arrays |
||
| 78 | $this->placeholders_regex = $this->arrayToRegexArray($this->placeholders); |
||
| 79 | $this->pathes_regex = $this->arrayToRegexArray($this->pathes); |
||
| 80 | } |
||
| 81 | |||
| 82 | /** |
||
| 83 | * Converts a path passed by parameter from services.yaml (which can be an absolute path or relative to project dir) |
||
| 84 | * to an absolute path. When a relative path is passed, the directory must exist or null is returned. |
||
| 85 | * Returns an absolute path with "/" no matter, what system is used. |
||
| 86 | * |
||
| 87 | * @internal |
||
| 88 | * |
||
| 89 | * @param string|null $param_path The parameter value that should be converted to a absolute path |
||
| 90 | */ |
||
| 91 | public function parameterToAbsolutePath(?string $param_path): ?string |
||
| 92 | { |
||
| 93 | if (null === $param_path) { |
||
| 94 | return null; |
||
| 95 | } |
||
| 96 | |||
| 97 | $fs = new Filesystem(); |
||
| 98 | //If current string is already an absolute path, then we have nothing to do |
||
| 99 | if ($fs->isAbsolutePath($param_path)) { |
||
| 100 | $tmp = realpath($param_path); |
||
| 101 | //Disable ressource if path is not existing |
||
| 102 | if (false === $tmp) { |
||
| 103 | return null; |
||
| 104 | } |
||
| 105 | |||
| 106 | return $tmp; |
||
| 107 | } |
||
| 108 | |||
| 109 | //Otherwise prepend the project path |
||
| 110 | $tmp = realpath($this->project_dir.DIRECTORY_SEPARATOR.$param_path); |
||
| 111 | |||
| 112 | //If path does not exist then disable the placeholder |
||
| 113 | if (false === $tmp) { |
||
| 114 | return null; |
||
| 115 | } |
||
| 116 | |||
| 117 | //Normalize file path (use / instead of \) |
||
| 118 | return str_replace('\\', '/', $tmp); |
||
| 119 | } |
||
| 120 | |||
| 121 | /** |
||
| 122 | * Converts an relative placeholder filepath (with %MEDIA% or older %BASE%) to an absolute filepath on disk. |
||
| 123 | * The directory separator is always /. Relative pathes are not realy possible (.. is striped). |
||
| 124 | * |
||
| 125 | * @param string $placeholder_path the filepath with placeholder for which the real path should be determined |
||
| 126 | * |
||
| 127 | * @return string|null The absolute real path of the file, or null if the placeholder path is invalid |
||
| 128 | */ |
||
| 129 | public function placeholderToRealPath(string $placeholder_path): ?string |
||
| 130 | { |
||
| 131 | //The new attachments use %MEDIA% as placeholders, which is the directory set in media_directory |
||
| 132 | //Older path entries are given via %BASE% which was the project root |
||
| 133 | |||
| 134 | $count = 0; |
||
| 135 | |||
| 136 | //When path is a footprint we have to first run the string through our lecagy german mapping functions |
||
| 137 | if (strpos($placeholder_path, '%FOOTPRINTS%') !== false) { |
||
| 138 | $placeholder_path = $this->convertOldFootprintPath($placeholder_path); |
||
| 139 | } |
||
| 140 | |||
| 141 | $placeholder_path = preg_replace($this->placeholders_regex, $this->pathes, $placeholder_path, -1, $count); |
||
| 142 | |||
| 143 | //A valid placeholder can have only one |
||
| 144 | if (1 !== $count) { |
||
| 145 | return null; |
||
| 146 | } |
||
| 147 | |||
| 148 | //If we have now have a placeholder left, the string is invalid: |
||
| 149 | if (preg_match('#%\w+%#', $placeholder_path)) { |
||
| 150 | return null; |
||
| 151 | } |
||
| 152 | |||
| 153 | //Path is invalid if path is directory traversal |
||
| 154 | if (false !== strpos($placeholder_path, '..')) { |
||
| 155 | return null; |
||
| 156 | } |
||
| 157 | |||
| 158 | //Normalize path and remove .. (to prevent directory traversal attack) |
||
| 159 | return str_replace(['\\'], ['/'], $placeholder_path); |
||
| 160 | } |
||
| 161 | |||
| 162 | /** |
||
| 163 | * Converts an real absolute filepath to a placeholder version. |
||
| 164 | * |
||
| 165 | * @param string $real_path the absolute path, for which the placeholder version should be generated |
||
| 166 | * @param bool $old_version By default the %MEDIA% placeholder is used, which is directly replaced with the |
||
| 167 | * media directory. If set to true, the old version with %BASE% will be used, which is the project directory. |
||
| 168 | * |
||
| 169 | * @return string The placeholder version of the filepath |
||
| 170 | */ |
||
| 171 | public function realPathToPlaceholder(string $real_path, bool $old_version = false): ?string |
||
| 172 | { |
||
| 173 | $count = 0; |
||
| 174 | |||
| 175 | //Normalize path |
||
| 176 | $real_path = str_replace('\\', '/', $real_path); |
||
| 177 | |||
| 178 | if ($old_version) { |
||
| 179 | //We need to remove the %MEDIA% placeholder (element 0) |
||
| 180 | $pathes = $this->pathes_regex; |
||
| 181 | $placeholders = $this->placeholders; |
||
| 182 | unset($pathes[0], $placeholders[0]); |
||
| 183 | $real_path = preg_replace($pathes, $placeholders, $real_path, -1, $count); |
||
| 184 | } else { |
||
| 185 | $real_path = preg_replace($this->pathes_regex, $this->placeholders, $real_path, -1, $count); |
||
| 186 | } |
||
| 187 | |||
| 188 | if (1 !== $count) { |
||
| 189 | return null; |
||
| 190 | } |
||
| 191 | |||
| 192 | //If the new string does not begin with a placeholder, it is invalid |
||
| 193 | if (!preg_match('#^%\w+%#', $real_path)) { |
||
| 194 | return null; |
||
| 195 | } |
||
| 196 | |||
| 197 | return $real_path; |
||
| 198 | } |
||
| 199 | |||
| 200 | /** |
||
| 201 | * The path where uploaded attachments is stored. |
||
| 202 | * |
||
| 203 | * @return string the absolute path to the media folder |
||
| 204 | */ |
||
| 205 | public function getMediaPath(): string |
||
| 206 | { |
||
| 207 | return $this->media_path; |
||
|
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||
| 208 | } |
||
| 209 | |||
| 210 | /** |
||
| 211 | * The path where secured attachments are stored. Must not be located in public/ folder, so it can only be accessed |
||
| 212 | * via the attachment controller. |
||
| 213 | * |
||
| 214 | * @return string the absolute path to the secure path |
||
| 215 | */ |
||
| 216 | public function getSecurePath(): string |
||
| 217 | { |
||
| 218 | return $this->secure_path; |
||
|
0 ignored issues
–
show
|
|||
| 219 | } |
||
| 220 | |||
| 221 | /** |
||
| 222 | * The string where the builtin footprints are stored. |
||
| 223 | * |
||
| 224 | * @return string|null The absolute path to the footprints folder. Null if built footprints were disabled. |
||
| 225 | */ |
||
| 226 | public function getFootprintsPath(): ?string |
||
| 227 | { |
||
| 228 | return $this->footprints_path; |
||
| 229 | } |
||
| 230 | |||
| 231 | /** |
||
| 232 | * The string where the builtin 3D models are stored. |
||
| 233 | * |
||
| 234 | * @return string|null The absolute path to the models folder. Null if builtin models were disabled. |
||
| 235 | */ |
||
| 236 | public function getModelsPath(): ?string |
||
| 237 | { |
||
| 238 | return $this->models_path; |
||
| 239 | } |
||
| 240 | |||
| 241 | /** |
||
| 242 | * Create an array usable for preg_replace out of an array of placeholders or pathes. |
||
| 243 | * Slashes and other chars become escaped. |
||
| 244 | * For example: '%TEST%' becomes '/^%TEST%/'. |
||
| 245 | */ |
||
| 246 | protected function arrayToRegexArray(array $array): array |
||
| 247 | { |
||
| 248 | $ret = []; |
||
| 249 | |||
| 250 | foreach ($array as $item) { |
||
| 251 | $item = str_replace(['\\'], ['/'], $item); |
||
| 252 | $ret[] = '/'.preg_quote($item, '/').'/'; |
||
| 253 | } |
||
| 254 | |||
| 255 | return $ret; |
||
| 256 | } |
||
| 257 | |||
| 258 | private const OLD_FOOTPINT_PATH_REPLACEMENT = [ |
||
| 259 | 'Aktiv' => 'Active', |
||
| 260 | 'Bedrahtet' => 'THT', |
||
| 261 | 'Dioden' => 'Diodes', |
||
| 262 | 'Gleichrichter' => 'Rectifier', |
||
| 263 | 'GLEICHRICHTER' => 'RECTIFIER', |
||
| 264 | 'Oszillatoren' => 'Oscillator', |
||
| 265 | 'Keramikresonatoren_SMD' => 'CeramicResonator_SMD', |
||
| 266 | 'Quarze_bedrahtet' => 'Crystals_THT', |
||
| 267 | 'QUARZ' => 'CRYSTAL', |
||
| 268 | 'Quarze_SMD' => 'Crystals_SMD', |
||
| 269 | 'Quarzoszillatoren_bedrahtet' => 'CrystalOscillator_THT', |
||
| 270 | 'QUARZOSZILLATOR' => 'CRYSTAL_OSCILLATOR', |
||
| 271 | 'Quarzoszillatoren_SMD' => 'CrystalOscillator_SMD', |
||
| 272 | 'Schaltregler' => 'SwitchingRegulator', |
||
| 273 | 'SCHALTREGLER' => 'SWITCHING_REGULATOR', |
||
| 274 | 'Akustik' => 'Acoustics', |
||
| 275 | 'Elektromechanik' => 'Electromechanics', |
||
| 276 | 'Drahtbruecken' => 'WireJumpers', |
||
| 277 | 'DRAHTBRUECKE' => 'WIREJUMPER', |
||
| 278 | 'IC-Sockel' => 'IC-Socket', |
||
| 279 | 'SOCKEL' => 'SOCKET', |
||
| 280 | 'Kuehlkoerper' => 'Heatsinks', |
||
| 281 | 'KUEHLKOERPER' => 'HEATSINK', |
||
| 282 | 'Relais' => 'Relays', |
||
| 283 | 'RELAIS' => 'RELAY', |
||
| 284 | 'Schalter_Taster' => 'Switches_Buttons', |
||
| 285 | 'Drehschalter' => 'RotarySwitches', |
||
| 286 | 'DREHSCHALTER' => 'ROTARY_SWITCH', |
||
| 287 | 'Drucktaster' => 'Button', |
||
| 288 | 'TASTER' => 'BUTTON', |
||
| 289 | 'Kippschalter' => 'ToggleSwitch', |
||
| 290 | 'KIPPSCHALTER' => 'TOGGLE_SWITCH', |
||
| 291 | 'Gewinde' => 'Threaded', |
||
| 292 | 'abgewinkelt' => 'angled', |
||
| 293 | 'hochkant' => 'vertical', |
||
| 294 | 'stehend' => 'vertical', |
||
| 295 | 'liegend' => 'horizontal', |
||
| 296 | '_WECHSLER' => '', |
||
| 297 | 'Schiebeschalter' => 'SlideSwitch', |
||
| 298 | 'SCHIEBESCHALTER' => 'SLIDE_SWITCH', |
||
| 299 | 'Sicherungshalter' => 'Fuseholder', |
||
| 300 | 'SICHERUNGSHALTER_Laengs' => 'FUSEHOLDER_Lenghtway', |
||
| 301 | 'SICHERUNGSHALTER_Quer' => 'FUSEHOLDER_Across', |
||
| 302 | 'Speicherkartenslots' => 'MemoryCardSlots', |
||
| 303 | 'KARTENSLOT' => 'CARD_SLOT', |
||
| 304 | 'SD-Karte' => 'SD_Card', |
||
| 305 | 'Rot' => 'Red', |
||
| 306 | 'Schwarz' => 'Black', |
||
| 307 | 'Verbinder' => 'Connectors', |
||
| 308 | 'BUCHSE' => 'SOCKET', |
||
| 309 | 'Buchsenleisten' => 'SocketStrips', |
||
| 310 | 'Reihig' => 'Row', |
||
| 311 | 'gerade' => 'straight', |
||
| 312 | 'flach' => 'flat', |
||
| 313 | 'praezisions' => 'precision', |
||
| 314 | 'praezision' => 'precision', |
||
| 315 | 'BUCHSENLEISTE' => 'SOCKET_STRIP', |
||
| 316 | 'GERADE' => 'STRAIGHT', |
||
| 317 | 'FLACH' => 'FLAT', |
||
| 318 | 'PRAEZISION' => 'PRECISION', |
||
| 319 | 'ABGEWINKELT' => 'ANGLED', |
||
| 320 | 'Federkraftklemmen' => 'SpringClamps', |
||
| 321 | 'SCHRAUBKLEMME' => 'SCREW_CLAMP', |
||
| 322 | 'KLEMME' => 'CLAMP', |
||
| 323 | 'VERBINDER' => 'CONNECTOR', |
||
| 324 | 'Loetoesen' => 'SolderingPads', |
||
| 325 | 'LOETOESE' => 'SOLDERING_PAD', |
||
| 326 | 'Rundsteckverbinder' => 'DINConnectors', |
||
| 327 | 'Schraubklemmen' => 'ScrewClamps', |
||
| 328 | 'Sonstiges' => 'Miscellaneous', |
||
| 329 | 'Stiftleisten' => 'PinHeaders', |
||
| 330 | 'STIFTLEISTE' => 'PIN_HEADER', |
||
| 331 | 'mit_Rahmen' => 'with_frame', |
||
| 332 | 'RAHMEN' => 'FRAME', |
||
| 333 | 'Maennlich' => 'Male', |
||
| 334 | 'Platinenmontage' => 'PCBMount', |
||
| 335 | 'PLATINENMONTAGE' => 'PCB_MOUNT', |
||
| 336 | 'Weiblich' => 'Female', |
||
| 337 | 'Optik' => 'Optics', |
||
| 338 | 'BLAU' => 'BLUE', |
||
| 339 | 'GELD' => 'YELLOW', |
||
| 340 | 'GRUEN' => 'GREEN', |
||
| 341 | 'ROT' => 'RED', |
||
| 342 | 'eckig' => 'square', |
||
| 343 | 'Passiv' => 'Passive', |
||
| 344 | 'EMV' => 'EMC', |
||
| 345 | 'Induktivitaeten' => 'Inductors', |
||
| 346 | 'SPULE' => 'COIL', |
||
| 347 | 'Kondensatoren' => 'Capacitors', |
||
| 348 | 'ELKO' => 'Electrolyte', |
||
| 349 | 'Elektrolyt' => 'Electrolyte', |
||
| 350 | 'Folie' => 'Film', |
||
| 351 | 'FOLIENKONDENSATOR' => 'FILM_CAPACITOR', |
||
| 352 | 'Keramik' => 'Ceramic', |
||
| 353 | 'KERKO' => 'Ceramic', |
||
| 354 | 'Tantal' => 'Tantalum', |
||
| 355 | 'TANTAL' => 'TANTALUM', |
||
| 356 | 'Trimmkondensatoren' => 'TrimmerCapacitors', |
||
| 357 | 'TRIMMKONDENSATOR' => 'TRIMMER_CAPACITOR', |
||
| 358 | 'KONDENSATOR' => 'CAPACITOR', |
||
| 359 | 'Transformatoren' => 'Transformers', |
||
| 360 | 'TRAFO' => 'TRANSFORMER', |
||
| 361 | 'Widerstaende' => 'Resistors', |
||
| 362 | 'WIDERSTAND' => 'RESISTOR', |
||
| 363 | 'Dickschicht' => 'ThickFilm', |
||
| 364 | 'DICKSCHICHT' => 'THICK_FILM', |
||
| 365 | 'KERAMIK' => 'CERAMIC', |
||
| 366 | 'Kohleschicht' => 'Carbon', |
||
| 367 | 'KOHLE' => 'CARBON', |
||
| 368 | 'Sonstige' => 'Miscellaneous', //Have to be last (after "Sonstiges") |
||
| 369 | ]; |
||
| 370 | |||
| 371 | public function convertOldFootprintPath(string $old_path): string |
||
| 372 | { |
||
| 373 | //Only do the conversion if it contains a german string (meaning it has one of the four former base folders in its path) |
||
| 374 | if (!preg_match('/%FOOTPRINTS%\/(Passiv|Aktiv|Akustik|Elektromechanik|Optik)\//', $old_path)) { |
||
| 375 | return $old_path; |
||
| 376 | } |
||
| 377 | |||
| 378 | return strtr($old_path, self::OLD_FOOTPINT_PATH_REPLACEMENT); |
||
| 379 | } |
||
| 380 | } |
||
| 381 |