project_dir = $project_dir; //Determine the path for our ressources $this->media_path = $this->parameterToAbsolutePath($media_path); $this->footprints_path = $this->parameterToAbsolutePath($footprints_path); $this->models_path = $this->parameterToAbsolutePath($models_path); $this->secure_path = $this->parameterToAbsolutePath($secure_path); //Here we define the valid placeholders and their replacement values $this->placeholders = ['%MEDIA%', '%BASE%/data/media', '%FOOTPRINTS%', '%FOOTPRINTS_3D%', '%SECURE%']; $this->pathes = [$this->media_path, $this->media_path, $this->footprints_path, $this->models_path, $this->secure_path]; //Remove all disabled placeholders foreach ($this->pathes as $key => $path) { if ($path === null) { unset($this->placeholders[$key], $this->pathes[$key]); } } //Create the regex arrays $this->placeholders_regex = $this->arrayToRegexArray($this->placeholders); $this->pathes_regex = $this->arrayToRegexArray($this->pathes); } /** * Converts a path passed by parameter from services.yaml (which can be an absolute path or relative to project dir) * to an absolute path. When a relative path is passed, the directory must exist or null is returned. * @internal * @param string|null $param_path The parameter value that should be converted to a absolute path * @return string|null */ public function parameterToAbsolutePath(?string $param_path) : ?string { if ($param_path === null) { return null; } $fs = new Filesystem(); //If current string is already an absolute path, then we have nothing to do if ($fs->isAbsolutePath($param_path)) { $tmp = realpath($param_path); //Disable ressource if path is not existing if ($tmp === false) { return null; } return $tmp; } //Otherwise prepend the project path $tmp = realpath($this->project_dir . DIRECTORY_SEPARATOR . $param_path); //If path does not exist then disable the placeholder if ($tmp === false) { return null; } //Otherwise return resolved path return $tmp; } /** * Create an array usable for preg_replace out of an array of placeholders or pathes. * Slashes and other chars become escaped. * For example: '%TEST%' becomes '/^%TEST%/'. * @param array $array * @return array */ protected function arrayToRegexArray(array $array) : array { $ret = []; foreach ($array as $item) { $item = str_replace(['\\'], ['/'], $item); $ret[] = '/' . preg_quote($item, '/') . '/'; } return $ret; } /** * Converts an relative placeholder filepath (with %MEDIA% or older %BASE%) to an absolute filepath on disk. * The directory separator is always /. Relative pathes are not realy possible (.. is striped) * @param string $placeholder_path The filepath with placeholder for which the real path should be determined. * @return string|null The absolute real path of the file, or null if the placeholder path is invalid */ public function placeholderToRealPath(string $placeholder_path) : ?string { //The new attachments use %MEDIA% as placeholders, which is the directory set in media_directory //Older path entries are given via %BASE% which was the project root $count = 0; $placeholder_path = preg_replace($this->placeholders_regex, $this->pathes, $placeholder_path,-1,$count); //A valid placeholder can have only one if ($count !== 1) { return null; } //If we have now have a placeholder left, the string is invalid: if (preg_match('/%\w+%/', $placeholder_path)) { return null; } //Path is invalid if path is directory traversal if (strpos($placeholder_path, '..') !== false) { return null; } //Normalize path and remove .. (to prevent directory traversal attack) $placeholder_path = str_replace(['\\'], ['/'], $placeholder_path); return $placeholder_path; } /** * Converts an real absolute filepath to a placeholder version. * @param string $real_path The absolute path, for which the placeholder version should be generated. * @param bool $old_version By default the %MEDIA% placeholder is used, which is directly replaced with the * media directory. If set to true, the old version with %BASE% will be used, which is the project directory. * @return string The placeholder version of the filepath */ public function realPathToPlaceholder(string $real_path, bool $old_version = false) : ?string { $count = 0; //Normalize path $real_path = str_replace('\\', '/', $real_path); if ($old_version) { //We need to remove the %MEDIA% placeholder (element 0) $pathes = $this->pathes_regex; $placeholders = $this->placeholders; unset($pathes[0], $placeholders[0]); $real_path = preg_replace($pathes, $placeholders, $real_path, -1, $count); } else { $real_path = preg_replace($this->pathes_regex, $this->placeholders, $real_path, -1, $count); } if ($count !== 1) { return null; } //If the new string does not begin with a placeholder, it is invalid if (!preg_match('/^%\w+%/', $real_path)) { return null; } return $real_path; } /** * The path where uploaded attachments is stored. * @return string The absolute path to the media folder. */ public function getMediaPath() : string { return $this->media_path; } /** * The path where secured attachments are stored. Must not be located in public/ folder, so it can only be accessed * via the attachment controller. * @return string The absolute path to the secure path. */ public function getSecurePath() : string { return $this->secure_path; } /** * The string where the builtin footprints are stored * @return string|null The absolute path to the footprints folder. Null if built footprints were disabled. */ public function getFootprintsPath() : ?string { return $this->footprints_path; } /** * The string where the builtin 3D models are stored * @return string|null The absolute path to the models folder. Null if builtin models were disabled. */ public function getModelsPath() : ?string { return $this->models_path; } }