mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-11-10 08:09:17 -05:00
Packages are stored as pkg:deb/{name}, without a vendor and whichever configured archive they came from. Package rules may name a vendor the way PURLs usually do (pkg:deb/debian/curl, pkg:deb/ubuntu/curl); it's dropped, so a rule covers the package from every archive. The docs row warns that Debian and Ubuntu archives only keep the current version per suite.
The mirror can't fetch deb packages, so there's no mirror test.
Refs #306
290 lines
11 KiB
Go
290 lines
11 KiB
Go
package handler
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/git-pkgs/purl"
|
|
"github.com/git-pkgs/registries/fetch"
|
|
)
|
|
|
|
func TestDebianHandler_parsePoolPath(t *testing.T) {
|
|
h := &DebianHandler{}
|
|
|
|
assertPathParser(t, "parsePoolPath", h.parsePoolPath, []pathParseCase{
|
|
{"pool/main/n/nginx/nginx_1.18.0-6_amd64.deb", "nginx", "1.18.0-6", "amd64"},
|
|
{"pool/main/libn/libncurses/libncurses6_6.2-1_amd64.deb", "libncurses6", "6.2-1", "amd64"},
|
|
{"pool/contrib/v/virtualbox/virtualbox_6.1.38-1_amd64.deb", "virtualbox", "6.1.38-1", "amd64"},
|
|
{"pool/main/g/git/git_2.39.2-1_arm64.deb", "git", "2.39.2-1", "arm64"},
|
|
{
|
|
"pool/universe/n/nmap/nmap_7.91+dfsg1+really7.80+dfsg1-2ubuntu0.1_amd64.deb",
|
|
"nmap", "7.91+dfsg1+really7.80+dfsg1-2ubuntu0.1", "amd64",
|
|
},
|
|
{"pool/main/o/openssl/openssl_3.0.2-0ubuntu1.15~build1_amd64.deb", "openssl", "3.0.2-0ubuntu1.15~build1", "amd64"},
|
|
{"invalid/path", "", "", ""},
|
|
{"pool/main/n/nginx/nginx.deb", "", "", ""},
|
|
})
|
|
}
|
|
|
|
func TestDebianHandler_Routes(t *testing.T) {
|
|
h := NewDebianHandler(nil, "http://localhost:8080", "", nil)
|
|
assertRoutesBasics(t, h.Routes(), "/dists/stable/Release", "/pool/../../../etc/passwd")
|
|
}
|
|
|
|
// TestDebianHandler_LegacyCacheKeysUnchanged pins the cache identities the
|
|
// main archive used before named repositories existed. Deployments carry warm
|
|
// caches across upgrades, so a changed key here silently discards them.
|
|
func TestDebianHandler_LegacyCacheKeysUnchanged(t *testing.T) {
|
|
const (
|
|
poolPath = "pool/main/h/hello/hello_2.10-3_amd64.deb"
|
|
distPath = "dists/trixie/InRelease"
|
|
)
|
|
|
|
h := NewDebianHandler(nil, "http://proxy.example", "https://archive.test", map[string]string{
|
|
"security": "https://security.test",
|
|
})
|
|
|
|
// Artifact cache: the bare filename, with no repository prefix.
|
|
filename := poolPath[strings.LastIndex(poolPath, "/")+1:]
|
|
if got := h.artifactCacheFilename("", filename); got != "hello_2.10-3_amd64.deb" {
|
|
t.Errorf("legacy artifact cache filename = %q, want %q", got, "hello_2.10-3_amd64.deb")
|
|
}
|
|
|
|
// Metadata cache: path separators replaced with underscores.
|
|
if got := h.metadataCacheKeyFor("", h.upstreamURL, distPath); got != "dists_trixie_InRelease" {
|
|
t.Errorf("legacy metadata cache key = %q, want %q", got, "dists_trixie_InRelease")
|
|
}
|
|
|
|
// A named repository must not reuse either identity.
|
|
if got := h.artifactCacheFilename("security", filename); got == "hello_2.10-3_amd64.deb" {
|
|
t.Error("named repository reused the legacy artifact cache filename")
|
|
}
|
|
if got := h.metadataCacheKeyFor("security", "https://security.test", distPath); got == "dists_trixie_InRelease" {
|
|
t.Error("named repository reused the legacy metadata cache key")
|
|
}
|
|
}
|
|
|
|
// TestDebianHandler_NamedRepositoryRouting checks that a named repository
|
|
// reaches its own archive while the main archive keeps serving /dists/ and
|
|
// /pool/ unchanged.
|
|
func TestDebianHandler_NamedRepositoryRouting(t *testing.T) {
|
|
mainRelease := "main archive InRelease"
|
|
mainArchive := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/dists/trixie/InRelease" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
_, _ = fmt.Fprint(w, mainRelease)
|
|
}))
|
|
defer mainArchive.Close()
|
|
|
|
securityRelease := "security archive InRelease"
|
|
securityArchive := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/dists/trixie-security/InRelease" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
_, _ = fmt.Fprint(w, securityRelease)
|
|
}))
|
|
defer securityArchive.Close()
|
|
|
|
proxy, _, _, _ := setupTestProxy(t)
|
|
proxy.CacheMetadata = true
|
|
proxy.MetadataTTL = time.Hour
|
|
proxy.HTTPClient = http.DefaultClient
|
|
|
|
h := NewDebianHandler(proxy, "http://proxy.example", mainArchive.URL, map[string]string{
|
|
"security": securityArchive.URL,
|
|
})
|
|
|
|
got := serveDebianRequest(h, "/security/dists/trixie-security/InRelease")
|
|
if got.Code != http.StatusOK || got.Body.String() != securityRelease {
|
|
t.Errorf("named repository: status = %d, body = %q, want 200 %q",
|
|
got.Code, got.Body.String(), securityRelease)
|
|
}
|
|
|
|
got = serveDebianRequest(h, "/dists/trixie/InRelease")
|
|
if got.Code != http.StatusOK || got.Body.String() != mainRelease {
|
|
t.Errorf("main archive: status = %d, body = %q, want 200 %q",
|
|
got.Code, got.Body.String(), mainRelease)
|
|
}
|
|
|
|
// An unconfigured name is not a repository, so it addresses the main
|
|
// archive as a plain path.
|
|
got = serveDebianRequest(h, "/unknown/dists/trixie/InRelease")
|
|
if got.Code == http.StatusOK {
|
|
t.Errorf("unknown repository: status = 200, want the main archive's 404")
|
|
}
|
|
}
|
|
|
|
// TestDebianHandler_UnknownRepositoryReportsConfiguredNames covers a misspelled
|
|
// repository name. {name}/dists/ is never a main-archive path, so the handler
|
|
// answers directly rather than forwarding upstream, where the reply would be an
|
|
// opaque HTML 404 that does not mention the repository at all.
|
|
func TestDebianHandler_UnknownRepositoryReportsConfiguredNames(t *testing.T) {
|
|
var upstreamHits int
|
|
mainArchive := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
upstreamHits++
|
|
http.Error(w, "<html>404 Not Found</html>", http.StatusNotFound)
|
|
}))
|
|
defer mainArchive.Close()
|
|
|
|
proxy, _, _, _ := setupTestProxy(t)
|
|
proxy.HTTPClient = http.DefaultClient
|
|
|
|
h := NewDebianHandler(proxy, "http://proxy.example", mainArchive.URL, map[string]string{
|
|
"security": "http://security.example",
|
|
"ghcli": "http://ghcli.example",
|
|
})
|
|
|
|
got := serveDebianRequest(h, "/secuirty/dists/trixie-security/InRelease")
|
|
if got.Code != http.StatusNotFound {
|
|
t.Errorf("status = %d, want %d", got.Code, http.StatusNotFound)
|
|
}
|
|
body := got.Body.String()
|
|
if !strings.Contains(body, `"secuirty"`) {
|
|
t.Errorf("body = %q, want it to name the misspelled repository", body)
|
|
}
|
|
// Sorted, so the message is stable across map iteration order.
|
|
if !strings.Contains(body, "ghcli, security") {
|
|
t.Errorf("body = %q, want it to list the configured repositories", body)
|
|
}
|
|
if upstreamHits != 0 {
|
|
t.Errorf("upstream hits = %d, want 0: the handler should answer without forwarding", upstreamHits)
|
|
}
|
|
|
|
// A root-level main-archive path still falls through, since the main
|
|
// archive really does serve files of its own there.
|
|
got = serveDebianRequest(h, "/project/trace/README")
|
|
if got.Code != http.StatusNotFound || upstreamHits != 1 {
|
|
t.Errorf("main-archive path: status = %d, upstream hits = %d, want 404 and 1",
|
|
got.Code, upstreamHits)
|
|
}
|
|
}
|
|
|
|
// TestDebianHandler_MetadataCacheKeysDoNotCollideAcrossRepositories guards the
|
|
// hashed metadata cache key. The main archive's key replaces '/' with '_', so
|
|
// a repository named "security" serving dists/trixie/InRelease would otherwise
|
|
// collide with the main archive's own security_dists_trixie_InRelease entry,
|
|
// serving one archive's signed metadata to clients of the other.
|
|
func TestDebianHandler_MetadataCacheKeysDoNotCollideAcrossRepositories(t *testing.T) {
|
|
mainRelease := "main archive InRelease"
|
|
mainArchive := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = fmt.Fprint(w, mainRelease)
|
|
}))
|
|
defer mainArchive.Close()
|
|
|
|
securityRelease := "security archive InRelease"
|
|
securityArchive := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = fmt.Fprint(w, securityRelease)
|
|
}))
|
|
defer securityArchive.Close()
|
|
|
|
proxy, _, _, _ := setupTestProxy(t)
|
|
proxy.CacheMetadata = true
|
|
proxy.MetadataTTL = time.Hour
|
|
proxy.HTTPClient = http.DefaultClient
|
|
|
|
h := NewDebianHandler(proxy, "http://proxy.example", mainArchive.URL, map[string]string{
|
|
"security": securityArchive.URL,
|
|
})
|
|
|
|
// Main archive path whose separator-based key is "security_dists_...".
|
|
first := serveDebianRequest(h, "/dists/security/dists/trixie/InRelease")
|
|
if first.Code != http.StatusOK || first.Body.String() != mainRelease {
|
|
t.Fatalf("main archive: status = %d, body = %q, want 200 %q",
|
|
first.Code, first.Body.String(), mainRelease)
|
|
}
|
|
|
|
// Served within the metadata TTL: a colliding key would return
|
|
// mainRelease here.
|
|
second := serveDebianRequest(h, "/security/dists/trixie/InRelease")
|
|
if second.Code != http.StatusOK {
|
|
t.Fatalf("named repository: status = %d, want 200: %s", second.Code, second.Body.String())
|
|
}
|
|
if second.Body.String() != securityRelease {
|
|
t.Errorf("named repository served %q, want %q (cache key collision)",
|
|
second.Body.String(), securityRelease)
|
|
}
|
|
}
|
|
|
|
// TestDebianHandler_PackageCacheScopedPerRepository checks that the same
|
|
// package filename in two archives does not resolve to one cached artifact.
|
|
func TestDebianHandler_PackageCacheScopedPerRepository(t *testing.T) {
|
|
const pkgPath = "/pool/main/h/hello/hello_2.10-3_amd64.deb"
|
|
|
|
mainPkg := "main archive package bytes"
|
|
mainArchive := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = fmt.Fprint(w, mainPkg)
|
|
}))
|
|
defer mainArchive.Close()
|
|
|
|
securityPkg := "security archive package bytes"
|
|
securityArchive := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = fmt.Fprint(w, securityPkg)
|
|
}))
|
|
defer securityArchive.Close()
|
|
|
|
proxy, _, _, _ := setupTestProxy(t)
|
|
fetcher := fetch.NewFetcher(fetch.WithHTTPClient(mainArchive.Client()), fetch.WithMaxRetries(0))
|
|
proxy.Fetcher = fetcher
|
|
t.Cleanup(func() { _ = fetcher.Close() })
|
|
|
|
h := NewDebianHandler(proxy, "http://proxy.example", mainArchive.URL, map[string]string{
|
|
"security": securityArchive.URL,
|
|
})
|
|
|
|
first := serveDebianRequest(h, pkgPath)
|
|
if first.Code != http.StatusOK || first.Body.String() != mainPkg {
|
|
t.Fatalf("main archive: status = %d, body = %q, want 200 %q",
|
|
first.Code, first.Body.String(), mainPkg)
|
|
}
|
|
|
|
second := serveDebianRequest(h, "/security"+pkgPath)
|
|
if second.Code != http.StatusOK {
|
|
t.Fatalf("named repository: status = %d, want 200: %s", second.Code, second.Body.String())
|
|
}
|
|
if second.Body.String() != securityPkg {
|
|
t.Errorf("named repository served %q, want %q (artifact cache collision)",
|
|
second.Body.String(), securityPkg)
|
|
}
|
|
}
|
|
|
|
func serveDebianRequest(h *DebianHandler, target string) *httptest.ResponseRecorder {
|
|
w := httptest.NewRecorder()
|
|
h.Routes().ServeHTTP(w, httptest.NewRequest(http.MethodGet, target, nil))
|
|
return w
|
|
}
|
|
|
|
func TestDebRetentionKey(t *testing.T) {
|
|
tests := []struct {
|
|
key string
|
|
want string
|
|
ok bool
|
|
}{
|
|
{"pkg:deb/curl", "pkg:deb/curl", true},
|
|
{"pkg:deb/debian/curl", "pkg:deb/curl", true},
|
|
{"pkg:deb/ubuntu/Curl", "pkg:deb/curl", true},
|
|
{"pkg:deb/debian/libstdc++6", "pkg:deb/libstdc%2B%2B6", true},
|
|
{"pkg:deb/debian/main/curl", "", false},
|
|
{"pkg:deb/debian/pool%2Fcurl", "", false},
|
|
{"pkg:apk/debian/curl", "", false},
|
|
}
|
|
for _, tt := range tests {
|
|
p, err := purl.Parse(tt.key)
|
|
if err != nil {
|
|
t.Fatalf("Parse(%s): %v", tt.key, err)
|
|
}
|
|
got, ok := debRetentionKey(p)
|
|
if ok != tt.ok || (ok && got != tt.want) {
|
|
t.Errorf("debRetentionKey(%s) = %q, %v, want %q, %v", tt.key, got, ok, tt.want, tt.ok)
|
|
}
|
|
}
|
|
if _, ok := debRetentionKey(nil); ok {
|
|
t.Error("debRetentionKey(nil) accepted")
|
|
}
|
|
}
|