app/classes/ReleaseInsights/Request.php (101 lines of code) (raw):

<?php declare(strict_types=1); namespace ReleaseInsights; class Request { public string $request = '/'; public string $path = '/'; public ?string $query = null; public bool $invalid_slashes = true; public function __construct(string $path) { $request = parse_url($path); // Paths that start with multiple slashes don't have a correct (or any) 'path' field via parse_url() if (str_starts_with($path, '//')) { $this->invalid_slashes = true; } // Real files are not processes as paths to route if ($request !== false) { // We sometimes use a fake query on a static asset to force the browser to refresh the cache, // we take the query out when checking if the file path exists if (file_exists($_SERVER['DOCUMENT_ROOT'] . explode('?', $path)[0])) { $this->invalid_slashes = false; $this->path = explode('?', $path)[0]; } else { // We have a real path to route and clean up before usage $this->request = $path; if (isset($request['path'])) { $this->path = $this->cleanPath($request['path']); } if (isset($request['query'])) { $this->query = $request['query']; } if (isset($request['path'])) { if (str_ends_with($request['path'], '//')) { // Multiple slashes at the end of the path $this->invalid_slashes = true; } elseif (! str_ends_with($request['path'], '/')) { // Missing slash at the end of the path $this->invalid_slashes = true; } else { $this->invalid_slashes = false; } } } } } /** * Load the controller file * @codeCoverageIgnore */ public function loadController(): void { include CONTROLLERS . $this->getController() . '.php'; } /** * Return the name of the controller file for the requested URL * If the path is unknown, we send a 404 response */ public function getController(): string { return match ($this->path) { '/' => 'homepage', '/about/' => 'about', '/beta/' => 'beta', '/nightly/' => 'nightly', '/release/' => 'release', '/api/beta/crashes/' => 'api/beta_crashes', '/api/firefox/chemspills/' => 'api/chemspill_releases', '/api/external/' => 'api/external', '/api/nightly/' => 'api/nightly', '/api/nightly/crashes/' => 'api/nightly_crashes', '/api/release/schedule/' => 'api/release_schedule', '/api/esr/releases/' => 'api/esr_releases', '/api/firefox/releases/' => 'api/firefox_releases', '/api/firefox/releases/esr/' => 'api/esr_release_pairs', '/api/firefox/releases/future/' => 'api/firefox_releases_future', '/api/firefox/calendar/future/' => 'api/future_calendar', '/api/release/owners/' => 'api/release_owners', '/api/wellness/days/' => 'api/wellness_days', '/calendar/' => 'calendar', '/calendar/monthly/' => 'calendar_monthly', '/calendar/release/schedule/' => 'ics_release_schedule', '/release/owners/' => 'release_owners', '/rss/' => 'rss', '/sitemap/' => 'sitemap', default => '404', }; } /** * Normalize path before comparing the string to a list of valid paths */ public function cleanPath(string $path): string { if ($path == '/' || $path == '//') { return '/'; } $path = explode('/', $path); $path = array_filter($path); // Remove empty items $path = array_values($path); // Reorder keys return '/' . implode('/', $path) . '/'; } /** * Display a static page. * Send all the necessary headers to bypass http server caching * @codeCoverageIgnore */ public static function waitingPage(string $action): void { if ($action == 'load') { // This is a long-running process when we fetch and generate data set_time_limit(0); // We need to set the encoding or the browser will wait to parse the content header('Content-type: text/html; charset=utf-8'); // Explicitly disable caching so Varnish and other upstreams won't cache. header("Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"); // Emulate the header BigPipe sends so we can test through Varnish. header('Surrogate-Control: BigPipe/1.0'); // Disable gzip compression to allow sending a chunk of html header('Content-Encoding: none'); // Setting this header instructs Nginx to disable fastcgi_buffering and disable gzip for this request. header('X-Accel-Buffering: no'); // Display a waiting page while we process data. This file contains the flushing logic. include VIEWS . 'waiting_page.html.php'; } elseif ($action == 'leave') { // heavy processing is done, let the browser refresh the page echo '<meta http-equiv="refresh" content="0">'; exit; } elseif ($action == 'hide') { // heavy processing is done, let the browser refresh the page echo '<style nonce="' . NONCE . '">.waitingpage .container { display:none; }</style>'; } } }