Add deb (Debian/apt) local and remote repository support #111

Merged
benvin merged 2 commits from benvin/deb-local-remote into master 2026-08-11 23:21:08 +10:00
2 changed files with 58 additions and 1 deletions
Showing only changes of commit d7e6ec43ff - Show all commits
+11 -1
View File
@@ -14,6 +14,7 @@ import (
"io"
"log/slog"
"net/http"
"path"
"regexp"
"strconv"
"strings"
@@ -305,7 +306,16 @@ func parseControlFields(control string) map[string]string {
return fields
}
func (p *Provider) ServeLocalIndex(w http.ResponseWriter, r *http.Request, files provider.FileStore, repoName, path string) bool {
// normalizeIndexPath collapses apt's verbatim dist prefix from a flat-repo
// request. For `deb ... <repo>/ ./`, apt appends the "./" dist literally and asks
// for "./Packages" (and "./Release", "./InRelease"); dot-segments must be
// collapsed so the index matcher sees "Packages". A no-op for pool/*.deb paths.
func normalizeIndexPath(p string) string {
return strings.TrimPrefix(path.Clean("/"+p), "/")
}
func (p *Provider) ServeLocalIndex(w http.ResponseWriter, r *http.Request, files provider.FileStore, repoName, reqPath string) bool {
path := normalizeIndexPath(reqPath)
switch path {
case "Packages", "Packages.gz", "Release":
default:
+47
View File
@@ -295,6 +295,53 @@ func TestDebServeLocalIndex(t *testing.T) {
}
}
// Real apt appends the flat-repo dist "./" verbatim, so it requests "./Packages"
// / "./Release" (curl pre-normalizes /./ which masks this). The handler must
// collapse the dot-segment and return the same bytes as the un-prefixed request.
func TestDebServeLocalIndexAptDotSegment(t *testing.T) {
p := &Provider{}
reader := fakeDebReader{metas: []provider.DebMetadata{
{Name: "aaa", Version: "1.0", Architecture: "amd64", FilePath: "pool/aaa_1.0_amd64.deb",
Control: "Package: aaa\nVersion: 1.0\nArchitecture: amd64", Size: 100, MD5: "md5aaa", SHA256: "sha256aaa"},
}}
serve := func(path string) *httptest.ResponseRecorder {
w := httptest.NewRecorder()
r := httptest.NewRequest(http.MethodGet, "/"+path, nil)
if !p.ServeLocalIndex(w, r, reader, "myrepo", path) {
t.Fatalf("ServeLocalIndex returned false for %q", path)
}
return w
}
// Packages is deterministic: require exact byte identity.
if plain, dotted := serve("Packages"), serve("./Packages"); plain.Code != 200 || dotted.Code != 200 {
t.Fatalf("Packages: plain=%d dotted=%d, want 200/200", plain.Code, dotted.Code)
} else if !bytes.Equal(plain.Body.Bytes(), dotted.Body.Bytes()) {
t.Error("./Packages body differs from Packages body")
}
// Release carries a Date: header stamped from time.Now(); compare the rest.
plain, dotted := serve("Release"), serve("./Release")
if plain.Code != 200 || dotted.Code != 200 {
t.Fatalf("Release: plain=%d dotted=%d, want 200/200", plain.Code, dotted.Code)
}
if stripDate(plain.Body.String()) != stripDate(dotted.Body.String()) {
t.Error("./Release body differs from Release body (ignoring Date)")
}
}
func stripDate(s string) string {
var out []string
for _, line := range strings.Split(s, "\n") {
if strings.HasPrefix(line, "Date:") {
continue
}
out = append(out, line)
}
return strings.Join(out, "\n")
}
func TestDebServeMetadataError(t *testing.T) {
p := &Provider{}
for _, path := range []string{"Packages", "Packages.gz", "Release"} {