diff --git a/server/admin/versions.php b/server/admin/versions.php index c42def6..7e1a769 100644 --- a/server/admin/versions.php +++ b/server/admin/versions.php @@ -88,6 +88,27 @@ function saveManifest(string $path, array $manifest): void ); } +/** + * Normalise un nom de fichier ZIP saisi par l'admin : + * - strip tout chemin (path traversal défense, basename) + * - whitelist [a-zA-Z0-9_.-] (laisse les majuscules, points, tirets, underscores) + * - assure le suffixe .zip + * - retombe sur la valeur par défaut si l'input est vide ou inexploitable + */ +function ps_normalize_zip_filename(string $raw, string $version): string +{ + $default = "proserve-{$version}.zip"; + $name = trim($raw); + if ($name === '') return $default; + // Strip tout chemin → garde juste le filename (sécurité) + $name = basename($name); + // Whitelist : on garde un set de chars filename-safe sur Windows + Linux + $name = preg_replace('/[^a-zA-Z0-9_.\-]/', '', $name); + if ($name === '' || $name === '.zip' || $name === '.') return $default; + if (!str_ends_with(strtolower($name), '.zip')) $name .= '.zip'; + return $name; +} + if ($_SERVER['REQUEST_METHOD'] === 'POST') { Auth::checkCsrf(); $action = $_POST['action'] ?? ''; @@ -119,15 +140,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $base = $config['base_url'] ?? 'https://asterionvr.com/PS_Launcher'; // Pour un channel non-default, le ZIP vit dans builds/{channel}/. - // L'admin peut surcharger l'URL après-coup si la convention diffère. $zipPathPrefix = $channel === '' ? 'builds' : "builds/{$channel}"; + // Le nom du fichier ZIP est libre : par défaut proserve-{version}.zip, + // mais l'admin peut surcharger pour distinguer des builds homonymes + // entre channels (ex. proserve-1.4.6-asterion.zip vs proserve-1.4.6-foo.zip) + // ou pour un naming custom du fournisseur. + $zipFilename = ps_normalize_zip_filename((string)($_POST['zip_filename'] ?? ''), $version); $entry = [ 'version' => $version, 'releasedAt' => $releasedAtIso, 'executable' => 'PROSERVE_UE_5_5.exe', 'installFolderTemplate' => 'PROSERVE v{version}', 'download' => [ - 'url' => "{$base}/{$zipPathPrefix}/proserve-{$version}.zip", + 'url' => "{$base}/{$zipPathPrefix}/{$zipFilename}", 'sizeBytes' => 0, 'sha256' => 'REPLACE_AFTER_BUILD', ], @@ -161,6 +186,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $version = $_POST['version'] ?? ''; $minLicDate = trim($_POST['min_license_date'] ?? ''); $releasedAt = trim($_POST['released_at'] ?? ''); + // Renommer le ZIP : on accepte un nouveau filename qui remplace + // celui en bout d'URL (le préfixe builds/{channel}/ est conservé). + // Vide = pas de changement. + $newZipName = trim((string)($_POST['zip_filename'] ?? '')); if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $minLicDate)) { throw new Exception('min_license_date invalide.'); } @@ -170,12 +199,29 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($releasedAt !== '') { $v['releasedAt'] = (new DateTime($releasedAt, new DateTimeZone('UTC')))->format('Y-m-d\TH:i:s\Z'); } + if ($newZipName !== '') { + $normalized = ps_normalize_zip_filename($newZipName, $version); + // Reconstruit l'URL en remplaçant juste le filename, pas + // le path. Permet de garder un éventuel sous-dossier custom + // tout en renommant le ZIP. Force aussi la re-vérif du sha256 + // au prochain Sync (le ZIP cherché change physiquement). + $oldUrl = $v['download']['url'] ?? ''; + $parsed = parse_url($oldUrl); + if ($parsed !== false && isset($parsed['path'])) { + $newPath = rtrim(dirname($parsed['path']), '/') . '/' . $normalized; + $newUrl = ($parsed['scheme'] ?? 'https') . '://' . ($parsed['host'] ?? 'asterionvr.com') . $newPath; + $v['download']['url'] = $newUrl; + // Invalide l'ancien hash : un nouveau ZIP est attendu + $v['download']['sha256'] = 'REPLACE_AFTER_BUILD'; + $v['download']['sizeBytes'] = 0; + } + } break; } } unset($v); saveManifest($manifestPath, $manifest); - $message = "Méta de v{$version} mises à jour."; + $message = "Méta de v{$version} mises à jour" . ($newZipName !== '' ? ". ⚠️ Le ZIP a été renommé : upload le nouveau fichier puis re-Sync." : '.'); } elseif ($action === 'set_beta') { $version = $_POST['version'] ?? ''; @@ -390,6 +436,10 @@ Layout::header('Versions', 'versions'); +