fix(deb): normalize apt flat-repo ./ dist prefix in local index
Real apt appends the flat-repo dist "./" verbatim, requesting <repo>/./Packages (and ./Release, ./InRelease). ServeLocalIndex matched the literal filename and 404d, breaking the documented `deb ... <repo>/ ./` one-liner; curl/browsers pre-normalize /./ which masked it. Collapse the dot-segment via a small normalizeIndexPath helper before matching, a no-op for pool/*.deb lookups.
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -305,7 +306,16 @@ func parseControlFields(control string) map[string]string {
|
|||||||
return fields
|
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 {
|
switch path {
|
||||||
case "Packages", "Packages.gz", "Release":
|
case "Packages", "Packages.gz", "Release":
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -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) {
|
func TestDebServeMetadataError(t *testing.T) {
|
||||||
p := &Provider{}
|
p := &Provider{}
|
||||||
for _, path := range []string{"Packages", "Packages.gz", "Release"} {
|
for _, path := range []string{"Packages", "Packages.gz", "Release"} {
|
||||||
|
|||||||
Reference in New Issue
Block a user