<?php require_once 'redirect.class.php'; /** * ---------------------------------------------------------------------- * _____ __ __ _ _____ _ _ _ * |_ _| \/ | | | __ \ | (_) | | * | | | \ / | | | |__) |___ __| |_ _ __ ___ ___| |_ * | | | |\/| | | | _ // _ \/ _` | | '__/ _ \/ __| __| * _| |_| | | | |____ | | \ \ __/ (_| | | | | __/ (__| |_ * |_____|_| |_|______| |_| \_\___|\__,_|_|_| \___|\___|\__| * * ---------------------------------------------------------------------- * Loads a config json from outside webroot and makes a 3xx redirect * if the definition exists * ---------------------------------------------------------------------- * 2020-05-11 v1.4 ah rewrite as class * 2022-02-03 v1.5 ah add method isEnabled * 2022-05-23 v1.6 ah add http head check+render output; * 2022-05-31 v1.7 ah optical changes * 2023-08-28 v1.8 ah remove php warning if there is no config yet * 2024-10-03 v1.9 ah php8 only: typed variables */ /** * Description of redirect * * @author axel */ class redirectadmin extends redirect { /** * Get default curl options * @return array */ protected function _getCurlOptions(): array { $aReturn = [ CURLOPT_HEADER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_USERAGENT => strip_tags($this->sAbout), CURLOPT_VERBOSE => false, CURLOPT_ENCODING => 'gzip, deflate', // to fetch encoding CURLOPT_HTTPHEADER => [ 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language: en', 'DNT: 1', ], // TODO: this is unsafe .. better: let the user configure it CURLOPT_SSL_VERIFYHOST => false, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_TIMEOUT => 5, ]; return $aReturn; } /** * Make a single http(s) get request and return the response body * @param string $url url to fetch * @param boolean $bHeaderOnly optional: true=make HEAD request; default: false (=GET) * @return string */ public function httpGet(string $url, bool $bHeaderOnly = false): bool|string { $ch = curl_init($url); foreach ($this->_getCurlOptions() as $sCurlOption => $sCurlValue) { curl_setopt($ch, $sCurlOption, $sCurlValue); } if ($bHeaderOnly) { curl_setopt($ch, CURLOPT_HEADER, 1); curl_setopt($ch, CURLOPT_NOBODY, 1); } $res = curl_exec($ch); curl_close($ch); return ($res); } /** * Get html code for a response header of a request * * @param string $sHeader * @return string */ public function renderHttpResponseHeader(string $sHeader): string { $sReturn = $sHeader; if (!$sReturn) { $sReturn = '<pre><span class="status status-error">Request failed. </span><br>' . 'No data... no response.<br>' . 'Maybe ... ' . '<ul>' . '<li>the nostname does not exist ... </li>' . '<li>or there is a network problem ... or </li>' . '<li>the webservice on the target system does not run.</li>' . '</ul>' . '</pre>' ; } else { $sReturn = preg_replace('/(HTTP.*)\\r/', '</pre><pre><strong>$1</strong>', $sReturn); $sReturn = preg_replace('/(HTTP.*200.*)/', '<span class="status status-ok">$1</span>', $sReturn); $sReturn = preg_replace('/(HTTP.*30.*)/', '<span class="status status-redirect">$1</span>', $sReturn); $sReturn = preg_replace('/(HTTP.*40.*)/', '<span class="status status-error">$1</span>', $sReturn); $sReturn = preg_replace('/(HTTP.*50.*)/', '<span class="status status-error">$1</span>', $sReturn); $sReturn = preg_replace('/\ ([1-5][0-9][0-9])\ /', ' <span class="statuscode">$1</span> ', $sReturn); $sReturn = preg_replace('/(x-debug-.*)\\r/i', '<span class="debug">$1</span>', $sReturn); $sReturn = preg_replace('/(location:.*)\\r/i', '<span class="location">$1</span>', $sReturn); $sReturn .= '</pre>'; preg_match_all('/(HTTP\/.*)/i', $sReturn, $aTmp); // $sReturn.=print_r($aTmp, 1); $iHops = (count($aTmp[0]) - 1); $sReturn = ($iHops > 0 ? 'Found hops: <strong>' . $iHops . '</strong>' . ($iHops > 1 ? ' <span class="warning"> ⚠️ Verify your redirect to skip unneeded hops.</span>' : '') . '<br><br>' : '' ) . $sReturn ; } return $sReturn; } /** * Check if admin is enabled * * @return bool */ public function isEnabled(): bool { $sFile2Enable = __DIR__ . '/' . basename(__FILE__) . '_enabled.txt'; return file_exists($sFile2Enable); } /** * Get ip address of a given hostname as ip (string) or false * @return string|bool */ protected function _getIp(string $sHostname): string { $sIp = gethostbyname($sHostname); return $sIp === $sHostname ? '' : $sIp; } /** * Get an array with all config entries in all json files * including some error checking * * @return array */ public function getHosts(): array { $aReturn = []; $aErrors = []; foreach (glob($this->sConfigDir . '/redirects_*.json') as $sFilename) { $sMyHost = str_replace(['redirects_', '.json'], ['', ''], basename($sFilename)); $aReturn[$sMyHost] = [ 'type' => 'config', 'file' => $sFilename, 'ip' => $this->_getIp($sMyHost), 'aliases' => [], 'redirects' => json_decode(file_get_contents($sFilename), 1), ]; if (!$aReturn[$sMyHost]['ip']) { $aErrors[] = basename($sFilename) . ': The hostname was not found in DNS: ' . $sMyHost; } } $aAliases = $this->_getAliases(); if (is_array($aAliases) && count($aAliases)) { foreach ($aAliases as $sAlias => $sConfig) { if (isset($aReturn[$sAlias])) { $aErrors[] = "alias.json: A configuration for alias [$sAlias] is useless. There exists a file redirects_{$sAlias}.json (which has priority)."; } else { if (!isset($aReturn[$sConfig])) { $aErrors[] = "alias.json: [$sAlias] points to a non existing host [$sConfig] - a file redirects_$sConfig.yml does not exist."; } else { $aReturn[$sConfig]['aliases'][] = $sAlias; $aReturn[$sAlias] = [ 'type' => 'alias', 'target' => $sConfig, 'ip' => $this->_getIp($sAlias), ]; if (!$aReturn[$sAlias]['ip']) { $aErrors[] = 'alias.json: The hostname was not found in DNS: ' . $sAlias; } } } } } $aReturn['_errors'] = $aErrors; ksort($aReturn); return $aReturn; } }