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}