Stop using HTTPRequest for package update server requests
authorMarcel Werk <burntime@woltlab.com>
Fri, 9 Aug 2024 12:44:31 +0000 (14:44 +0200)
committerMarcel Werk <burntime@woltlab.com>
Fri, 9 Aug 2024 12:44:31 +0000 (14:44 +0200)
wcfsetup/install/files/acp/templates/packageUpdateUnauthorized.tpl
wcfsetup/install/files/lib/system/package/PackageInstallationScheduler.class.php
wcfsetup/install/files/lib/system/package/PackageUpdateDispatcher.class.php
wcfsetup/install/files/lib/system/package/PackageUpdateUnauthorizedException.class.php
wcfsetup/install/files/lib/util/HTTPRequest.class.php
wcfsetup/install/files/lib/util/exception/HTTPException.class.php

index dcdb0924c6774b7c933e825b9ebc0575f36d73c1..09c845212c0d78dcc0aedae7bad2e7095524952f 100644 (file)
@@ -2,7 +2,7 @@
        {if $authInsufficient}
                <woltlab-core-notice type="warning">{lang}wcf.acp.package.update.authInsufficient{/lang}</woltlab-core-notice>
        {else}
-               <woltlab-core-notice type="{if $serverReply[statusCode] == 401}error{else}warning{/if}">{lang}wcf.acp.package.update.errorCode.{@$serverReply[statusCode]}{/lang}</woltlab-core-notice>
+               <woltlab-core-notice type="{if $responseStatusCode == 401}error{else}warning{/if}">{lang}wcf.acp.package.update.errorCode.{$responseStatusCode}{/lang}</woltlab-core-notice>
        {/if}
 {/if}
 
        {/if}
        <dl>
                <dt>{lang}wcf.acp.package.update.server.url{/lang}</dt>
-               <dd>{@$updateServer->getHighlightedURL()}</dd>
+               <dd>{unsafe:$updateServer->getHighlightedURL()}</dd>
        </dl>
        
        <dl>
                <dt>{lang}wcf.acp.package.update.server.message{/lang}</dt>
-               <dd>{$serverReply[body]}</dd>
+               <dd>{$responseMessage}</dd>
        </dl>
 </section>
 
@@ -51,5 +51,5 @@
 </section>
 
 <div class="formSubmit">
-       <button type="button" class="button buttonPrimary" data-type="submit" data-package-update-server-id="{@$updateServer->packageUpdateServerID}">{lang}wcf.global.button.submit{/lang}</button>
+       <button type="button" class="button buttonPrimary" data-type="submit" data-package-update-server-id="{$updateServer->packageUpdateServerID}">{lang}wcf.global.button.submit{/lang}</button>
 </div>
index 81e5cccd769626f9a710f352ba6c7360e1b96a77..d1d8635618d11530d129da488801d2018204ab27 100644 (file)
@@ -2,20 +2,21 @@
 
 namespace wcf\system\package;
 
+use GuzzleHttp\Exception\ClientException;
+use GuzzleHttp\Psr7\Request;
+use GuzzleHttp\RequestOptions;
 use wcf\data\package\Package;
 use wcf\data\package\PackageCache;
 use wcf\data\package\update\PackageUpdate;
 use wcf\data\package\update\server\PackageUpdateServer;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
-use wcf\system\exception\HTTPUnauthorizedException;
 use wcf\system\exception\NamedUserException;
 use wcf\system\exception\SystemException;
-use wcf\system\io\File;
+use wcf\system\io\HttpFactory;
 use wcf\system\package\exception\IncoherentUpdatePath;
 use wcf\system\package\exception\UnknownUpdatePath;
 use wcf\system\WCF;
 use wcf\util\FileUtil;
-use wcf\util\HTTPRequest;
 
 /**
  * Contains business logic related to preparation of package installations.
@@ -266,14 +267,26 @@ final class PackageInstallationScheduler
         foreach ($packageUpdateVersions as $packageUpdateVersion) {
             // get auth data
             $authData = $this->getAuthData($packageUpdateVersion);
+            $options = [];
+            if (!empty($authData)) {
+                $options[RequestOptions::AUTH] = [
+                    $authData['username'],
+                    $authData['password'],
+                ];
+            }
+            $client = HttpFactory::makeClient($options);
 
             if ($packageUpdateVersion['filename']) {
-                $request = new HTTPRequest(
+                $request = new Request(
+                    'POST',
                     $packageUpdateVersion['filename'],
-                    (!empty($authData) ? ['auth' => $authData] : []),
-                    [
-                        'apiVersion' => PackageUpdate::API_VERSION,
-                    ]
+                    ['Content-Type' => 'application/x-www-form-urlencoded'],
+                    \http_build_query(
+                        ['apiVersion' => PackageUpdate::API_VERSION],
+                        '',
+                        '&',
+                        \PHP_QUERY_RFC1738
+                    )
                 );
             } else {
                 $parameters = [
@@ -285,37 +298,43 @@ final class PackageInstallationScheduler
                     $parameters['instanceId'] = \hash_hmac('sha256', 'api.woltlab.com', \WCF_UUID);
                 }
 
-                $request = new HTTPRequest(
+                $request = new Request(
+                    'POST',
                     $this->packageUpdateServers[$packageUpdateVersion['packageUpdateServerID']]->getDownloadURL(),
-                    (!empty($authData) ? ['auth' => $authData] : []),
-                    $parameters
+                    ['Content-Type' => 'application/x-www-form-urlencoded'],
+                    \http_build_query(
+                        $parameters,
+                        '',
+                        '&',
+                        \PHP_QUERY_RFC1738
+                    )
                 );
             }
 
             try {
-                $request->execute();
-            } catch (HTTPUnauthorizedException $e) {
+                $response = $client->send($request);
+            } catch (ClientException $e) {
                 throw new PackageUpdateUnauthorizedException(
-                    $request,
+                    $e->getResponse()->getStatusCode(),
+                    $e->getResponse()->getHeaders(),
+                    $e->getResponse()->getBody(),
                     $this->packageUpdateServers[$packageUpdateVersion['packageUpdateServerID']],
                     $packageUpdateVersion
                 );
             }
 
-            $response = $request->getReply();
-
             // check response
-            if ($response['statusCode'] != 200) {
+            if ($response->getStatusCode() !== 200) {
                 throw new SystemException(WCF::getLanguage()->getDynamicVariable(
                     'wcf.acp.package.error.downloadFailed',
                     ['__downloadPackage' => $package]
-                ) . ' (' . $response['body'] . ')');
+                ) . ' (' . $response->getBody() . ')');
             }
 
             // write content to tmp file
             $filename = FileUtil::getTemporaryFilename('package_');
-            \file_put_contents($filename, $response['body']);
-            unset($response['body']);
+            \file_put_contents($filename, $response->getBody());
+            unset($response);
 
             // test package
             $archive = new PackageArchive($filename);
index b3abe9b6d86bd233ea3e010e0d6821d1537e7135..414d5b891049ffc777aaf2274152fb2bde2d2dd5 100644 (file)
@@ -2,7 +2,9 @@
 
 namespace wcf\system\package;
 
+use GuzzleHttp\Exception\ClientException;
 use GuzzleHttp\Psr7\Request;
+use GuzzleHttp\RequestOptions;
 use Psr\Http\Client\ClientExceptionInterface;
 use wcf\data\package\Package;
 use wcf\data\package\update\server\PackageUpdateServer;
@@ -11,13 +13,11 @@ use wcf\event\package\PackageUpdateListChanged;
 use wcf\system\cache\builder\PackageUpdateCacheBuilder;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\event\EventHandler;
-use wcf\system\exception\HTTPUnauthorizedException;
 use wcf\system\exception\SystemException;
 use wcf\system\io\HttpFactory;
 use wcf\system\package\validation\PackageValidationException;
 use wcf\system\SingletonFactory;
 use wcf\system\WCF;
-use wcf\util\HTTPRequest;
 use wcf\util\JSON;
 use wcf\util\StringUtil;
 use wcf\util\XML;
@@ -76,7 +76,7 @@ final class PackageUpdateDispatcher extends SingletonFactory
             } catch (SystemException $e) {
                 $errorMessage = $e->getMessage();
             } catch (PackageUpdateUnauthorizedException $e) {
-                $body = $e->getRequest()->getReply()['body'];
+                $body = $e->getResponseMessage();
 
                 // Try to find the page <title>.
                 if (\preg_match('~<title>(?<title>.*?)</title>~', $body, $matches)) {
@@ -147,23 +147,22 @@ final class PackageUpdateDispatcher extends SingletonFactory
      */
     private function getPackageUpdateXML(PackageUpdateServer $updateServer)
     {
-        $settings = [];
         $authData = $updateServer->getAuthData();
-        if ($authData) {
-            $settings['auth'] = $authData;
+        $options = [];
+        if (!empty($authData)) {
+            $options[RequestOptions::AUTH] = [
+                $authData['username'],
+                $authData['password'],
+            ];
         }
-
-        $request = new HTTPRequest($updateServer->getListURL(), $settings);
+        $client = HttpFactory::makeClient($options);
+        $headers = [];
 
         $requestedVersion = \wcf\getMinorVersion();
         if (PackageUpdateServer::isUpgradeOverrideEnabled()) {
             $requestedVersion = WCF::AVAILABLE_UPGRADE_VERSION;
         }
-
-        $request->addHeader(
-            'requested-woltlab-suite-version',
-            $requestedVersion
-        );
+        $headers['requested-woltlab-suite-version'] = $requestedVersion;
 
         $apiVersion = $updateServer->apiVersion;
         if (\in_array($apiVersion, ['2.1', '3.1'])) {
@@ -174,26 +173,34 @@ final class PackageUpdateDispatcher extends SingletonFactory
             ) {
                 $metaData = $updateServer->getMetaData();
                 if (isset($metaData['list']['etag'])) {
-                    $request->addHeader('if-none-match', $metaData['list']['etag']);
+                    $headers['if-none-match'] = $metaData['list']['etag'];
                 }
                 if (isset($metaData['list']['lastModified'])) {
-                    $request->addHeader('if-modified-since', $metaData['list']['lastModified']);
+                    $headers['if-modified-since'] = $metaData['list']['lastModified'];
                 }
             }
         }
 
-        try {
-            $request->execute();
-            $reply = $request->getReply();
-        } catch (HTTPUnauthorizedException $e) {
-            throw new PackageUpdateUnauthorizedException($request, $updateServer);
-        } catch (SystemException $e) {
-            $reply = $request->getReply();
+        $request = new Request(
+            'GET',
+            $updateServer->getListURL(),
+            $headers
+        );
 
-            $statusCode = \is_array($reply['statusCode']) ? \reset($reply['statusCode']) : $reply['statusCode'];
+        try {
+            $response = $client->send($request);
+        } catch (ClientException $e) {
+            throw new PackageUpdateUnauthorizedException(
+                $e->getResponse()->getStatusCode(),
+                $e->getResponse()->getHeaders(),
+                $e->getResponse()->getBody(),
+                $updateServer,
+            );
+        }
 
+        if ($response->getStatusCode() !== 200 && $response->getStatusCode() !== 304) {
             throw new SystemException(
-                WCF::getLanguage()->get('wcf.acp.package.update.error.listNotFound') . ' (' . $statusCode . ')'
+                WCF::getLanguage()->get('wcf.acp.package.update.error.listNotFound') . ' (' . $response->getStatusCode() . ')'
             );
         }
 
@@ -204,8 +211,8 @@ final class PackageUpdateDispatcher extends SingletonFactory
         ];
 
         // check if server indicates support for a newer API
-        if ($updateServer->apiVersion !== '3.1' && !empty($reply['httpHeaders']['wcf-update-server-api'])) {
-            $apiVersions = \explode(' ', \reset($reply['httpHeaders']['wcf-update-server-api']));
+        if ($updateServer->apiVersion !== '3.1' && !empty($response->getHeaders()['wcf-update-server-api'])) {
+            $apiVersions = \explode(' ', \reset($response->getHeaders()['wcf-update-server-api']));
             if (\in_array('3.1', $apiVersions)) {
                 $apiVersion = $data['apiVersion'] = '3.1';
             } elseif (\in_array('2.1', $apiVersions)) {
@@ -215,27 +222,27 @@ final class PackageUpdateDispatcher extends SingletonFactory
 
         // parse given package update xml
         $allNewPackages = false;
-        if ($apiVersion === '2.0' || $reply['statusCode'] != 304) {
-            $allNewPackages = $this->parsePackageUpdateXML($updateServer, $reply['body'], $apiVersion);
+        if ($apiVersion === '2.0' || $response->getStatusCode() != 304) {
+            $allNewPackages = $this->parsePackageUpdateXML($updateServer, $response->getBody(), $apiVersion);
         }
 
         $metaData = [];
         if (\in_array($apiVersion, ['2.1', '3.1'])) {
-            if (empty($reply['httpHeaders']['etag']) && empty($reply['httpHeaders']['last-modified'])) {
+            if (empty($response->getHeaders()['etag']) && empty($response->getHeaders()['last-modified'])) {
                 throw new SystemException("Missing required HTTP headers 'etag' and 'last-modified'.");
             }
 
             $metaData['list'] = [];
-            if (!empty($reply['httpHeaders']['etag'])) {
-                $metaData['list']['etag'] = \reset($reply['httpHeaders']['etag']);
+            if (!empty($response->getHeaders()['etag'])) {
+                $metaData['list']['etag'] = \reset($response->getHeaders()['etag']);
             }
-            if (!empty($reply['httpHeaders']['last-modified'])) {
-                $metaData['list']['lastModified'] = \reset($reply['httpHeaders']['last-modified']);
+            if (!empty($response->getHeaders()['last-modified'])) {
+                $metaData['list']['lastModified'] = \reset($response->getHeaders()['last-modified']);
             }
         }
         $data['metaData'] = \serialize($metaData);
 
-        unset($request, $reply);
+        unset($request, $response);
 
         if ($allNewPackages !== false) {
             // purge package list
index 808e3c813e4b13eb08ee5067aa79f26dd9e693cb..8ac5b02e480c7a55c05e3a6b2759dfb8288ce71c 100644 (file)
@@ -5,64 +5,37 @@ namespace wcf\system\package;
 use wcf\data\package\update\server\PackageUpdateServer;
 use wcf\system\exception\UserException;
 use wcf\system\WCF;
-use wcf\util\HTTPRequest;
 
 /**
- * Credentials for update server are either missing or invalid.
+ * Handles the case that the credentials for update server are either missing or invalid.
  *
- * @author  Alexander Ebert
- * @copyright   2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @author      Alexander Ebert
+ * @copyright   2001-2024 WoltLab GmbH
+ * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  */
-class PackageUpdateUnauthorizedException extends UserException
+final class PackageUpdateUnauthorizedException extends UserException
 {
     /**
-     * package update version
-     * @var array
-     */
-    protected $packageUpdateVersion = [];
-
-    /**
-     * HTTP request object
-     * @var HTTPRequest
-     */
-    protected $request;
-
-    /**
-     * package update server object
-     * @var PackageUpdateServer
-     */
-    protected $updateServer;
-
-    /**
-     * Creates a new PackageUpdateUnauthorizedException object.
-     *
-     * @param HTTPRequest $request
-     * @param PackageUpdateServer $updateServer
-     * @param array $packageUpdateVersion
+     * @param string[] $responseHeaders
+     * @param mixed[] $packageUpdateVersion
      */
     public function __construct(
-        HTTPRequest $request,
-        PackageUpdateServer $updateServer,
-        array $packageUpdateVersion = []
+        private readonly int $responseStatusCode,
+        private readonly array $responseHeaders,
+        private readonly string $responseMessage,
+        private readonly PackageUpdateServer $updateServer,
+        private readonly array $packageUpdateVersion = []
     ) {
-        $this->request = $request;
-        $this->updateServer = $updateServer;
-        $this->packageUpdateVersion = $packageUpdateVersion;
     }
 
     /**
      * Returns the rendered template.
-     *
-     * @return  string
      */
-    public function getRenderedTemplate()
+    public function getRenderedTemplate(): string
     {
-        $serverReply = $this->request->getReply();
-
         $requiresPaidUpgrade = false;
         if ($this->updateServer->isWoltLabStoreServer() && !empty($this->packageUpdateVersion['pluginStoreFileID'])) {
-            $requiresPaidUpgrade = ($serverReply['httpHeaders']['wcf-update-server-requires-paid-upgrade'][0] ?? '') === 'true';
+            $requiresPaidUpgrade = ($this->responseHeaders['wcf-update-server-requires-paid-upgrade'][0] ?? '') === 'true';
         }
 
         if ($requiresPaidUpgrade) {
@@ -74,7 +47,7 @@ class PackageUpdateUnauthorizedException extends UserException
             return WCF::getTPL()->fetch('packageUpdateUnauthorizedPaidUpgrade');
         }
 
-        $authInsufficient = (($serverReply['httpHeaders']['wcf-update-server-auth'][0] ?? '') === 'unauthorized');
+        $authInsufficient = (($this->responseHeaders['wcf-update-server-auth'][0] ?? '') === 'unauthorized');
         if ($authInsufficient && !empty($this->packageUpdateVersion['pluginStoreFileID'])) {
             $hasOnlyTrustedServers = true;
             foreach (PackageUpdateServer::getActiveUpdateServers() as $updateServer) {
@@ -97,43 +70,19 @@ class PackageUpdateUnauthorizedException extends UserException
         WCF::getTPL()->assign([
             'authInsufficient' => $authInsufficient,
             'packageUpdateVersion' => $this->packageUpdateVersion,
-            'request' => $this->request,
             'updateServer' => $this->updateServer,
             'serverAuthData' => $this->updateServer->getAuthData(),
-            'serverReply' => $serverReply,
             'requiresPaidUpgrade' => $requiresPaidUpgrade,
+            'responseStatusCode' => $this->responseStatusCode,
+            'responseHeaders' => $this->responseHeaders,
+            'responseMessage' => $this->responseMessage,
         ]);
 
         return WCF::getTPL()->fetch('packageUpdateUnauthorized');
     }
 
-    /**
-     * Returns package update version.
-     *
-     * @return  array
-     */
-    public function getPackageUpdateVersion()
-    {
-        return $this->packageUpdateVersion;
-    }
-
-    /**
-     * Returns the HTTP request object.
-     *
-     * @return  HTTPRequest
-     */
-    public function getRequest()
-    {
-        return $this->request;
-    }
-
-    /**
-     * Returns package update server object.
-     *
-     * @return  PackageUpdateServer
-     */
-    public function getUpdateServer()
+    public function getResponseMessage(): string
     {
-        return $this->updateServer;
+        return $this->responseMessage;
     }
 }
index 9dca0d48c5686501eb566aae3aeb985d90cb08d2..50bf70962b65c4f62f02c0afabe10ddd1af90c6e 100644 (file)
@@ -26,7 +26,7 @@ use wcf\util\exception\HTTPException;
  * @author  Tim Duesterhus
  * @copyright   2001-2019 WoltLab GmbH
  * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @deprecated  5.3 - Use Guzzle via \wcf\system\io\HttpFactory.
+ * @deprecated  5.3 - Use Guzzle via \wcf\system\io\HttpFactory. Will be removed with 7.0.
  */
 final class HTTPRequest
 {
index aa5ffbe51183acb7f360a4cc18b0c2f1639ec0d1..0e53ad10a3cb68f0dea287c991eb2f764cdfa314 100644 (file)
@@ -14,7 +14,7 @@ use wcf\util\StringUtil;
  * @copyright   2001-2019 WoltLab GmbH
  * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @since   3.0
- * @deprecated  5.3 This exception is intimately tied to HTTPRequest which is deprecated.
+ * @deprecated  5.3 This exception is intimately tied to HTTPRequest which is deprecated. Will be removed with 7.0.
  */
 class HTTPException extends SystemException implements IExtraInformationException
 {