93 lines
2.7 KiB
Go
93 lines
2.7 KiB
Go
// Package cargo proxies a Cargo sparse registry (RFC 2789): mutable index
|
|
// files plus immutable .crate downloads, with config.json synthesized so
|
|
// cargo fetches crates back through artifactapi.
|
|
package cargo
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"git.unkin.net/unkin/artifactapi/internal/auth"
|
|
"git.unkin.net/unkin/artifactapi/internal/provider"
|
|
"git.unkin.net/unkin/artifactapi/pkg/models"
|
|
)
|
|
|
|
func init() {
|
|
provider.Register(&Provider{})
|
|
}
|
|
|
|
const (
|
|
crateDir = "crates/"
|
|
cratesIOIndex = "index.crates.io"
|
|
cratesIODownload = "https://static.crates.io"
|
|
)
|
|
|
|
type Provider struct{}
|
|
|
|
func (p *Provider) Type() models.PackageType { return models.PackageCargo }
|
|
|
|
func (p *Provider) Classify(path string) provider.Mutability {
|
|
if isCrate(path) {
|
|
return provider.Immutable
|
|
}
|
|
return provider.Mutable
|
|
}
|
|
|
|
func (p *Provider) ContentType(path string) string {
|
|
if isCrate(path) {
|
|
return "application/gzip"
|
|
}
|
|
if path == "config.json" {
|
|
return "application/json"
|
|
}
|
|
return "text/plain"
|
|
}
|
|
|
|
// UpstreamURL maps crates/{crate}/{crate}-{version}.crate to the download host
|
|
// and everything else to the index. crates.io splits the two across hosts;
|
|
// any other upstream is expected to serve both.
|
|
func (p *Provider) UpstreamURL(remote models.Remote, path string) string {
|
|
path = strings.TrimLeft(path, "/")
|
|
base := strings.TrimRight(remote.BaseURL, "/")
|
|
if isCrate(path) {
|
|
if u, err := url.Parse(base); err == nil && u.Host == cratesIOIndex {
|
|
base = cratesIODownload
|
|
}
|
|
}
|
|
return base + "/" + path
|
|
}
|
|
|
|
func (p *Provider) RewriteResponse(_ []byte, _ models.Remote, _ string) ([]byte, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (p *Provider) AuthHeaders(_ context.Context, remote models.Remote) (http.Header, error) {
|
|
return auth.BasicHeaders(remote), nil
|
|
}
|
|
|
|
// ServeRemote answers config.json itself: dl must name the requesting host,
|
|
// which a cached upstream body cannot.
|
|
func (p *Provider) ServeRemote(w http.ResponseWriter, r *http.Request, remote models.Remote, path, proxyBaseURL string, _ provider.RemoteMetadataStore) bool {
|
|
if strings.TrimLeft(path, "/") != "config.json" {
|
|
return false
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(Config(remote, proxyBaseURL))
|
|
return true
|
|
}
|
|
|
|
// Config builds the sparse registry config.json pointing downloads at proxyBaseURL.
|
|
func Config(remote models.Remote, proxyBaseURL string) map[string]string {
|
|
return map[string]string{
|
|
"dl": strings.TrimRight(proxyBaseURL, "/") + "/api/v1/remote/" + remote.Name + "/" + crateDir + "{crate}/{crate}-{version}.crate",
|
|
}
|
|
}
|
|
|
|
func isCrate(path string) bool {
|
|
path = strings.TrimLeft(path, "/")
|
|
return strings.HasPrefix(path, crateDir) && strings.HasSuffix(path, ".crate")
|
|
}
|