Upload via GUI (452 Dateien)

This commit is contained in:
2026-11-05 21:21:48 +00:00
parent e90c1a4563
commit 10578475f0
14 changed files with 543 additions and 18 deletions
@@ -81,3 +81,88 @@ function update_http_headers(): array
[$token] = update_token_info();
return $token !== '' ? ['Authorization: token ' . $token] : [];
}
/**
* Gehört diese Datei aus dem Update-Paket nicht auf die Installation? Plugins, Uploads, Backups und App-Dateien gehören dem System, die Plugin-Webseite
* und Entwickler-Ordner gehören nicht auf Kunden-Installationen. Ausnahme: Das Handbuch (docs/DeskHub-Handbuch.pdf) kommt mit jedem Update (Adminportal → Hilfe).
*/
function update_path_is_skipped(string $relNorm): bool
{
// Die Konfiguration gehört der Installation: nichts aus config/ wird je überschrieben oder neu angelegt (auch nicht bei anderer Groß-/Kleinschreibung)
if (preg_match('#^(\./)*config(/|$)#i', $relNorm) || preg_match('#(^|/)(\.env|config\.php)$#i', $relNorm) && !str_contains($relNorm, '/')) return true;
if ($relNorm === 'docs/DeskHub-Handbuch.pdf' || $relNorm === 'tools/fix-permissions.sh') return false;
return (bool) (preg_match('#^(plugins|uploads|backups|app_downloads)/#', $relNorm)
|| preg_match('#^(plugin-webseite|tools|tests|docs|desktop|mobile|theme-packages|vorschaubilder|\.gitea|\.github)/#', $relNorm));
}
// ─── Updates von der Plugin-Webseite ─────────────────────────────────────────
/**
* Adresse der Update-Schnittstelle auf der offiziellen Plugin-Webseite (app-release.php neben dem Katalog) oder '' ohne offiziellen Katalog.
* Der Zugangsschlüssel wird nur an diesen Server geschickt (catalog_access_headers).
*/
function update_publisher_endpoint(array $query): string
{
$base = function_exists('catalog_defaults') ? (string) (catalog_defaults()['plugins_url'] ?? '') : '';
$parts = parse_url($base);
if (!$parts || ($parts['scheme'] ?? '') !== 'https' || empty($parts['host'])) return '';
$path = rtrim(str_replace('\\', '/', dirname((string) ($parts['path'] ?? '/'))), '/');
return 'https://' . $parts['host'] . (isset($parts['port']) ? ':' . (int) $parts['port'] : '') . $path . '/app-release.php?' . http_build_query(['platform' => 'server'] + $query);
}
/**
* Die auf der Plugin-Webseite veröffentlichten System-Updates, neueste zuerst. Leer, wenn die Seite nicht antwortet oder nichts
* veröffentlicht ist (dann greift die Ausweichquelle). Jeder Eintrag hat dieselben Felder wie ein Gitea-Release (tag_name, published_at, body)
* plus sha256, size, signature und source = 'publisher'.
*
* @return list<array<string,mixed>>
*/
function update_publisher_releases(): array
{
static $cache = null;
if ($cache !== null && !isset($GLOBALS['deskhub_http_get'])) return $cache;
$url = update_publisher_endpoint(['list' => 1]);
$out = [];
if ($url !== '') {
[$status, $body] = plugin_http_get($url, 1048576, 8);
$data = $status === 200 && is_string($body) ? json_decode($body, true) : null;
foreach ((array) ($data['releases'] ?? []) as $r) {
$v = (string) ($r['version'] ?? '');
if (!is_array($r) || !preg_match('/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/', $v) || !preg_match('/^[0-9a-f]{64}$/i', (string) ($r['sha256'] ?? ''))) continue;
$out[] = ['tag_name' => $v, 'published_at' => (string) ($r['created_at'] ?? ''), 'body' => (string) ($r['notes'] ?? ''), 'sha256' => strtolower((string) $r['sha256']),
'size' => (int) ($r['size'] ?? 0), 'signature' => (string) ($r['signature'] ?? ''), 'source' => 'publisher'];
}
usort($out, fn($a, $b) => version_compare($b['tag_name'], $a['tag_name']));
}
return $cache = $out;
}
/** Ein Eintrag aus update_publisher_releases() zu einem Versions-Tag (mit oder ohne „v“) oder null. */
function update_publisher_find(string $tag): ?array
{
$v = ltrim(trim($tag), 'vV');
foreach (update_publisher_releases() as $r) if ($r['tag_name'] === $v) return $r;
return null;
}
/**
* Lädt das Paket einer Version von der Plugin-Webseite und prüft Prüfsumme und (bei eingebautem Herausgeber-Schlüssel) Signatur.
*
* @return array{0:?string,1:?string} [Pfad zur ZIP-Datei, Fehlertext]; bei Fehler ist die Datei schon gelöscht
*/
function update_publisher_fetch(array $release): array
{
$url = update_publisher_endpoint(['version' => $release['tag_name'], 'download' => 1]);
if ($url === '') return [null, 'Die Plugin-Webseite ist nicht eingetragen.'];
[$status, $body, $err] = plugin_http_get($url, 300 * 1048576, 180);
if ($status !== 200 || !is_string($body)) return [null, 'Download von der Plugin-Webseite fehlgeschlagen' . ($err ? ': ' . $err : " (HTTP $status).")];
if (!hash_equals((string) $release['sha256'], hash('sha256', $body))) return [null, 'Die Prüfsumme des Pakets stimmt nicht. Es wird nicht installiert.'];
$tmp = tempnam(sys_get_temp_dir(), 'dh_') . '.zip';
file_put_contents($tmp, $body);
if (update_signature_required()) {
$problem = update_verify_package((string) $release['tag_name'], $tmp, (string) $release['signature']);
if ($problem !== null) { @unlink($tmp); return [null, $problem]; }
}
return [$tmp, null];
}