Watch
1
0
Fork
You've already forked pkg-proxy
1
mirror of https://github.com/git-pkgs/proxy.git synced 2026-11-09 15:49:16 -05:00
pkg-proxy/internal/handler/package_routes.go
Simon Chrzanowski e92afe967e Mirror packages from the configured upstreams
proxy mirror and mirror API jobs resolved download URLs with a bare
resolver, so npm, cargo, gem, golang, hex, pub, maven and nuget packages
came from the public registries whatever upstream.* says. The resolver
now takes the configured upstreams as base URLs.

npm packages in upstream.npm_routes are fetched from their route and
stored under the route's filename prefix, the way the npm handler does,
so a public package can't stand in for a routed one and clients are
served the mirrored tarball.

Refs #414
2026-10-22 16:06:56 +02:00

92 lines
2.9 KiB
Go

package handler
import (
"crypto/sha256"
"encoding/hex"
"path"
"sort"
"strings"
)
// routeIDLength is the number of hex characters of the upstream URL digest
// that qualify cache entries of a routed package.
const routeIDLength = 16
// packageRoutes sends packages whose names match a pattern to a dedicated
// upstream instead of the ecosystem's default registry. A matched package is
// never looked up anywhere else, so a public package with the same name cannot
// replace it.
type packageRoutes struct {
routes []packageRoute // longest pattern first
}
type packageRoute struct {
pattern string
url string
id string
}
func newPackageRoutes(patterns map[string]string) packageRoutes {
routes := make([]packageRoute, 0, len(patterns))
for pattern, upstreamURL := range patterns {
upstreamURL = strings.TrimSuffix(upstreamURL, "/")
routes = append(routes, packageRoute{
pattern: strings.ToLower(pattern),
url: upstreamURL,
id: routeID(upstreamURL),
})
}
sort.Slice(routes, func(i, j int) bool {
if len(routes[i].pattern) != len(routes[j].pattern) {
return len(routes[i].pattern) > len(routes[j].pattern)
}
return routes[i].pattern < routes[j].pattern
})
return packageRoutes{routes: routes}
}
// routeID identifies a route's upstream in cache keys and filenames.
func routeID(upstreamURL string) string {
digest := sha256.Sum256([]byte(strings.TrimSuffix(upstreamURL, "/")))
return hex.EncodeToString(digest[:])[:routeIDLength]
}
// RoutedFilenamePrefix returns what the npm and Composer handlers put in
// front of the artifact filenames of a package routed to routeURL.
func RoutedFilenamePrefix(routeURL string) string {
return routeID(routeURL) + "/"
}
// match returns the route for a package name, if any pattern matches it.
func (r packageRoutes) match(packageName string) (packageRoute, bool) {
name := strings.ToLower(packageName)
for _, route := range r.routes {
if ok, _ := path.Match(route.pattern, name); ok {
return route, true
}
}
return packageRoute{}, false
}
// cacheKey qualifies a package's metadata cache key with the route's upstream,
// so entries cached from the default registry, or from a route's previous
// upstream, are never served for a routed package.
func (r packageRoute) cacheKey(packageName string) string {
return "routes/" + r.id + "/" + packageName
}
// cacheFilename qualifies a routed package's artifact filename the same way as
// cacheKey, and leaves other packages' filenames unchanged.
func (r packageRoutes) cacheFilename(packageName, filename string) string {
if route, routed := r.match(packageName); routed {
return route.id + "/" + filename
}
return filename
}
// RoutedPackageURL returns the upstream URL a route in routes sends
// packageName to, as the npm and Composer handlers pick it.
func RoutedPackageURL(routes map[string]string, packageName string) (string, bool) {
route, ok := newPackageRoutes(routes).match(packageName)
return route.url, ok
}