Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions apps/theming/lib/Controller/ThemingController.php
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,13 @@ public function getImage(string $key, bool $useSvg = true) {
$csp->allowInlineStyle();
$response->setContentSecurityPolicy($csp);
$response->cacheFor(3600);
$response->addHeader('Content-Type', $file->getMimeType());
// The original stored file has no extension (e.g. "logo"), so getMimeType() returns
// application/octet-stream for it. Use the config-stored MIME type for the original
// file, and getMimeType() only for converted files which have a proper extension.
$mimeType = $file->getName() === $key
? $this->appConfig->getAppValueString($key . 'Mime', '')
: $file->getMimeType();
$response->addHeader('Content-Type', $mimeType);
$response->addHeader('Content-Disposition', 'attachment; filename="' . $key . '"');
return $response;
}
Expand Down Expand Up @@ -435,7 +441,7 @@ public function getThemeStylesheet(string $themeId, bool $plain = false, bool $w
#[BruteForceProtection(action: 'manifest')]
#[OpenAPI(scope: OpenAPI::SCOPE_DEFAULT)]
public function getManifest(string $app): JSONResponse {
$cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
$cacheBusterValue = $this->appConfig->getAppValueString('cachebuster', '0');
if ($app === 'core' || $app === 'settings') {
$name = $this->themingDefaults->getName();
$shortName = $this->themingDefaults->getName();
Expand Down
40 changes: 15 additions & 25 deletions apps/theming/lib/ImageManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

use OCA\Theming\AppInfo\Application;
use OCA\Theming\Service\BackgroundService;
use OCP\AppFramework\Services\IAppConfig;
use OCP\Files\IAppData;
use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
Expand All @@ -30,6 +31,7 @@ public function __construct(
private LoggerInterface $logger,
private ITempManager $tempManager,
private BackgroundService $backgroundService,
private IAppConfig $appConfig,
) {
}

Expand All @@ -40,7 +42,7 @@ public function __construct(
* @return string the image url
*/
public function getImageUrl(string $key): string {
$cacheBusterCounter = $this->config->getAppValue(Application::APP_ID, 'cachebuster', '0');
$cacheBusterCounter = (string)$this->appConfig->getAppValueInt(ConfigLexicon::CACHE_BUSTER);
if ($this->hasImage($key)) {
return $this->urlGenerator->linkToRoute('theming.Theming.getImage', [ 'key' => $key ]) . '?v=' . $cacheBusterCounter;
} elseif ($key === 'backgroundDark' && $this->hasImage('background')) {
Expand Down Expand Up @@ -85,31 +87,14 @@ public function getImageUrlAbsolute(string $key): string {
public function getImage(string $key, bool $useSvg = true): ISimpleFile {
$mime = $this->config->getAppValue('theming', $key . 'Mime', '');
$folder = $this->getRootFolder()->getFolder('images');
$useSvg = $useSvg && $this->canConvert('SVG');

if ($mime === '' || !$folder->fileExists($key)) {
throw new NotFoundException();
}
// if SVG was requested and is supported
if ($useSvg) {
if (!$folder->fileExists($key . '.svg')) {
try {
$finalIconFile = new \Imagick();
$finalIconFile->setBackgroundColor('none');
$finalIconFile->readImageBlob($folder->getFile($key)->getContent());
$finalIconFile->setImageFormat('SVG');
$svgFile = $folder->newFile($key . '.svg');
$svgFile->putContent($finalIconFile->getImageBlob());
return $svgFile;
} catch (\ImagickException $e) {
$this->logger->info('The image was requested to be no SVG file, but converting it to SVG failed: ' . $e->getMessage());
}
} else {
return $folder->getFile($key . '.svg');
}
}
// if SVG was not requested, but PNG is supported
if (!$useSvg && $this->canConvert('PNG')) {
// only convert SVG originals to PNG when SVG output is not desired;
// converting raster images to SVG produces broken output and is not useful
$isOriginalSvg = ($mime === 'image/svg+xml' || $mime === 'image/svg');
if ($isOriginalSvg && !$useSvg && $this->canConvert('SVG') && $this->canConvert('PNG')) {
if (!$folder->fileExists($key . '.png')) {
try {
$finalIconFile = new \Imagick();
Expand All @@ -120,13 +105,12 @@ public function getImage(string $key, bool $useSvg = true): ISimpleFile {
$pngFile->putContent($finalIconFile->getImageBlob());
return $pngFile;
} catch (\ImagickException $e) {
$this->logger->info('The image was requested to be no SVG file, but converting it to PNG failed: ' . $e->getMessage());
$this->logger->info('Converting SVG to PNG failed: ' . $e->getMessage());
}
} else {
return $folder->getFile($key . '.png');
}
}
// fallback to the original file
return $folder->getFile($key);
}

Expand Down Expand Up @@ -157,7 +141,7 @@ public function getCustomImages(): array {
* @throws NotPermittedException
*/
public function getCacheFolder(): ISimpleFolder {
$cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
$cacheBusterValue = (string)$this->appConfig->getAppValueInt(ConfigLexicon::CACHE_BUSTER);
try {
$folder = $this->getRootFolder()->getFolder($cacheBusterValue);
} catch (NotFoundException $e) {
Expand Down Expand Up @@ -214,6 +198,12 @@ public function delete(string $key): void {
} catch (NotFoundException $e) {
} catch (NotPermittedException $e) {
}
try {
$file = $this->getRootFolder()->getFolder('images')->getFile($key . '.svg');
$file->delete();
} catch (NotFoundException $e) {
} catch (NotPermittedException $e) {
}

if ($key === 'logo') {
$this->config->deleteAppValue('theming', 'logoDimensions');
Expand Down
29 changes: 26 additions & 3 deletions apps/theming/tests/Controller/ThemingControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,29 @@ public function testGetLogo(): void {
}


public function testGetLogoOriginalFile(): void {
$file = $this->createMock(ISimpleFile::class);
$file->method('getName')->willReturn('logo');
$file->method('getMTime')->willReturn(42);
$this->imageManager->expects($this->once())
->method('getImage')
->willReturn($file);
$this->appConfig
->expects($this->once())
->method('getAppValueString')
->with('logoMime', '')
->willReturn('image/png');

@$expected = new FileDisplayResponse($file);
$expected->cacheFor(3600);
$expected->addHeader('Content-Type', 'image/png');
$expected->addHeader('Content-Disposition', 'attachment; filename="logo"');
$csp = new ContentSecurityPolicy();
$csp->allowInlineStyle();
$expected->setContentSecurityPolicy($csp);
@$this->assertEquals($expected, $this->themingController->getImage('logo', false));
}

public function testGetLoginBackgroundNotExistent(): void {
$this->imageManager->method('getImage')
->with($this->equalTo('background'))
Expand Down Expand Up @@ -708,10 +731,10 @@ public static function dataGetManifest(): array {

#[\PHPUnit\Framework\Attributes\DataProvider('dataGetManifest')]
public function testGetManifest(bool $standalone): void {
$this->config
$this->appConfig
->expects($this->once())
->method('getAppValue')
->with('theming', 'cachebuster', '0')
->method('getAppValueString')
->with('cachebuster', '0')
->willReturn('0');
$this->themingDefaults
->expects($this->any())
Expand Down
Loading
Loading