diff --git a/www/index.php b/www/index.php index e9c705a..52a72bd 100644 --- a/www/index.php +++ b/www/index.php @@ -1,6 +1,9 @@ init(); $URL_RULES = [ @@ -79,6 +82,8 @@ $URL_RULES = [ 'url' => ['404'], 'target' => 'pages/error_404.php', 'options' => [], ], ]; +$site->serve($URL_RULES); + //############################################################################# try { diff --git a/www/internals/pageframeoptions.php b/www/internals/pageframeoptions.php new file mode 100644 index 0000000..2d35753 --- /dev/null +++ b/www/internals/pageframeoptions.php @@ -0,0 +1,26 @@ +isProd()) + $requri = $_SERVER['REQUEST_URI']; + else + $requri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : 'localhost:80/'; + + $parse = parse_url($requri); + + $path = isset($parse['path']) ? $parse['path'] : ''; + $pathparts = preg_split('@/@', $path, NULL, PREG_SPLIT_NO_EMPTY); + $partcount = count($pathparts); + + foreach ($urlConfig as $rule) + { + $route = self::testRule($app, $rule, $requri, $pathparts, $partcount); + if ($route === null) continue; + + if ($app->getCurrentRights() >= $route->minimal_access_rights) return $route; + + if ($app->isLoggedIn()) return URLRoute::getInsufficentRightsRoute($requri); + + if (!$app->isLoggedIn()) return URLRoute::getLoginRoute($route, $requri); + } + + return URLRoute::getNotFoundRoute($requri); + } + + private static function testRule(Website $app, array $rule, string $requri, array $pathparts, int $partcount) + { + if ($partcount !== count($rule['url'])) return null; + + $urlparams = []; + + $match = true; + for($i = 0; $i < $partcount; $i++) + { + $comp = $rule['url'][$i]; + if (startsWith($comp, '?{') && endsWith($comp, '}')) + { + $ident = substr($comp, 2, strlen($comp)-3); + $urlparams[$ident] = $pathparts[$i]; + } + else if ($comp === '*') + { + if (!isset($urlparams['*'])) $urlparams['*'] = []; + $urlparams['*'] []= $pathparts[$i]; + } + else + { + if (strtolower($comp) !== strtolower($pathparts[$i])) { $match = false; break; } + } + } + if (!$match) return null; + + $route = new URLRoute($rule['target'], $requri); + + foreach($rule['parameter'] as $optname => $optvalue) + { + $value = $optvalue; + + if ($value === '%GET%') + { + if (!isset($_GET[$optname])) { $match = false; break; } + $value = $_GET[$optname]; + } + else if ($value === '%POST%') + { + if (!isset($_POST[$optname])) { $match = false; break; } + $value = $_POST[$optname]; + } + else if ($value === '%URL%') + { + if (!isset($urlparams[$optname])) { $match = false; break; } + $value = urldecode($urlparams[$optname]); + } + + $route->parameter[strtolower($optname)] = $value; + } + if (!$match) return null; + + $ctrlOpt = $rule['options']; + + if (in_array('disabled', $ctrlOpt)) return null; + if (in_array('api', $ctrlOpt)) $route->isAPI = true; + + if (isset($ctrlOpt['method']) && $_SERVER["REQUEST_METHOD"] !== $ctrlOpt['method']) return null; + + $route->minimal_access_rights = (($rule['rights']===null) ? 0 : $rule['rights']); + + if ($app->isProd() && $app->config->app_enforce_https && isHTTPRequest() && !in_array('http', $ctrlOpt)) + { + // enforce https + $redirect = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + ob_end_clean(); + header('HTTP/1.1 301 Moved Permanently'); + header('Location: ' . $redirect); + exit(); + } + + return $route; + } +} diff --git a/www/internals/urlroute.php b/www/internals/urlroute.php new file mode 100644 index 0000000..671b447 --- /dev/null +++ b/www/internals/urlroute.php @@ -0,0 +1,122 @@ +targetpath = __DIR__ . '/../pages/' . $target; + $this->full_url = $url; + $this->parameter = []; + $this->minimal_access_rights = 0; + $this->isAPI = false; + } + + /** + * @param VApp $app + * @return PageFrameOptions + */ + public function get(Website $app): PageFrameOptions + { + $pfo = new PageFrameOptions(); + + $pfo->title = $app->config->verein_kurzel . " Orga"; // default title + if ($this->isAPI) + { + $pfo->frame = 'no_frame.php'; + $pfo->contentType = 'application/json'; + } + + return $this->getDirect($app, $pfo); + } + + /** + * @param Website $app + * @param PageFrameOptions $pfo + * @return PageFrameOptions + */ + public function getDirect(Website $app, PageFrameOptions $pfo): PageFrameOptions + { + @ob_end_clean(); + ob_start(); + + global $ROUTE; + global $FRAME_OPTIONS; + global $APP; + $ROUTE = $this; + $FRAME_OPTIONS = $pfo; + $APP = $app; + + /** @noinspection PhpIncludeInspection */ + require $this->targetpath; + + $FRAME_OPTIONS->raw = ob_get_clean(); + + return $FRAME_OPTIONS; + } + + /** + * @param string $requri + * @return URLRoute + */ + public static function getInsufficentRightsRoute(string $requri): URLRoute + { + $r = new URLRoute('errors/insufficent_rights.php', $requri); + $r->parameter = []; + $r->minimal_access_rights = 0; + return $r; + } + + /** + * @param URLRoute $route + * @param string $requri + * @return URLRoute + */ + public static function getLoginRoute(URLRoute $route, string $requri): URLRoute + { + $r = new URLRoute('login.php', $requri); + $r->parameter = [ 'redirect' => $route->full_url ]; + $r->minimal_access_rights = 0; + return $r; + } + + /** + * @param string $requri + * @return URLRoute + */ + public static function getNotFoundRoute(string $requri): URLRoute + { + $r = new URLRoute('errors/not_found.php', $requri); + $r->parameter = []; + $r->minimal_access_rights = 0; + return $r; + } + + /** + * @param string $requri + * @return URLRoute + */ + public static function getServerErrorRoute(string $requri): URLRoute + { + $r = new URLRoute('errors/server_error.php', $requri); + $r->parameter = []; + $r->minimal_access_rights = 0; + return $r; + } +} \ No newline at end of file diff --git a/www/internals/base.php b/www/internals/utils.php similarity index 97% rename from www/internals/base.php rename to www/internals/utils.php index 52d2eee..bf8dbd7 100644 --- a/www/internals/base.php +++ b/www/internals/utils.php @@ -11,22 +11,6 @@ global $ADDITIONAL_STYLESHEETS; $ADDITIONAL_SCRIPTS = []; $ADDITIONAL_STYLESHEETS = []; -function InitPHP() { - - set_error_handler("exception_error_handler"); // errors as exceptions for global catch - - ob_start(); // buffer outpt so it can be discarded in httpError - -} - -function exception_error_handler($severity, $message, $file, $line) { - if (!(error_reporting() & $severity)) { - // This error code is not included in error_reporting - return; - } - throw new ErrorException($message, 0, $severity, $file, $line); -} - function startsWith($haystack, $needle) { $length = strlen($needle); diff --git a/www/internals/website.php b/www/internals/website.php new file mode 100644 index 0000000..5955de6 --- /dev/null +++ b/www/internals/website.php @@ -0,0 +1,176 @@ +config = require (__DIR__ . "/config.php"); + } + catch (exception $e) + { + $this->serveServerError("config.php not found", formatException($e), null); + } + + try + { + if (!$this->config['prod']) + { + ini_set('display_errors', 1); + ini_set('display_startup_errors', 1); + error_reporting(E_ALL); + } + + self::$instance = $this; + } + catch (exception $e) + { + $this->serveServerError("Initialization failed", formatException($e), null); + } + } + + public static function getInstance() + { + return self::$instance; + } + + public function serve($rules) + { + try + { + $route = RuleEngine::findRoute($this, $rules); + + $result = $route->get($this); + + if ($result->force_404) + { + $this->serveCustom404($route->full_url, $result); + exit(); + } + + if ($result->contentType !== null) header('Content-Type: ' . $result->contentType); + http_response_code($result->statuscode); + + $this->output($result, $route); + + exit(); + } + catch (Exception $e) + { + $this->serveServerError(null, formatException($e), null); + } + } + + private function serveCustom404(string $uri, PageFrameOptions $frameOpt) + { + try + { + @ob_end_clean(); + + $frameOpt->statuscode = 404; + $frameOpt->title = 'Page not found'; + + $route = URLRoute::getNotFoundRoute($uri); + + $result = $route->getDirect($this, $frameOpt); + + $this->output($result, $route); + } + catch (Exception $e) + { + $this->serveServerError(null, formatException($e), null); + } + + exit(); + } + + /** + * @param string|null $message + * @param string|null $debugInfo + * @param PageFrameOptions|null $frameOpt + */ + private function serveServerError($message, $debugInfo, $frameOpt) + { + try + { + @ob_end_clean(); + + if ($frameOpt === null) $frameOpt = new PageFrameOptions(); + + $frameOpt->statuscode = 500; + $frameOpt->title = 'Internal Server Error'; + $frameOpt->frame = 'error_frame.php'; + + $route = URLRoute::getServerErrorRoute($_SERVER['REQUEST_URI']); + + $route->parameter['message'] = $message; + $route->parameter['debuginfo'] = $debugInfo; + + $result = $route->getDirect($this, $frameOpt); + + $this->output($result, $route); + } + catch (Exception $e) + { + http_response_code(500); + die('Internal Server Error'); + } + + exit(); + } + + /** + * @param PageFrameOptions $pfo + * @param URLRoute $route + */ + private function output(PageFrameOptions $pfo, URLRoute $route) + { + if ($pfo->contentType !== null) header('Content-Type: ' . $pfo->contentType); + http_response_code($pfo->statuscode); + + global $ROUTE; + global $FRAME_OPTIONS; + global $APP; + $ROUTE = $route; + $FRAME_OPTIONS = $pfo; + $APP = $this; + + /** @noinspection PhpIncludeInspection */ + require __DIR__ . '/../pages/frame/' . $FRAME_OPTIONS->frame; + } + + /** + * @return bool + */ + public function isProd() + { + if ($this->config == null) return true; + return $this->config['prod']; + } +} + +/** + * @param $severity + * @param $message + * @param $file + * @param $line + * @throws ErrorException + */ +function exception_error_handler($severity, $message, $file, $line) { + // This error code is not included in error_reporting + if (!(error_reporting() & $severity)) return; + throw new ErrorException($message, 0, $severity, $file, $line); +} \ No newline at end of file