%PDF- %PDF-
Direktori : /var/www/projetos/suporte.iigd.com.br/src/Http/ |
Current File : /var/www/projetos/suporte.iigd.com.br/src/Http/ProxyRouter.php |
<?php /** * --------------------------------------------------------------------- * * GLPI - Gestionnaire Libre de Parc Informatique * * http://glpi-project.org * * @copyright 2015-2024 Teclib' and contributors. * @copyright 2003-2014 by the INDEPNET Development Team. * @licence https://www.gnu.org/licenses/gpl-3.0.html * * --------------------------------------------------------------------- * * LICENSE * * This file is part of GLPI. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. * * --------------------------------------------------------------------- */ namespace Glpi\Http; /** * @since 10.0.7 */ final class ProxyRouter { /** * GLPI root directory. * @var string */ private string $root_dir; /** * Target path. * @var string */ private string $path; /** * PathInfo (extra information added next to path). * @var string */ private ?string $pathinfo; public function __construct(string $root_dir, string $uri) { $this->root_dir = $root_dir; $uri = preg_replace('/\/{2,}/', '/', $uri); // remove duplicates `/` $path = null; $pathinfo = null; // Parse URI to find requested script and PathInfo $slash_pos = 0; while ($slash_pos !== false && ($dot_pos = strpos($uri, '.', $slash_pos)) !== false) { $slash_pos = strpos($uri, '/', $dot_pos); $filepath = substr($uri, 0, $slash_pos !== false ? $slash_pos : strlen($uri)); if (is_file($this->root_dir . $filepath)) { $path = $filepath; $pathinfo = substr($uri, strlen($filepath)); if ($pathinfo !== '') { // On any regular PHP script that is directly served by Apache, `$_SERVER['PATH_INFO']` // contains decoded URL. // We have to reproduce this decoding operation to prevent issues with endoded chars. $pathinfo = urldecode($pathinfo); } else { $pathinfo = null; } break; } } if ($path === null) { // Fallback to requested URI $path = $uri; // Clean trailing `/`. $path = rtrim($path, '/'); // If URI matches a directory path, consider `index.php` is the requested script. if (is_dir($this->root_dir . $path) && is_file($this->root_dir . $path . '/index.php')) { $path .= '/index.php'; } } $this->path = $path; $this->pathinfo = $pathinfo; } /** * Return target path. * * @return string */ public function getTargetPath(): string { return $this->path; } /** * Return target PathInfo. * * @return string|null */ public function getTargetPathInfo(): ?string { return $this->pathinfo; } /** * Return target file. * * @return string|null */ public function getTargetFile(): ?string { $target_file = $this->root_dir . $this->path; return is_file($target_file) ? $target_file : null; } /** * Determine whether the requested path is allowed. * * @param string $file_relative_path * @return bool */ public function isPathAllowed(): bool { // Check global exclusion patterns. $excluded_path_patterns = [ // hidden file or `.`/`..` path traversal component '\/\.', // config files '^\/config\/', // data files '^\/files\/', // tests files (should anyway not be present in dist) '^\/tests\/', // old-styles CLI tools (should anyway not be present in dist) '^\/tools\/', // `node_modules` and `vendor`, in GLPI root directory or in any plugin root directory '^(\/(marketplace|plugins)\/[^\/]+)?\/(node_modules|vendor)\/', ]; if (preg_match('/(' . implode('|', $excluded_path_patterns) . ')/i', $this->path) === 1) { return false; } // Check rules related to PHP files. // Check extension on path even if file not exists, to be able to send a 403 error even if file not exists. if ($this->isTargetAPhpScript()) { $glpi_path_patterns = [ // `/ajax/` scripts 'ajax\/', // `/front/` scripts 'front\/', // install/update scripts 'install\/(install|update)\.php$', // endpoints located on root directory '(apirest|apixmlrpc|caldav|index|status)\.php', ]; $plugins_path_patterns = [ // `/ajax/` scripts 'ajax\/', // `/front/` scripts 'front\/', // `/public/` scripts 'public\/', // Any `index.php` script '(.+\/)?index\.php', // PHP scripts located in root directory, except `setup.php` and `hook.php` '(?!setup\.php|hook\.php)[^\/]+\.php', // dynamic CSS and JS files '.+\.(css|js)\.php', // `reports` plugin specific URLs (used by many plugins) 'report\/', ]; $allowed_path_pattern = '/^' . '(' . sprintf('\/(%s)', implode('|', $glpi_path_patterns)) . '|' . sprintf('\/(marketplace|plugins)\/[^\/]+\/(%s)', implode('|', $plugins_path_patterns)) . ')' . '/'; return preg_match($allowed_path_pattern, $this->path) === 1; } // Check rules related to non-PHP files. $allowed_path_pattern = [ // files in `/public` directories '^(\/(marketplace|plugins)\/[^\/]+)?\/public\/', // static pages '\.html?$', // JS/CSS files '\.(js|css)$', // JS/CSS files sourcemaps used in dev env (it is to the publisher responsibility to remove them in dist packages) '\.(js|css)\.map$', // images '\.(gif|jpe?g|png|svg)$', // audios '\.(mp3|ogg|wav)$', // videos '\.(mp4|ogm|ogv|webm)$', // webfonts '\.(eot|otf|ttf|woff2?)$', // JSON files in plugins (except `composer.json`, `package.json` and `package-lock.json` located on root) '^\/(marketplace|plugins)\/[^\/]+\/(?!composer\.json|package\.json|package-lock\.json).+\.json$', // favicon '(^|\/)favicon\.ico$', ]; if (preg_match('/(' . implode('|', $allowed_path_pattern) . ')/i', $this->path) === 1) { return true; } return false; } /** * Check whether target is a PHP script. * * @return bool */ public function isTargetAPhpScript(): bool { // Check extension on path directly to be able to recognize that target is supposed to be a PHP // script even if it not exists. This is usefull to send most appropriate response code (i.e. 403 VS 404). if (preg_match('/^php\d*$/', pathinfo($this->path, PATHINFO_EXTENSION)) === 1) { return true; } return false; } /** * Serve the requested file. * * @return void */ public function proxify(): void { if ($this->isPathAllowed() === false) { http_response_code(403); return; } $target_file = $this->getTargetFile(); if ($target_file === null) { http_response_code(404); return; } if ($this->isTargetAPhpScript()) { // PHP specific case. // Requested file has to be included in global scope so is done in `/public/index.php` script. // Anyway, this check has to be kept to ensure that PHP scripts are not served as static files. http_response_code(500); // 500 error as this is a bug if such case happen return; } $extension = pathinfo($target_file, PATHINFO_EXTENSION); // Serve static files if web server is not configured to do it directly. $etag = md5_file($target_file); $last_modified = filemtime($target_file); $mime = null; switch ($extension) { case 'css': $mime = 'text/css'; break; case 'js': $mime = 'application/javascript'; break; case 'woff': $mime = 'font/woff'; break; case 'woff2': $mime = 'font/woff2'; break; default: $mime = mime_content_type($target_file); if ($mime === false) { $mime = 'application/octet-stream'; } break; } // HTTP_IF_NONE_MATCH takes precedence over HTTP_IF_MODIFIED_SINCE. // see http://tools.ietf.org/html/rfc7232#section-3.3 $is_not_modified = trim($_SERVER['HTTP_IF_NONE_MATCH'] ?? '') === $etag || @strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'] ?? '') >= $last_modified; http_response_code($is_not_modified ? 304 : 200); header(sprintf('Last-Modified: %s GMT', gmdate('D, d M Y H:i:s', $last_modified))); header(sprintf('Etag: %s', $etag)); header_remove('Pragma'); header('Cache-Control: public, max-age=2592000, must-revalidate'); // 30 days cache header(sprintf('Content-type: %s', $mime)); if ($is_not_modified) { // Do not send contents if not modified. return; } header(sprintf('Content-Length: %s', filesize($target_file))); readfile($target_file); } }