xref: /template/mikio/css.php (revision 7261b2131a163c1c97bf9ea41b42faae0d771c9e)
1*7261b213SJames Collins<?php
2ab45ba71SJames Collins/**
3a8eebd82SJames Collins * Mikio CSS/LESS Engine
4ab45ba71SJames Collins *
5ab45ba71SJames Collins * @link    http://dokuwiki.org/template:mikio
6*7261b213SJames Collins * @license GPLv2
7*7261b213SJames Collins * @author  James Collins
8ab45ba71SJames Collins */
9d24f6ec2SJames Collins
10*7261b213SJames Collinsrequire(__DIR__ . '/inc/polyfill-ctype.php');
11692c64c6SJames Collins
127a37170aSJames Collinsif (!class_exists('lessc')) {
13*7261b213SJames Collins    require(__DIR__ . '/inc/stemmechanics/lesserphp/lessc.inc.php');
147a37170aSJames Collins}
157a37170aSJames Collins
16*7261b213SJames Collinsfunction logInvalidRequest($reason, $input) {
17*7261b213SJames Collins    error_log("[css.php] $reason | input: $input | IP: " . ($_SERVER['REMOTE_ADDR'] ?? 'unknown'));
18fd3ef33aSJames Collins}
19fd3ef33aSJames Collins
20ab45ba71SJames Collinstry {
21*7261b213SJames Collins    if (!isset($_GET['css'])) {
22*7261b213SJames Collins        http_response_code(404);
23*7261b213SJames Collins        echo "The requested file could not be found";
24*7261b213SJames Collins        exit;
258ddabb4eSJames Collins    }
26ab45ba71SJames Collins
27*7261b213SJames Collins    $cssFileList = explode(',', $_GET['css']);
28*7261b213SJames Collins    $pluginRoot = realpath(__DIR__ . '/');
29*7261b213SJames Collins    $allowedDirs = [
30*7261b213SJames Collins        realpath($pluginRoot . '/assets'),
31*7261b213SJames Collins        realpath($pluginRoot . '/styles')
32*7261b213SJames Collins    ];
33*7261b213SJames Collins    $allowedExtensions = ['css', 'less'];
34*7261b213SJames Collins    $css = '';
35*7261b213SJames Collins    $failed = false;
36*7261b213SJames Collins
37*7261b213SJames Collins    foreach ($cssFileList as $rawInput) {
38*7261b213SJames Collins        // Strip query/hash
39*7261b213SJames Collins        $cleanInput = explode('?', $rawInput, 2)[0];
40*7261b213SJames Collins        $cleanInput = trim(str_replace(['..', '\\'], '', $cleanInput));
41*7261b213SJames Collins        if (empty($cleanInput)) {
42*7261b213SJames Collins            $failed = true;
43*7261b213SJames Collins            logInvalidRequest("Empty or invalid path", $rawInput);
44*7261b213SJames Collins            continue;
458ddabb4eSJames Collins        }
46ab45ba71SJames Collins
47*7261b213SJames Collins        $resolvedPath = realpath($pluginRoot . '/' . $cleanInput);
48*7261b213SJames Collins        $ext = pathinfo($resolvedPath, PATHINFO_EXTENSION);
49*7261b213SJames Collins
50*7261b213SJames Collins        if (
51*7261b213SJames Collins            !$resolvedPath ||
52*7261b213SJames Collins            !file_exists($resolvedPath) ||
53*7261b213SJames Collins            !in_array($ext, $allowedExtensions, true)
54*7261b213SJames Collins        ) {
55*7261b213SJames Collins            $failed = true;
56*7261b213SJames Collins            logInvalidRequest("Invalid file or extension", $rawInput);
57*7261b213SJames Collins            continue;
588ddabb4eSJames Collins        }
59692c64c6SJames Collins
60*7261b213SJames Collins        // Confirm file is within allowed directories
61*7261b213SJames Collins        $allowed = false;
62*7261b213SJames Collins        foreach ($allowedDirs as $dir) {
63*7261b213SJames Collins            if (strpos($resolvedPath, $dir) === 0) {
64*7261b213SJames Collins                $allowed = true;
65*7261b213SJames Collins                break;
66*7261b213SJames Collins            }
67*7261b213SJames Collins        }
68*7261b213SJames Collins
69*7261b213SJames Collins        if (!$allowed) {
70*7261b213SJames Collins            $failed = true;
71*7261b213SJames Collins            logInvalidRequest("File outside allowed directory", $rawInput);
72*7261b213SJames Collins            continue;
73*7261b213SJames Collins        }
74*7261b213SJames Collins
75*7261b213SJames Collins        $css .= file_get_contents($resolvedPath);
76*7261b213SJames Collins    }
77*7261b213SJames Collins
78*7261b213SJames Collins    if ($failed) {
79*7261b213SJames Collins        http_response_code(404);
80*7261b213SJames Collins        echo "The requested file could not be found";
81*7261b213SJames Collins        exit;
82*7261b213SJames Collins    }
83ab45ba71SJames Collins
84e876e764SJames Collins    header('Content-Type: text/css; charset=utf-8');
85e876e764SJames Collins
86ab45ba71SJames Collins    $less = new lessc();
87ab45ba71SJames Collins    $less->setPreserveComments(false);
88a8eebd82SJames Collins
89*7261b213SJames Collins    // Optional variables (future-proofed)
90*7261b213SJames Collins    $rawVars = [];
91*7261b213SJames Collins    $vars = [];
92a8eebd82SJames Collins    if (isset($rawVars['replacements'])) {
93a8eebd82SJames Collins        foreach ($rawVars['replacements'] as $key => $val) {
94*7261b213SJames Collins            if (strpos($key, '__') === 0 && substr($key, -2) === '__') {
95a8eebd82SJames Collins                $vars['ini_' . substr($key, 2, -2)] = $val;
96a8eebd82SJames Collins            }
97a8eebd82SJames Collins        }
98a8eebd82SJames Collins    }
99a8eebd82SJames Collins
100*7261b213SJames Collins    if (!empty($vars)) {
101a8eebd82SJames Collins        $less->setVariables($vars);
102a8eebd82SJames Collins    }
103a8eebd82SJames Collins
104*7261b213SJames Collins    echo $less->compile($css);
105*7261b213SJames Collins
106*7261b213SJames Collins} catch (Exception $e) {
107*7261b213SJames Collins    error_log("[css.php] Exception: " . $e->getMessage());
108*7261b213SJames Collins    http_response_code(500);
109692c64c6SJames Collins    header('Content-Type: text/css; charset=utf-8');
110*7261b213SJames Collins    echo "/* An error occurred while processing the CSS. */";
1118ddabb4eSJames Collins}