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