‪TYPO3CMS  11.5
TerExtensionRemote.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use Psr\Http\Message\ResponseInterface;
26 
39 {
43  protected ‪$identifier;
44 
49 
53  protected ‪$remoteBase = 'https://extensions.typo3.org/fileadmin/ter/';
54 
55  public function ‪__construct(string ‪$identifier, array $options = [])
56  {
57  $this->identifier = ‪$identifier;
58  $this->localExtensionListCacheFile = ‪Environment::getVarPath() . '/extensionmanager/' . $this->identifier . '.extensions.xml.gz';
59 
60  if ($options['remoteBase'] ?? '') {
61  $this->remoteBase = $options['remoteBase'];
62  }
63  }
64 
65  public function ‪getIdentifier(): string
66  {
67  return ‪$this->identifier;
68  }
69 
75  public function ‪getAvailablePackages(bool $force = false): void
76  {
77  if ($force || $this->‪needsUpdate()) {
78  $this->‪fetchPackageList();
79  }
80  }
81 
82  public function ‪needsUpdate(): bool
83  {
84  $threshold = new \DateTimeImmutable('-7 days');
85  if ($this->‪getLastUpdate() < $threshold) {
86  return true;
87  }
88  return $this->‪isDownloadedExtensionListUpToDate() !== true;
89  }
90 
96  protected function ‪isDownloadedExtensionListUpToDate(): bool
97  {
98  if (!file_exists($this->localExtensionListCacheFile)) {
99  return false;
100  }
101  try {
102  $response = $this->‪downloadFile('extensions.md5');
103  $md5SumOfRemoteExtensionListFile = $response->getBody()->getContents();
104  return hash_equals($md5SumOfRemoteExtensionListFile, md5_file($this->localExtensionListCacheFile) ?: '');
105  } catch (DownloadFailedException $exception) {
106  return false;
107  }
108  }
109 
110  public function ‪getLastUpdate(): \DateTimeInterface
111  {
112  if (file_exists($this->localExtensionListCacheFile) && filesize($this->localExtensionListCacheFile) > 0) {
113  $mtime = filemtime($this->localExtensionListCacheFile);
114  return (new \DateTimeImmutable())->setTimestamp($mtime);
115  }
116  // Select a very old date (hint: easter egg)
117  return new \DateTimeImmutable('1975-04-13');
118  }
119 
123  protected function ‪fetchPackageList(): void
124  {
125  try {
126  $extensionListXml = $this->‪downloadFile('extensions.xml.gz');
127  ‪GeneralUtility::writeFileToTypo3tempDir($this->localExtensionListCacheFile, $extensionListXml->getBody()->getContents());
128  GeneralUtility::makeInstance(BulkExtensionRepositoryWriter::class)->import($this->localExtensionListCacheFile, $this->identifier);
129  } catch (DownloadFailedException $e) {
130  // Do not update package list
131  }
132  }
133 
140  protected function ‪downloadFile(string $remotePath): ResponseInterface
141  {
142  try {
143  $requestFactory = GeneralUtility::makeInstance(RequestFactory::class);
144  return $requestFactory->request($this->remoteBase . $remotePath);
145  } catch (\Throwable $e) {
146  throw new DownloadFailedException(sprintf('The file "%s" could not be fetched. Possible reasons: network problems, allow_url_fopen is off, cURL is not available', $this->remoteBase . $remotePath), 1334426297);
147  }
148  }
149 
161  public function ‪downloadExtension(string $extensionKey, string $version, FileHandlingUtility $fileHandler, string $verificationHash = null, string $pathType = 'Local'): void
162  {
163  $extensionPath = strtolower($extensionKey);
164  $remotePath = $extensionPath[0] . '/' . $extensionPath[1] . '/' . $extensionPath . '_' . $version . '.t3x';
165  try {
166  $downloadedContent = (string)$this->‪downloadFile($remotePath)->getBody()->getContents();
167  } catch (\Throwable $e) {
168  throw new DownloadFailedException(sprintf('The T3X file "%s" could not be fetched. Possible reasons: network problems, allow_url_fopen is off, cURL is not available.', $this->remoteBase . $remotePath), 1334426097);
169  }
170  if ($verificationHash && !$this->‪isDownloadedPackageValid($verificationHash, $downloadedContent)) {
171  throw new VerificationFailedException('MD5 hash of downloaded file not as expected: ' . md5($downloadedContent) . ' != ' . $verificationHash, 1334426098);
172  }
173  $extensionData = $this->‪decodeExchangeData($downloadedContent);
174  if (!empty($extensionData['extKey']) && is_string($extensionData['extKey'])) {
175  $fileHandler->unpackExtensionFromExtensionDataArray($extensionData['extKey'], $extensionData, $pathType);
176  } else {
177  throw new VerificationFailedException('Downloaded t3x file could not be extracted', 1334426698);
178  }
179  }
180 
188  protected function ‪isDownloadedPackageValid(string $expectedHash, string $fileContents): bool
189  {
190  return hash_equals($expectedHash, md5($fileContents));
191  }
192 
201  protected function ‪decodeExchangeData(string $stream): array
202  {
203  [$expectedHash, $compressionType, $contents] = explode(':', $stream, 3);
204  if ($compressionType === 'gzcompress') {
205  if (function_exists('gzuncompress')) {
206  $contents = gzuncompress($contents) ?: '';
207  } else {
208  throw new VerificationFailedException('No decompressor available for compressed content. gzcompress()/gzuncompress() functions are not available', 1601370681);
209  }
210  }
211  if ($this->‪isDownloadedPackageValid($expectedHash, $contents)) {
212  ‪$output = unserialize($contents, ['allowed_classes' => false]);
213  if (!is_array(‪$output)) {
214  throw new VerificationFailedException('Content could not be unserialized to an array. Strange (since MD5 hashes match!)', 1601370682);
215  }
216  } else {
217  throw new VerificationFailedException('MD5 mismatch. Maybe the extension file was downloaded and saved as a text file by the browser and thereby corrupted.', 1601370683);
218  }
219  return ‪$output;
220  }
221 }
‪TYPO3\CMS\Extensionmanager\Domain\Repository\BulkExtensionRepositoryWriter
Definition: BulkExtensionRepositoryWriter.php:32
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\isDownloadedPackageValid
‪bool isDownloadedPackageValid(string $expectedHash, string $fileContents)
Definition: TerExtensionRemote.php:185
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\$identifier
‪string $identifier
Definition: TerExtensionRemote.php:42
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\__construct
‪__construct(string $identifier, array $options=[])
Definition: TerExtensionRemote.php:52
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\getAvailablePackages
‪getAvailablePackages(bool $force=false)
Definition: TerExtensionRemote.php:72
‪TYPO3\CMS\Extensionmanager\Remote\VerificationFailedException
Definition: VerificationFailedException.php:25
‪TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility
Definition: FileHandlingUtility.php:36
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\needsUpdate
‪needsUpdate()
Definition: TerExtensionRemote.php:79
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\downloadExtension
‪downloadExtension(string $extensionKey, string $version, FileHandlingUtility $fileHandler, string $verificationHash=null, string $pathType='Local')
Definition: TerExtensionRemote.php:158
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\getIdentifier
‪getIdentifier()
Definition: TerExtensionRemote.php:62
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote
Definition: TerExtensionRemote.php:39
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\decodeExchangeData
‪array decodeExchangeData(string $stream)
Definition: TerExtensionRemote.php:198
‪TYPO3\CMS\Extensionmanager\Remote\ExtensionDownloaderRemoteInterface
Definition: ExtensionDownloaderRemoteInterface.php:28
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\$remoteBase
‪string $remoteBase
Definition: TerExtensionRemote.php:50
‪TYPO3\CMS\Extensionmanager\Remote
Definition: DownloadFailedException.php:18
‪TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility\unpackExtensionFromExtensionDataArray
‪unpackExtensionFromExtensionDataArray(string $extensionKey, array $extensionData, $pathType='Local')
Definition: FileHandlingUtility.php:87
‪TYPO3\CMS\Core\Http\RequestFactory
Definition: RequestFactory.php:31
‪$output
‪$output
Definition: annotationChecker.php:121
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\isDownloadedExtensionListUpToDate
‪bool isDownloadedExtensionListUpToDate()
Definition: TerExtensionRemote.php:93
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:43
‪TYPO3\CMS\Extensionmanager\Remote\ListableRemoteInterface
Definition: ListableRemoteInterface.php:24
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:50
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\$localExtensionListCacheFile
‪string $localExtensionListCacheFile
Definition: TerExtensionRemote.php:46
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\downloadFile
‪ResponseInterface downloadFile(string $remotePath)
Definition: TerExtensionRemote.php:137
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\getLastUpdate
‪getLastUpdate()
Definition: TerExtensionRemote.php:107
‪TYPO3\CMS\Extensionmanager\Remote\DownloadFailedException
Definition: DownloadFailedException.php:25
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\fetchPackageList
‪fetchPackageList()
Definition: TerExtensionRemote.php:120
‪TYPO3\CMS\Core\Core\Environment\getVarPath
‪static string getVarPath()
Definition: Environment.php:218
‪TYPO3\CMS\Core\Utility\GeneralUtility\writeFileToTypo3tempDir
‪static string null writeFileToTypo3tempDir($filepath, $content)
Definition: GeneralUtility.php:1814