v1.0.5 — Fix /download-url sur manifests multi-channels (same-version)
Bug rapporté : sur un manifest avec deux entrées partageant le numéro de
version 1.5.4.32 (channels proserve-firefighter vs proserve-full), le
client se prenait au démarrage de l'install le garde-fou :
« Incohérence serveur : l'endpoint /download-url retourne un nom de
fichier différent du manifest. Manifest : proserve-full-1.5.4.32.zip.
Signé : proserve-firefighter-1.5.4.32.zip. → DownloadUrl.php côté
serveur doit lire le filename depuis manifest.download.url, pas via
un template hardcodé. »
Root cause : DownloadUrl.php faisait un `foreach ... if version match {
break; }` — il retournait donc TOUJOURS la première entrée matchant le
numéro, quel que soit le channel réellement cliqué côté client. Pareil
que le bug de sync_one (même famille de problèmes), mais côté endpoint
runtime du client.
Fix côté serveur : /download-url accepte maintenant un query param optionnel
`?filename=proserve-full-1.5.4.32.zip`. Si présent, le foreach filtre sur
(numéro version AND basename(download.url) == filename attendu). Whitelist
défensive sur le filename (path traversal). Rétro-compat : sans param, le
1er match par numéro gagne comme avant.
Fix côté client : le client extrait le filename attendu de `row.Remote.
Download.Url` (déjà connu, signé Ed25519) et le passe à l'endpoint. Deux
sites d'appel modifiés : le call initial dans InstallVersionAsync + le
callback RefreshUrlAsync (utilisé quand un segment reçoit 403/410 mid-DL
et qu'il faut re-signer). Sans le refresh à jour, un DL long sur ADSL
tomberait au 1er refresh forcé.
Extension d'interface : ILicenseService.GetSignedDownloadUrlAsync prend
maintenant un `string? expectedFilename` en 2e param. Callers qui passent
null continuent de fonctionner comme avant (utile pour les tests).
Bump : 1.0.4 → 1.0.5 (bug fix ciblé sur les setups multi-channel).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1531,7 +1531,15 @@ public sealed partial class MainViewModel : ObservableObject
|
||||
else
|
||||
{
|
||||
_currentPeerHost = null;
|
||||
var signed = await _licenseService.GetSignedDownloadUrlAsync(row.Version, ct);
|
||||
// Extrait le filename attendu depuis l'URL du manifest signé pour le
|
||||
// passer à /download-url. Nécessaire sur les manifestes multi-channels
|
||||
// où plusieurs entrées partagent un numéro de version (firefighter vs
|
||||
// full sur v1.5.4.32) : sans ça le serveur retourne l'URL du 1er match
|
||||
// par numéro → mismatch filename → abort avec l'erreur InvalidOperation
|
||||
// ci-dessous. Rétro-compat : un serveur qui ignore ?filename= retombe
|
||||
// sur son comportement historique.
|
||||
var expectedFilename = Path.GetFileName(new Uri(row.Remote.Download.Url).AbsolutePath);
|
||||
var signed = await _licenseService.GetSignedDownloadUrlAsync(row.Version, expectedFilename, ct);
|
||||
var urlString = signed ?? row.Remote.Download.Url;
|
||||
url = new Uri(urlString);
|
||||
|
||||
@@ -1573,7 +1581,10 @@ public sealed partial class MainViewModel : ObservableObject
|
||||
async Task<Uri?> RefreshUrlAsync(CancellationToken c)
|
||||
{
|
||||
if (peerSrc is not null) return peerSrc.ZipUrl;
|
||||
var fresh = await _licenseService.GetSignedDownloadUrlAsync(row.Version, c);
|
||||
// Passe le filename attendu — le refresh doit re-signer la même
|
||||
// ligne channel qu'à l'origine, pas la 1re entrée par version.
|
||||
var refreshFilename = Path.GetFileName(new Uri(row.Remote.Download.Url).AbsolutePath);
|
||||
var fresh = await _licenseService.GetSignedDownloadUrlAsync(row.Version, refreshFilename, c);
|
||||
return fresh is null ? null : new Uri(fresh);
|
||||
}
|
||||
var job = new DownloadJob(row.Version, url, row.Remote.Download.SizeBytes, row.Remote.Download.Sha256)
|
||||
|
||||
Reference in New Issue
Block a user