forked from mirrors/pkg-proxy
* Fix all golangci-lint issues across the codebase Resolve 77 lint issues reported by golangci-lint with gocritic, gocognit, gocyclo, maintidx, dupl, mnd, unparam, ireturn, goconst, and errcheck enabled. Net reduction of ~175 lines through shared helpers and deduplication. * Suppress staticcheck SA1019 for intentional deprecated field usage The Storage.Path field is deprecated but still read for backwards compatibility with existing configs that haven't migrated to the URL field.
130 lines
4 KiB
Go
130 lines
4 KiB
Go
package handler
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
// Default upstream for Fedora packages
|
|
defaultRPMUpstream = "https://dl.fedoraproject.org/pub/fedora/linux"
|
|
rpmMatchCount = 5 // full match + name + version + release + arch
|
|
)
|
|
|
|
// RPMHandler handles RPM/Yum repository protocol requests.
|
|
// It proxies requests to upstream RPM repositories and caches .rpm packages.
|
|
type RPMHandler struct {
|
|
proxy *Proxy
|
|
upstreamURL string
|
|
proxyURL string
|
|
}
|
|
|
|
// NewRPMHandler creates a new RPM/Yum protocol handler.
|
|
func NewRPMHandler(proxy *Proxy, proxyURL string) *RPMHandler {
|
|
return &RPMHandler{
|
|
proxy: proxy,
|
|
upstreamURL: defaultRPMUpstream,
|
|
proxyURL: strings.TrimSuffix(proxyURL, "/"),
|
|
}
|
|
}
|
|
|
|
// Routes returns the HTTP handler for RPM requests.
|
|
// Mount this at /rpm on your router.
|
|
func (h *RPMHandler) Routes() http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet && r.Method != http.MethodHead {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
path := strings.TrimPrefix(r.URL.Path, "/")
|
|
|
|
if containsPathTraversal(path) {
|
|
http.Error(w, "invalid path", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Route based on path type
|
|
switch {
|
|
case strings.HasSuffix(path, ".rpm"):
|
|
// Package downloads - cache these
|
|
h.handlePackageDownload(w, r, path)
|
|
case strings.Contains(path, "/repodata/"):
|
|
// Repository metadata - proxy without caching (changes frequently)
|
|
h.handleMetadata(w, r, path)
|
|
default:
|
|
// Other files - proxy directly
|
|
h.proxyFile(w, r, path)
|
|
}
|
|
})
|
|
}
|
|
|
|
// handlePackageDownload fetches and caches .rpm packages.
|
|
// Path format varies by repo structure, e.g.:
|
|
// - releases/39/Everything/x86_64/os/Packages/n/nginx-1.24.0-1.fc39.x86_64.rpm
|
|
// - updates/39/Everything/x86_64/Packages/n/nginx-1.24.0-2.fc39.x86_64.rpm
|
|
func (h *RPMHandler) handlePackageDownload(w http.ResponseWriter, r *http.Request, path string) {
|
|
// Parse the path to extract package info
|
|
name, version, arch := h.parseRPMPath(path)
|
|
if name == "" {
|
|
// Can't parse, just proxy directly
|
|
h.proxyFile(w, r, path)
|
|
return
|
|
}
|
|
|
|
filename := path[strings.LastIndex(path, "/")+1:]
|
|
downloadURL := fmt.Sprintf("%s/%s", h.upstreamURL, path)
|
|
|
|
h.proxy.Logger.Info("rpm package download",
|
|
"name", name, "version", version, "arch", arch, "filename", filename)
|
|
|
|
result, err := h.proxy.GetOrFetchArtifactFromURL(
|
|
r.Context(), "rpm", name, version, filename, downloadURL)
|
|
if err != nil {
|
|
h.proxy.Logger.Error("failed to get rpm package", "error", err)
|
|
http.Error(w, "failed to fetch package", http.StatusBadGateway)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/x-rpm")
|
|
ServeArtifact(w, result)
|
|
}
|
|
|
|
// handleMetadata proxies repository metadata files (repomd.xml, primary.xml.gz, etc.).
|
|
// These change frequently so we don't cache them.
|
|
func (h *RPMHandler) handleMetadata(w http.ResponseWriter, r *http.Request, path string) {
|
|
h.proxy.ProxyMetadata(w, r, fmt.Sprintf("%s/%s", h.upstreamURL, path), "rpm")
|
|
}
|
|
|
|
// proxyFile proxies any file directly without caching.
|
|
func (h *RPMHandler) proxyFile(w http.ResponseWriter, r *http.Request, path string) {
|
|
h.proxy.ProxyFile(w, r, fmt.Sprintf("%s/%s", h.upstreamURL, path))
|
|
}
|
|
|
|
// rpmPackagePattern matches .rpm filenames to extract name, version, release, and arch.
|
|
// Format: {name}-{version}-{release}.{arch}.rpm
|
|
// Examples:
|
|
// - nginx-1.24.0-1.fc39.x86_64.rpm
|
|
// - kernel-core-6.5.5-200.fc38.x86_64.rpm
|
|
var rpmPackagePattern = regexp.MustCompile(`^(.+)-([^-]+)-([^-]+)\.([^.]+)\.rpm$`)
|
|
|
|
// parseRPMPath extracts package info from a path containing an RPM filename.
|
|
func (h *RPMHandler) parseRPMPath(path string) (name, version, arch string) {
|
|
// Get the filename
|
|
idx := strings.LastIndex(path, "/")
|
|
filename := path
|
|
if idx >= 0 {
|
|
filename = path[idx+1:]
|
|
}
|
|
|
|
// Parse the filename
|
|
matches := rpmPackagePattern.FindStringSubmatch(filename)
|
|
if len(matches) != rpmMatchCount {
|
|
return "", "", ""
|
|
}
|
|
|
|
// name, version-release, arch
|
|
return matches[1], matches[2] + "-" + matches[3], matches[4]
|
|
}
|