xref: /plugin/mikioplugin/css.php (revision 12a6b3a2e20c767c8e9fce98557f307860e4e355)
12393fff5SJames Collins<?php
22393fff5SJames Collins/**
33cffc6f4SJames Collins * Mikio CSS/LESS Engine
42393fff5SJames Collins *
53cffc6f4SJames Collins * @link    http://dokuwiki.org/template:mikio
6*12a6b3a2SJames Collins * @license GPLv2
7*12a6b3a2SJames Collins * @author  James Collins
82393fff5SJames Collins */
92393fff5SJames Collins
10*12a6b3a2SJames Collinsrequire(__DIR__ . '/inc/polyfill-ctype.php');
113cffc6f4SJames Collins
124afa967aSJames Collinsif (!class_exists('lessc')) {
13*12a6b3a2SJames Collins    require(__DIR__ . '/inc/stemmechanics/lesserphp/lessc.inc.php');
144afa967aSJames Collins}
154afa967aSJames Collins
16*12a6b3a2SJames Collinsfunction logInvalidRequest($reason, $input) {
17*12a6b3a2SJames Collins    error_log("[css.php] $reason | input: $input | IP: " . ($_SERVER['REMOTE_ADDR'] ?? 'unknown'));
1817dbafb6SJames Collins}
1917dbafb6SJames Collins
202393fff5SJames Collinstry {
21*12a6b3a2SJames Collins    if (!isset($_GET['css'])) {
22*12a6b3a2SJames Collins        http_response_code(404);
23*12a6b3a2SJames Collins        echo "The requested file could not be found";
24*12a6b3a2SJames Collins        exit;
25*12a6b3a2SJames Collins    }
26*12a6b3a2SJames Collins
27*12a6b3a2SJames Collins    $cssFileList = explode(',', $_GET['css']);
28*12a6b3a2SJames Collins    $pluginRoot = realpath(__DIR__ . '/');
29*12a6b3a2SJames Collins    $allowedDirs = [
30*12a6b3a2SJames Collins        realpath($pluginRoot . '/assets'),
31*12a6b3a2SJames Collins        realpath($pluginRoot . '/styles')
32*12a6b3a2SJames Collins    ];
33*12a6b3a2SJames Collins    $allowedExtensions = ['css', 'less'];
343cffc6f4SJames Collins    $css = '';
35*12a6b3a2SJames Collins    $failed = false;
363cffc6f4SJames Collins
37*12a6b3a2SJames Collins    foreach ($cssFileList as $rawInput) {
38*12a6b3a2SJames Collins        // Strip query/hash
39*12a6b3a2SJames Collins        $cleanInput = explode('?', $rawInput, 2)[0];
40*12a6b3a2SJames Collins        $cleanInput = trim(str_replace(['..', '\\'], '', $cleanInput));
41*12a6b3a2SJames Collins        if (empty($cleanInput)) {
423cffc6f4SJames Collins            $failed = true;
43*12a6b3a2SJames Collins            logInvalidRequest("Empty or invalid path", $rawInput);
44*12a6b3a2SJames Collins            continue;
45*12a6b3a2SJames Collins        }
46*12a6b3a2SJames Collins
47*12a6b3a2SJames Collins        $resolvedPath = realpath($pluginRoot . '/' . $cleanInput);
48*12a6b3a2SJames Collins        $ext = pathinfo($resolvedPath, PATHINFO_EXTENSION);
49*12a6b3a2SJames Collins
50*12a6b3a2SJames Collins        if (
51*12a6b3a2SJames Collins            !$resolvedPath ||
52*12a6b3a2SJames Collins            !file_exists($resolvedPath) ||
53*12a6b3a2SJames Collins            !in_array($ext, $allowedExtensions, true)
54*12a6b3a2SJames Collins        ) {
55*12a6b3a2SJames Collins            $failed = true;
56*12a6b3a2SJames Collins            logInvalidRequest("Invalid file or extension", $rawInput);
57*12a6b3a2SJames Collins            continue;
58*12a6b3a2SJames Collins        }
59*12a6b3a2SJames Collins
60*12a6b3a2SJames Collins        // Confirm file is within allowed directories
61*12a6b3a2SJames Collins        $allowed = false;
62*12a6b3a2SJames Collins        foreach ($allowedDirs as $dir) {
63*12a6b3a2SJames Collins            if (strpos($resolvedPath, $dir) === 0) {
64*12a6b3a2SJames Collins                $allowed = true;
65*12a6b3a2SJames Collins                break;
66518c05ebSJames Collins            }
67518c05ebSJames Collins        }
682393fff5SJames Collins
69*12a6b3a2SJames Collins        if (!$allowed) {
70*12a6b3a2SJames Collins            $failed = true;
71*12a6b3a2SJames Collins            logInvalidRequest("File outside allowed directory", $rawInput);
72*12a6b3a2SJames Collins            continue;
73*12a6b3a2SJames Collins        }
74*12a6b3a2SJames Collins
75*12a6b3a2SJames Collins        $css .= file_get_contents($resolvedPath);
76*12a6b3a2SJames Collins    }
77*12a6b3a2SJames Collins
78*12a6b3a2SJames Collins    if ($failed) {
79*12a6b3a2SJames Collins        http_response_code(404);
80*12a6b3a2SJames Collins        echo "The requested file could not be found";
81*12a6b3a2SJames Collins        exit;
82*12a6b3a2SJames Collins    }
833cffc6f4SJames Collins
842393fff5SJames Collins    header('Content-Type: text/css; charset=utf-8');
852393fff5SJames Collins
862393fff5SJames Collins    $less = new lessc();
872393fff5SJames Collins    $less->setPreserveComments(false);
882393fff5SJames Collins
89*12a6b3a2SJames Collins    // Optional variables (future-proofed)
90*12a6b3a2SJames Collins    $rawVars = [];
91*12a6b3a2SJames Collins    $vars = [];
923cffc6f4SJames Collins    if (isset($rawVars['replacements'])) {
933cffc6f4SJames Collins        foreach ($rawVars['replacements'] as $key => $val) {
94*12a6b3a2SJames Collins            if (strpos($key, '__') === 0 && substr($key, -2) === '__') {
953cffc6f4SJames Collins                $vars['ini_' . substr($key, 2, -2)] = $val;
963cffc6f4SJames Collins            }
973cffc6f4SJames Collins        }
983cffc6f4SJames Collins    }
993cffc6f4SJames Collins
100*12a6b3a2SJames Collins    if (!empty($vars)) {
1013cffc6f4SJames Collins        $less->setVariables($vars);
1023cffc6f4SJames Collins    }
1033cffc6f4SJames Collins
104*12a6b3a2SJames Collins    echo $less->compile($css);
105*12a6b3a2SJames Collins
106*12a6b3a2SJames Collins} catch (Exception $e) {
107*12a6b3a2SJames Collins    error_log("[css.php] Exception: " . $e->getMessage());
108*12a6b3a2SJames Collins    http_response_code(500);
1093cffc6f4SJames Collins    header('Content-Type: text/css; charset=utf-8');
110*12a6b3a2SJames Collins    echo "/* An error occurred while processing the CSS. */";
1112393fff5SJames Collins}