diff --git a/.phpcs.xml b/.phpcs.xml index 4ab85427..1fc70ae8 100644 --- a/.phpcs.xml +++ b/.phpcs.xml @@ -11,6 +11,7 @@ node_modules/ var/ public/assets + .docker diff --git a/i18n/en.json b/i18n/en.json index 58c68423..783523b2 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -25,6 +25,7 @@ "translate-button": "Translate", "no-translations": "This file does not have any labels available for translation. Please pick another image.", "not-found": "The file you requested could not be found. Please pick another image.", + "invalid-format": "Only SVG files are supported.", "pick-another": "← pick another file", "view-on-commons": "View on Commons", diff --git a/i18n/qqq.json b/i18n/qqq.json index 5be4dfd5..d6e7cdd2 100644 --- a/i18n/qqq.json +++ b/i18n/qqq.json @@ -29,6 +29,7 @@ "translate-button": "Search button text.\n{{Identical|Translate}}", "no-translations": "Error message displayed for SVGs that have nothing to translate.", "not-found": "Error message displayed if the requested file could not be found.", + "invalid-format": "Error message displayed if the requested file is not an SVG image.", "pick-another": "Label for backlink from the translate page to the search page.", "view-on-commons": "Link to view the file on Commons. The whole phrase is linked.", "opens-in-new-tab": "Tooltip text for links that will open in a new browser tab.", diff --git a/public/assets/i18n/app/en.json b/public/assets/i18n/app/en.json index 58c68423..783523b2 100644 --- a/public/assets/i18n/app/en.json +++ b/public/assets/i18n/app/en.json @@ -25,6 +25,7 @@ "translate-button": "Translate", "no-translations": "This file does not have any labels available for translation. Please pick another image.", "not-found": "The file you requested could not be found. Please pick another image.", + "invalid-format": "Only SVG files are supported.", "pick-another": "← pick another file", "view-on-commons": "View on Commons", diff --git a/public/assets/i18n/app/qqq.json b/public/assets/i18n/app/qqq.json index 5be4dfd5..d6e7cdd2 100644 --- a/public/assets/i18n/app/qqq.json +++ b/public/assets/i18n/app/qqq.json @@ -29,6 +29,7 @@ "translate-button": "Search button text.\n{{Identical|Translate}}", "no-translations": "Error message displayed for SVGs that have nothing to translate.", "not-found": "Error message displayed if the requested file could not be found.", + "invalid-format": "Error message displayed if the requested file is not an SVG image.", "pick-another": "Label for backlink from the translate page to the search page.", "view-on-commons": "Link to view the file on Commons. The whole phrase is linked.", "opens-in-new-tab": "Tooltip text for links that will open in a new browser tab.", diff --git a/src/Controller/TranslateController.php b/src/Controller/TranslateController.php index 06608f6f..0f6cf4fd 100644 --- a/src/Controller/TranslateController.php +++ b/src/Controller/TranslateController.php @@ -4,6 +4,7 @@ namespace App\Controller; use App\Exception\ImageNotFoundException; +use App\Exception\InvalidFormatException; use App\Model\Svg\SvgFile; use App\Model\Title; use App\OOUI\TranslationsFieldset; @@ -55,18 +56,15 @@ public function translate( // Fetch the SVG from Commons. try { + $this->assertFileType($normalizedFilename); $path = $cache->getPath($filename); } catch (ImageNotFoundException $exception) { - $notFoundMessage = $this->renderView( - 'error_message.html.twig', - ['msg_name' => 'not-found'] - ); - // Flash the message to show to the user under the search form. - $this->addFlash('search-errors', (string)$notFoundMessage); - // Also flash the failed filename so we can populate the search form. - $this->addFlash('search-redirect', $normalizedFilename); - return $this->redirectToRoute('home'); + return $this->showError('not-found', $normalizedFilename); + } catch (InvalidFormatException $exception) { + return $this->showError('invalid-format', $normalizedFilename); } + + $svgFile = new SvgFile($path); // If there are no strings to translate, tell the user. @@ -227,4 +225,39 @@ public function updownload( return $this->redirectToRoute('translate', ['filename' => $filename]); } } + + /** + * Shows an error related to the selected file + * + * @param string $messageKey + * @param string $fileName + * @return Response + */ + private function showError(string $messageKey, string $fileName): Response + { + $message = $this->renderView( + 'error_message.html.twig', + ['msg_name' => $messageKey] + ); + // Flash the message to show to the user under the search form. + $this->addFlash('search-errors', (string)$message); + // Also flash the failed filename so we can populate the search form. + $this->addFlash('search-redirect', $fileName); + + return $this->redirectToRoute('home'); + } + + /** + * Throws an exception if the given filename is not of an SVG file. + * Exceptions are used to unify handling with other places that might encounter this problem. + * + * @param string $fileName + */ + private function assertFileType(string $fileName): void + { + $extension = mb_strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); + if ('svg' !== $extension) { + throw new InvalidFormatException($extension); + } + } } diff --git a/src/Exception/InvalidFormatException.php b/src/Exception/InvalidFormatException.php new file mode 100644 index 00000000..2fe0a485 --- /dev/null +++ b/src/Exception/InvalidFormatException.php @@ -0,0 +1,14 @@ +get($url); + $contentType = $response->getHeader('Content-Type'); + $contentType = reset($contentType); + if ('image/svg+xml' !== $contentType) { + throw new InvalidFormatException($contentType); + } return $response->getBody()->getContents(); } } diff --git a/tests/Controller/TranslateControllerTest.php b/tests/Controller/TranslateControllerTest.php index f9fa8956..aa600b91 100644 --- a/tests/Controller/TranslateControllerTest.php +++ b/tests/Controller/TranslateControllerTest.php @@ -19,4 +19,12 @@ public function testExists(): void static::assertEquals(200, $response->getStatusCode()); static::assertContains('Test.svg', $crawler->filter('h1')->text()); } + + public function testNonSvgHandling(): void + { + $client = static::createClient(); + $client->request('GET', '/File:Test.jpg'); + $response = $client->getResponse(); + static::assertEquals(302, $response->getStatusCode()); + } }