Fix Composer minified metadata expansion and namespaced package routing (#63)
* Fix Composer minified metadata expansion and namespaced package routing
Packagist serves metadata in a minified format where only the first version
entry has all fields and subsequent entries inherit from the previous one.
The proxy was passing this through without expanding it, which meant cooldown
filtering could break the inheritance chain (losing fields like `name`) and
`~dev` sentinel markers were silently dropped.
The proxy now expands the minified format before filtering and rewriting,
ensuring every version entry is self-contained.
Web UI and API routes used single-segment chi URL params for package names,
which broke for Composer's `vendor/name` format. `/package/composer/monolog/monolog`
would match the version show route instead of the package show route.
All `/package/` and related API routes now use wildcard paths with a
`resolvePackageName` helper that tries increasingly longer path prefixes as
package names via DB lookup, correctly handling namespaced packages across
all endpoints (show, version, browse, compare, vulns).
Fixes #61, fixes #62
* Add namespaced package routing tests for all affected ecosystems
Verifies the wildcard routing handles slashes in package names for
npm (@babel/core), Go modules (github.com/stretchr/testify),
OCI images (library/nginx), Conda (conda-forge/numpy), and
Conan (zlib/1.2.13@demo/stable).
* Regenerate swagger docs after route refactor
The swagger annotations for the old per-endpoint handlers were removed
during the wildcard routing refactor. Regenerate to match current state.
2026-04-06 13:07:02 +01:00
|
|
|
package server
|
|
|
|
|
|
|
|
|
|
import (
|
2026-08-14 11:38:08 +02:00
|
|
|
"net/http"
|
|
|
|
|
"net/http/httptest"
|
Fix Composer minified metadata expansion and namespaced package routing (#63)
* Fix Composer minified metadata expansion and namespaced package routing
Packagist serves metadata in a minified format where only the first version
entry has all fields and subsequent entries inherit from the previous one.
The proxy was passing this through without expanding it, which meant cooldown
filtering could break the inheritance chain (losing fields like `name`) and
`~dev` sentinel markers were silently dropped.
The proxy now expands the minified format before filtering and rewriting,
ensuring every version entry is self-contained.
Web UI and API routes used single-segment chi URL params for package names,
which broke for Composer's `vendor/name` format. `/package/composer/monolog/monolog`
would match the version show route instead of the package show route.
All `/package/` and related API routes now use wildcard paths with a
`resolvePackageName` helper that tries increasingly longer path prefixes as
package names via DB lookup, correctly handling namespaced packages across
all endpoints (show, version, browse, compare, vulns).
Fixes #61, fixes #62
* Add namespaced package routing tests for all affected ecosystems
Verifies the wildcard routing handles slashes in package names for
npm (@babel/core), Go modules (github.com/stretchr/testify),
OCI images (library/nginx), Conda (conda-forge/numpy), and
Conan (zlib/1.2.13@demo/stable).
* Regenerate swagger docs after route refactor
The swagger annotations for the old per-endpoint handlers were removed
during the wildcard routing refactor. Regenerate to match current state.
2026-04-06 13:07:02 +01:00
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
2026-05-03 09:14:18 +01:00
|
|
|
"strings"
|
Fix Composer minified metadata expansion and namespaced package routing (#63)
* Fix Composer minified metadata expansion and namespaced package routing
Packagist serves metadata in a minified format where only the first version
entry has all fields and subsequent entries inherit from the previous one.
The proxy was passing this through without expanding it, which meant cooldown
filtering could break the inheritance chain (losing fields like `name`) and
`~dev` sentinel markers were silently dropped.
The proxy now expands the minified format before filtering and rewriting,
ensuring every version entry is self-contained.
Web UI and API routes used single-segment chi URL params for package names,
which broke for Composer's `vendor/name` format. `/package/composer/monolog/monolog`
would match the version show route instead of the package show route.
All `/package/` and related API routes now use wildcard paths with a
`resolvePackageName` helper that tries increasingly longer path prefixes as
package names via DB lookup, correctly handling namespaced packages across
all endpoints (show, version, browse, compare, vulns).
Fixes #61, fixes #62
* Add namespaced package routing tests for all affected ecosystems
Verifies the wildcard routing handles slashes in package names for
npm (@babel/core), Go modules (github.com/stretchr/testify),
OCI images (library/nginx), Conda (conda-forge/numpy), and
Conan (zlib/1.2.13@demo/stable).
* Regenerate swagger docs after route refactor
The swagger annotations for the old per-endpoint handlers were removed
during the wildcard routing refactor. Regenerate to match current state.
2026-04-06 13:07:02 +01:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"github.com/git-pkgs/proxy/internal/database"
|
2026-08-14 11:38:08 +02:00
|
|
|
"github.com/go-chi/chi/v5"
|
Fix Composer minified metadata expansion and namespaced package routing (#63)
* Fix Composer minified metadata expansion and namespaced package routing
Packagist serves metadata in a minified format where only the first version
entry has all fields and subsequent entries inherit from the previous one.
The proxy was passing this through without expanding it, which meant cooldown
filtering could break the inheritance chain (losing fields like `name`) and
`~dev` sentinel markers were silently dropped.
The proxy now expands the minified format before filtering and rewriting,
ensuring every version entry is self-contained.
Web UI and API routes used single-segment chi URL params for package names,
which broke for Composer's `vendor/name` format. `/package/composer/monolog/monolog`
would match the version show route instead of the package show route.
All `/package/` and related API routes now use wildcard paths with a
`resolvePackageName` helper that tries increasingly longer path prefixes as
package names via DB lookup, correctly handling namespaced packages across
all endpoints (show, version, browse, compare, vulns).
Fixes #61, fixes #62
* Add namespaced package routing tests for all affected ecosystems
Verifies the wildcard routing handles slashes in package names for
npm (@babel/core), Go modules (github.com/stretchr/testify),
OCI images (library/nginx), Conda (conda-forge/numpy), and
Conan (zlib/1.2.13@demo/stable).
* Regenerate swagger docs after route refactor
The swagger annotations for the old per-endpoint handlers were removed
during the wildcard routing refactor. Regenerate to match current state.
2026-04-06 13:07:02 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func newTestDB(t *testing.T) (*database.DB, func()) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
dir, err := os.MkdirTemp("", "resolve-test-*")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
db, err := database.Create(filepath.Join(dir, "test.db"))
|
|
|
|
|
if err != nil {
|
|
|
|
|
_ = os.RemoveAll(dir)
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
return db, func() { _ = db.Close(); _ = os.RemoveAll(dir) }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func seedPackage(t *testing.T, db *database.DB, ecosystem, name, purl string) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
if err := db.UpsertPackage(&database.Package{
|
|
|
|
|
PURL: purl, Ecosystem: ecosystem, Name: name,
|
|
|
|
|
}); err != nil {
|
|
|
|
|
t.Fatalf("failed to upsert package %s: %v", name, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestResolvePackageName(t *testing.T) {
|
|
|
|
|
db, cleanup := newTestDB(t)
|
|
|
|
|
defer cleanup()
|
|
|
|
|
|
|
|
|
|
seedPackage(t, db, "npm", "lodash", "pkg:npm/lodash")
|
|
|
|
|
seedPackage(t, db, "composer", "monolog/monolog", "pkg:composer/monolog/monolog")
|
|
|
|
|
seedPackage(t, db, "composer", "symfony/console", "pkg:composer/symfony/console")
|
|
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
ecosystem string
|
|
|
|
|
segments []string
|
|
|
|
|
wantName string
|
|
|
|
|
wantRest []string
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "simple package", ecosystem: "npm",
|
|
|
|
|
segments: []string{"lodash"}, wantName: "lodash", wantRest: nil,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "simple package with version", ecosystem: "npm",
|
|
|
|
|
segments: []string{"lodash", "4.17.21"}, wantName: "lodash", wantRest: []string{"4.17.21"},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "namespaced package", ecosystem: "composer",
|
|
|
|
|
segments: []string{"monolog", "monolog"}, wantName: "monolog/monolog", wantRest: nil,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "namespaced package with version", ecosystem: "composer",
|
|
|
|
|
segments: []string{"symfony", "console", "6.0.0"}, wantName: "symfony/console", wantRest: []string{"6.0.0"},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "namespaced with version and action", ecosystem: "composer",
|
|
|
|
|
segments: []string{"symfony", "console", "6.0.0", "browse"},
|
|
|
|
|
wantName: "symfony/console", wantRest: []string{"6.0.0", "browse"},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "not found", ecosystem: "npm",
|
|
|
|
|
segments: []string{"nonexistent"}, wantName: "", wantRest: []string{"nonexistent"},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
name, rest := resolvePackageName(db, tt.ecosystem, tt.segments)
|
|
|
|
|
if name != tt.wantName {
|
|
|
|
|
t.Errorf("name = %q, want %q", name, tt.wantName)
|
|
|
|
|
}
|
|
|
|
|
if len(rest) != len(tt.wantRest) {
|
|
|
|
|
t.Errorf("rest = %v, want %v", rest, tt.wantRest)
|
|
|
|
|
} else {
|
|
|
|
|
for i := range rest {
|
|
|
|
|
if rest[i] != tt.wantRest[i] {
|
|
|
|
|
t.Errorf("rest[%d] = %q, want %q", i, rest[i], tt.wantRest[i])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSplitWildcardPath(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
2026-08-14 11:38:08 +02:00
|
|
|
input string
|
|
|
|
|
encoded bool
|
|
|
|
|
want []string
|
Fix Composer minified metadata expansion and namespaced package routing (#63)
* Fix Composer minified metadata expansion and namespaced package routing
Packagist serves metadata in a minified format where only the first version
entry has all fields and subsequent entries inherit from the previous one.
The proxy was passing this through without expanding it, which meant cooldown
filtering could break the inheritance chain (losing fields like `name`) and
`~dev` sentinel markers were silently dropped.
The proxy now expands the minified format before filtering and rewriting,
ensuring every version entry is self-contained.
Web UI and API routes used single-segment chi URL params for package names,
which broke for Composer's `vendor/name` format. `/package/composer/monolog/monolog`
would match the version show route instead of the package show route.
All `/package/` and related API routes now use wildcard paths with a
`resolvePackageName` helper that tries increasingly longer path prefixes as
package names via DB lookup, correctly handling namespaced packages across
all endpoints (show, version, browse, compare, vulns).
Fixes #61, fixes #62
* Add namespaced package routing tests for all affected ecosystems
Verifies the wildcard routing handles slashes in package names for
npm (@babel/core), Go modules (github.com/stretchr/testify),
OCI images (library/nginx), Conda (conda-forge/numpy), and
Conan (zlib/1.2.13@demo/stable).
* Regenerate swagger docs after route refactor
The swagger annotations for the old per-endpoint handlers were removed
during the wildcard routing refactor. Regenerate to match current state.
2026-04-06 13:07:02 +01:00
|
|
|
}{
|
2026-08-14 11:38:08 +02:00
|
|
|
{"lodash", false, []string{"lodash"}},
|
|
|
|
|
{"lodash/4.17.21", false, []string{"lodash", "4.17.21"}},
|
|
|
|
|
{"monolog/monolog", false, []string{"monolog", "monolog"}},
|
|
|
|
|
{"symfony/console/6.0.0/browse", false, []string{"symfony", "console", "6.0.0", "browse"}},
|
|
|
|
|
{"", false, nil},
|
|
|
|
|
{"/", false, nil},
|
|
|
|
|
// chi routes on the raw path when it differs from the canonical
|
|
|
|
|
// encoding of the decoded path, so segments arrive percent-encoded and
|
|
|
|
|
// must be decoded.
|
|
|
|
|
{
|
|
|
|
|
"nmap/7.91%2Bdfsg1%2Breally7.80%2Bdfsg1-2ubuntu0.1", true,
|
|
|
|
|
[]string{"nmap", "7.91+dfsg1+really7.80+dfsg1-2ubuntu0.1"},
|
|
|
|
|
},
|
|
|
|
|
{"%40babel/core/7.0.0", true, []string{"@babel", "core", "7.0.0"}},
|
|
|
|
|
// An encoded separator stays inside its segment rather than splitting.
|
|
|
|
|
{"vendor%2Fname/1.0.0", true, []string{"vendor/name", "1.0.0"}},
|
|
|
|
|
// Invalid escapes are passed through untouched.
|
|
|
|
|
{"lodash/1.0%zz", true, []string{"lodash", "1.0%zz"}},
|
|
|
|
|
// When chi routed on the already-decoded path, an escape that survived
|
|
|
|
|
// is part of the value: a version whose text is "1.0%2B" reaches here
|
|
|
|
|
// as "1.0%2B" and decoding it again would yield "1.0+".
|
|
|
|
|
{"nmap/1.0%2B", false, []string{"nmap", "1.0%2B"}},
|
Fix Composer minified metadata expansion and namespaced package routing (#63)
* Fix Composer minified metadata expansion and namespaced package routing
Packagist serves metadata in a minified format where only the first version
entry has all fields and subsequent entries inherit from the previous one.
The proxy was passing this through without expanding it, which meant cooldown
filtering could break the inheritance chain (losing fields like `name`) and
`~dev` sentinel markers were silently dropped.
The proxy now expands the minified format before filtering and rewriting,
ensuring every version entry is self-contained.
Web UI and API routes used single-segment chi URL params for package names,
which broke for Composer's `vendor/name` format. `/package/composer/monolog/monolog`
would match the version show route instead of the package show route.
All `/package/` and related API routes now use wildcard paths with a
`resolvePackageName` helper that tries increasingly longer path prefixes as
package names via DB lookup, correctly handling namespaced packages across
all endpoints (show, version, browse, compare, vulns).
Fixes #61, fixes #62
* Add namespaced package routing tests for all affected ecosystems
Verifies the wildcard routing handles slashes in package names for
npm (@babel/core), Go modules (github.com/stretchr/testify),
OCI images (library/nginx), Conda (conda-forge/numpy), and
Conan (zlib/1.2.13@demo/stable).
* Regenerate swagger docs after route refactor
The swagger annotations for the old per-endpoint handlers were removed
during the wildcard routing refactor. Regenerate to match current state.
2026-04-06 13:07:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
2026-08-14 11:38:08 +02:00
|
|
|
got := splitWildcardPath(tt.input, tt.encoded)
|
Fix Composer minified metadata expansion and namespaced package routing (#63)
* Fix Composer minified metadata expansion and namespaced package routing
Packagist serves metadata in a minified format where only the first version
entry has all fields and subsequent entries inherit from the previous one.
The proxy was passing this through without expanding it, which meant cooldown
filtering could break the inheritance chain (losing fields like `name`) and
`~dev` sentinel markers were silently dropped.
The proxy now expands the minified format before filtering and rewriting,
ensuring every version entry is self-contained.
Web UI and API routes used single-segment chi URL params for package names,
which broke for Composer's `vendor/name` format. `/package/composer/monolog/monolog`
would match the version show route instead of the package show route.
All `/package/` and related API routes now use wildcard paths with a
`resolvePackageName` helper that tries increasingly longer path prefixes as
package names via DB lookup, correctly handling namespaced packages across
all endpoints (show, version, browse, compare, vulns).
Fixes #61, fixes #62
* Add namespaced package routing tests for all affected ecosystems
Verifies the wildcard routing handles slashes in package names for
npm (@babel/core), Go modules (github.com/stretchr/testify),
OCI images (library/nginx), Conda (conda-forge/numpy), and
Conan (zlib/1.2.13@demo/stable).
* Regenerate swagger docs after route refactor
The swagger annotations for the old per-endpoint handlers were removed
during the wildcard routing refactor. Regenerate to match current state.
2026-04-06 13:07:02 +01:00
|
|
|
if len(got) != len(tt.want) {
|
2026-08-14 11:38:08 +02:00
|
|
|
t.Errorf("splitWildcardPath(%q, %v) = %v, want %v", tt.input, tt.encoded, got, tt.want)
|
Fix Composer minified metadata expansion and namespaced package routing (#63)
* Fix Composer minified metadata expansion and namespaced package routing
Packagist serves metadata in a minified format where only the first version
entry has all fields and subsequent entries inherit from the previous one.
The proxy was passing this through without expanding it, which meant cooldown
filtering could break the inheritance chain (losing fields like `name`) and
`~dev` sentinel markers were silently dropped.
The proxy now expands the minified format before filtering and rewriting,
ensuring every version entry is self-contained.
Web UI and API routes used single-segment chi URL params for package names,
which broke for Composer's `vendor/name` format. `/package/composer/monolog/monolog`
would match the version show route instead of the package show route.
All `/package/` and related API routes now use wildcard paths with a
`resolvePackageName` helper that tries increasingly longer path prefixes as
package names via DB lookup, correctly handling namespaced packages across
all endpoints (show, version, browse, compare, vulns).
Fixes #61, fixes #62
* Add namespaced package routing tests for all affected ecosystems
Verifies the wildcard routing handles slashes in package names for
npm (@babel/core), Go modules (github.com/stretchr/testify),
OCI images (library/nginx), Conda (conda-forge/numpy), and
Conan (zlib/1.2.13@demo/stable).
* Regenerate swagger docs after route refactor
The swagger annotations for the old per-endpoint handlers were removed
during the wildcard routing refactor. Regenerate to match current state.
2026-04-06 13:07:02 +01:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
for i := range got {
|
|
|
|
|
if got[i] != tt.want[i] {
|
2026-08-14 11:38:08 +02:00
|
|
|
t.Errorf("splitWildcardPath(%q, %v)[%d] = %q, want %q",
|
|
|
|
|
tt.input, tt.encoded, i, got[i], tt.want[i])
|
Fix Composer minified metadata expansion and namespaced package routing (#63)
* Fix Composer minified metadata expansion and namespaced package routing
Packagist serves metadata in a minified format where only the first version
entry has all fields and subsequent entries inherit from the previous one.
The proxy was passing this through without expanding it, which meant cooldown
filtering could break the inheritance chain (losing fields like `name`) and
`~dev` sentinel markers were silently dropped.
The proxy now expands the minified format before filtering and rewriting,
ensuring every version entry is self-contained.
Web UI and API routes used single-segment chi URL params for package names,
which broke for Composer's `vendor/name` format. `/package/composer/monolog/monolog`
would match the version show route instead of the package show route.
All `/package/` and related API routes now use wildcard paths with a
`resolvePackageName` helper that tries increasingly longer path prefixes as
package names via DB lookup, correctly handling namespaced packages across
all endpoints (show, version, browse, compare, vulns).
Fixes #61, fixes #62
* Add namespaced package routing tests for all affected ecosystems
Verifies the wildcard routing handles slashes in package names for
npm (@babel/core), Go modules (github.com/stretchr/testify),
OCI images (library/nginx), Conda (conda-forge/numpy), and
Conan (zlib/1.2.13@demo/stable).
* Regenerate swagger docs after route refactor
The swagger annotations for the old per-endpoint handlers were removed
during the wildcard routing refactor. Regenerate to match current state.
2026-04-06 13:07:02 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-03 09:14:18 +01:00
|
|
|
|
|
|
|
|
func TestValidatePackagePath(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
path string
|
|
|
|
|
wantErr bool
|
|
|
|
|
}{
|
|
|
|
|
{"simple", "lodash", false},
|
|
|
|
|
{"with version", "lodash/4.17.21", false},
|
|
|
|
|
{"npm scoped", "@babel/core/7.0.0", false},
|
|
|
|
|
{"composer namespaced", "symfony/console/6.0.0", false},
|
|
|
|
|
{"maven coordinates", "org.apache.commons/commons-lang3/3.12.0", false},
|
|
|
|
|
{"unicode", "café/1.0.0", false},
|
2026-08-14 11:38:08 +02:00
|
|
|
{"encoded plus in version", "nmap/7.91%2Bdfsg1-2ubuntu0.1", false},
|
2026-05-03 09:14:18 +01:00
|
|
|
{"empty", "", true},
|
|
|
|
|
{"null byte", "lodash\x00/4.17.21", true},
|
2026-08-14 11:38:08 +02:00
|
|
|
{"encoded null byte", "lodash/%00", true},
|
|
|
|
|
{"encoded newline", "lodash/1.0%0A", true},
|
|
|
|
|
{"parent segment", "lodash/../4.17.21", true},
|
|
|
|
|
{"encoded parent segment", "lodash/%2E%2E/4.17.21", true},
|
|
|
|
|
// A decoded segment can contain slashes, so traversal can hide inside
|
|
|
|
|
// one segment. Registries interpolate the resolved name straight into
|
|
|
|
|
// an upstream URL, and Go sends dot-segments verbatim.
|
|
|
|
|
{"traversal inside one segment", "pkg%2F..%2F..%2Fadmin", true},
|
|
|
|
|
{"traversal via encoded dots and slash", "pkg%2f%2e%2e%2fadmin", true},
|
|
|
|
|
{"encoded slash alone is allowed", "vendor%2Fname/1.0.0", false},
|
2026-05-03 09:14:18 +01:00
|
|
|
{"null byte suffix", "lodash\x00", true},
|
|
|
|
|
{"newline", "lodash\n4.17.21", true},
|
|
|
|
|
{"carriage return", "lodash\r", true},
|
|
|
|
|
{"escape", "lodash\x1b[31m", true},
|
|
|
|
|
{"delete", "lodash\x7f", true},
|
|
|
|
|
{"too long", strings.Repeat("a", maxPackagePathLen+1), true},
|
|
|
|
|
{"at limit", strings.Repeat("a", maxPackagePathLen), false},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
2026-08-14 11:38:08 +02:00
|
|
|
// The verdict must not depend on whether chi routed on the raw or
|
|
|
|
|
// on the already-decoded path: an escape that reaches a handler
|
|
|
|
|
// undecoded is decoded by the upstream registry instead, so it is
|
|
|
|
|
// rejected either way.
|
|
|
|
|
for _, encoded := range []bool{false, true} {
|
|
|
|
|
err := validatePackagePath(tt.path, encoded)
|
|
|
|
|
if (err != nil) != tt.wantErr {
|
|
|
|
|
t.Errorf("validatePackagePath(%q, %v) error = %v, wantErr %v",
|
|
|
|
|
tt.path, encoded, err, tt.wantErr)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestPackagePathSegments drives the real router, which is what decides whether
|
|
|
|
|
// the wildcard still carries percent-encoding. Go decodes the request path
|
|
|
|
|
// itself unless the escaping is non-canonical, so the same version can arrive
|
|
|
|
|
// either way and only one of the two forms may be decoded again.
|
|
|
|
|
func TestPackagePathSegments(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
target string
|
|
|
|
|
want []string
|
|
|
|
|
}{
|
|
|
|
|
{"plain", "/pkg/npm/lodash/4.17.21", []string{"lodash", "4.17.21"}},
|
|
|
|
|
{"encoded plus", "/pkg/deb/nmap/7.91%2Bdfsg1-2ubuntu0.1", []string{"nmap", "7.91+dfsg1-2ubuntu0.1"}},
|
|
|
|
|
{"decoded plus", "/pkg/deb/nmap/7.91+dfsg1-2ubuntu0.1", []string{"nmap", "7.91+dfsg1-2ubuntu0.1"}},
|
|
|
|
|
// An encoded slash is one segment, not a separator.
|
|
|
|
|
{"encoded slash", "/pkg/composer/vendor%2Fname/1.0.0", []string{"vendor/name", "1.0.0"}},
|
|
|
|
|
{"question mark", "/pkg/npm/example/v1%3Fbuild", []string{"example", "v1?build"}},
|
|
|
|
|
// "1.0%252B" is the escaped form of the version "1.0%2B"; net/url
|
|
|
|
|
// already decoded it once, so it must not be decoded again.
|
|
|
|
|
{"literal percent escape", "/pkg/npm/example/1.0%252B", []string{"example", "1.0%2B"}},
|
|
|
|
|
{"browse suffix", "/pkg/deb/nmap/7.91%2Bdfsg1/browse", []string{"nmap", "7.91+dfsg1", "browse"}},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
var got []string
|
|
|
|
|
var gotErr error
|
|
|
|
|
|
|
|
|
|
router := chi.NewRouter()
|
|
|
|
|
router.Get("/pkg/{ecosystem}/*", func(_ http.ResponseWriter, r *http.Request) {
|
|
|
|
|
got, gotErr = packagePathSegments(r)
|
|
|
|
|
})
|
|
|
|
|
router.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest("GET", tt.target, nil))
|
|
|
|
|
|
|
|
|
|
if gotErr != nil {
|
|
|
|
|
t.Fatalf("packagePathSegments(%q) failed: %v", tt.target, gotErr)
|
|
|
|
|
}
|
|
|
|
|
if len(got) != len(tt.want) {
|
|
|
|
|
t.Fatalf("segments for %q = %v, want %v", tt.target, got, tt.want)
|
|
|
|
|
}
|
|
|
|
|
for i := range got {
|
|
|
|
|
if got[i] != tt.want[i] {
|
|
|
|
|
t.Errorf("segments for %q [%d] = %q, want %q", tt.target, i, got[i], tt.want[i])
|
|
|
|
|
}
|
2026-05-03 09:14:18 +01:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|