aneamal/main.php
<?php
/* Copyright 2010-2024 Martin Janecke <martin@aneamal.org>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
// declare (strict_types = 1); // only during development
namespace prlbr\aneamal;
// error_reporting (E_ALL); // only during development
// Constants
const vmain = '31';
const CACHE_READ = 1;
const CACHE_FEED = 2;
const CACHE_DROP = 4;
// filename
$query = $_SERVER['QUERY_STRING'];
$file = __DIR__ . '/..' . $query;
$base = basename ($query);
header ('Content-Type: text/html; charset=UTF-8');
// Restrict further processing to readable .nml files. The file extentsion check
// prevents sensitive data such as in .htaccess or .php from becoming public.
if (!str_ends_with ($base, '.nml') or !is_readable ($file)):
header ('HTTP/1.1 404 Not Found');
print <<<ANT
<!doctype html>
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
ANT;
exit;
endif;
// Aneamal file names with an initial @-symbol are for automatic inclusion in
// other pages and not made to be accessed directly, hence the 403 status. They
// are still processed normally when accessed directly for trouble shooting.
if ($forbidden = $base[0] === '@'):
header ('HTTP/1.1 403 Forbidden');
endif;
// QUERY_HASH is used for caching here, func.php, maybe modules. Do not change.
$hash = md5 ($query);
$hash[2] = '/';
define (__NAMESPACE__ . '\\QUERY_HASH', $hash);
// Determine the caching mode. AneamalCache 'on' means use cache except for
// HTTP POST requests; 'forced' uses the cache independent of HTTP method; an
// integer is like 'on' with a maximum usability period given in seconds.
$mode = 0;
if ($rule = getenv ('AneamalCache')):
$rule = strtolower ($rule);
$cache = __DIR__ . '/private/cache/' . QUERY_HASH . '.html';
if ($_SERVER['REQUEST_METHOD'] === 'POST' and $rule !== 'forced'):
file_exists ($cache) and $mode = CACHE_DROP;
elseif ($life = (int) $rule or $rule === 'on' or $rule === 'forced'):
if ($time = @filemtime ($cache)): // checks existence and time
$mode = $life && time () > $time + $life || filemtime ($file) >= $time? CACHE_FEED: CACHE_READ;
else:
$mode = CACHE_FEED;
endif;
endif;
endif;
// Get and output the page from the cache.
if ($mode === CACHE_READ):
// Implements https://tools.ietf.org/html/rfc7232#section-2.2
header ('Last-Modified: ' . gmdate ('D, d M Y H:i:s', $time) . ' GMT');
// Implements https://tools.ietf.org/html/rfc7232#section-3.3
if (
isset ($_SERVER['HTTP_IF_MODIFIED_SINCE'])
and $_SERVER['REQUEST_METHOD'] === 'GET' || $_SERVER['REQUEST_METHOD'] === 'HEAD'
and @strtotime ($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $time
and !$forbidden
):
header ('HTTP/1.1 304 Not Modified');
exit;
elseif (@readfile ($cache)):
exit;
endif;
endif;
// start timing of page generation
$start = microtime (true);
// include the class file which does the Aneamal-HTML-translation
require __DIR__ . '/html.php';
// get the Aneamal root directory relative to the document root; the 17
// removed characters from the SCRIPT_NAME are "/aneamal/main.php"
$home = getenv ('AneamalHome') ?: substr ($_SERVER['SCRIPT_NAME'], 0, -17);
// create an object doing the Aneamal-HTML-translation and pass the
// Aneamal file contents to it
$translator = new nml2html (file_get_contents ($file), dirname ($query), $home, $base);
$html = $translator->document ();
// stop timing
$duration = microtime (true) - $start;
// print the HTML page
print $html . '<!--' . strval ($duration) . '-->';
// write the file to the cache, create directory if necessary
if ($mode === CACHE_FEED):
if (@file_put_contents ($cache, $html . '<!--' . QUERY_HASH . '-from-cache-->')):
print '<!--written-to-cache-->';
elseif (is_dir ($dir = dirname ($cache)) or !@mkdir ($dir, 0777, true)):
print '<!--failed-to-cache-->';
elseif (@file_put_contents ($cache, $html . '<!--' . QUERY_HASH . '-from-cache-->')):
print '<!--written-to-cache-->';
else:
print '<!--failed-to-cache-->';
endif;
elseif ($mode === CACHE_DROP):
touch ($cache, 0);
endif;
// The End