Merge pull request #8078 from nextcloud/theming-validate-scss
SCSS hardening
This commit is contained in:
commit
4fcdfbf5de
|
@ -91,18 +91,20 @@
|
|||
}
|
||||
|
||||
/* override styles for login screen in guest.css */
|
||||
#header .logo {
|
||||
background-image: url(#{$image-logo});
|
||||
@if $theming-logo-mime != '' {
|
||||
@if variable_exists('theming-logo-mime') {
|
||||
#header .logo {
|
||||
background-image: url(#{$image-logo});
|
||||
background-size: contain;
|
||||
}
|
||||
}
|
||||
|
||||
#body-login,
|
||||
#firstrunwizard .firstrunwizard-header,
|
||||
#theming-preview {
|
||||
background-image: url(#{$image-login-background});
|
||||
background-color: $color-primary;
|
||||
@if variable_exists('theming-background-mime') {
|
||||
#body-login,
|
||||
#firstrunwizard .firstrunwizard-header,
|
||||
#theming-preview {
|
||||
background-image: url(#{$image-login-background});
|
||||
background-color: $color-primary;
|
||||
}
|
||||
}
|
||||
|
||||
input.primary,
|
||||
|
|
|
@ -242,8 +242,8 @@ class ThemingDefaults extends \OC_Defaults {
|
|||
'theming-background-mime' => "'" . $this->config->getAppValue('theming', 'backgroundMime', '') . "'"
|
||||
];
|
||||
|
||||
$variables['image-logo'] = "'".$this->urlGenerator->getAbsoluteURL($this->getLogo())."'";
|
||||
$variables['image-login-background'] = "'".$this->urlGenerator->getAbsoluteURL($this->getBackground())."'";
|
||||
$variables['image-logo'] = "'".$this->getLogo()."'";
|
||||
$variables['image-login-background'] = "'".$this->getBackground()."'";
|
||||
$variables['image-login-plain'] = 'false';
|
||||
|
||||
if ($this->config->getAppValue('theming', 'color', null) !== null) {
|
||||
|
|
|
@ -517,18 +517,12 @@ class ThemingDefaultsTest extends TestCase {
|
|||
['theming.Theming.getLoginBackground', [], 'custom-background'],
|
||||
]);
|
||||
|
||||
$this->urlGenerator->expects($this->exactly(2))
|
||||
->method('getAbsoluteURL')
|
||||
->willReturnCallback(function ($path) {
|
||||
return 'absolute-' . $path;
|
||||
});
|
||||
|
||||
$expected = [
|
||||
'theming-cachebuster' => '\'0\'',
|
||||
'theming-logo-mime' => '\'jpeg\'',
|
||||
'theming-background-mime' => '\'jpeg\'',
|
||||
'image-logo' => "'absolute-custom-logo?v=0'",
|
||||
'image-login-background' => "'absolute-custom-background?v=0'",
|
||||
'image-logo' => "'custom-logo?v=0'",
|
||||
'image-login-background' => "'custom-background?v=0'",
|
||||
'color-primary' => $this->defaults->getColorPrimary(),
|
||||
'color-primary-text' => '#ffffff',
|
||||
'image-login-plain' => 'false',
|
||||
|
|
|
@ -57,12 +57,18 @@ class SCSSCacher {
|
|||
/** @var IConfig */
|
||||
protected $config;
|
||||
|
||||
/** @var \OC_Defaults */
|
||||
private $defaults;
|
||||
|
||||
/** @var string */
|
||||
protected $serverRoot;
|
||||
|
||||
/** @var ICache */
|
||||
protected $depsCache;
|
||||
|
||||
/** @var null|string */
|
||||
private $injectedVariables;
|
||||
|
||||
/**
|
||||
* @param ILogger $logger
|
||||
* @param Factory $appDataFactory
|
||||
|
@ -90,12 +96,14 @@ class SCSSCacher {
|
|||
|
||||
/**
|
||||
* Process the caching process if needed
|
||||
*
|
||||
* @param string $root Root path to the nextcloud installation
|
||||
* @param string $file
|
||||
* @param string $app The app name
|
||||
* @return boolean
|
||||
* @throws NotPermittedException
|
||||
*/
|
||||
public function process($root, $file, $app) {
|
||||
public function process(string $root, string $file, string $app): bool {
|
||||
$path = explode('/', $root . '/' . $file);
|
||||
|
||||
$fileNameSCSS = array_pop($path);
|
||||
|
@ -123,7 +131,7 @@ class SCSSCacher {
|
|||
* @param $fileName
|
||||
* @return ISimpleFile
|
||||
*/
|
||||
public function getCachedCSS($appName, $fileName) {
|
||||
public function getCachedCSS(string $appName, string $fileName): ISimpleFile {
|
||||
$folder = $this->appData->getFolder($appName);
|
||||
return $folder->getFile($this->prependBaseurlPrefix($fileName));
|
||||
}
|
||||
|
@ -134,7 +142,7 @@ class SCSSCacher {
|
|||
* @param ISimpleFolder $folder
|
||||
* @return boolean
|
||||
*/
|
||||
private function isCached($fileNameCSS, ISimpleFolder $folder) {
|
||||
private function isCached(string $fileNameCSS, ISimpleFolder $folder) {
|
||||
try {
|
||||
$cachedFile = $folder->getFile($fileNameCSS);
|
||||
if ($cachedFile->getSize() > 0) {
|
||||
|
@ -148,13 +156,14 @@ class SCSSCacher {
|
|||
}
|
||||
$deps = json_decode($deps, true);
|
||||
|
||||
foreach ($deps as $file=>$mtime) {
|
||||
foreach ((array)$deps as $file=>$mtime) {
|
||||
if (!file_exists($file) || filemtime($file) > $mtime) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
} catch(NotFoundException $e) {
|
||||
return false;
|
||||
}
|
||||
|
@ -164,7 +173,7 @@ class SCSSCacher {
|
|||
* Check if the variables file has changed
|
||||
* @return bool
|
||||
*/
|
||||
private function variablesChanged() {
|
||||
private function variablesChanged(): bool {
|
||||
$injectedVariables = $this->getInjectedVariables();
|
||||
if($this->config->getAppValue('core', 'scss.variables') !== md5($injectedVariables)) {
|
||||
$this->resetCache();
|
||||
|
@ -176,14 +185,16 @@ class SCSSCacher {
|
|||
|
||||
/**
|
||||
* Cache the file with AppData
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $fileNameCSS
|
||||
* @param string $fileNameSCSS
|
||||
* @param ISimpleFolder $folder
|
||||
* @param string $webDir
|
||||
* @return boolean
|
||||
* @throws NotPermittedException
|
||||
*/
|
||||
private function cache($path, $fileNameCSS, $fileNameSCSS, ISimpleFolder $folder, $webDir) {
|
||||
private function cache(string $path, string $fileNameCSS, string $fileNameSCSS, ISimpleFolder $folder, string $webDir) {
|
||||
$scss = new Compiler();
|
||||
$scss->setImportPaths([
|
||||
$path,
|
||||
|
@ -250,13 +261,14 @@ class SCSSCacher {
|
|||
* We need to regenerate all files when variables change
|
||||
*/
|
||||
private function resetCache() {
|
||||
$this->injectedVariables = null;
|
||||
$appDirectory = $this->appData->getDirectoryListing();
|
||||
if(empty($appDirectory)){
|
||||
return;
|
||||
}
|
||||
foreach ($appDirectory as $folder) {
|
||||
foreach ($folder->getDirectoryListing() as $file) {
|
||||
if (substr($file->getName(), -3) === "css" || substr($file->getName(), -4) === "deps") {
|
||||
if (substr($file->getName(), -3) === 'css' || substr($file->getName(), -4) === 'deps') {
|
||||
$file->delete();
|
||||
}
|
||||
}
|
||||
|
@ -266,11 +278,24 @@ class SCSSCacher {
|
|||
/**
|
||||
* @return string SCSS code for variables from OC_Defaults
|
||||
*/
|
||||
private function getInjectedVariables() {
|
||||
private function getInjectedVariables(): string {
|
||||
if ($this->injectedVariables !== null) {
|
||||
return $this->injectedVariables;
|
||||
}
|
||||
$variables = '';
|
||||
foreach ($this->defaults->getScssVariables() as $key => $value) {
|
||||
$variables .= '$' . $key . ': ' . $value . ';';
|
||||
}
|
||||
|
||||
// check for valid variables / otherwise fall back to defaults
|
||||
try {
|
||||
$scss = new Compiler();
|
||||
$scss->compile($variables);
|
||||
$this->injectedVariables = $variables;
|
||||
} catch (ParserException $e) {
|
||||
$this->logger->error($e, ['app' => 'core']);
|
||||
}
|
||||
|
||||
return $variables;
|
||||
}
|
||||
|
||||
|
@ -280,8 +305,8 @@ class SCSSCacher {
|
|||
* @param string $webDir
|
||||
* @return string
|
||||
*/
|
||||
private function rebaseUrls($css, $webDir) {
|
||||
$re = '/url\([\'"]([\.\w?=\/-]*)[\'"]\)/x';
|
||||
private function rebaseUrls(string $css, string $webDir): string {
|
||||
$re = '/url\([\'"]([^\/][\.\w?=\/-]*)[\'"]\)/x';
|
||||
$subst = 'url(\''.$webDir.'/$1\')';
|
||||
return preg_replace($re, $subst, $css);
|
||||
}
|
||||
|
@ -292,20 +317,20 @@ class SCSSCacher {
|
|||
* @param string $fileName
|
||||
* @return string
|
||||
*/
|
||||
public function getCachedSCSS($appName, $fileName) {
|
||||
public function getCachedSCSS(string $appName, string $fileName): string {
|
||||
$tmpfileLoc = explode('/', $fileName);
|
||||
$fileName = array_pop($tmpfileLoc);
|
||||
$fileName = $this->prependBaseurlPrefix(str_replace('.scss', '.css', $fileName));
|
||||
|
||||
return substr($this->urlGenerator->linkToRoute('core.Css.getCss', array('fileName' => $fileName, 'appName' => $appName)), strlen(\OC::$WEBROOT) + 1);
|
||||
return substr($this->urlGenerator->linkToRoute('core.Css.getCss', ['fileName' => $fileName, 'appName' => $appName]), strlen(\OC::$WEBROOT) + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend hashed base url to the css file
|
||||
* @param $cssFile
|
||||
* @param string$cssFile
|
||||
* @return string
|
||||
*/
|
||||
private function prependBaseurlPrefix($cssFile) {
|
||||
private function prependBaseurlPrefix(string $cssFile): string {
|
||||
$frontendController = ($this->config->getSystemValue('htaccess.IgnoreFrontController', false) === true || getenv('front_controller_active') === 'true');
|
||||
return substr(md5($this->urlGenerator->getBaseUrl() . $frontendController), 0, 8) . '-' . $cssFile;
|
||||
}
|
||||
|
@ -318,7 +343,7 @@ class SCSSCacher {
|
|||
* @param string $webRoot the nextcloud installation root path
|
||||
* @return string the webDir
|
||||
*/
|
||||
private function getWebDir($path, $appName, $serverRoot, $webRoot) {
|
||||
private function getWebDir(string $path, string $appName, string $serverRoot, string $webRoot): string {
|
||||
// Detect if path is within server root AND if path is within an app path
|
||||
if ( strpos($path, $serverRoot) === false && $appWebPath = \OC_App::getAppWebPath($appName)) {
|
||||
// Get the file path within the app directory
|
||||
|
|
|
@ -351,11 +351,21 @@ class SCSSCacherTest extends \Test\TestCase {
|
|||
$this->assertFalse($actual);
|
||||
}
|
||||
|
||||
public function testRebaseUrls() {
|
||||
public function dataRebaseUrls() {
|
||||
return [
|
||||
['#id { background-image: url(\'../img/image.jpg\'); }','#id { background-image: url(\'/apps/files/css/../img/image.jpg\'); }'],
|
||||
['#id { background-image: url("../img/image.jpg"); }','#id { background-image: url(\'/apps/files/css/../img/image.jpg\'); }'],
|
||||
['#id { background-image: url(\'/img/image.jpg\'); }','#id { background-image: url(\'/img/image.jpg\'); }'],
|
||||
['#id { background-image: url("http://example.com/test.jpg"); }','#id { background-image: url("http://example.com/test.jpg"); }'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataRebaseUrls
|
||||
*/
|
||||
public function testRebaseUrls($scss, $expected) {
|
||||
$webDir = '/apps/files/css';
|
||||
$css = '#id { background-image: url(\'../img/image.jpg\'); }';
|
||||
$actual = self::invokePrivate($this->scssCacher, 'rebaseUrls', [$css, $webDir]);
|
||||
$expected = '#id { background-image: url(\'/apps/files/css/../img/image.jpg\'); }';
|
||||
$actual = self::invokePrivate($this->scssCacher, 'rebaseUrls', [$scss, $webDir]);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue