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.
135 lines
3.7 KiB
Go
135 lines
3.7 KiB
Go
package handler
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
goUpstream = "https://proxy.golang.org"
|
|
asciiCaseOffset = 32 // difference between lowercase and uppercase ASCII letters
|
|
)
|
|
|
|
// GoHandler handles Go module proxy protocol requests.
|
|
type GoHandler struct {
|
|
proxy *Proxy
|
|
upstreamURL string
|
|
proxyURL string
|
|
}
|
|
|
|
// NewGoHandler creates a new Go module proxy handler.
|
|
func NewGoHandler(proxy *Proxy, proxyURL string) *GoHandler {
|
|
return &GoHandler{
|
|
proxy: proxy,
|
|
upstreamURL: goUpstream,
|
|
proxyURL: strings.TrimSuffix(proxyURL, "/"),
|
|
}
|
|
}
|
|
|
|
// Routes returns the HTTP handler for Go proxy requests.
|
|
func (h *GoHandler) Routes() http.Handler {
|
|
// Go module paths can contain slashes, so just use the handler directly
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
h.handleRequest(w, r)
|
|
})
|
|
}
|
|
|
|
// handleRequest routes Go proxy requests based on the URL pattern.
|
|
func (h *GoHandler) handleRequest(w http.ResponseWriter, r *http.Request) {
|
|
path := strings.TrimPrefix(r.URL.Path, "/")
|
|
|
|
// Sumdb requests - proxy through
|
|
if strings.HasPrefix(path, "sumdb/") {
|
|
h.proxyUpstream(w, r)
|
|
return
|
|
}
|
|
|
|
// Check for @v/ pattern to identify module requests
|
|
if idx := strings.Index(path, "/@v/"); idx >= 0 {
|
|
module := path[:idx]
|
|
rest := path[idx+4:] // after "/@v/"
|
|
|
|
switch {
|
|
case rest == "list":
|
|
// GET /{module}/@v/list - list versions
|
|
h.proxyUpstream(w, r)
|
|
|
|
case strings.HasSuffix(rest, ".info"):
|
|
// GET /{module}/@v/{version}.info - version metadata
|
|
h.proxyUpstream(w, r)
|
|
|
|
case strings.HasSuffix(rest, ".mod"):
|
|
// GET /{module}/@v/{version}.mod - go.mod file
|
|
h.proxyUpstream(w, r)
|
|
|
|
case strings.HasSuffix(rest, ".zip"):
|
|
// GET /{module}/@v/{version}.zip - source archive (cache this)
|
|
version := strings.TrimSuffix(rest, ".zip")
|
|
h.handleDownload(w, r, module, version)
|
|
|
|
default:
|
|
http.NotFound(w, r)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Check for @latest
|
|
if strings.HasSuffix(path, "/@latest") {
|
|
h.proxyUpstream(w, r)
|
|
return
|
|
}
|
|
|
|
http.NotFound(w, r)
|
|
}
|
|
|
|
// handleDownload serves a module zip, fetching and caching from upstream if needed.
|
|
func (h *GoHandler) handleDownload(w http.ResponseWriter, r *http.Request, module, version string) {
|
|
// Decode module path (! followed by lowercase = uppercase)
|
|
decodedModule := decodeGoModule(module)
|
|
filename := fmt.Sprintf("%s@%s.zip", lastComponent(decodedModule), version)
|
|
|
|
h.proxy.Logger.Info("go module download request",
|
|
"module", decodedModule, "version", version)
|
|
|
|
result, err := h.proxy.GetOrFetchArtifact(r.Context(), "golang", decodedModule, version, filename)
|
|
if err != nil {
|
|
h.proxy.Logger.Error("failed to get artifact", "error", err)
|
|
http.Error(w, "failed to fetch module", http.StatusBadGateway)
|
|
return
|
|
}
|
|
|
|
ServeArtifact(w, result)
|
|
}
|
|
|
|
// proxyUpstream forwards a request to proxy.golang.org without caching.
|
|
func (h *GoHandler) proxyUpstream(w http.ResponseWriter, r *http.Request) {
|
|
h.proxy.ProxyUpstream(w, r, h.upstreamURL+r.URL.Path, nil)
|
|
}
|
|
|
|
// decodeGoModule decodes an encoded module path.
|
|
// In the encoding, uppercase letters are represented as "!" followed by lowercase.
|
|
func decodeGoModule(encoded string) string {
|
|
var b strings.Builder
|
|
for i := 0; i < len(encoded); i++ {
|
|
if encoded[i] == '!' && i+1 < len(encoded) {
|
|
b.WriteByte(encoded[i+1] - asciiCaseOffset) // lowercase to uppercase
|
|
i++
|
|
} else {
|
|
b.WriteByte(encoded[i])
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
// lastComponent returns the last path component of a module path.
|
|
func lastComponent(path string) string {
|
|
if idx := strings.LastIndex(path, "/"); idx >= 0 {
|
|
return path[idx+1:]
|
|
}
|
|
return path
|
|
}
|