diff --git a/script/qemu_cache/classify_test.go b/script/qemu_cache/classify_test.go index 0d7dbbe..c5379f0 100644 --- a/script/qemu_cache/classify_test.go +++ b/script/qemu_cache/classify_test.go @@ -239,6 +239,46 @@ func TestLesMetadonneesRPMParEmpreinteSontImmuables(t *testing.T) { } } +// Un miroir préfixe le chemin à sa guise, et le chemin entier donnait deux +// clés pour un même octet. Relevé sur un journal réel : 1124 noms de paquets +// vivaient sous plusieurs chemins, et 3,18 Gio repartaient à l'amont pour des +// fichiers que le magasin détenait déjà. +func TestLesPrefixesDeMiroirNeFontQuUneCle(t *testing.T) { + for _, cas := range [][2]string{ + { + "https://a.example/rocky/10.2/AppStream/x86_64/os/Packages/r/rust-1.92.0-2.el10_2.x86_64.rpm", + "https://b.example/mirror/rocky-linux/10.2/AppStream/x86_64/os/Packages/r/rust-1.92.0-2.el10_2.x86_64.rpm", + }, + { + "https://a.example/pub/archive/fedora/linux/updates/42/Everything/x86_64/Packages/n/nodejs-libs-22.22.0-2.fc42.x86_64.rpm", + "https://b.example/pub/fedora-archive/fedora/linux/updates/42/Everything/x86_64/Packages/n/nodejs-libs-22.22.0-2.fc42.x86_64.rpm", + }, + { + "https://a.example/pub/rocky//10.2/BaseOS/x86_64/os/Packages/a/avahi-0.9-2.el10.x86_64.rpm", + "https://a.example/pub/rocky/10.2/BaseOS/x86_64/os/Packages/a/avahi-0.9-2.el10.x86_64.rpm", + }, + } { + a, _ := url.Parse(cas[0]) + b, _ := url.Parse(cas[1]) + if KeySansHote("GET", a) != KeySansHote("GET", b) { + t.Errorf("deux chemins du même fichier donnent deux clés :\n %s\n %s", + cas[0], cas[1]) + } + } +} + +// Deux distributions publient le même NOM pour d'autres octets : les confondre +// servirait le paquet de l'une à l'autre. Six segments gardent ce qui les +// sépare, cinq l'effaceraient — 38 noms en collision sur le journal relevé. +func TestDeuxDistributionsNePartagentPasLaCle(t *testing.T) { + deb, _ := url.Parse("https://deb.example/debian/pool/main/p/poppler-data/poppler-data_0.4.12-1_all.deb") + ubu, _ := url.Parse("https://ubu.example/ubuntu/pool/main/p/poppler-data/poppler-data_0.4.12-1_all.deb") + if KeySansHote("GET", deb) == KeySansHote("GET", ubu) { + t.Error("deux distributions partagent une clé : l'une serait servie" + + " avec le paquet de l'autre") + } +} + // Le même index par empreinte, servi par deux miroirs. Sans clé portable, // changer de miroir vide le cache de ses index : une installation hors ligne // échoue alors sur des octets que le magasin détient pourtant, et le message diff --git a/script/qemu_cache/main.go b/script/qemu_cache/main.go index be5503f..f770e11 100644 --- a/script/qemu_cache/main.go +++ b/script/qemu_cache/main.go @@ -25,7 +25,7 @@ import ( "time" ) -const version = "0.2.13" +const version = "0.2.14" func main() { var ( diff --git a/script/qemu_cache/store.go b/script/qemu_cache/store.go index c1e3248..ef7ec4c 100644 --- a/script/qemu_cache/store.go +++ b/script/qemu_cache/store.go @@ -95,8 +95,35 @@ func Key(method, rawURL string) string { // Réservé aux fichiers dont le NOM porte l'identité — paquets, index de // dépôt. L'appliquer à tout ferait entrer en collision les « /index.html » de // deux sites sans rapport. +// +// Le chemin ENTIER ne suffisait pas : un miroir le préfixe à sa guise — +// « /rocky/10.2/… », « /mirror/rocky-linux/10.2/… », « /pub/archive/fedora/… » +// — et le même octet prenait alors deux clés. Seule la FIN du chemin est +// retenue (voir SegmentsDeCle), ce qui réunit ces copies sans jamais +// confondre deux distributions. +// SegmentsDeCle : combien de segments de FIN de chemin identifient un fichier. +// +// Six, et pas moins : un chemin Debian en porte exactement six — +// « debian/pool/main/p//.deb » — si bien que cinq +// effaceraient le segment de distribution et donneraient la même clé au +// paquet d'Ubuntu, qui porte le même nom pour d'autres octets. Relevé sur un +// journal d'accès de 7099 noms livrés : six réunit 1021 des 1124 noms vus +// sous plusieurs chemins, sans confondre aucun contenu ; cinq en confond 38. +const SegmentsDeCle = 6 + func KeySansHote(method string, u *url.URL) string { - chemin := u.Path + segments := make([]string, 0, SegmentsDeCle+2) + for _, s := range strings.Split(u.Path, "/") { + // Les segments vides tombent : un miroir écrit « /pub/rocky//10.2 », + // et deux écritures d'un même chemin feraient sinon deux clés. + if s != "" { + segments = append(segments, s) + } + } + if len(segments) > SegmentsDeCle { + segments = segments[len(segments)-SegmentsDeCle:] + } + chemin := strings.Join(segments, "/") if u.RawQuery != "" { chemin += "?" + u.RawQuery }