‪TYPO3CMS  ‪main
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 
73  public function ‪getAvailablePackages(bool $force = false): void
74  {
75  if ($force || $this->‪needsUpdate()) {
76  $this->‪fetchPackageList();
77  }
78  }
79 
80  public function ‪needsUpdate(): bool
81  {
82  $threshold = new \DateTimeImmutable('-7 days');
83  if ($this->‪getLastUpdate() < $threshold) {
84  return true;
85  }
86  return $this->‪isDownloadedExtensionListUpToDate() !== true;
87  }
88 
93  protected function ‪isDownloadedExtensionListUpToDate(): bool
94  {
95  if (!file_exists($this->localExtensionListCacheFile)) {
96  return false;
97  }
98  try {
99  $response = $this->‪downloadFile('extensions.md5');
100  $md5SumOfRemoteExtensionListFile = $response->getBody()->getContents();
101  return hash_equals($md5SumOfRemoteExtensionListFile, md5_file($this->localExtensionListCacheFile) ?: '');
102  } catch (DownloadFailedException $exception) {
103  return false;
104  }
105  }
106 
107  public function ‪getLastUpdate(): \DateTimeInterface
108  {
109  if (file_exists($this->localExtensionListCacheFile) && filesize($this->localExtensionListCacheFile) > 0) {
110  $mtime = filemtime($this->localExtensionListCacheFile);
111  return new \DateTimeImmutable('@' . $mtime);
112  }
113  // Select a very old date (hint: easter egg)
114  return new \DateTimeImmutable('1975-04-13');
115  }
116 
120  protected function ‪fetchPackageList(): void
121  {
122  try {
123  $extensionListXml = $this->‪downloadFile('extensions.xml.gz');
124  ‪GeneralUtility::writeFileToTypo3tempDir($this->localExtensionListCacheFile, $extensionListXml->getBody()->getContents());
125  GeneralUtility::makeInstance(BulkExtensionRepositoryWriter::class)->import($this->localExtensionListCacheFile, $this->identifier);
126  } catch (DownloadFailedException $e) {
127  // Do not update package list
128  }
129  }
130 
135  protected function ‪downloadFile(string $remotePath): ResponseInterface
136  {
137  try {
138  $requestFactory = GeneralUtility::makeInstance(RequestFactory::class);
139  return $requestFactory->request($this->remoteBase . $remotePath);
140  } catch (\Throwable $e) {
141  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);
142  }
143  }
144 
152  public function ‪downloadExtension(string $extensionKey, string $version, FileHandlingUtility $fileHandler, string $verificationHash = null, string $pathType = 'Local'): void
153  {
154  $extensionPath = strtolower($extensionKey);
155  $remotePath = $extensionPath[0] . '/' . $extensionPath[1] . '/' . $extensionPath . '_' . $version . '.t3x';
156  try {
157  $downloadedContent = (string)$this->‪downloadFile($remotePath)->getBody()->getContents();
158  } catch (\Throwable $e) {
159  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);
160  }
161  if ($verificationHash && !$this->‪isDownloadedPackageValid($verificationHash, $downloadedContent)) {
162  throw new VerificationFailedException('MD5 hash of downloaded file not as expected: ' . md5($downloadedContent) . ' != ' . $verificationHash, 1334426098);
163  }
164  $extensionData = $this->‪decodeExchangeData($downloadedContent);
165  if (!empty($extensionData['extKey']) && is_string($extensionData['extKey'])) {
166  $fileHandler->unpackExtensionFromExtensionDataArray($extensionData['extKey'], $extensionData, $pathType);
167  } else {
168  throw new VerificationFailedException('Downloaded t3x file could not be extracted', 1334426698);
169  }
170  }
171 
175  protected function ‪isDownloadedPackageValid(string $expectedHash, string $fileContents): bool
176  {
177  return hash_equals($expectedHash, md5($fileContents));
178  }
179 
188  protected function ‪decodeExchangeData(string $stream): array
189  {
190  [$expectedHash, $compressionType, $contents] = explode(':', $stream, 3);
191  if ($compressionType === 'gzcompress') {
192  if (function_exists('gzuncompress')) {
193  $contents = gzuncompress($contents) ?: '';
194  } else {
195  throw new VerificationFailedException('No decompressor available for compressed content. gzcompress()/gzuncompress() functions are not available', 1601370681);
196  }
197  }
198  if ($this->‪isDownloadedPackageValid($expectedHash, $contents)) {
199  ‪$output = unserialize($contents, ['allowed_classes' => false]);
200  if (!is_array(‪$output)) {
201  throw new VerificationFailedException('Content could not be unserialized to an array. Strange (since MD5 hashes match!)', 1601370682);
202  }
203  } else {
204  throw new VerificationFailedException('MD5 mismatch. Maybe the extension file was downloaded and saved as a text file by the browser and thereby corrupted.', 1601370683);
205  }
206  return ‪$output;
207  }
208 }
‪TYPO3\CMS\Extensionmanager\Domain\Repository\BulkExtensionRepositoryWriter
Definition: BulkExtensionRepositoryWriter.php:32
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\$identifier
‪string $identifier
Definition: TerExtensionRemote.php:42
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\isDownloadedExtensionListUpToDate
‪isDownloadedExtensionListUpToDate()
Definition: TerExtensionRemote.php:90
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\__construct
‪__construct(string $identifier, array $options=[])
Definition: TerExtensionRemote.php:52
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\downloadFile
‪downloadFile(string $remotePath)
Definition: TerExtensionRemote.php:132
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\getAvailablePackages
‪getAvailablePackages(bool $force=false)
Definition: TerExtensionRemote.php:70
‪TYPO3\CMS\Core\Core\Environment\getVarPath
‪static getVarPath()
Definition: Environment.php:197
‪TYPO3\CMS\Extensionmanager\Remote\VerificationFailedException
Definition: VerificationFailedException.php:26
‪TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility
Definition: FileHandlingUtility.php:38
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\needsUpdate
‪needsUpdate()
Definition: TerExtensionRemote.php:77
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\downloadExtension
‪downloadExtension(string $extensionKey, string $version, FileHandlingUtility $fileHandler, string $verificationHash=null, string $pathType='Local')
Definition: TerExtensionRemote.php:149
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\isDownloadedPackageValid
‪isDownloadedPackageValid(string $expectedHash, string $fileContents)
Definition: TerExtensionRemote.php:172
‪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:185
‪TYPO3\CMS\Extensionmanager\Remote\ExtensionDownloaderRemoteInterface
Definition: ExtensionDownloaderRemoteInterface.php:28
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\$remoteBase
‪string $remoteBase
Definition: TerExtensionRemote.php:50
‪TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility\unpackExtensionFromExtensionDataArray
‪unpackExtensionFromExtensionDataArray(string $extensionKey, array $extensionData, string $pathType='Local')
Definition: FileHandlingUtility.php:56
‪TYPO3\CMS\Extensionmanager\Remote
Definition: DownloadFailedException.php:18
‪TYPO3\CMS\Core\Http\RequestFactory
Definition: RequestFactory.php:30
‪$output
‪$output
Definition: annotationChecker.php:119
‪TYPO3\CMS\Core\Core\Environment
Definition: Environment.php:41
‪TYPO3\CMS\Extensionmanager\Remote\ListableRemoteInterface
Definition: ListableRemoteInterface.php:24
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:51
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\$localExtensionListCacheFile
‪string $localExtensionListCacheFile
Definition: TerExtensionRemote.php:46
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\getLastUpdate
‪getLastUpdate()
Definition: TerExtensionRemote.php:104
‪TYPO3\CMS\Extensionmanager\Remote\DownloadFailedException
Definition: DownloadFailedException.php:26
‪TYPO3\CMS\Extensionmanager\Remote\TerExtensionRemote\fetchPackageList
‪fetchPackageList()
Definition: TerExtensionRemote.php:117
‪TYPO3\CMS\Core\Utility\GeneralUtility\writeFileToTypo3tempDir
‪static string null writeFileToTypo3tempDir($filepath, $content)
Definition: GeneralUtility.php:1544