Watch
1
0
Fork
You've already forked pkg-proxy
1
mirror of https://github.com/git-pkgs/proxy.git synced 2026-08-23 12:24:57 -04:00
pkg-proxy/internal/server/resolve.go

146 lines
5.3 KiB
Go
Raw Permalink Normal View History

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 (
"fmt"
"net/http"
"net/url"
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
"strings"
"unicode"
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
"github.com/git-pkgs/proxy/internal/database"
"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
)
// maxPackagePathLen bounds the wildcard portion of package routes (name plus
// version and any suffix). npm caps names at 214 and Maven coordinates can be
// longer, so 512 leaves room without admitting pathological inputs.
const maxPackagePathLen = 512
// packagePathSegments validates the wildcard portion of a package route and
// splits it into decoded path segments.
func packagePathSegments(r *http.Request) ([]string, error) {
wildcard := chi.URLParam(r, "*")
encoded := wildcardIsEncoded(r)
if err := validatePackagePath(wildcard, encoded); err != nil {
return nil, err
}
return splitWildcardPath(wildcard, encoded), nil
}
// wildcardIsEncoded reports whether the chi wildcard for this request is still
// percent-encoded.
//
// chi routes on r.URL.RawPath when it is set and on r.URL.Path otherwise, and
// net/url only sets RawPath when the request's escaping differs from the
// canonical encoding of the decoded path. A version such as "release%2F1" is
// therefore routed raw, while "1.0%252B" (a version whose text contains a
// literal "%2B") encodes canonically and arrives already decoded once. The
// distinction decides whether the segments still need decoding: decoding the
// second case again would turn it into "1.0+" and resolve a different version.
func wildcardIsEncoded(r *http.Request) bool {
return r.URL.RawPath != ""
}
// validatePackagePath rejects wildcard package paths that cannot be valid in
// any supported ecosystem. It is a coarse filter applied before database or
// enrichment lookups; ecosystem-specific name rules are layered on top.
//
// encoded has the meaning described on wildcardIsEncoded.
func validatePackagePath(path string, encoded bool) error {
if path == "" {
return fmt.Errorf("package name required")
}
if len(path) > maxPackagePathLen {
return fmt.Errorf("package path exceeds %d bytes", maxPackagePathLen)
}
// Validate the decoded segments: the handlers work with decoded values, so
// an escape such as "%00" or "%2E%2E" must not slip past these checks.
for _, seg := range splitWildcardPath(path, encoded) {
// Each segment is checked both as the handlers see it and decoded once
// more: a segment can reach a handler with escapes intact, and the
// upstream registry is then the one that decodes them.
for _, value := range []string{seg, decodePathSegment(seg)} {
// A decoded segment can itself contain slashes (from "%2F"), and
// the segments are later rejoined into a package name that
// registries interpolate straight into an upstream URL. Check every
// path element, not just the segment as a whole, or
// "a%2F..%2F..%2Fb" traverses.
for _, elem := range strings.Split(value, "/") {
if elem == ".." {
return fmt.Errorf("package path contains parent directory segment")
}
}
for _, r := range value {
if r == 0 {
return fmt.Errorf("package path contains null byte")
}
if unicode.IsControl(r) {
return fmt.Errorf("package path contains control character %#U", r)
}
}
}
}
return nil
}
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
// resolvePackageName determines the package name from a wildcard path by
// checking the database. This handles namespaced packages like Composer's
// vendor/name format where the package name contains a slash.
//
// It tries the full path as a package name first. If not found, it splits
// off the last segment as a non-name suffix (version, action, etc.) and
// tries again, working backwards until a match is found or segments run out.
//
// Returns the package name and the remaining path segments after the name.
// If no package is found, returns empty name and the original segments.
func resolvePackageName(db *database.DB, ecosystem string, segments []string) (name string, rest []string) {
// Try increasingly longer prefixes as the package name.
// Start with the longest possible name (all segments) and work down.
for i := len(segments); i >= 1; i-- {
candidate := strings.Join(segments[:i], "/")
pkg, err := db.GetPackageByEcosystemName(ecosystem, candidate)
if err == nil && pkg != nil {
return candidate, segments[i:]
}
}
return "", segments
}
// splitWildcardPath splits a chi wildcard path value into segments,
// trimming any leading/trailing slashes.
//
// When encoded is set the value is still percent-encoded (see
// wildcardIsEncoded), so each segment is decoded after splitting. Splitting
// first keeps an encoded "%2F" inside a name from being mistaken for a
// separator. Decoding matters for versions such as "1.0%2Bbuild1", which must
// reach the handlers as "1.0+build1" so that rebuilding the PURL yields the
// value that was stored rather than a double-encoded one.
func splitWildcardPath(path string, encoded bool) []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
path = strings.Trim(path, "/")
if path == "" {
return nil
}
segments := strings.Split(path, "/")
if !encoded {
return segments
}
for i, seg := range segments {
segments[i] = decodePathSegment(seg)
}
return segments
}
// decodePathSegment percent-decodes a single URL path segment, returning it
// unchanged if it is not valid percent-encoding.
func decodePathSegment(seg string) string {
if !strings.Contains(seg, "%") {
return seg
}
decoded, err := url.PathUnescape(seg)
if err != nil {
return seg
}
return decoded
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
}