From d93d742f912b6f7b41108c60dff2910160eb4c38 Mon Sep 17 00:00:00 2001
From: Andreas Fernandez <a.fernandez@scripting-base.de>
Date: Fri, 29 Nov 2019 18:18:49 +0100
Subject: [PATCH] [BUGFIX] Use RequestFactory for downloading mirrors and check
 response properly

The LanguagePackService now uses the RequestFactory to download the
mirrors.xml.gz file, since it throws proper exceptions on failures.

In case of e.g. a timeout, the thrown exception is properly caught and
the designed fallback kicks in.

Resolves: #89810
Releases: master, 9.5
Change-Id: Ie4e8bcf02c33eb18e58f505f1a791c1233d6f593
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/62496
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
Tested-by: Richard Haeser <richard@maxserv.com>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Oliver Bartsch <bo@cedev.de>
Reviewed-by: Richard Haeser <richard@maxserv.com>
---
 .../Classes/Service/LanguagePackService.php   | 70 ++++++++++++++-----
 1 file changed, 51 insertions(+), 19 deletions(-)

diff --git a/typo3/sysext/install/Classes/Service/LanguagePackService.php b/typo3/sysext/install/Classes/Service/LanguagePackService.php
index 2455718c1f8d..5d6bcc52ec05 100644
--- a/typo3/sysext/install/Classes/Service/LanguagePackService.php
+++ b/typo3/sysext/install/Classes/Service/LanguagePackService.php
@@ -16,9 +16,12 @@ namespace TYPO3\CMS\Install\Service;
  */
 
 use Psr\EventDispatcher\EventDispatcherInterface;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerAwareTrait;
 use Symfony\Component\Finder\Finder;
 use TYPO3\CMS\Core\Configuration\Features;
 use TYPO3\CMS\Core\Core\Environment;
+use TYPO3\CMS\Core\Http\RequestFactory;
 use TYPO3\CMS\Core\Http\Uri;
 use TYPO3\CMS\Core\Localization\Locales;
 use TYPO3\CMS\Core\Package\PackageManager;
@@ -33,8 +36,10 @@ use TYPO3\CMS\Core\Utility\PathUtility;
  *
  * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API.
  */
-class LanguagePackService
+class LanguagePackService implements LoggerAwareInterface
 {
+    use LoggerAwareTrait;
+
     /**
      * @var Locales
      */
@@ -50,14 +55,20 @@ class LanguagePackService
      */
     protected $eventDispatcher;
 
+    /**
+     * @var RequestFactory
+     */
+    protected $requestFactory;
+
     private const DEFAULT_LANGUAGE_PACK_URL = 'https://typo3.org/fileadmin/ter/';
     private const BETA_LANGUAGE_PACK_URL = 'https://beta-translation.typo3.org/fileadmin/ter/';
 
-    public function __construct(EventDispatcherInterface $eventDispatcher = null)
+    public function __construct(EventDispatcherInterface $eventDispatcher = null, RequestFactory $requestFactory = null)
     {
         $this->eventDispatcher = $eventDispatcher ?? GeneralUtility::getContainer()->get(EventDispatcherInterface::class);
         $this->locales = GeneralUtility::makeInstance(Locales::class);
         $this->registry = GeneralUtility::makeInstance(Registry::class);
+        $this->requestFactory = $requestFactory ?? GeneralUtility::makeInstance(RequestFactory::class);
     }
 
     /**
@@ -183,15 +194,26 @@ class LanguagePackService
      */
     public function updateMirrorBaseUrl(): string
     {
+        $repositoryUrl = 'https://repositories.typo3.org/mirrors.xml.gz';
         $downloadBaseUrl = false;
         try {
-            $xmlContent = GeneralUtility::getUrl('https://repositories.typo3.org/mirrors.xml.gz');
-            $xmlContent = GeneralUtility::xml2array(@gzdecode($xmlContent));
-            if (!empty($xmlContent['mirror']['host']) && !empty($xmlContent['mirror']['path'])) {
-                $downloadBaseUrl = 'https://' . $xmlContent['mirror']['host'] . $xmlContent['mirror']['path'];
+            $response = $this->requestFactory->request($repositoryUrl);
+            if ($response->getStatusCode() === 200) {
+                $xmlContent = @gzdecode($response->getBody()->getContents());
+                if (!empty($xmlContent['mirror']['host']) && !empty($xmlContent['mirror']['path'])) {
+                    $downloadBaseUrl = 'https://' . $xmlContent['mirror']['host'] . $xmlContent['mirror']['path'];
+                }
+            } else {
+                $this->logger->warning(sprintf(
+                    'Requesting %s was not successful, got status code %d (%s)',
+                    $repositoryUrl,
+                    $response->getStatusCode(),
+                    $response->getReasonPhrase()
+                ));
             }
         } catch (\Exception $e) {
             // Catch generic exception, fallback handled below
+            $this->logger->error('Failed to download list of mirrors', ['exception' => $e]);
         }
         if (empty($downloadBaseUrl)) {
             // Hard coded fallback if something went wrong fetching & parsing mirror list
@@ -265,20 +287,30 @@ class LanguagePackService
 
         $operationResult = false;
         try {
-            $languagePackContent = GeneralUtility::getUrl($languagePackBaseUrl . $packageUrl);
-            if (!empty($languagePackContent)) {
-                $operationResult = true;
-                if ($packExists) {
-                    $operationResult = GeneralUtility::rmdir($absoluteExtractionPath, true);
-                }
-                if ($operationResult) {
-                    GeneralUtility::mkdir_deep(Environment::getVarPath() . '/transient/');
-                    $operationResult = GeneralUtility::writeFileToTypo3tempDir($absolutePathToZipFile, $languagePackContent) === null;
-                }
-                $this->unzipTranslationFile($absolutePathToZipFile, $absoluteLanguagePath);
-                if ($operationResult) {
-                    $operationResult = unlink($absolutePathToZipFile);
+            $response = $this->requestFactory->request($languagePackBaseUrl . $packageUrl);
+            if ($response->getStatusCode() === 200) {
+                $languagePackContent = $response->getBody()->getContents();
+                if (!empty($languagePackContent)) {
+                    $operationResult = true;
+                    if ($packExists) {
+                        $operationResult = GeneralUtility::rmdir($absoluteExtractionPath, true);
+                    }
+                    if ($operationResult) {
+                        GeneralUtility::mkdir_deep(Environment::getVarPath() . '/transient/');
+                        $operationResult = GeneralUtility::writeFileToTypo3tempDir($absolutePathToZipFile, $languagePackContent) === null;
+                    }
+                    $this->unzipTranslationFile($absolutePathToZipFile, $absoluteLanguagePath);
+                    if ($operationResult) {
+                        $operationResult = unlink($absolutePathToZipFile);
+                    }
                 }
+            } else {
+                $this->logger->warning(sprintf(
+                    'Requesting %s was not successful, got status code %d (%s)',
+                    $languagePackBaseUrl . $packageUrl,
+                    $response->getStatusCode(),
+                    $response->getReasonPhrase()
+                ));
             }
         } catch (\Exception $e) {
             $operationResult = false;
-- 
GitLab