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
49 changes: 43 additions & 6 deletions index.php
Original file line number Diff line number Diff line change
Expand Up @@ -579,28 +579,65 @@ private function getUpdateServerResponse(): array {
$updateURL = $updaterServer . '?version=' . str_replace('.', 'x', $this->getConfigOptionMandatoryString('version')) . 'xxx' . $releaseChannel . 'xx' . urlencode($this->buildTime) . 'x' . PHP_MAJOR_VERSION . 'x' . PHP_MINOR_VERSION . 'x' . PHP_RELEASE_VERSION;
$this->silentLog('[info] updateURL: ' . $updateURL);

$maxRetries = 2;
$lastException = null;

for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
try {
return $this->fetchUpdateServerResponse($updateURL);
} catch (\Exception $e) {
$lastException = $e;
$this->silentLog('[warn] attempt ' . $attempt . '/' . $maxRetries . ' failed: ' . $e->getMessage());
if ($attempt < $maxRetries) {
sleep(1);
}
}
}

throw $lastException;
}

/**
* @throws \Exception
*/
private function fetchUpdateServerResponse(string $updateURL): array {
// Download update response
$curl = $this->getCurl($updateURL);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($curl, CURLOPT_TIMEOUT, 30);

/** @var false|string $response */
$response = curl_exec($curl);

if ($response === false) {
throw new \Exception('Could not do request to updater server: ' . curl_error($curl));
$curlError = curl_error($curl);
curl_close($curl);
throw new \Exception('Could not do request to updater server: ' . $curlError);
}

$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);

if ($httpCode !== 200 && $httpCode !== 204) {
$this->silentLog('[warn] update server returned HTTP ' . $httpCode);
throw new \Exception('Update server returned unexpected HTTP status ' . $httpCode);
}

// Response can be empty when no update is available
if ($response === '') {
return [];
}

libxml_use_internal_errors(true);
$xml = simplexml_load_string($response);
if ($xml === false) {
$content = strlen($response) > 200 ? substr($response, 0, 200) . '…' : $response;
$errors = implode("\n", array_map(fn ($error) => $error->message, libxml_get_errors()));
throw new \Exception('Could not parse updater server XML response: ' . $content . "\nErrors:\n" . $errors);
try {
$xml = simplexml_load_string($response);
if ($xml === false) {
$content = strlen($response) > 200 ? substr($response, 0, 200) . '…' : $response;
$errors = implode("\n", array_map(fn ($error) => $error->message, libxml_get_errors()));
throw new \Exception('Could not parse updater server XML response: ' . $content . "\nErrors:\n" . $errors);
}
} finally {
libxml_clear_errors();
}

$response = get_object_vars($xml);
Expand Down
49 changes: 43 additions & 6 deletions lib/Updater.php
Original file line number Diff line number Diff line change
Expand Up @@ -563,28 +563,65 @@
$updateURL = $updaterServer . '?version=' . str_replace('.', 'x', $this->getConfigOptionMandatoryString('version')) . 'xxx' . $releaseChannel . 'xx' . urlencode($this->buildTime) . 'x' . PHP_MAJOR_VERSION . 'x' . PHP_MINOR_VERSION . 'x' . PHP_RELEASE_VERSION;
$this->silentLog('[info] updateURL: ' . $updateURL);

$maxRetries = 2;
$lastException = null;

Check failure on line 567 in lib/Updater.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

UnusedVariable

lib/Updater.php:567:3: UnusedVariable: $lastException is never referenced or the value is not used (see https://psalm.dev/077)

for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
try {
return $this->fetchUpdateServerResponse($updateURL);
} catch (\Exception $e) {
$lastException = $e;
$this->silentLog('[warn] attempt ' . $attempt . '/' . $maxRetries . ' failed: ' . $e->getMessage());
if ($attempt < $maxRetries) {
sleep(1);
}
}
}

throw $lastException;
}

/**
* @throws \Exception
*/
private function fetchUpdateServerResponse(string $updateURL): array {
// Download update response
$curl = $this->getCurl($updateURL);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($curl, CURLOPT_TIMEOUT, 30);

/** @var false|string $response */
$response = curl_exec($curl);

if ($response === false) {
throw new \Exception('Could not do request to updater server: ' . curl_error($curl));
$curlError = curl_error($curl);
curl_close($curl);
throw new \Exception('Could not do request to updater server: ' . $curlError);
}

$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);

if ($httpCode !== 200 && $httpCode !== 204) {
$this->silentLog('[warn] update server returned HTTP ' . $httpCode);
throw new \Exception('Update server returned unexpected HTTP status ' . $httpCode);
}

// Response can be empty when no update is available
if ($response === '') {
return [];
}

libxml_use_internal_errors(true);
$xml = simplexml_load_string($response);
if ($xml === false) {
$content = strlen($response) > 200 ? substr($response, 0, 200) . '…' : $response;
$errors = implode("\n", array_map(fn ($error) => $error->message, libxml_get_errors()));
throw new \Exception('Could not parse updater server XML response: ' . $content . "\nErrors:\n" . $errors);
try {
$xml = simplexml_load_string($response);
if ($xml === false) {
$content = strlen($response) > 200 ? substr($response, 0, 200) . '…' : $response;
$errors = implode("\n", array_map(fn ($error) => $error->message, libxml_get_errors()));
throw new \Exception('Could not parse updater server XML response: ' . $content . "\nErrors:\n" . $errors);
}
} finally {
libxml_clear_errors();
}

$response = get_object_vars($xml);
Expand Down
52 changes: 51 additions & 1 deletion tests/features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ public function theCliUpdaterIsRun() {
copy($this->buildDir . 'updater.phar', $this->serverDir . 'nextcloud/updater/updater');
chdir($this->serverDir . 'nextcloud/updater');
chmod($this->serverDir . 'nextcloud/updater/updater', 0755);
exec('./updater -n', $output, $returnCode);
exec('./updater -n 2>&1', $output, $returnCode);

// sleep to let the opcache do it's work and invalidate the status.php
sleep(5);
Expand Down Expand Up @@ -564,4 +564,54 @@ public function thereIsAConfigForASecondaryAppsDirectoryCalled($name) {
public function phpIsAtLeastInVersion($version) {
$this->skipIt = in_array(version_compare($version, PHP_VERSION, '<'), [0, false], true);
}

/**
* @Given the update server returns HTTP status :statusCode
*/
public function theUpdateServerReturnsHttpStatus(int $statusCode) {
if ($this->skipIt) {
return;
}

$this->runUpdateServer();

$content = '<?php http_response_code(' . $statusCode . '); echo "Server Error";';
file_put_contents($this->updateServerDir . 'index.php', $content);
}

/**
* @Given the update server returns invalid XML
*/
public function theUpdateServerReturnsInvalidXml() {
if ($this->skipIt) {
return;
}

$this->runUpdateServer();

$content = '<?php header("Content-Type: application/xml"); echo "this is not valid xml <><><";';
file_put_contents($this->updateServerDir . 'index.php', $content);
}

/**
* @Given the update server is unreachable
*/
public function theUpdateServerIsUnreachable() {
if ($this->skipIt) {
return;
}

// Point updater.server.url at a port with nothing listening
$configFile = $this->serverDir . 'nextcloud/config/config.php';
$content = file_get_contents($configFile);
$content = preg_replace(
'!\$CONFIG\s*=\s*array\s*\(!',
"\$CONFIG = array(\n 'updater.server.url' => 'http://localhost:8871/',",
$content
);
file_put_contents($configFile, $content);

// Intentionally do NOT start any server on port 8871
}
}

30 changes: 30 additions & 0 deletions tests/features/cli.feature
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,33 @@ Feature: CLI updater
And the installed version should be 26.0.0
And maintenance mode should be off
And upgrade is not required

Scenario: Update server returns HTTP 500 - 26.0.0
Given the current installed version is 26.0.0
And the update server returns HTTP status 500
When the CLI updater is run
Then the return code should not be 0
And the output should contain "Update server returned unexpected HTTP status 500"
And the installed version should be 26.0.0
And maintenance mode should be off
And upgrade is not required

Scenario: Update server returns invalid XML - 26.0.0
Given the current installed version is 26.0.0
And the update server returns invalid XML
When the CLI updater is run
Then the return code should not be 0
And the output should contain "Could not parse updater server XML response"
And the installed version should be 26.0.0
And maintenance mode should be off
And upgrade is not required

Scenario: Update server is unreachable - 26.0.0
Given the current installed version is 26.0.0
And the update server is unreachable
When the CLI updater is run
Then the return code should not be 0
And the output should contain "Could not do request to updater server"
And the installed version should be 26.0.0
And maintenance mode should be off
And upgrade is not required
Binary file modified updater.phar
Binary file not shown.
Loading