Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand All @@ -17,12 +17,12 @@
{/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>

Expand Down Expand Up @@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -266,14 +267,26 @@ protected function downloadPackage($package, $packageUpdateVersions, $validateIn
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 = [
Expand All @@ -285,37 +298,43 @@ protected function downloadPackage($package, $packageUpdateVersions, $validateIn
$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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -11,13 +13,11 @@
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;
Expand Down Expand Up @@ -76,7 +76,7 @@ public function refreshPackageDatabase(array $packageUpdateServerIDs = [], bool
} 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)) {
Expand Down Expand Up @@ -147,23 +147,22 @@ private function getPurchasedVersions()
*/
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'])) {
Expand All @@ -174,26 +173,34 @@ private function getPackageUpdateXML(PackageUpdateServer $updateServer)
) {
$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() . ')'
);
}

Expand All @@ -204,8 +211,8 @@ private function getPackageUpdateXML(PackageUpdateServer $updateServer)
];

// 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)) {
Expand All @@ -215,27 +222,27 @@ private function getPackageUpdateXML(PackageUpdateServer $updateServer)

// 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']);
Comment on lines +236 to +237
Copy link
Copy Markdown
Member

@TimWolla TimWolla Aug 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Careful here (and in the other places where getHeaders() is used): The casing depends on the casing returned by the server [1], which is not necessarily lowercased [2].

The correct solution is $response->getHeaderLine('etag') (returns string, duplicate headers are comma-separated) or $response->getHeader('etag') (returns array) and possibly $response->hasHeader('etag').

[1]

/** @var string[][] Map of all registered headers, as original name => array of values */
private $headers = [];

[2] HTTP/2 guarantees lowercase and recent HAProxy versions emit lowercase by default even with HTTP/1.1, but other web servers will tend to use an uppercase character after every hyphen or so.

}
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
Expand Down
Loading