mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-08-01 10:08:08 -04:00
Compare commits
46 changed files with 459 additions and 1538 deletions
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
|
|
@ -17,12 +17,12 @@ jobs:
|
|||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
|
|
@ -35,12 +35,12 @@ jobs:
|
|||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: '1.25'
|
||||
|
||||
|
|
|
|||
8
.github/workflows/publish.yml
vendored
8
.github/workflows/publish.yml
vendored
|
|
@ -20,12 +20,12 @@ jobs:
|
|||
contents: read
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@06fb636fac595d6fb4b28a5dfcb21a6f5091859c
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
|
@ -33,12 +33,12 @@ jobs:
|
|||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302
|
||||
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
|
|
|||
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
|
|
@ -14,7 +14,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
|
@ -22,12 +22,12 @@ jobs:
|
|||
- uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: false
|
||||
|
||||
- uses: goreleaser/goreleaser-action@f06c13b6b1a9625abc9e6e439d9c05a8f2190e94 # v7.2.3
|
||||
- uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7.2.2
|
||||
with:
|
||||
version: "~> v2"
|
||||
args: release --clean
|
||||
|
|
|
|||
4
.github/workflows/swagger.yml
vendored
4
.github/workflows/swagger.yml
vendored
|
|
@ -12,12 +12,12 @@ jobs:
|
|||
swagger:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: '1.25'
|
||||
|
||||
|
|
|
|||
4
.github/workflows/zizmor.yml
vendored
4
.github/workflows/zizmor.yml
vendored
|
|
@ -21,9 +21,9 @@ jobs:
|
|||
security-events: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run zizmor
|
||||
uses: zizmorcore/zizmor-action@6fc4b006235f201fdab3722e17240ab420d580e5 # v0.6.1
|
||||
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
FROM golang:1.26.5-alpine AS builder
|
||||
FROM golang:1.26.4-alpine AS builder
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ COPY . .
|
|||
# Build the binary
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /proxy ./cmd/proxy
|
||||
|
||||
FROM alpine:3.24.1
|
||||
FROM alpine:3.23.4
|
||||
|
||||
RUN apk add --no-cache ca-certificates
|
||||
|
||||
|
|
|
|||
|
|
@ -9,11 +9,6 @@ listen: ":8080"
|
|||
# so users know what to point their package manager at.
|
||||
base_url: "http://localhost:8080"
|
||||
|
||||
# Timeout for individual upstream HTTP requests made by protocol handlers
|
||||
# (metadata fetches, pass-through file requests). Uses Go duration syntax.
|
||||
# Set to "0" to disable the timeout. Default: "30s".
|
||||
# http_timeout: "30s"
|
||||
|
||||
# Public URL where the web UI is reached. Defaults to base_url when unset.
|
||||
# Set this separately when the UI is served on a different hostname than the
|
||||
# package endpoints — for example, the UI on a public domain behind auth while
|
||||
|
|
@ -168,8 +163,7 @@ cooldown:
|
|||
# npm: "7d"
|
||||
# cargo: "0"
|
||||
|
||||
# Per-package overrides (keyed by PURL). Keys are normalized, so npm scopes
|
||||
# may use either @scope or the canonical %40scope form.
|
||||
# Per-package overrides (keyed by PURL)
|
||||
# packages:
|
||||
# "pkg:npm/lodash": "0"
|
||||
# "pkg:npm/@babel/core": "14d"
|
||||
|
|
|
|||
|
|
@ -234,8 +234,6 @@ cooldown:
|
|||
|
||||
Durations support days (`7d`), hours (`48h`), and minutes (`30m`). Set to `0` to disable.
|
||||
|
||||
Package PURL keys are normalized to canonical form before matching, so `pkg:npm/@babel/core` and `pkg:npm/%40babel/core` are equivalent, as are `pkg:pypi/Django` and `pkg:pypi/django`. If both forms configure the same package, the canonical entry wins.
|
||||
|
||||
Resolution order: package override, then ecosystem override, then global default. This lets you set a conservative default while exempting trusted packages.
|
||||
|
||||
Currently supported for npm, PyPI, pub.dev, Composer, Cargo, NuGet, Conda, RubyGems, and Hex. These ecosystems include publish timestamps in their metadata.
|
||||
|
|
@ -278,18 +276,6 @@ metadata_max_size: "100MB" # default
|
|||
|
||||
Or via environment variable: `PROXY_METADATA_MAX_SIZE=250MB`.
|
||||
|
||||
## Upstream HTTP timeout
|
||||
|
||||
Protocol handlers use a shared HTTP client for upstream requests such as metadata fetches and pass-through file downloads. `http_timeout` sets that client's per-request timeout. Raise it if slow upstreams or large metadata responses cause `context deadline exceeded` errors.
|
||||
|
||||
```yaml
|
||||
http_timeout: "30s" # default
|
||||
```
|
||||
|
||||
Or via environment variable: `PROXY_HTTP_TIMEOUT=2m`.
|
||||
|
||||
Set to `"0"` to disable the timeout entirely (requests then rely only on the server's write timeout).
|
||||
|
||||
## Mirror API
|
||||
|
||||
The `/api/mirror` endpoints are disabled by default. Enable them to allow starting mirror jobs via HTTP:
|
||||
|
|
|
|||
89
go.mod
89
go.mod
|
|
@ -5,27 +5,26 @@ go 1.25.6
|
|||
require (
|
||||
github.com/BurntSushi/toml v1.6.0
|
||||
github.com/CycloneDX/cyclonedx-go v0.11.0
|
||||
github.com/git-pkgs/archives v0.4.0
|
||||
github.com/git-pkgs/archives v0.3.0
|
||||
github.com/git-pkgs/cooldown v0.1.1
|
||||
github.com/git-pkgs/enrichment v0.6.4
|
||||
github.com/git-pkgs/magic v0.1.0
|
||||
github.com/git-pkgs/purl v0.1.15
|
||||
github.com/git-pkgs/registries v0.6.4
|
||||
github.com/git-pkgs/enrichment v0.2.3
|
||||
github.com/git-pkgs/purl v0.1.12
|
||||
github.com/git-pkgs/registries v0.6.1
|
||||
github.com/git-pkgs/spdx v0.1.4
|
||||
github.com/git-pkgs/vers v0.3.0
|
||||
github.com/git-pkgs/vulns v0.2.1
|
||||
github.com/go-chi/chi/v5 v5.3.1
|
||||
github.com/git-pkgs/vers v0.2.6
|
||||
github.com/git-pkgs/vulns v0.1.5
|
||||
github.com/go-chi/chi/v5 v5.3.0
|
||||
github.com/jmoiron/sqlx v1.4.0
|
||||
github.com/lib/pq v1.12.3
|
||||
github.com/prometheus/client_golang v1.24.0
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/prometheus/client_model v0.6.2
|
||||
github.com/spdx/tools-golang v0.5.7
|
||||
github.com/swaggo/swag v1.16.6
|
||||
gocloud.dev v0.46.0
|
||||
golang.org/x/sync v0.22.0
|
||||
gocloud.dev v0.45.0
|
||||
golang.org/x/sync v0.20.0
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.55.0
|
||||
modernc.org/sqlite v1.51.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
|
@ -45,11 +44,13 @@ require (
|
|||
github.com/Antonboom/errname v1.1.1 // indirect
|
||||
github.com/Antonboom/nilnil v1.1.1 // indirect
|
||||
github.com/Antonboom/testifylint v1.6.4 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.3 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.1 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect
|
||||
github.com/Djarvur/go-err113 v0.1.1 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||
|
|
@ -70,22 +71,23 @@ require (
|
|||
github.com/ashanbrown/makezero/v2 v2.1.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.20 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.25 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/transfermanager v0.2.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.25 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.25 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.26 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.18 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.25 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.25 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.102.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.1.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.42.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.16 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.8 // indirect
|
||||
github.com/aws/smithy-go v1.26.0 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
|
|
@ -116,7 +118,7 @@ require (
|
|||
github.com/denis-tingaikin/go-header v0.5.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/ecosyste-ms/ecosystems-go v0.4.0 // indirect
|
||||
github.com/ecosyste-ms/ecosystems-go v0.1.1 // indirect
|
||||
github.com/ettle/strcase v0.2.0 // indirect
|
||||
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
|
|
@ -126,7 +128,7 @@ require (
|
|||
github.com/fzipp/gocyclo v0.6.0 // indirect
|
||||
github.com/ghostiam/protogetter v0.3.20 // indirect
|
||||
github.com/git-pkgs/packageurl-go v0.3.1 // indirect
|
||||
github.com/git-pkgs/pom v0.1.5 // indirect
|
||||
github.com/git-pkgs/pom v0.1.4 // indirect
|
||||
github.com/github/go-spdx/v2 v2.7.0 // indirect
|
||||
github.com/go-critic/go-critic v0.14.3 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
|
|
@ -147,7 +149,7 @@ require (
|
|||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/godoc-lint/godoc-lint v0.11.2 // indirect
|
||||
github.com/gofrs/flock v0.13.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
|
||||
github.com/golangci/asciicheck v0.5.0 // indirect
|
||||
github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect
|
||||
github.com/golangci/go-printf-func-name v0.1.1 // indirect
|
||||
|
|
@ -163,8 +165,8 @@ require (
|
|||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/google/wire v0.7.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.19.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.12 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
|
||||
github.com/gordonklaus/ineffassign v0.2.0 // indirect
|
||||
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
|
||||
github.com/gostaticanalysis/comment v1.5.0 // indirect
|
||||
|
|
@ -218,16 +220,15 @@ require (
|
|||
github.com/nishanths/exhaustive v0.12.0 // indirect
|
||||
github.com/nishanths/predeclared v0.2.2 // indirect
|
||||
github.com/nunnatsa/ginkgolinter v0.23.0 // indirect
|
||||
github.com/oapi-codegen/nullable v1.1.0 // indirect
|
||||
github.com/oapi-codegen/runtime v1.6.0 // indirect
|
||||
github.com/oapi-codegen/runtime v1.2.0 // indirect
|
||||
github.com/package-url/packageurl-go v0.1.6 // indirect
|
||||
github.com/pandatix/go-cvss v0.6.2 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/common v0.70.0 // indirect
|
||||
github.com/prometheus/procfs v0.21.1 // indirect
|
||||
github.com/prometheus/common v0.67.5 // indirect
|
||||
github.com/prometheus/procfs v0.20.1 // indirect
|
||||
github.com/quasilyte/go-ruleguard v0.4.5 // indirect
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.23 // indirect
|
||||
github.com/quasilyte/gogrep v0.5.0 // indirect
|
||||
|
|
@ -267,7 +268,7 @@ require (
|
|||
github.com/timonwong/loggercheck v0.11.0 // indirect
|
||||
github.com/tomarrell/wrapcheck/v2 v2.12.0 // indirect
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
|
||||
github.com/ulikunitz/xz v0.5.16 // indirect
|
||||
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||
github.com/ultraware/funlen v0.2.0 // indirect
|
||||
github.com/ultraware/whitespace v0.2.0 // indirect
|
||||
github.com/urfave/cli/v2 v2.3.0 // indirect
|
||||
|
|
@ -291,25 +292,25 @@ require (
|
|||
go.opentelemetry.io/otel/trace v1.44.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.1 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.53.0 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 // indirect
|
||||
golang.org/x/mod v0.37.0 // indirect
|
||||
golang.org/x/net v0.56.0 // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/net v0.51.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/text v0.38.0 // indirect
|
||||
golang.org/x/tools v0.47.0 // indirect
|
||||
golang.org/x/sys v0.45.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/tools v0.42.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||
google.golang.org/api v0.272.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
|
||||
google.golang.org/grpc v1.82.1 // indirect
|
||||
google.golang.org/api v0.269.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
|
||||
google.golang.org/grpc v1.81.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
honnef.co/go/tools v0.7.0 // indirect
|
||||
modernc.org/libc v1.74.1 // indirect
|
||||
modernc.org/libc v1.72.3 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
mvdan.cc/gofumpt v0.9.2 // indirect
|
||||
|
|
|
|||
238
go.sum
238
go.sum
|
|
@ -16,8 +16,8 @@ cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
|
|||
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
|
||||
cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE=
|
||||
cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI=
|
||||
cloud.google.com/go/storage v1.61.3 h1:VS//ZfBuPGDvakfD9xyPW1RGF1Vy3BWUoVZXgW1KMOg=
|
||||
cloud.google.com/go/storage v1.61.3/go.mod h1:JtqK8BBB7TWv0HVGHubtUdzYYrakOQIsMLffZ2Z/HWk=
|
||||
cloud.google.com/go/storage v1.57.2 h1:sVlym3cHGYhrp6XZKkKb+92I1V42ks2qKKpB0CF5Mb4=
|
||||
cloud.google.com/go/storage v1.57.2/go.mod h1:n5ijg4yiRXXpCu0sJTD6k+eMf7GRrJmPyr9YxLXGHOk=
|
||||
codeberg.org/chavacava/garif v0.2.0 h1:F0tVjhYbuOCnvNcU3YSpO6b3Waw6Bimy4K0mM8y6MfY=
|
||||
codeberg.org/chavacava/garif v0.2.0/go.mod h1:P2BPbVbT4QcvLZrORc2T29szK3xEOlnl0GiPTJmEqBQ=
|
||||
codeberg.org/polyfloyd/go-errorlint v1.9.0 h1:VkdEEmA1VBpH6ecQoMR4LdphVI3fA4RrCh2an7YmodI=
|
||||
|
|
@ -27,8 +27,8 @@ dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88
|
|||
dev.gaijin.team/go/golib v0.6.0 h1:v6nnznFTs4bppib/NyU1PQxobwDHwCXXl15P7DV5Zgo=
|
||||
dev.gaijin.team/go/golib v0.6.0/go.mod h1:uY1mShx8Z/aNHWDyAkZTkX+uCi5PdX7KsG1eDQa2AVE=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=
|
||||
filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8=
|
||||
github.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c=
|
||||
github.com/Abirdcfly/dupword v0.1.7 h1:2j8sInznrje4I0CMisSL6ipEBkeJUJAmK1/lfoNGWrQ=
|
||||
|
|
@ -43,8 +43,8 @@ github.com/Antonboom/nilnil v1.1.1 h1:9Mdr6BYd8WHCDngQnNVV0b554xyisFioEKi30sksuf
|
|||
github.com/Antonboom/nilnil v1.1.1/go.mod h1:yCyAmSw3doopbOWhJlVci+HuyNRuHJKIv6V2oYQa8II=
|
||||
github.com/Antonboom/testifylint v1.6.4 h1:gs9fUEy+egzxkEbq9P4cpcMB6/G0DYdMeiFS87UiqmQ=
|
||||
github.com/Antonboom/testifylint v1.6.4/go.mod h1:YO33FROXX2OoUfwjz8g+gUxQXio5i9qpVy7nXGbxDD4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
|
||||
|
|
@ -53,12 +53,16 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDo
|
|||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 h1:/Zt+cDPnpC3OVDm/JKLOs7M2DKmLRIIp3XIx9pHHiig=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1/go.mod h1:Ng3urmn6dYe8gnbCMoHHVl5APYz2txho3koEkV2o2HA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4 h1:jWQK1GI+LeGGUKBADtcH2rRqPxYB1Ljwms5gFA2LqrM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4/go.mod h1:8mwH4klAm9DUgR2EEHyEEAQlRDvLPyg5fQry3y+cDew=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.3 h1:ZJJNFaQ86GVKQ9ehwqyAFE6pIfyicpuJ8IkVaPBc6/4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.3/go.mod h1:URuDvhmATVKqHBH9/0nOiNKk0+YcwfQ3WkK5PqHKxc8=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.1 h1:CxNHBqdzTr7rLtdrtb5CMjJcDut+WNGCVv7OmS5+lTc=
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.1/go.mod h1:EtaofgU4zmtvn1zT2ARsjRFdq9vXx0YWtmElwL+GZ9M=
|
||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
|
||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0 h1:4iB+IesclUXdP0ICgAabvq2FYLXrJWKx1fJQ+GxSo3Y=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
|
|
@ -66,12 +70,12 @@ github.com/CycloneDX/cyclonedx-go v0.11.0 h1:GokP8FiRC+foiuwWhSSLpSD5H4hSWtGnR3w
|
|||
github.com/CycloneDX/cyclonedx-go v0.11.0/go.mod h1:vUvbCXQsEm48OI6oOlanxstwNByXjCZ2wuleUlwGEO8=
|
||||
github.com/Djarvur/go-err113 v0.1.1 h1:eHfopDqXRwAi+YmCUas75ZE0+hoBHJ2GQNLYRSxao4g=
|
||||
github.com/Djarvur/go-err113 v0.1.1/go.mod h1:IaWJdYFLg76t2ihfflPZnM1LIQszWOsFDh2hhhAVF6k=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0 h1:rIkQfkCOVKc1OiRCNcSDD8ml5RJlZbH/Xsq7lbpynwc=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.32.0/go.mod h1:RD2SsorTmYhF6HkTmDw7KmPYQk8OBYwTkuasChwv7R4=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 h1:UnDZ/zFfG1JhH/DqxIZYU/1CUAlTUScoXD/LcM2Ykk8=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0/go.mod h1:IA1C1U7jO/ENqm/vhi7V9YYpBsp+IMyqNrEN94N7tVc=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 h1:0s6TxfCu2KHkkZPnBfsQ2y5qia0jl3MMrmBhu3nCOYk=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0 h1:lhhYARPUu3LmHysQ/igznQphfzynnqI3D75oUyw1HXk=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.54.0/go.mod h1:l9rva3ApbBpEJxSNYnwT9N4CDLrWgtq3u8736C5hyJw=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0 h1:s0WlVbf9qpvkh1c/uDAPElam0WrL7fHRIidgZJ7UqZI=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.54.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||
|
|
@ -115,18 +119,20 @@ github.com/aws/aws-sdk-go-v2 v1.41.9 h1:/rYeyO2+HrMztAmxAq9++XJtFMqSIpSsNA0yDGAL
|
|||
github.com/aws/aws-sdk-go-v2 v1.41.9/go.mod h1:+HsoOEX80qAVUitj1A2DhCNTjmb3edVyuDypb6LNEeo=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.11 h1:h5+3VT69KUBK24grGuuA5saDJTj2IIjLb9au668Fo5I=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.11/go.mod h1:dnakxebH6UwFvcvujL0LVggYQ8nEvBGjU4G/V79Nv94=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.20 h1:8VMDnWc/kEzxsI/1ngGM9mG81a8IGmIHD8KLcYGwagc=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.20/go.mod h1:PuwEpciweIXGULWeOeSTXtSbH4CW9mWdWrhdCKQI1sM=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.19 h1:yuFzSV1U0aRNYCQGVaTY2zW2M/L93pYHnXnrJUphYhU=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.19/go.mod h1:7y63L1kGzeoDlJaQ3Z578KrnmfBut96JjvJUzGwR+YE=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.25 h1:0w6dCiO8iez+YKwRhRBlL1CH/E3GTfdkuzrwj1by8vo=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.25/go.mod h1:9FDWUothyr5RCRAHc45XOiVCzUR8n/IhCYX+uVqw6vk=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/transfermanager v0.2.3 h1:w5OoDiMN6x53ROmiIImGzmVcxXv2q1GXY+aKV4WAJYM=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/transfermanager v0.2.3/go.mod h1:dAhgYp776bX3LuWvnSCFwQEjNs6fuFg7YXIy5PXcP3Q=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.11 h1:ftxI5sgz8jZkckuUHXfC/wMUc8u3fG1vQS0plr2F2Zs=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.11/go.mod h1:twF11+6ps9aNRKEDimksp923o44w/Thk9+8YIlzWMmo=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.11 h1:NdV8cwCcAXrCWyxArt58BrvZJ9pZ9Fhf9w6Uh5W3Uyc=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.11/go.mod h1:30yY2zqkMPdrvxBqzI9xQCM+WrlrZKSOpSJEsylVU+8=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.19 h1:INUvJxmhdEbVulJYHI061k4TVuS3jzzthNvjqvVvTKM=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.19/go.mod h1:FpZN2QISLdEBWkayloda+sZjVJL+e9Gl0k1SyTgcswU=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.5 h1:4nC6vsVBU6vClZxxF6XLEozLUY/PgUCXYlGGB/VaC8M=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.5/go.mod h1:N5c+La/yy7H4YnF9rFgUqwgbfw+MloWoCHQ0RJH2EBE=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.25 h1:Uii3frf9ztec/ABM2/FSH9/z7PLzxfpG8h4RpkUFflQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.25/go.mod h1:G6kntsA2GorAxDPbap6xgB2F+amSLUF8GJTi7PUoX44=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.25 h1:r1+/l6m+WaUJF9HISEsNOLHSNj5EXYQxK8VX6Cz9NlA=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.25/go.mod h1:cKf+D+NMDK1LndD7BowHbBZPgR9V0/5HubH0PFWvA+c=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 h1:clHU5fm//kWS1C2HgtgWxfQbFbx4b6rx+5jzhgX9HrI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.26 h1:A1PmWU2zfkIm9EyFlJncFXL4W4phML+h8KjltUsCvNQ=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.26/go.mod h1:dY4MRzXEizrD4hqtpKvWVGPX7QleSGGVY+EBolo1RmM=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.10 h1:d5/908OJ4bXg8lyjeMPvXetEKqoDoLi5Owy1zNue3yg=
|
||||
|
|
@ -139,14 +145,14 @@ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.25 h1:2pQEbwf+/6EDb
|
|||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.25/go.mod h1:KvT6NCcQ0EZ+ZkVRrlBMt04Po3ok23YELEp7WimhLhM=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.102.2 h1:ie4ElCmUKS26pzrZcIk/lmt4yWjAqLLcawstyQCh298=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.102.2/go.mod h1:zjsomFeX5duj+4PlMB+o4JoWTIx+G0XMyzjYrUbQkN0=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.1.1 h1:1VwbP3qMNfxUDEXWki4rCE5iA+44VA1lokTz9HasGzw=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.1.1/go.mod h1:vUtyoSj0OPji3kjIVSc/GlKuWEiL33f/WFxl6dmpy/A=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.19 h1:N6pIsdFOW1Kd9S4KyFKXdGRBojPPxkP32+uHFWLv4Hc=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.19/go.mod h1:3gt5WJArFooNmyLONS+h/R4J+o86II8du38IgCwj9dE=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.2 h1:hc+lBYiiTr8Zk4MTzIsQ92MeDWCIDvWGmzKUWOaBcOg=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.2/go.mod h1:hU6fqB3OJA6/ePheD47LQnxvjYk6br6PtQxs+Q9ojvk=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.42.3 h1:ErklX/7uhSbkAAeyQD/Y1OoQ9hO3SJXQNEgksORW3Js=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.42.3/go.mod h1:ULe4HCzfKPiR6R3HEurE3b1upEkuk8AkMrOKtaOxKO8=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.7 h1:Y2cAXlClHsXkkOvWZFXATr34b0hxxloeQu/pAZz2row=
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.7/go.mod h1:idzZ7gmDeqeNrSPkdbtMp9qWMgcBwykA7P7Rzh5DXVU=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.12 h1:iSsvB9EtQ09YrsmIc44Heqlx5ByGErqhPK1ZQLppias=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.12/go.mod h1:fEWYKTRGoZNl8tZ77i61/ccwOMJdGxwOhWCkp6TXAr0=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.16 h1:EnUdUqRP1CNzt2DkV67tJx6XDN4xlfBFm+bzeNOQVb0=
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.16/go.mod h1:Jic/xv0Rq/pFNCh3WwpH4BEqdbSAl+IyHro8LbibHD8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.8 h1:XQTQTF75vnug2TXS8m7CVJfC2nniYPZnO1D4Np761Oo=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.8/go.mod h1:Xgx+PR1NUOjNmQY+tRMnouRp83JRM8pRMw/vCaVhPkI=
|
||||
github.com/aws/smithy-go v1.26.0 h1:9ouqbi+NyKP7fV3Te7UElCwdAb6Y8uk7LGwPE5tVe/s=
|
||||
github.com/aws/smithy-go v1.26.0/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
|
|
@ -217,8 +223,8 @@ github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZ
|
|||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/ecosyste-ms/ecosystems-go v0.4.0 h1:5A+zF+XWT8sYYYjlc2/tI1SmiDGzbHLyT9CapVc5dGA=
|
||||
github.com/ecosyste-ms/ecosystems-go v0.4.0/go.mod h1:FVswCrp3DQkur1HjVqfDF/gYrDSEmiFflntcB1G0DbA=
|
||||
github.com/ecosyste-ms/ecosystems-go v0.1.1 h1:YYiBK9TCCTeE+BtmpN2FssaRFcmF+T0v4LrupIOjehQ=
|
||||
github.com/ecosyste-ms/ecosystems-go v0.1.1/go.mod h1:VczXs1CO9nL8XbL1NwvgmwIaqzMsAxcsXnTpRtwi9gU=
|
||||
github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A=
|
||||
|
|
@ -244,32 +250,30 @@ github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
|
|||
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
|
||||
github.com/ghostiam/protogetter v0.3.20 h1:oW7OPFit2FxZOpmMRPP9FffU4uUpfeE/rEdE1f+MzD0=
|
||||
github.com/ghostiam/protogetter v0.3.20/go.mod h1:FjIu5Yfs6FT391m+Fjp3fbAYJ6rkL/J6ySpZBfnODuI=
|
||||
github.com/git-pkgs/archives v0.4.0 h1:KNmmIsLiSH27lUdT27EfUkQXFaLgXV5KezE81iyOIgo=
|
||||
github.com/git-pkgs/archives v0.4.0/go.mod h1:tfio0OIuPKEBKHs/UCL5XBUvYmKpnvtnba2iDlfSd6g=
|
||||
github.com/git-pkgs/archives v0.3.0 h1:iXKyO83jEFub1PGEDlHmk2tQ7XeV5LySTc0sEkH3x78=
|
||||
github.com/git-pkgs/archives v0.3.0/go.mod h1:LTJ1iQVFA7otizWMOyiI82NYVmyBWAPRzwu/e30rcXU=
|
||||
github.com/git-pkgs/cooldown v0.1.1 h1:9OqqzCB8gANz/y44SmqGD0Jp8Qtu81D1sCbKl6Ehg7w=
|
||||
github.com/git-pkgs/cooldown v0.1.1/go.mod h1:v7APuK/UouTiu8mWQZbdDmj7DfxxkGUeuhjaRB5gv9E=
|
||||
github.com/git-pkgs/enrichment v0.6.4 h1:mGrfenttwmcUfPXRkWpB0wBJiiGj55ltniUh66Pq4bU=
|
||||
github.com/git-pkgs/enrichment v0.6.4/go.mod h1:zz1vPUak/w8Jhajll0KDRN2MjKaEYeCzQTxumWnVhqY=
|
||||
github.com/git-pkgs/magic v0.1.0 h1:xLrqq7CMXB9g5bJnmJyKw17Rvlh0GFiEmO6e5RFsoeY=
|
||||
github.com/git-pkgs/magic v0.1.0/go.mod h1:3ndidt+yvFaI1M0aEkkzkOlFnLPkeVQASIUojazcxCI=
|
||||
github.com/git-pkgs/enrichment v0.2.3 h1:42mqoUhQZNGhlEO671pboI/Cu6F+DoffJoFbVhb2jlw=
|
||||
github.com/git-pkgs/enrichment v0.2.3/go.mod h1:MBv5nhHzjwLxeSgx2+7waCcpReUjhCD+9B0bvufpMO0=
|
||||
github.com/git-pkgs/packageurl-go v0.3.1 h1:WM3RBABQZLaRBxgKyYughc3cVBE8KyQxbSC6Jt5ak7M=
|
||||
github.com/git-pkgs/packageurl-go v0.3.1/go.mod h1:rcIxiG37BlQLB6FZfgdj9Fm7yjhRQd3l+5o7J0QPAk4=
|
||||
github.com/git-pkgs/pom v0.1.5 h1:TGT8Az2OMxGWsXnSagtUMGzZm7Oax8HrSCteA+mi0qY=
|
||||
github.com/git-pkgs/pom v0.1.5/go.mod h1:ufdMBe1lKzqOeP9IUb9NPZ458xKV8E8NvuyBMxOfwIk=
|
||||
github.com/git-pkgs/purl v0.1.15 h1:iQ3clh0Cw41rkM0rf24B7ShnN9Z+UtLMAFlNDUs+Qd4=
|
||||
github.com/git-pkgs/purl v0.1.15/go.mod h1:PqCLVBDeZrZgHysR803/AntMELgIr2LFZVNCcwLH2m0=
|
||||
github.com/git-pkgs/registries v0.6.4 h1:Kq/KlStjaQyE83UXT/tKuzCrIzc4keGeBjtroMqgoHA=
|
||||
github.com/git-pkgs/registries v0.6.4/go.mod h1:YkGHbxHIe2Ha/ROH6zNkS5PJUUoa9g0Ti/s2XhZnrak=
|
||||
github.com/git-pkgs/pom v0.1.4 h1:C6st+XSbF75eKuwfdkDZZtYHoTcaWRIEQYar5VtszUo=
|
||||
github.com/git-pkgs/pom v0.1.4/go.mod h1:ufdMBe1lKzqOeP9IUb9NPZ458xKV8E8NvuyBMxOfwIk=
|
||||
github.com/git-pkgs/purl v0.1.12 h1:qCskrEU1LWQhCkIVZd992W5++Bsxazvx2Cx1/65qCvU=
|
||||
github.com/git-pkgs/purl v0.1.12/go.mod h1:ofp4mHsR0cUeVONQaf33n6Wxg2QTEvtUdRfCedI8ouA=
|
||||
github.com/git-pkgs/registries v0.6.1 h1:xZfVZQmffIfdeJthn5o2EozbVJ6gBeImYwKQnfdKUfU=
|
||||
github.com/git-pkgs/registries v0.6.1/go.mod h1:a3BP/56VW3O/CFRqiJCtSy+OqRrSH25wF1PWHP76ka0=
|
||||
github.com/git-pkgs/spdx v0.1.4 h1:eQ0waEV3uUeItpWAOvdN1K1rL9hTgsU7fF74r1mDXMs=
|
||||
github.com/git-pkgs/spdx v0.1.4/go.mod h1:cqRoZcvl530s/W+oGNvwjt4ODN8T1W6D/20MUZEFdto=
|
||||
github.com/git-pkgs/vers v0.3.0 h1:xM4LLUCRmqzdDfe+/pVQUx4SRyFXRVth6tOsJ14wMKU=
|
||||
github.com/git-pkgs/vers v0.3.0/go.mod h1:biTbSQK1qdbrsxDEKnqe3Jzclxz8vW6uDcwKjfUGcOo=
|
||||
github.com/git-pkgs/vulns v0.2.1 h1:tWGhOfPVDZwkM2Y9vRkMpMR+gjtlu2jhERS5JeNBoKQ=
|
||||
github.com/git-pkgs/vulns v0.2.1/go.mod h1:/0gHKHQR5SWttZVEMqgOvCXssKFwAtbac/PfkhBax9o=
|
||||
github.com/git-pkgs/vers v0.2.6 h1:IelZd7BP/JhzTloUTDY67nehUgoYva3g9viqAMCHJg8=
|
||||
github.com/git-pkgs/vers v0.2.6/go.mod h1:biTbSQK1qdbrsxDEKnqe3Jzclxz8vW6uDcwKjfUGcOo=
|
||||
github.com/git-pkgs/vulns v0.1.5 h1:mtX88/27toFl+B95kaH5QbAdOCQ3YIDGjJrlrrnqQTE=
|
||||
github.com/git-pkgs/vulns v0.1.5/go.mod h1:bZFikfrR/5gC0ZMwXh7qcEu2gpKfXMBhVsy4kF12Ae0=
|
||||
github.com/github/go-spdx/v2 v2.7.0 h1:GzfXx4wFdlilARxmFRXW/mgUy3A4vSqZocCMFV6XFdQ=
|
||||
github.com/github/go-spdx/v2 v2.7.0/go.mod h1:Ftc45YYG1WzpzwEPKRVm9Jv8vDqOrN4gWoCkK+bHer0=
|
||||
github.com/go-chi/chi/v5 v5.3.1 h1:3j4HZLGZQ3JpMCrPJF/Jl3mYJfWLKBfNJ6quurUGCf8=
|
||||
github.com/go-chi/chi/v5 v5.3.1/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
|
||||
github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM=
|
||||
github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
|
||||
github.com/go-critic/go-critic v0.14.3 h1:5R1qH2iFeo4I/RJU8vTezdqs08Egi4u5p6vOESA0pog=
|
||||
github.com/go-critic/go-critic v0.14.3/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
|
|
@ -325,8 +329,8 @@ github.com/godoc-lint/godoc-lint v0.11.2 h1:Bp0FkJWoSdNsBikdNgIcgtaoo+xz6I/Y9s5W
|
|||
github.com/godoc-lint/godoc-lint v0.11.2/go.mod h1:iVpGdL1JCikNH2gGeAn3Hh+AgN5Gx/I/cxV+91L41jo=
|
||||
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
|
||||
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golangci/asciicheck v0.5.0 h1:jczN/BorERZwK8oiFBOGvlGPknhvq0bjnysTj4nUfo0=
|
||||
|
|
@ -370,10 +374,10 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4=
|
||||
github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
||||
github.com/googleapis/gax-go/v2 v2.19.0 h1:fYQaUOiGwll0cGj7jmHT/0nPlcrZDFPrZRhTsoCr8hE=
|
||||
github.com/googleapis/gax-go/v2 v2.19.0/go.mod h1:w2ROXVdfGEVFXzmlciUU4EdjHgWvB5h2n6x/8XSTTJA=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.12 h1:Fg+zsqzYEs1ZnvmcztTYxhgCBsx3eEhEwQ1W/lHq/sQ=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.12/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
|
||||
github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
|
||||
github.com/gordonklaus/ineffassign v0.2.0 h1:Uths4KnmwxNJNzq87fwQQDDnbNb7De00VOk9Nu0TySs=
|
||||
github.com/gordonklaus/ineffassign v0.2.0/go.mod h1:TIpymnagPSexySzs7F9FnO1XFTy8IT3a59vmZp5Y9Lw=
|
||||
github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk=
|
||||
|
|
@ -424,8 +428,8 @@ github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3
|
|||
github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8=
|
||||
github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE=
|
||||
github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg=
|
||||
github.com/klauspost/compress v1.19.0 h1:sXLILfc9jV2QYWkzFOPWStmcUVH2RHEB1JCdY2oVvCQ=
|
||||
github.com/klauspost/compress v1.19.0/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
|
|
@ -512,10 +516,8 @@ github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm
|
|||
github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c=
|
||||
github.com/nunnatsa/ginkgolinter v0.23.0 h1:x3o4DGYOWbBMP/VdNQKgSj+25aJKx2Pe6lHr8gBcgf8=
|
||||
github.com/nunnatsa/ginkgolinter v0.23.0/go.mod h1:9qN1+0akwXEccwV1CAcCDfcoBlWXHB+ML9884pL4SZ4=
|
||||
github.com/oapi-codegen/nullable v1.1.0 h1:eAh8JVc5430VtYVnq00Hrbpag9PFRGWLjxR1/3KntMs=
|
||||
github.com/oapi-codegen/nullable v1.1.0/go.mod h1:KUZ3vUzkmEKY90ksAmit2+5juDIhIZhfDl+0PwOQlFY=
|
||||
github.com/oapi-codegen/runtime v1.6.0 h1:7Xx+GlueD6nRuyKoCPzL434Jfi3BetbiJOrzCHp/VPU=
|
||||
github.com/oapi-codegen/runtime v1.6.0/go.mod h1:GwV7hC2hviaMzj+ITfHVRESK5J2W/GefVwIND/bMGvU=
|
||||
github.com/oapi-codegen/runtime v1.2.0 h1:RvKc1CVS1QeKSNzO97FBQbSMZyQ8s6rZd+LpmzwHMP4=
|
||||
github.com/oapi-codegen/runtime v1.2.0/go.mod h1:Y7ZhmmlE8ikZOmuHRRndiIm7nf3xcVv+YMweKgG1DT0=
|
||||
github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=
|
||||
github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
|
||||
github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
|
||||
|
|
@ -543,14 +545,14 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgm
|
|||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI=
|
||||
github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.70.0 h1:bcpru3tWPVnxGnETLgOV5jbp/JRXgYEyv65CuBLAMMI=
|
||||
github.com/prometheus/common v0.70.0/go.mod h1:S/SFasQmgGiYH6C81LKCtYa8QACgthGg5zxL2udV7SY=
|
||||
github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI=
|
||||
github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY=
|
||||
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
|
||||
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
|
||||
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
|
||||
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
|
||||
github.com/quasilyte/go-ruleguard v0.4.5 h1:AGY0tiOT5hJX9BTdx/xBdoCubQUAE2grkqY2lSwvZcA=
|
||||
github.com/quasilyte/go-ruleguard v0.4.5/go.mod h1:Vl05zJ538vcEEwu16V/Hdu7IYZWyKSwIy4c88Ro1kRE=
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.23 h1:lxjt5B6ZCiBeeNO8/oQsegE6fLeCzuMRoVWSkXC4uvY=
|
||||
|
|
@ -656,8 +658,8 @@ github.com/tomarrell/wrapcheck/v2 v2.12.0 h1:H/qQ1aNWz/eeIhxKAFvkfIA+N7YDvq6TWVF
|
|||
github.com/tomarrell/wrapcheck/v2 v2.12.0/go.mod h1:AQhQuZd0p7b6rfW+vUwHm5OMCGgp63moQ9Qr/0BpIWo=
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw=
|
||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=
|
||||
github.com/ulikunitz/xz v0.5.16 h1:ld6NyySjx5lowVKwJvMRLnW5nxKX/xnpSiFYZ/Lxur0=
|
||||
github.com/ulikunitz/xz v0.5.16/go.mod h1:H9Rt/W6/Qj27PGauhQc6nfCDy7vHpzsOThBSaYDoEhw=
|
||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI=
|
||||
github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA=
|
||||
github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g=
|
||||
|
|
@ -704,12 +706,12 @@ go.augendre.info/fatcontext v0.9.0 h1:Gt5jGD4Zcj8CDMVzjOJITlSb9cEch54hjRRlN3qDoj
|
|||
go.augendre.info/fatcontext v0.9.0/go.mod h1:L94brOAT1OOUNue6ph/2HnwxoNlds9aXDF2FcUntbNw=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.43.0 h1:62yY3dT7/ShwOxzA0RsKRgshBmfElKI4d/Myu2OxDFU=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.43.0/go.mod h1:RyaZMFY7yi1kAs45S6mbFGz8O8rqB0dTY14uzvG4LCs=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.42.0 h1:kpt2PEJuOuqYkPcktfJqWWDjTEd/FNgrxcniL7kQrXQ=
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.42.0/go.mod h1:W9zQ439utxymRrXsUOzZbFX4JhLxXU4+ZnCt8GG7yA8=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
|
||||
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||
|
|
@ -728,20 +730,20 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
|||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
gocloud.dev v0.46.0 h1:niIuZwSjMtBx8K+ITB2s5kZullB13PGOS2ZoQPZxQ4Q=
|
||||
gocloud.dev v0.46.0/go.mod h1:ACQe+2qO+hEO+pdcvvsM+RB63r8TyGD1W3ESCLFyzvM=
|
||||
gocloud.dev v0.45.0 h1:WknIK8IbRdmynDvara3Q7G6wQhmEiOGwpgJufbM39sY=
|
||||
gocloud.dev v0.45.0/go.mod h1:0kXKmkCLG6d31N7NyLZWzt7jDSQura9zD/mWgiB6THI=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
|
|
@ -757,8 +759,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
|
|||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
||||
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
|
|
@ -773,8 +775,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
|
@ -786,8 +788,8 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
@ -809,8 +811,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
|
|
@ -825,10 +827,10 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
|
|
@ -841,8 +843,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
|
|||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
||||
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
|
||||
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
|
||||
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
|
||||
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
|
||||
|
|
@ -855,16 +857,16 @@ golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhS
|
|||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/api v0.272.0 h1:eLUQZGnAS3OHn31URRf9sAmRk3w2JjMx37d2k8AjJmA=
|
||||
google.golang.org/api v0.272.0/go.mod h1:wKjowi5LNJc5qarNvDCvNQBn3rVK8nSy6jg2SwRwzIA=
|
||||
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 h1:JNfk58HZ8lfmXbYK2vx/UvsqIL59TzByCxPIX4TDmsE=
|
||||
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:x5julN69+ED4PcFk/XWayw35O0lf/nGa4aNgODCmNmw=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
|
||||
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
|
||||
google.golang.org/api v0.269.0 h1:qDrTOxKUQ/P0MveH6a7vZ+DNHxJQjtGm/uvdbdGXCQg=
|
||||
google.golang.org/api v0.269.0/go.mod h1:N8Wpcu23Tlccl0zSHEkcAZQKDLdquxK+l9r2LkwAauE=
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM=
|
||||
google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:tu/dtnW1o3wfaxCOjSLn5IRX4YDcJrtlpzYkhHhGaC4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
|
||||
google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
@ -884,20 +886,20 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU=
|
||||
honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc=
|
||||
modernc.org/cc/v4 v4.29.0 h1:CXgwL8cvxmyzBQZzbSl/6xFtMCryb6u8IOqDci39cgc=
|
||||
modernc.org/cc/v4 v4.29.0/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||
modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU=
|
||||
modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk=
|
||||
modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY=
|
||||
modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||
modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ=
|
||||
modernc.org/ccgo/v4 v4.34.0/go.mod h1:AS5WYMyBakQ+fhsHhtP8mWB82KTGPkNNJDGfGQCe0/A=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI=
|
||||
modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.74.1 h1:bdR4VTKFMC4966QSNZ05XLGI/VwzVa2kTUX51Dm0riQ=
|
||||
modernc.org/libc v1.74.1/go.mod h1:uH4t5bOx3G3g9Xcmj10YKlTcVISlRDwv8VoQJG9n8Os=
|
||||
modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU=
|
||||
modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
|
|
@ -906,8 +908,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
|||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.55.0 h1:hIFh0MCH0rGinQ/4KYb5/UbCkRkb+UP+OkLCVWa5MTM=
|
||||
modernc.org/sqlite v1.55.0/go.mod h1:4ntCLuNmnH8+GNqjka1wNg7KJd5/Hi5FYp8K+XQ7GZw=
|
||||
modernc.org/sqlite v1.51.0 h1:aH/MMSoayAIhozZ7uJbVTT9QO/VhzBf0J9tymmmuC/U=
|
||||
modernc.org/sqlite v1.51.0/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
|
|
|||
|
|
@ -54,12 +54,10 @@ import (
|
|||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/git-pkgs/purl"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
|
|
@ -112,12 +110,6 @@ type Config struct {
|
|||
// size return ErrMetadataTooLarge. Default: "100MB".
|
||||
MetadataMaxSize string `json:"metadata_max_size" yaml:"metadata_max_size"`
|
||||
|
||||
// HTTPTimeout is the timeout for individual upstream HTTP requests made
|
||||
// by protocol handlers (metadata fetches, pass-through file requests).
|
||||
// Uses Go duration syntax (e.g. "30s", "2m"). Default: "30s".
|
||||
// Set to "0" to disable the timeout entirely.
|
||||
HTTPTimeout string `json:"http_timeout" yaml:"http_timeout"`
|
||||
|
||||
// MirrorAPI enables the /api/mirror endpoints for starting mirror jobs via HTTP.
|
||||
// Disabled by default to prevent unauthenticated users from triggering downloads.
|
||||
MirrorAPI bool `json:"mirror_api" yaml:"mirror_api"`
|
||||
|
|
@ -139,38 +131,9 @@ type CooldownConfig struct {
|
|||
Ecosystems map[string]string `json:"ecosystems" yaml:"ecosystems"`
|
||||
|
||||
// Packages overrides the cooldown for specific packages (keyed by PURL).
|
||||
// Valid PURL keys are normalized to canonical form before use.
|
||||
Packages map[string]string `json:"packages" yaml:"packages"`
|
||||
}
|
||||
|
||||
// NormalizedPackages returns a copy of the package overrides with valid PURL
|
||||
// keys in canonical form. An explicitly canonical key wins over an equivalent
|
||||
// noncanonical key, and invalid keys are preserved unchanged.
|
||||
func (c *CooldownConfig) NormalizedPackages() map[string]string {
|
||||
if c == nil || c.Packages == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(c.Packages))
|
||||
for key := range c.Packages {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
normalized := make(map[string]string, len(c.Packages))
|
||||
for _, key := range keys {
|
||||
canonical := key
|
||||
if parsed, err := purl.Parse(key); err == nil {
|
||||
canonical = parsed.String()
|
||||
}
|
||||
if _, exists := normalized[canonical]; exists && key != canonical {
|
||||
continue
|
||||
}
|
||||
normalized[canonical] = c.Packages[key]
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
// StorageConfig configures artifact storage.
|
||||
type StorageConfig struct {
|
||||
// URL is the storage backend URL.
|
||||
|
|
@ -256,21 +219,6 @@ type DatabaseConfig struct {
|
|||
URL string `json:"url" yaml:"url"`
|
||||
}
|
||||
|
||||
// String returns a human-readable description of the configured database
|
||||
// suitable for logging. For postgres the password in the connection URL is
|
||||
// redacted; if the URL cannot be parsed only the driver name is returned to
|
||||
// avoid leaking credentials.
|
||||
func (d DatabaseConfig) String() string {
|
||||
if d.Driver == "postgres" {
|
||||
u, err := url.Parse(d.URL)
|
||||
if err != nil || u.Host == "" {
|
||||
return "postgres"
|
||||
}
|
||||
return u.Redacted()
|
||||
}
|
||||
return d.Path
|
||||
}
|
||||
|
||||
// LogConfig configures logging.
|
||||
type LogConfig struct {
|
||||
// Level is the minimum log level: "debug", "info", "warn", "error".
|
||||
|
|
@ -497,9 +445,6 @@ func (c *Config) LoadFromEnv() {
|
|||
if v := os.Getenv("PROXY_METADATA_MAX_SIZE"); v != "" {
|
||||
c.MetadataMaxSize = v
|
||||
}
|
||||
if v := os.Getenv("PROXY_HTTP_TIMEOUT"); v != "" {
|
||||
c.HTTPTimeout = v
|
||||
}
|
||||
if v := os.Getenv("PROXY_GRADLE_BUILD_CACHE_READ_ONLY"); v != "" {
|
||||
c.Gradle.BuildCache.ReadOnly = v == "true" || v == "1"
|
||||
}
|
||||
|
|
@ -607,10 +552,6 @@ func (c *Config) Validate() error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := validateHTTPTimeout(c.HTTPTimeout); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.Health.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -680,7 +621,6 @@ func (g *GradleBuildCacheConfig) Validate() error {
|
|||
const (
|
||||
defaultMetadataTTL = 5 * time.Minute //nolint:mnd // sensible default
|
||||
defaultDirectServeTTL = 15 * time.Minute //nolint:mnd // sensible default
|
||||
defaultHTTPTimeout = 30 * time.Second //nolint:mnd // sensible default
|
||||
defaultMetadataMaxSize = 100 << 20
|
||||
defaultGradleBuildCacheMaxUploadSize = 100 << 20
|
||||
defaultGradleBuildCacheSweepInterval = 10 * time.Minute
|
||||
|
|
@ -701,20 +641,6 @@ func (c *Config) ParseMaxSize() int64 {
|
|||
return size
|
||||
}
|
||||
|
||||
func validateHTTPTimeout(s string) error {
|
||||
if s == "" || s == "0" {
|
||||
return nil
|
||||
}
|
||||
d, err := time.ParseDuration(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid http_timeout %q: %w", s, err)
|
||||
}
|
||||
if d < 0 {
|
||||
return fmt.Errorf("invalid http_timeout %q: must be non-negative", s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateMetadataMaxSize(s string) error {
|
||||
if s == "" {
|
||||
return nil
|
||||
|
|
@ -742,22 +668,6 @@ func (c *Config) ParseMetadataMaxSize() int64 {
|
|||
return size
|
||||
}
|
||||
|
||||
// ParseHTTPTimeout returns the upstream HTTP client timeout.
|
||||
// Returns 30s if unset, 0 (no timeout) if explicitly set to "0".
|
||||
func (c *Config) ParseHTTPTimeout() time.Duration {
|
||||
if c.HTTPTimeout == "" {
|
||||
return defaultHTTPTimeout
|
||||
}
|
||||
if c.HTTPTimeout == "0" {
|
||||
return 0
|
||||
}
|
||||
d, err := time.ParseDuration(c.HTTPTimeout)
|
||||
if err != nil || d < 0 {
|
||||
return defaultHTTPTimeout
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// ParseMetadataTTL returns the metadata TTL duration.
|
||||
// Returns 5 minutes if unset, 0 if explicitly disabled.
|
||||
func (c *Config) ParseMetadataTTL() time.Duration {
|
||||
|
|
|
|||
|
|
@ -363,34 +363,6 @@ cooldown:
|
|||
if cfg.Cooldown.Packages["pkg:npm/@babel/core"] != "14d" {
|
||||
t.Errorf("Cooldown.Packages[@babel/core] = %q, want %q", cfg.Cooldown.Packages["pkg:npm/@babel/core"], "14d")
|
||||
}
|
||||
if got := cfg.Cooldown.NormalizedPackages()["pkg:npm/%40babel/core"]; got != "14d" {
|
||||
t.Errorf("normalized Cooldown.Packages[@babel/core] = %q, want %q", got, "14d")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCooldownConfigNormalizedPackages(t *testing.T) {
|
||||
rawScoped := "pkg:npm/@typescript/typescript-darwin-arm64"
|
||||
canonicalScoped := "pkg:npm/%40typescript/typescript-darwin-arm64"
|
||||
cfg := CooldownConfig{Packages: map[string]string{
|
||||
rawScoped: "2d",
|
||||
canonicalScoped: "3d",
|
||||
"not-a-purl": "4d",
|
||||
}}
|
||||
|
||||
got := cfg.NormalizedPackages()
|
||||
|
||||
if got[canonicalScoped] != "3d" {
|
||||
t.Errorf("canonical scoped package duration = %q, want %q", got[canonicalScoped], "3d")
|
||||
}
|
||||
if _, exists := got[rawScoped]; exists {
|
||||
t.Errorf("raw scoped package key %q was not canonicalized", rawScoped)
|
||||
}
|
||||
if got["not-a-purl"] != "4d" {
|
||||
t.Errorf("invalid PURL duration = %q, want preserved value %q", got["not-a-purl"], "4d")
|
||||
}
|
||||
if cfg.Packages[rawScoped] != "2d" {
|
||||
t.Error("NormalizedPackages mutated the source map")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadCooldownFromEnv(t *testing.T) {
|
||||
|
|
@ -552,69 +524,6 @@ func TestValidateHealthStorageProbeInterval(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestParseHTTPTimeout(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
timeout string
|
||||
want time.Duration
|
||||
}{
|
||||
{"empty defaults to 30s", "", 30 * time.Second},
|
||||
{"explicit zero disables", "0", 0},
|
||||
{"2 minutes", "2m", 2 * time.Minute},
|
||||
{"90 seconds", "90s", 90 * time.Second},
|
||||
{"invalid defaults to 30s", "not-a-duration", 30 * time.Second},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg := Default()
|
||||
cfg.HTTPTimeout = tt.timeout
|
||||
got := cfg.ParseHTTPTimeout()
|
||||
if got != tt.want {
|
||||
t.Errorf("ParseHTTPTimeout() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateHTTPTimeout(t *testing.T) {
|
||||
cfg := Default()
|
||||
cfg.HTTPTimeout = "not-a-duration"
|
||||
if err := cfg.Validate(); err == nil {
|
||||
t.Error("expected validation error for invalid http_timeout")
|
||||
}
|
||||
|
||||
cfg.HTTPTimeout = "-5s"
|
||||
if err := cfg.Validate(); err == nil {
|
||||
t.Error("expected validation error for negative http_timeout")
|
||||
}
|
||||
|
||||
cfg.HTTPTimeout = "2m"
|
||||
if err := cfg.Validate(); err != nil {
|
||||
t.Errorf("unexpected error for valid http_timeout: %v", err)
|
||||
}
|
||||
|
||||
cfg.HTTPTimeout = "0"
|
||||
if err := cfg.Validate(); err != nil {
|
||||
t.Errorf("unexpected error for zero http_timeout: %v", err)
|
||||
}
|
||||
|
||||
cfg.HTTPTimeout = ""
|
||||
if err := cfg.Validate(); err != nil {
|
||||
t.Errorf("unexpected error for empty http_timeout: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadHTTPTimeoutFromEnv(t *testing.T) {
|
||||
cfg := Default()
|
||||
t.Setenv("PROXY_HTTP_TIMEOUT", "90s")
|
||||
cfg.LoadFromEnv()
|
||||
|
||||
if cfg.HTTPTimeout != "90s" {
|
||||
t.Errorf("HTTPTimeout = %q, want %q", cfg.HTTPTimeout, "90s")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadMetadataTTLFromEnv(t *testing.T) {
|
||||
cfg := Default()
|
||||
t.Setenv("PROXY_METADATA_TTL", "10m")
|
||||
|
|
@ -767,24 +676,3 @@ func TestValidateDirectServeBaseURL(t *testing.T) {
|
|||
t.Errorf("unexpected error for valid direct_serve_base_url: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDatabaseConfigString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg DatabaseConfig
|
||||
want string
|
||||
}{
|
||||
{"sqlite", DatabaseConfig{Driver: "sqlite", Path: "./cache/proxy.db"}, "./cache/proxy.db"},
|
||||
{"default driver", DatabaseConfig{Path: "/var/lib/proxy.db"}, "/var/lib/proxy.db"},
|
||||
{"postgres no password", DatabaseConfig{Driver: "postgres", URL: "postgres://user@localhost:5432/proxy"}, "postgres://user@localhost:5432/proxy"},
|
||||
{"postgres redacts password", DatabaseConfig{Driver: "postgres", URL: "postgres://user:secret@localhost:5432/proxy?sslmode=disable"}, "postgres://user:xxxxx@localhost:5432/proxy?sslmode=disable"},
|
||||
{"postgres unparseable url", DatabaseConfig{Driver: "postgres", URL: "host=localhost user=foo password=bar"}, "postgres"},
|
||||
{"postgres ignores sqlite path", DatabaseConfig{Driver: "postgres", URL: "postgres://localhost/db", Path: "./cache/proxy.db"}, "postgres://localhost/db"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if got := tt.cfg.String(); got != tt.want {
|
||||
t.Errorf("%s: String() = %q, want %q", tt.name, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/git-pkgs/purl"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -29,18 +30,11 @@ type CargoHandler struct {
|
|||
}
|
||||
|
||||
// NewCargoHandler creates a new cargo protocol handler.
|
||||
func NewCargoHandler(proxy *Proxy, proxyURL, indexURL, downloadURL string) *CargoHandler {
|
||||
if strings.TrimSpace(indexURL) == "" {
|
||||
indexURL = cargoUpstream
|
||||
}
|
||||
if strings.TrimSpace(downloadURL) == "" {
|
||||
downloadURL = cargoDownloadBase
|
||||
}
|
||||
|
||||
func NewCargoHandler(proxy *Proxy, proxyURL string) *CargoHandler {
|
||||
return &CargoHandler{
|
||||
proxy: proxy,
|
||||
indexURL: strings.TrimSuffix(indexURL, "/"),
|
||||
downloadURL: strings.TrimSuffix(downloadURL, "/"),
|
||||
indexURL: cargoUpstream,
|
||||
downloadURL: cargoDownloadBase,
|
||||
proxyURL: strings.TrimSuffix(proxyURL, "/"),
|
||||
}
|
||||
}
|
||||
|
|
@ -149,7 +143,7 @@ func (h *CargoHandler) applyCooldownFiltering(downstreamResponse http.ResponseWr
|
|||
continue
|
||||
}
|
||||
|
||||
cratePURL := canonicalPackagePURL("cargo", crate.Name)
|
||||
cratePURL := purl.MakePURLString("cargo", crate.Name, "")
|
||||
|
||||
if !h.proxy.Cooldown.IsAllowed("cargo", cratePURL, publishedAt) {
|
||||
h.proxy.Logger.Info("cooldown: filtering cargo version",
|
||||
|
|
@ -197,17 +191,10 @@ func (h *CargoHandler) handleDownload(w http.ResponseWriter, r *http.Request) {
|
|||
h.proxy.Logger.Info("cargo download request",
|
||||
"crate", name, "version", version, "filename", filename)
|
||||
|
||||
downloadURL := fmt.Sprintf(
|
||||
"%s/%s/%s",
|
||||
h.downloadURL,
|
||||
url.PathEscape(name),
|
||||
url.PathEscape(filename),
|
||||
)
|
||||
result, err := h.proxy.GetOrFetchArtifactFromURL(
|
||||
r.Context(), "cargo", name, version, filename, downloadURL,
|
||||
)
|
||||
result, err := h.proxy.GetOrFetchArtifact(r.Context(), "cargo", name, version, filename)
|
||||
if err != nil {
|
||||
h.proxy.serveArtifactError(w, err, "failed to fetch crate")
|
||||
h.proxy.Logger.Error("failed to get artifact", "error", err)
|
||||
http.Error(w, "failed to fetch crate", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package handler
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
|
@ -11,7 +10,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/git-pkgs/cooldown"
|
||||
"github.com/git-pkgs/registries/fetch"
|
||||
)
|
||||
|
||||
func cargoTestProxy() *Proxy {
|
||||
|
|
@ -72,75 +70,6 @@ func TestCargoConfigEndpoint(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCargoHandlerUsesConfiguredUpstreams(t *testing.T) {
|
||||
t.Run("index", func(t *testing.T) {
|
||||
var requestPath, authHeader string
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requestPath = r.URL.Path
|
||||
authHeader = r.Header.Get("Authorization")
|
||||
if authHeader != "Bearer cargo-token" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
_, _ = io.WriteString(w, `{"name":"serde","vers":"1.0.0"}`)
|
||||
}))
|
||||
defer upstream.Close()
|
||||
|
||||
proxy, _, _, _ := setupTestProxy(t)
|
||||
proxy.HTTPClient = upstream.Client()
|
||||
proxy.AuthForURL = func(string) (string, string) {
|
||||
return "Authorization", "Bearer cargo-token"
|
||||
}
|
||||
h := NewCargoHandler(
|
||||
proxy,
|
||||
"http://proxy.test",
|
||||
upstream.URL+"/index/",
|
||||
"https://crates.example.test/files/",
|
||||
)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/se/rd/serde", nil)
|
||||
w := httptest.NewRecorder()
|
||||
h.Routes().ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d; body: %s", w.Code, http.StatusOK, w.Body.String())
|
||||
}
|
||||
if requestPath != "/index/se/rd/serde" {
|
||||
t.Errorf("upstream path = %q, want %q", requestPath, "/index/se/rd/serde")
|
||||
}
|
||||
if authHeader != "Bearer cargo-token" {
|
||||
t.Errorf("Authorization = %q, want %q", authHeader, "Bearer cargo-token")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("download", func(t *testing.T) {
|
||||
proxy, _, _, artifactFetcher := setupTestProxy(t)
|
||||
artifactFetcher.artifact = &fetch.Artifact{
|
||||
Body: io.NopCloser(strings.NewReader("crate")),
|
||||
ContentType: "application/gzip",
|
||||
}
|
||||
h := NewCargoHandler(
|
||||
proxy,
|
||||
"http://proxy.test",
|
||||
"https://index.example.test/root/",
|
||||
"https://crates.example.test/files/",
|
||||
)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/crates/serde/1.0.0/download", nil)
|
||||
w := httptest.NewRecorder()
|
||||
h.Routes().ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d; body: %s", w.Code, http.StatusOK, w.Body.String())
|
||||
}
|
||||
want := "https://crates.example.test/files/serde/serde-1.0.0.crate"
|
||||
if artifactFetcher.fetchedURL != want {
|
||||
t.Errorf("fetched URL = %q, want %q", artifactFetcher.fetchedURL, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCargoIndexProxy(t *testing.T) {
|
||||
// Create a mock upstream index server
|
||||
indexContent := `{"name":"serde","vers":"1.0.0","deps":[],"cksum":"abc123"}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
|
@ -10,6 +9,8 @@ import (
|
|||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/git-pkgs/purl"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -214,7 +215,7 @@ func deepCopyValue(v any) any {
|
|||
// filterAndRewriteVersions applies cooldown filtering and rewrites dist URLs
|
||||
// for a single package's version list.
|
||||
func (h *ComposerHandler) filterAndRewriteVersions(packageName string, versionList []any) []any {
|
||||
packagePURL := canonicalPackagePURL("composer", packageName)
|
||||
packagePURL := purl.MakePURLString("composer", packageName, "")
|
||||
|
||||
filtered := versionList[:0]
|
||||
for _, v := range versionList {
|
||||
|
|
@ -306,127 +307,50 @@ func (h *ComposerHandler) handleDownload(w http.ResponseWriter, r *http.Request)
|
|||
h.proxy.Logger.Info("composer download request",
|
||||
"package", packageName, "version", version, "filename", filename)
|
||||
|
||||
// We need to fetch the metadata to get the actual download URL since
|
||||
// Packagist URLs include a hash. Packagist serves dev versions (e.g.
|
||||
// "3.x-dev", "dev-master") from a separate "~dev" metadata file, while
|
||||
// tagged releases live in the regular file. Try the file most likely to
|
||||
// contain this version first, then fall back to the other so that both
|
||||
// stable and dev versions resolve correctly.
|
||||
metaURLs := h.metadataURLsForVersion(vendor, pkg, version)
|
||||
// We need to fetch the metadata to get the actual download URL
|
||||
// since Packagist URLs include a hash
|
||||
metaURL := fmt.Sprintf("%s/p2/%s/%s.json", h.repoURL, vendor, pkg)
|
||||
|
||||
h.proxy.Logger.Debug("resolving download URL",
|
||||
"package", packageName, "version", version,
|
||||
"metadata_urls", metaURLs)
|
||||
|
||||
var downloadURL string
|
||||
for _, metaURL := range metaURLs {
|
||||
url, err := h.findDownloadURLFromMetadata(r.Context(), metaURL, packageName, version)
|
||||
if err != nil {
|
||||
h.proxy.Logger.Error("failed to fetch metadata", "error", err, "url", metaURL)
|
||||
http.Error(w, "failed to fetch metadata", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
if url != "" {
|
||||
downloadURL = url
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if downloadURL == "" {
|
||||
h.proxy.Logger.Debug("version not found in any metadata source",
|
||||
"package", packageName, "version", version,
|
||||
"tried_urls", metaURLs)
|
||||
http.Error(w, "version not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
h.proxy.Logger.Debug("resolved download URL",
|
||||
"package", packageName, "version", version,
|
||||
"download_url", downloadURL)
|
||||
|
||||
result, err := h.proxy.GetOrFetchArtifactFromURL(r.Context(), "composer", packageName, version, filename, downloadURL)
|
||||
req, err := http.NewRequestWithContext(r.Context(), http.MethodGet, metaURL, nil)
|
||||
if err != nil {
|
||||
h.proxy.serveArtifactError(w, err, "failed to fetch package")
|
||||
http.Error(w, "failed to create request", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
ServeArtifact(w, result)
|
||||
}
|
||||
|
||||
// isDevVersion reports whether a Composer version string refers to a
|
||||
// development (unstable, branch) version rather than a tagged release.
|
||||
// Composer formats these as either "dev-<branch>" (e.g. "dev-master") or
|
||||
// "<alias>-dev" (e.g. "3.x-dev").
|
||||
func isDevVersion(version string) bool {
|
||||
return strings.HasPrefix(version, "dev-") || strings.HasSuffix(version, "-dev")
|
||||
}
|
||||
|
||||
// metadataURLsForVersion returns the upstream metadata URLs to consult for a
|
||||
// given version, in priority order. Dev versions are served from the "~dev"
|
||||
// file, tagged releases from the regular file; the other file is included as a
|
||||
// fallback so an unexpected classification still resolves.
|
||||
func (h *ComposerHandler) metadataURLsForVersion(vendor, pkg, version string) []string {
|
||||
stable := fmt.Sprintf("%s/p2/%s/%s.json", h.repoURL, vendor, pkg)
|
||||
dev := fmt.Sprintf("%s/p2/%s/%s~dev.json", h.repoURL, vendor, pkg)
|
||||
|
||||
if isDevVersion(version) {
|
||||
return []string{dev, stable}
|
||||
}
|
||||
return []string{stable, dev}
|
||||
}
|
||||
|
||||
// findDownloadURLFromMetadata fetches a metadata document and returns the dist
|
||||
// URL for the given version, or an empty string if the version is not present.
|
||||
// An error is returned only on transport failure; a missing document (non-200)
|
||||
// or a missing version both yield an empty string so the caller can fall back.
|
||||
func (h *ComposerHandler) findDownloadURLFromMetadata(ctx context.Context, metaURL, packageName, version string) (string, error) {
|
||||
h.proxy.Logger.Debug("fetching upstream metadata for download lookup",
|
||||
"url", metaURL, "package", packageName, "version", version)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, metaURL, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resp, err := h.proxy.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
h.proxy.Logger.Error("failed to fetch metadata", "error", err)
|
||||
http.Error(w, "failed to fetch metadata", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
h.proxy.Logger.Debug("upstream metadata response",
|
||||
"url", metaURL, "status", resp.StatusCode)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", nil
|
||||
http.Error(w, "package not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
var metadata map[string]any
|
||||
if err := json.NewDecoder(resp.Body).Decode(&metadata); err != nil {
|
||||
return "", err
|
||||
http.Error(w, "failed to parse metadata", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Expand minified Composer v2 format so that inherited fields (including
|
||||
// dist) are present on every version entry. Without this, versions that
|
||||
// inherit dist from a previous entry will appear to have no download URL.
|
||||
if metadata["minified"] == "composer/2.0" {
|
||||
h.proxy.Logger.Debug("expanding minified metadata", "url", metaURL)
|
||||
if packages, ok := metadata["packages"].(map[string]any); ok {
|
||||
for pkgName, versions := range packages {
|
||||
versionList, ok := versions.([]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
packages[pkgName] = expandMinifiedVersions(versionList)
|
||||
}
|
||||
}
|
||||
// Find the download URL for this version
|
||||
downloadURL := h.findDownloadURL(metadata, packageName, version)
|
||||
if downloadURL == "" {
|
||||
http.Error(w, "version not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
url := h.findDownloadURL(metadata, packageName, version)
|
||||
h.proxy.Logger.Debug("download URL lookup result",
|
||||
"url", metaURL, "package", packageName, "version", version,
|
||||
"download_url", url)
|
||||
return url, nil
|
||||
result, err := h.proxy.GetOrFetchArtifactFromURL(r.Context(), "composer", packageName, version, filename, downloadURL)
|
||||
if err != nil {
|
||||
h.proxy.Logger.Error("failed to get artifact", "error", err)
|
||||
http.Error(w, "failed to fetch package", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
ServeArtifact(w, result)
|
||||
}
|
||||
|
||||
// findDownloadURL finds the dist URL for a specific version in metadata.
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
|
@ -468,100 +465,6 @@ func TestComposerExpandMinifiedSharedDistReferences(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestComposerDownloadDevVersionUsesDevMetadata is a regression test for the
|
||||
// bug that made it impossible to install a *-dev dependency from dist.
|
||||
//
|
||||
// Packagist serves development versions (e.g. "3.x-dev", "dev-master") from a
|
||||
// separate "{package}~dev.json" metadata file; the regular "{package}.json"
|
||||
// file contains only tagged releases. The download handler used to fetch only
|
||||
// the regular file, so it could never find the dist URL for a dev version and
|
||||
// returned 404 — causing Composer to silently fall back to a git clone.
|
||||
//
|
||||
// This test serves both files from a mock upstream and asserts that:
|
||||
// - the OLD behavior (regular file only) cannot resolve the dev version, and
|
||||
// - the FIXED behavior (consulting the ~dev file) does.
|
||||
func TestComposerDownloadDevVersionUsesDevMetadata(t *testing.T) {
|
||||
const (
|
||||
pkg = "phpmd/phpmd"
|
||||
vendor = "phpmd"
|
||||
name = "phpmd"
|
||||
version = "3.x-dev"
|
||||
distURL = "https://api.github.com/repos/phpmd/phpmd/zipball/2a9217f60aaf27bf6ddad9188f254d020ab70745"
|
||||
)
|
||||
|
||||
// Regular metadata: tagged releases only — no dev versions.
|
||||
stableBody := `{
|
||||
"packages": {
|
||||
"phpmd/phpmd": [
|
||||
{"version": "2.15.0", "dist": {"url": "https://example.com/2.15.0.zip", "type": "zip"}}
|
||||
]
|
||||
}
|
||||
}`
|
||||
|
||||
// ~dev metadata: where the 3.x-dev version actually lives.
|
||||
devBody := `{
|
||||
"packages": {
|
||||
"phpmd/phpmd": [
|
||||
{"version": "3.x-dev", "dist": {"url": "` + distURL + `", "type": "zip"}}
|
||||
]
|
||||
}
|
||||
}`
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/p2/phpmd/phpmd.json":
|
||||
_, _ = w.Write([]byte(stableBody))
|
||||
case "/p2/phpmd/phpmd~dev.json":
|
||||
_, _ = w.Write([]byte(devBody))
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
h := &ComposerHandler{
|
||||
proxy: testProxy(),
|
||||
repoURL: srv.URL,
|
||||
proxyURL: "http://localhost:8080",
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// OLD behavior: fetching only the regular file fails to resolve the dev
|
||||
// version, which is what produced the 404 before the fix.
|
||||
stableURL := srv.URL + "/p2/phpmd/phpmd.json"
|
||||
got, err := h.findDownloadURLFromMetadata(ctx, stableURL, pkg, version)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error fetching regular metadata: %v", err)
|
||||
}
|
||||
if got != "" {
|
||||
t.Fatalf("regular metadata unexpectedly contained dev version %q (got %q); "+
|
||||
"the test no longer reproduces the original bug", version, got)
|
||||
}
|
||||
|
||||
// FIXED behavior: the handler consults the ~dev file (it is first in the
|
||||
// candidate list for dev versions) and resolves the dist URL.
|
||||
urls := h.metadataURLsForVersion(vendor, name, version)
|
||||
if len(urls) == 0 || !strings.HasSuffix(urls[0], "/p2/phpmd/phpmd~dev.json") {
|
||||
t.Fatalf("dev version should consult the ~dev metadata file first, got %v", urls)
|
||||
}
|
||||
|
||||
var resolved string
|
||||
for _, u := range urls {
|
||||
resolved, err = h.findDownloadURLFromMetadata(ctx, u, pkg, version)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error fetching metadata %q: %v", u, err)
|
||||
}
|
||||
if resolved != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if resolved != distURL {
|
||||
t.Errorf("dev version dist URL = %q, want %q", resolved, distURL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComposerRewriteMetadataCooldown(t *testing.T) {
|
||||
now := time.Now()
|
||||
old := now.Add(-10 * 24 * time.Hour).Format(time.RFC3339)
|
||||
|
|
|
|||
|
|
@ -84,7 +84,8 @@ func (h *ConanHandler) handleRecipeFile(w http.ResponseWriter, r *http.Request)
|
|||
|
||||
result, err := h.proxy.GetOrFetchArtifactFromURL(r.Context(), "conan", packageName, storageVersion, storageFilename, upstreamURL)
|
||||
if err != nil {
|
||||
h.proxy.serveArtifactError(w, err, "failed to fetch file")
|
||||
h.proxy.Logger.Error("failed to get artifact", "error", err)
|
||||
http.Error(w, "failed to fetch file", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -121,7 +122,8 @@ func (h *ConanHandler) handlePackageFile(w http.ResponseWriter, r *http.Request)
|
|||
|
||||
result, err := h.proxy.GetOrFetchArtifactFromURL(r.Context(), "conan", packageName, storageVersion, storageFilename, upstreamURL)
|
||||
if err != nil {
|
||||
h.proxy.serveArtifactError(w, err, "failed to fetch file")
|
||||
h.proxy.Logger.Error("failed to get artifact", "error", err)
|
||||
http.Error(w, "failed to fetch file", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/git-pkgs/purl"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -72,7 +74,8 @@ func (h *CondaHandler) handleDownload(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
result, err := h.proxy.GetOrFetchArtifactFromURL(r.Context(), "conda", packageName, version, filename, upstreamURL)
|
||||
if err != nil {
|
||||
h.proxy.serveArtifactError(w, err, "failed to fetch package")
|
||||
h.proxy.Logger.Error("failed to get artifact", "error", err)
|
||||
http.Error(w, "failed to fetch package", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -215,7 +218,7 @@ func (h *CondaHandler) applyCooldownFiltering(body []byte) ([]byte, error) {
|
|||
continue
|
||||
}
|
||||
|
||||
packagePURL := canonicalPackagePURL("conda", name)
|
||||
packagePURL := purl.MakePURLString("conda", name, "")
|
||||
|
||||
if !h.proxy.Cooldown.IsAllowed("conda", packagePURL, publishedAt) {
|
||||
version, _ := entryMap["version"].(string)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package handler
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
|
@ -118,12 +117,8 @@ func (h *ContainerHandler) handleBlobDownload(w http.ResponseWriter, r *http.Req
|
|||
)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrUpstreamNotFound) {
|
||||
h.containerError(w, http.StatusNotFound, "BLOB_UNKNOWN", "blob unknown to registry")
|
||||
return
|
||||
}
|
||||
h.proxy.Logger.Error("failed to fetch blob", "error", err)
|
||||
h.containerError(w, http.StatusBadGateway, "INTERNAL_ERROR", "failed to fetch blob")
|
||||
h.containerError(w, http.StatusBadGateway, "BLOB_UNKNOWN", "failed to fetch blob")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,8 @@ func (h *CRANHandler) handleSourceDownload(w http.ResponseWriter, r *http.Reques
|
|||
|
||||
result, err := h.proxy.GetOrFetchArtifactFromURL(r.Context(), "cran", name, version, filename, upstreamURL)
|
||||
if err != nil {
|
||||
h.proxy.serveArtifactError(w, err, "failed to fetch package")
|
||||
h.proxy.Logger.Error("failed to get artifact", "error", err)
|
||||
http.Error(w, "failed to fetch package", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -106,7 +107,8 @@ func (h *CRANHandler) handleBinaryDownload(w http.ResponseWriter, r *http.Reques
|
|||
|
||||
result, err := h.proxy.GetOrFetchArtifactFromURL(r.Context(), "cran", name, storageVersion, filename, upstreamURL)
|
||||
if err != nil {
|
||||
h.proxy.serveArtifactError(w, err, "failed to fetch package")
|
||||
h.proxy.Logger.Error("failed to get artifact", "error", err)
|
||||
http.Error(w, "failed to fetch package", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,8 @@ func (h *DebianHandler) handlePackageDownload(w http.ResponseWriter, r *http.Req
|
|||
result, err := h.proxy.GetOrFetchArtifactFromURL(
|
||||
r.Context(), "deb", name, version, filename, downloadURL)
|
||||
if err != nil {
|
||||
h.proxy.serveArtifactError(w, err, "failed to fetch package")
|
||||
h.proxy.Logger.Error("failed to get debian package", "error", err)
|
||||
http.Error(w, "failed to fetch package", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type filenameDownload struct {
|
||||
ecosystem string
|
||||
suffix string
|
||||
parseErr string
|
||||
fetchErr string
|
||||
parse func(string) (name, version string)
|
||||
}
|
||||
|
||||
func (p *Proxy) handleFilenameDownload(w http.ResponseWriter, r *http.Request, d filenameDownload) {
|
||||
filename := r.PathValue("filename")
|
||||
if filename == "" || !strings.HasSuffix(filename, d.suffix) {
|
||||
http.Error(w, "invalid filename", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
name, version := d.parse(filename)
|
||||
if name == "" || version == "" {
|
||||
http.Error(w, d.parseErr, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
p.Logger.Info(d.ecosystem+" download request",
|
||||
"name", name, "version", version, "filename", filename)
|
||||
|
||||
result, err := p.GetOrFetchArtifact(r.Context(), d.ecosystem, name, version, filename)
|
||||
if err != nil {
|
||||
p.serveArtifactError(w, err, d.fetchErr)
|
||||
return
|
||||
}
|
||||
|
||||
ServeArtifact(w, result)
|
||||
}
|
||||
|
|
@ -8,6 +8,8 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/git-pkgs/purl"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -58,13 +60,30 @@ func (h *GemHandler) Routes() http.Handler {
|
|||
|
||||
// handleDownload serves a gem file, fetching and caching from upstream if needed.
|
||||
func (h *GemHandler) handleDownload(w http.ResponseWriter, r *http.Request) {
|
||||
h.proxy.handleFilenameDownload(w, r, filenameDownload{
|
||||
ecosystem: "gem",
|
||||
suffix: ".gem",
|
||||
parseErr: "could not parse gem filename",
|
||||
fetchErr: "failed to fetch gem",
|
||||
parse: h.parseGemFilename,
|
||||
})
|
||||
filename := r.PathValue("filename")
|
||||
if filename == "" || !strings.HasSuffix(filename, ".gem") {
|
||||
http.Error(w, "invalid filename", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract name and version from filename (e.g., "rails-7.1.0.gem")
|
||||
name, version := h.parseGemFilename(filename)
|
||||
if name == "" || version == "" {
|
||||
http.Error(w, "could not parse gem filename", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
h.proxy.Logger.Info("gem download request",
|
||||
"name", name, "version", version, "filename", filename)
|
||||
|
||||
result, err := h.proxy.GetOrFetchArtifact(r.Context(), "gem", name, version, filename)
|
||||
if err != nil {
|
||||
h.proxy.Logger.Error("failed to get artifact", "error", err)
|
||||
http.Error(w, "failed to fetch gem", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
ServeArtifact(w, result)
|
||||
}
|
||||
|
||||
// parseGemFilename extracts name and version from a gem filename.
|
||||
|
|
@ -247,7 +266,7 @@ func (h *GemHandler) fetchFilteredVersions(r *http.Request, name string) (map[st
|
|||
return nil, err
|
||||
}
|
||||
|
||||
packagePURL := canonicalPackagePURL("gem", name)
|
||||
packagePURL := purl.MakePURLString("gem", name, "")
|
||||
filtered := make(map[string]bool)
|
||||
|
||||
for _, v := range versions {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/git-pkgs/registries/fetch"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -103,10 +100,6 @@ func (h *GoHandler) handleDownload(w http.ResponseWriter, r *http.Request, modul
|
|||
|
||||
result, err := h.proxy.GetOrFetchArtifact(r.Context(), "golang", decodedModule, version, filename)
|
||||
if err != nil {
|
||||
if errors.Is(err, fetch.ErrNotFound) {
|
||||
http.Error(w, "not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
h.proxy.Logger.Error("failed to get artifact", "error", err)
|
||||
http.Error(w, "failed to fetch module", http.StatusBadGateway)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -1,49 +1,9 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/git-pkgs/registries/fetch"
|
||||
)
|
||||
|
||||
func TestGoModuleDownloadUpstreamErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fetchErr error
|
||||
wantStatus int
|
||||
}{
|
||||
{
|
||||
name: "module not found",
|
||||
fetchErr: fetch.ErrNotFound,
|
||||
wantStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "upstream failure",
|
||||
fetchErr: errors.New("connection refused"),
|
||||
wantStatus: http.StatusBadGateway,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
proxy, _, _, fetcher := setupTestProxy(t)
|
||||
fetcher.fetchErr = tt.fetchErr
|
||||
handler := NewGoHandler(proxy, "http://localhost:8080")
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/example.com/mod/@v/v1.0.0.zip", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
handler.Routes().ServeHTTP(resp, req)
|
||||
|
||||
if resp.Code != tt.wantStatus {
|
||||
t.Fatalf("status = %d, want %d", resp.Code, tt.wantStatus)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeGoModule(t *testing.T) {
|
||||
tests := []struct {
|
||||
encoded string
|
||||
|
|
|
|||
|
|
@ -48,14 +48,6 @@ func hasDotDotSegment(path string) bool {
|
|||
|
||||
const defaultHTTPTimeout = 30 * time.Second
|
||||
|
||||
// canonicalPackagePURL returns a versionless PURL in canonical form so cooldown
|
||||
// lookups match keys produced by config.CooldownConfig.NormalizedPackages.
|
||||
func canonicalPackagePURL(ecosystem, name string) string {
|
||||
p := purl.MakePURL(ecosystem, name, "")
|
||||
_ = p.Normalize()
|
||||
return p.String()
|
||||
}
|
||||
|
||||
const contentTypeJSON = "application/json"
|
||||
|
||||
const headerAcceptEncoding = "Accept-Encoding"
|
||||
|
|
@ -104,7 +96,6 @@ type Proxy struct {
|
|||
// storage at an internal one.
|
||||
DirectServeBaseURL string
|
||||
HTTPClient *http.Client
|
||||
AuthForURL func(string) (headerName, headerValue string)
|
||||
}
|
||||
|
||||
// NewProxy creates a new Proxy with the given dependencies.
|
||||
|
|
@ -252,9 +243,6 @@ func (p *Proxy) fetchAndCache(ctx context.Context, ecosystem, name, version, fil
|
|||
// Resolve download URL
|
||||
info, err := p.Resolver.Resolve(ctx, ecosystem, name, version)
|
||||
if err != nil {
|
||||
if errors.Is(err, fetch.ErrNotFound) {
|
||||
return nil, ErrUpstreamNotFound
|
||||
}
|
||||
return nil, fmt.Errorf("resolving download URL: %w", err)
|
||||
}
|
||||
|
||||
|
|
@ -274,9 +262,6 @@ func (p *Proxy) fetchAndCache(ctx context.Context, ecosystem, name, version, fil
|
|||
if err != nil {
|
||||
metrics.RecordUpstreamFetch(ecosystem, fetchDuration)
|
||||
metrics.RecordUpstreamError(ecosystem, "fetch_failed")
|
||||
if errors.Is(err, fetch.ErrNotFound) {
|
||||
return nil, ErrUpstreamNotFound
|
||||
}
|
||||
return nil, fmt.Errorf("fetching from upstream: %w", err)
|
||||
}
|
||||
metrics.RecordUpstreamFetch(ecosystem, fetchDuration)
|
||||
|
|
@ -406,7 +391,6 @@ func (p *Proxy) ProxyUpstream(w http.ResponseWriter, r *http.Request, upstreamUR
|
|||
req.Header.Set(header, v)
|
||||
}
|
||||
}
|
||||
p.applyUpstreamAuth(req)
|
||||
|
||||
resp, err := p.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
|
|
@ -433,7 +417,6 @@ func (p *Proxy) ProxyFile(w http.ResponseWriter, r *http.Request, upstreamURL st
|
|||
http.Error(w, "failed to create request", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
p.applyUpstreamAuth(req)
|
||||
|
||||
resp, err := p.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
|
|
@ -460,18 +443,7 @@ func JSONError(w http.ResponseWriter, status int, message string) {
|
|||
}
|
||||
|
||||
// ErrUpstreamNotFound indicates the upstream returned 404.
|
||||
var ErrUpstreamNotFound = fmt.Errorf("upstream: %w", fetch.ErrNotFound)
|
||||
|
||||
// serveArtifactError writes response for a failed fetch:
|
||||
// 404 when upstream reports artifact missing, 502 otherwise.
|
||||
func (p *Proxy) serveArtifactError(w http.ResponseWriter, err error, clientMsg string) {
|
||||
if errors.Is(err, ErrUpstreamNotFound) {
|
||||
http.Error(w, "not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
p.Logger.Error("failed to get artifact", "error", err)
|
||||
http.Error(w, clientMsg, http.StatusBadGateway)
|
||||
}
|
||||
var ErrUpstreamNotFound = fmt.Errorf("upstream: not found")
|
||||
|
||||
// errStale304 is returned when upstream sends 304 but the cached file is missing.
|
||||
var errStale304 = fmt.Errorf("upstream returned 304 but cached file is missing")
|
||||
|
|
@ -574,7 +546,6 @@ func (p *Proxy) fetchUpstreamMetadata(ctx context.Context, upstreamURL string, e
|
|||
return nil, "", "", zeroTime, fmt.Errorf("creating request: %w", err)
|
||||
}
|
||||
req.Header.Set("Accept", accept)
|
||||
p.applyUpstreamAuth(req)
|
||||
|
||||
if entry != nil && entry.ETag.Valid {
|
||||
req.Header.Set("If-None-Match", entry.ETag.String)
|
||||
|
|
@ -764,7 +735,6 @@ func (p *Proxy) proxyMetadataStream(w http.ResponseWriter, r *http.Request, upst
|
|||
accept = acceptHeaders[0]
|
||||
}
|
||||
req.Header.Set("Accept", accept)
|
||||
p.applyUpstreamAuth(req)
|
||||
|
||||
for _, header := range []string{headerAcceptEncoding, "If-Modified-Since", "If-None-Match"} {
|
||||
if v := r.Header.Get(header); v != "" {
|
||||
|
|
@ -789,17 +759,6 @@ func (p *Proxy) proxyMetadataStream(w http.ResponseWriter, r *http.Request, upst
|
|||
_, _ = io.Copy(w, resp.Body)
|
||||
}
|
||||
|
||||
func (p *Proxy) applyUpstreamAuth(req *http.Request) {
|
||||
if p.AuthForURL == nil {
|
||||
return
|
||||
}
|
||||
|
||||
headerName, headerValue := p.AuthForURL(req.URL.String())
|
||||
if headerName != "" && headerValue != "" {
|
||||
req.Header.Set(headerName, headerValue)
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrFetchArtifactFromURL retrieves an artifact from cache or fetches from a specific URL.
|
||||
// This is useful for registries where download URLs are determined from metadata.
|
||||
func (p *Proxy) GetOrFetchArtifactFromURL(ctx context.Context, ecosystem, name, version, filename, downloadURL string) (*CacheResult, error) {
|
||||
|
|
@ -828,9 +787,6 @@ func (p *Proxy) fetchAndCacheFromURL(ctx context.Context, ecosystem, name, versi
|
|||
|
||||
artifact, err := p.Fetcher.FetchWithHeaders(ctx, downloadURL, headers)
|
||||
if err != nil {
|
||||
if errors.Is(err, fetch.ErrNotFound) {
|
||||
return nil, ErrUpstreamNotFound
|
||||
}
|
||||
return nil, fmt.Errorf("fetching from upstream: %w", err)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/git-pkgs/proxy/internal/config"
|
||||
"github.com/git-pkgs/proxy/internal/database"
|
||||
"github.com/git-pkgs/proxy/internal/storage"
|
||||
"github.com/git-pkgs/registries/fetch"
|
||||
|
|
@ -1011,33 +1010,3 @@ func TestProxyCached_FreshResponse_NoWarningHeader(t *testing.T) {
|
|||
t.Errorf("Warning should be empty for fresh response, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCanonicalPackagePURLMatchesConfig ensures the runtime cooldown lookup key
|
||||
// agrees with config.CooldownConfig.NormalizedPackages for the same package,
|
||||
// so a configured override is actually found regardless of how the user wrote it.
|
||||
func TestCanonicalPackagePURLMatchesConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
ecosystem string
|
||||
requestName string
|
||||
configKey string
|
||||
}{
|
||||
{"npm", "@babel/core", "pkg:npm/@babel/core"},
|
||||
{"npm", "@babel/core", "pkg:npm/%40babel/core"},
|
||||
{"npm", "@typescript/typescript-darwin-arm64", "pkg:npm/@typescript/typescript-darwin-arm64"},
|
||||
{"pypi", "Django", "pkg:pypi/Django"},
|
||||
{"pypi", "django", "pkg:pypi/Django"},
|
||||
{"composer", "symfony/console", "pkg:composer/Symfony/Console"},
|
||||
{"cargo", "serde", "pkg:cargo/serde"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.ecosystem+"/"+tt.requestName+"<="+tt.configKey, func(t *testing.T) {
|
||||
cfg := config.CooldownConfig{Packages: map[string]string{tt.configKey: "1d"}}
|
||||
normalized := cfg.NormalizedPackages()
|
||||
|
||||
lookup := canonicalPackagePURL(tt.ecosystem, tt.requestName)
|
||||
if _, ok := normalized[lookup]; !ok {
|
||||
t.Errorf("lookup key %q not found in normalized config %v", lookup, normalized)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/git-pkgs/purl"
|
||||
"google.golang.org/protobuf/encoding/protowire"
|
||||
)
|
||||
|
||||
|
|
@ -53,13 +54,30 @@ func (h *HexHandler) Routes() http.Handler {
|
|||
|
||||
// handleDownload serves a package tarball, fetching and caching from upstream if needed.
|
||||
func (h *HexHandler) handleDownload(w http.ResponseWriter, r *http.Request) {
|
||||
h.proxy.handleFilenameDownload(w, r, filenameDownload{
|
||||
ecosystem: "hex",
|
||||
suffix: ".tar",
|
||||
parseErr: "could not parse tarball filename",
|
||||
fetchErr: "failed to fetch package",
|
||||
parse: h.parseTarballFilename,
|
||||
})
|
||||
filename := r.PathValue("filename")
|
||||
if filename == "" || !strings.HasSuffix(filename, ".tar") {
|
||||
http.Error(w, "invalid filename", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract name and version from filename (e.g., "phoenix-1.7.10.tar")
|
||||
name, version := h.parseTarballFilename(filename)
|
||||
if name == "" || version == "" {
|
||||
http.Error(w, "could not parse tarball filename", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
h.proxy.Logger.Info("hex download request",
|
||||
"name", name, "version", version, "filename", filename)
|
||||
|
||||
result, err := h.proxy.GetOrFetchArtifact(r.Context(), "hex", name, version, filename)
|
||||
if err != nil {
|
||||
h.proxy.Logger.Error("failed to get artifact", "error", err)
|
||||
http.Error(w, "failed to fetch package", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
ServeArtifact(w, result)
|
||||
}
|
||||
|
||||
// parseTarballFilename extracts name and version from a hex tarball filename.
|
||||
|
|
@ -219,7 +237,7 @@ func (h *HexHandler) fetchFilteredVersions(r *http.Request, name string) (map[st
|
|||
return nil, err
|
||||
}
|
||||
|
||||
packagePURL := canonicalPackagePURL("hex", name)
|
||||
packagePURL := purl.MakePURLString("hex", name, "")
|
||||
filtered := make(map[string]bool)
|
||||
|
||||
for _, release := range pkg.Releases {
|
||||
|
|
|
|||
|
|
@ -90,7 +90,8 @@ func (h *JuliaHandler) handleRegistry(w http.ResponseWriter, r *http.Request) {
|
|||
upstreamURL := h.upstreamURL + r.URL.Path
|
||||
result, err := h.proxy.GetOrFetchArtifactFromURL(r.Context(), "julia", juliaRegistryName, hash, hash+".tar.gz", upstreamURL)
|
||||
if err != nil {
|
||||
h.proxy.serveArtifactError(w, err, "failed to fetch registry")
|
||||
h.proxy.Logger.Error("failed to get registry", "error", err)
|
||||
http.Error(w, "failed to fetch registry", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -118,7 +119,8 @@ func (h *JuliaHandler) handlePackage(w http.ResponseWriter, r *http.Request) {
|
|||
upstreamURL := h.upstreamURL + r.URL.Path
|
||||
result, err := h.proxy.GetOrFetchArtifactFromURL(r.Context(), "julia", name, hash, hash+".tar.gz", upstreamURL)
|
||||
if err != nil {
|
||||
h.proxy.serveArtifactError(w, err, "failed to fetch package")
|
||||
h.proxy.Logger.Error("failed to get package", "error", err)
|
||||
http.Error(w, "failed to fetch package", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -139,7 +141,8 @@ func (h *JuliaHandler) handleArtifact(w http.ResponseWriter, r *http.Request) {
|
|||
upstreamURL := h.upstreamURL + r.URL.Path
|
||||
result, err := h.proxy.GetOrFetchArtifactFromURL(r.Context(), "julia", juliaArtifactName, hash, hash+".tar.gz", upstreamURL)
|
||||
if err != nil {
|
||||
h.proxy.serveArtifactError(w, err, "failed to fetch artifact")
|
||||
h.proxy.Logger.Error("failed to get artifact", "error", err)
|
||||
http.Error(w, "failed to fetch artifact", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -130,7 +130,12 @@ func (h *MavenHandler) handleDownload(w http.ResponseWriter, r *http.Request, ur
|
|||
}
|
||||
}
|
||||
if err != nil {
|
||||
h.proxy.serveArtifactError(w, err, "failed to fetch artifact")
|
||||
if errors.Is(err, ErrUpstreamNotFound) {
|
||||
http.Error(w, "not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
h.proxy.Logger.Error("failed to get artifact", "error", err)
|
||||
http.Error(w, "failed to fetch artifact", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,151 +0,0 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/git-pkgs/registries/fetch"
|
||||
)
|
||||
|
||||
func TestArtifactDownloadUpstreamNotFoundReturns404(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
handler func(p *Proxy) http.Handler
|
||||
}{
|
||||
{"debian", "/pool/main/n/nginx/nginx_1.18.0-6_amd64.deb",
|
||||
func(p *Proxy) http.Handler { return NewDebianHandler(p, "http://localhost").Routes() }},
|
||||
{"rpm", "/releases/39/Everything/x86_64/os/Packages/n/nginx-1.24.0-1.fc39.x86_64.rpm",
|
||||
func(p *Proxy) http.Handler { return NewRPMHandler(p, "http://localhost").Routes() }},
|
||||
{"nuget", "/v3-flatcontainer/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg",
|
||||
func(p *Proxy) http.Handler { return NewNuGetHandler(p, "http://localhost").Routes() }},
|
||||
{"pypi", "/packages/packages/ab/cd/ef0123456789/requests-2.31.0-py3-none-any.whl",
|
||||
func(p *Proxy) http.Handler { return NewPyPIHandler(p, "http://localhost").Routes() }},
|
||||
{"cran", "/src/contrib/ggplot2_3.4.4.tar.gz",
|
||||
func(p *Proxy) http.Handler { return NewCRANHandler(p, "http://localhost").Routes() }},
|
||||
{"conda", "/conda-forge/linux-64/numpy-1.26.0-py311_0.tar.bz2",
|
||||
func(p *Proxy) http.Handler { return NewCondaHandler(p, "http://localhost").Routes() }},
|
||||
{"conan", "/v1/files/zlib/1.3.1/_/_/0/recipe/conan_sources.tgz",
|
||||
func(p *Proxy) http.Handler { return NewConanHandler(p, "http://localhost").Routes() }},
|
||||
{"gem", "/gems/rails-7.1.0.gem",
|
||||
func(p *Proxy) http.Handler { return NewGemHandler(p, "http://localhost").Routes() }},
|
||||
{"hex", "/tarballs/phoenix-1.7.10.tar",
|
||||
func(p *Proxy) http.Handler { return NewHexHandler(p, "http://localhost").Routes() }},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
proxy, _, _, fetcher := setupTestProxy(t)
|
||||
fetcher.fetchErr = fetch.ErrNotFound
|
||||
|
||||
srv := httptest.NewServer(tt.handler(proxy))
|
||||
defer srv.Close()
|
||||
|
||||
resp, err := http.Get(srv.URL + tt.path)
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusNotFound {
|
||||
t.Errorf("want 404 for missing upstream artifact, got %d", resp.StatusCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJuliaPackageUpstreamNotFoundReturns404(t *testing.T) {
|
||||
proxy, _, _, fetcher := setupTestProxy(t)
|
||||
fetcher.fetchErr = fetch.ErrNotFound
|
||||
|
||||
dead := httptest.NewServer(http.NotFoundHandler())
|
||||
defer dead.Close()
|
||||
|
||||
h := NewJuliaHandler(proxy, "http://localhost")
|
||||
h.upstreamURL = dead.URL
|
||||
|
||||
srv := httptest.NewServer(h.Routes())
|
||||
defer srv.Close()
|
||||
|
||||
resp, err := http.Get(srv.URL +
|
||||
"/package/7876af07-990d-54b4-ab0e-23690620f79a/0123456789abcdef0123456789abcdef01234567")
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusNotFound {
|
||||
t.Errorf("want 404 for missing upstream package, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComposerDownloadUpstreamNotFoundReturns404(t *testing.T) {
|
||||
proxy, _, _, fetcher := setupTestProxy(t)
|
||||
fetcher.fetchErr = fetch.ErrNotFound
|
||||
|
||||
meta := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/p2/monolog/monolog.json" {
|
||||
_, _ = w.Write([]byte(`{
|
||||
"packages": {
|
||||
"monolog/monolog": [
|
||||
{"version": "2.9.1", "dist": {"url": "https://example.com/monolog-2.9.1.zip", "type": "zip"}}
|
||||
]
|
||||
}
|
||||
}`))
|
||||
return
|
||||
}
|
||||
http.NotFound(w, r)
|
||||
}))
|
||||
defer meta.Close()
|
||||
|
||||
h := &ComposerHandler{proxy: proxy, repoURL: meta.URL, proxyURL: "http://localhost"}
|
||||
srv := httptest.NewServer(h.Routes())
|
||||
defer srv.Close()
|
||||
|
||||
resp, err := http.Get(srv.URL + "/files/monolog/monolog/2.9.1/monolog-2.9.1.zip")
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusNotFound {
|
||||
t.Errorf("want 404 for missing upstream dist, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerBlobUpstreamNotFoundReturns404(t *testing.T) {
|
||||
authServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"token": "test-token-123"}`))
|
||||
}))
|
||||
defer authServer.Close()
|
||||
|
||||
proxy, _, _, _ := setupTestProxy(t)
|
||||
proxy.Fetcher = &mockFetcherWithHeaders{
|
||||
fetchFn: func(_ context.Context, _ string, _ http.Header) (*fetch.Artifact, error) {
|
||||
return nil, fetch.ErrNotFound
|
||||
},
|
||||
}
|
||||
|
||||
h := &ContainerHandler{
|
||||
proxy: proxy,
|
||||
registryURL: "https://registry-1.docker.io",
|
||||
authURL: authServer.URL,
|
||||
proxyURL: "http://localhost:8080",
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet,
|
||||
"/library/nginx/blobs/sha256:abc123def456abc123def456abc123def456abc123def456abc123def456abcd", nil)
|
||||
w := httptest.NewRecorder()
|
||||
h.Routes().ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusNotFound {
|
||||
t.Errorf("want 404 for missing upstream blob, got %d; body: %s", w.Code, w.Body.String())
|
||||
}
|
||||
if !strings.Contains(w.Body.String(), "BLOB_UNKNOWN") {
|
||||
t.Errorf("want BLOB_UNKNOWN error code in body, got: %s", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/git-pkgs/registries/fetch"
|
||||
)
|
||||
|
||||
func TestErrUpstreamNotFoundWrapsFetchErrNotFound(t *testing.T) {
|
||||
if !errors.Is(ErrUpstreamNotFound, fetch.ErrNotFound) {
|
||||
t.Fatal("ErrUpstreamNotFound does not wrap fetch.ErrNotFound")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrFetchArtifactFromURL_NotFound(t *testing.T) {
|
||||
proxy, _, _, fetcher := setupTestProxy(t)
|
||||
fetcher.fetchErr = fetch.ErrNotFound
|
||||
|
||||
_, err := proxy.GetOrFetchArtifactFromURL(context.Background(),
|
||||
"maven", "org.example:missing", "1.0", "missing-1.0.jar",
|
||||
"http://upstream.test/org/example/missing/1.0/missing-1.0.jar")
|
||||
|
||||
if !errors.Is(err, ErrUpstreamNotFound) {
|
||||
t.Fatalf("want ErrUpstreamNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMavenHandler_UpstreamNotFoundReturns404(t *testing.T) {
|
||||
proxy, _, _, fetcher := setupTestProxy(t)
|
||||
fetcher.fetchErr = fetch.ErrNotFound
|
||||
|
||||
h := NewMavenHandler(proxy, "http://localhost", "http://upstream.test", "http://portal.test")
|
||||
srv := httptest.NewServer(h.Routes())
|
||||
defer srv.Close()
|
||||
|
||||
resp, err := http.Get(srv.URL + "/org/example/missing/1.0/missing-1.0.jar")
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusNotFound {
|
||||
t.Errorf("want 404 for missing upstream artifact, got %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMavenHandler_PluginPortalFallback(t *testing.T) {
|
||||
proxy, _, _, fetcher := setupTestProxy(t)
|
||||
fetcher.fetchErrByURL = map[string]error{
|
||||
"http://upstream.test/org/example/plugin/1.0/plugin-1.0.jar": fetch.ErrNotFound,
|
||||
}
|
||||
fetcher.artifact = &fetch.Artifact{
|
||||
Body: io.NopCloser(strings.NewReader("portal artifact")),
|
||||
ContentType: "application/java-archive",
|
||||
}
|
||||
|
||||
h := NewMavenHandler(proxy, "http://localhost", "http://upstream.test", "http://portal.test")
|
||||
srv := httptest.NewServer(h.Routes())
|
||||
defer srv.Close()
|
||||
|
||||
resp, err := http.Get(srv.URL + "/org/example/plugin/1.0/plugin-1.0.jar")
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("want 200 via plugin portal fallback, got %d", resp.StatusCode)
|
||||
}
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
if string(body) != "portal artifact" {
|
||||
t.Errorf("want portal artifact body, got %q", body)
|
||||
}
|
||||
if fetcher.fetchedURL != "http://portal.test/org/example/plugin/1.0/plugin-1.0.jar" {
|
||||
t.Errorf("fallback did not hit plugin portal, last URL: %s", fetcher.fetchedURL)
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,8 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/git-pkgs/purl"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -25,14 +27,10 @@ type NPMHandler struct {
|
|||
}
|
||||
|
||||
// NewNPMHandler creates a new npm protocol handler.
|
||||
func NewNPMHandler(proxy *Proxy, proxyURL, upstreamURL string) *NPMHandler {
|
||||
if strings.TrimSpace(upstreamURL) == "" {
|
||||
upstreamURL = npmUpstream
|
||||
}
|
||||
|
||||
func NewNPMHandler(proxy *Proxy, proxyURL string) *NPMHandler {
|
||||
return &NPMHandler{
|
||||
proxy: proxy,
|
||||
upstreamURL: strings.TrimSuffix(upstreamURL, "/"),
|
||||
upstreamURL: npmUpstream,
|
||||
proxyURL: strings.TrimSuffix(proxyURL, "/"),
|
||||
}
|
||||
}
|
||||
|
|
@ -136,7 +134,7 @@ func (h *NPMHandler) applyCooldownFiltering(metadata map[string]any, versions ma
|
|||
return
|
||||
}
|
||||
|
||||
packagePURL := canonicalPackagePURL("npm", packageName)
|
||||
packagePURL := purl.MakePURLString("npm", packageName, "")
|
||||
|
||||
for version := range versions {
|
||||
publishedStr, ok := timeMap[version].(string)
|
||||
|
|
@ -265,20 +263,8 @@ func (h *NPMHandler) handleDownload(w http.ResponseWriter, r *http.Request) {
|
|||
h.proxy.Logger.Info("npm download request",
|
||||
"package", packageName, "version", version, "filename", filename)
|
||||
|
||||
downloadURL := fmt.Sprintf(
|
||||
"%s/%s/-/%s",
|
||||
h.upstreamURL,
|
||||
escapeNPMDownloadPackage(packageName),
|
||||
url.PathEscape(filename),
|
||||
)
|
||||
result, err := h.proxy.GetOrFetchArtifactFromURL(
|
||||
r.Context(), "npm", packageName, version, filename, downloadURL,
|
||||
)
|
||||
result, err := h.proxy.GetOrFetchArtifact(r.Context(), "npm", packageName, version, filename)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrUpstreamNotFound) {
|
||||
JSONError(w, http.StatusNotFound, "package not found")
|
||||
return
|
||||
}
|
||||
h.proxy.Logger.Error("failed to get artifact", "error", err)
|
||||
JSONError(w, http.StatusBadGateway, "failed to fetch package")
|
||||
return
|
||||
|
|
@ -287,14 +273,6 @@ func (h *NPMHandler) handleDownload(w http.ResponseWriter, r *http.Request) {
|
|||
ServeArtifact(w, result)
|
||||
}
|
||||
|
||||
func escapeNPMDownloadPackage(packageName string) string {
|
||||
scope, name, scoped := strings.Cut(packageName, "/")
|
||||
if scoped && strings.HasPrefix(scope, "@") && len(scope) > 1 && name != "" && !strings.Contains(name, "/") {
|
||||
return url.PathEscape(scope) + "/" + url.PathEscape(name)
|
||||
}
|
||||
return url.PathEscape(packageName)
|
||||
}
|
||||
|
||||
// extractPackageName extracts the package name from the request path.
|
||||
// Handles both scoped (@scope/name) and unscoped (name) packages.
|
||||
func (h *NPMHandler) extractPackageName(r *http.Request) string {
|
||||
|
|
|
|||
|
|
@ -2,16 +2,13 @@ package handler
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/git-pkgs/cooldown"
|
||||
"github.com/git-pkgs/registries/fetch"
|
||||
)
|
||||
|
||||
const testVersion100 = "1.0.0"
|
||||
|
|
@ -49,86 +46,6 @@ func TestNPMExtractVersionFromFilename(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNPMHandlerUsesConfiguredUpstream(t *testing.T) {
|
||||
t.Run("metadata", func(t *testing.T) {
|
||||
var requestPath, authHeader string
|
||||
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requestPath = r.URL.Path
|
||||
authHeader = r.Header.Get("Authorization")
|
||||
if authHeader != "Bearer npm-token" {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = io.WriteString(w, `{"versions":{}}`)
|
||||
}))
|
||||
defer upstream.Close()
|
||||
|
||||
proxy, _, _, _ := setupTestProxy(t)
|
||||
proxy.HTTPClient = upstream.Client()
|
||||
proxy.AuthForURL = func(string) (string, string) {
|
||||
return "Authorization", "Bearer npm-token"
|
||||
}
|
||||
h := NewNPMHandler(proxy, "http://proxy.test", upstream.URL+"/root/")
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/testpkg", nil)
|
||||
w := httptest.NewRecorder()
|
||||
h.Routes().ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d; body: %s", w.Code, http.StatusOK, w.Body.String())
|
||||
}
|
||||
if requestPath != "/root/testpkg" {
|
||||
t.Errorf("upstream path = %q, want %q", requestPath, "/root/testpkg")
|
||||
}
|
||||
if authHeader != "Bearer npm-token" {
|
||||
t.Errorf("Authorization = %q, want %q", authHeader, "Bearer npm-token")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("download", func(t *testing.T) {
|
||||
proxy, _, _, artifactFetcher := setupTestProxy(t)
|
||||
artifactFetcher.artifact = &fetch.Artifact{
|
||||
Body: io.NopCloser(strings.NewReader("package")),
|
||||
ContentType: "application/gzip",
|
||||
}
|
||||
h := NewNPMHandler(proxy, "http://proxy.test", "https://npm.example.test/root/")
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/testpkg/-/testpkg-1.0.0.tgz", nil)
|
||||
w := httptest.NewRecorder()
|
||||
h.Routes().ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d; body: %s", w.Code, http.StatusOK, w.Body.String())
|
||||
}
|
||||
want := "https://npm.example.test/root/testpkg/-/testpkg-1.0.0.tgz"
|
||||
if artifactFetcher.fetchedURL != want {
|
||||
t.Errorf("fetched URL = %q, want %q", artifactFetcher.fetchedURL, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("scoped download", func(t *testing.T) {
|
||||
proxy, _, _, artifactFetcher := setupTestProxy(t)
|
||||
artifactFetcher.artifact = &fetch.Artifact{
|
||||
Body: io.NopCloser(strings.NewReader("package")),
|
||||
ContentType: "application/gzip",
|
||||
}
|
||||
h := NewNPMHandler(proxy, "http://proxy.test", "https://npm.example.test/root/")
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/@scope/name/-/name-1.0.0.tgz", nil)
|
||||
w := httptest.NewRecorder()
|
||||
h.Routes().ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d; body: %s", w.Code, http.StatusOK, w.Body.String())
|
||||
}
|
||||
want := "https://npm.example.test/root/@scope/name/-/name-1.0.0.tgz"
|
||||
if artifactFetcher.fetchedURL != want {
|
||||
t.Errorf("fetched URL = %q, want %q", artifactFetcher.fetchedURL, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestNPMRewriteMetadata(t *testing.T) {
|
||||
h := &NPMHandler{
|
||||
proxy: testProxy(),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/git-pkgs/purl"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -269,7 +271,7 @@ func (h *NuGetHandler) applyCooldownFiltering(body []byte) ([]byte, error) {
|
|||
}
|
||||
}
|
||||
|
||||
packagePURL := canonicalPackagePURL("nuget", strings.ToLower(id))
|
||||
packagePURL := purl.MakePURLString("nuget", strings.ToLower(id), "")
|
||||
|
||||
if !h.proxy.Cooldown.IsAllowed("nuget", packagePURL, publishedAt) {
|
||||
h.proxy.Logger.Info("cooldown: filtering nuget version",
|
||||
|
|
@ -314,7 +316,8 @@ func (h *NuGetHandler) handleDownload(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
result, err := h.proxy.GetOrFetchArtifactFromURL(r.Context(), "nuget", name, version, filename, upstreamURL)
|
||||
if err != nil {
|
||||
h.proxy.serveArtifactError(w, err, "failed to fetch package")
|
||||
h.proxy.Logger.Error("failed to get artifact", "error", err)
|
||||
http.Error(w, "failed to fetch package", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/git-pkgs/purl"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -67,7 +69,8 @@ func (h *PubHandler) handleDownload(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
result, err := h.proxy.GetOrFetchArtifact(r.Context(), "pub", name, version, filename)
|
||||
if err != nil {
|
||||
h.proxy.serveArtifactError(w, err, "failed to fetch package")
|
||||
h.proxy.Logger.Error("failed to get artifact", "error", err)
|
||||
http.Error(w, "failed to fetch package", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -124,7 +127,7 @@ func (h *PubHandler) rewriteMetadata(name string, body []byte) ([]byte, error) {
|
|||
return body, nil
|
||||
}
|
||||
|
||||
packagePURL := canonicalPackagePURL("pub", name)
|
||||
packagePURL := purl.MakePURLString("pub", name, "")
|
||||
filtered := h.filterAndRewriteVersions(name, packagePURL, versions)
|
||||
metadata["versions"] = filtered
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/git-pkgs/purl"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -129,7 +131,7 @@ func (h *PyPIHandler) fetchFilteredVersions(r *http.Request, name string) map[st
|
|||
return nil
|
||||
}
|
||||
|
||||
packagePURL := canonicalPackagePURL("pypi", name)
|
||||
packagePURL := purl.MakePURLString("pypi", name, "")
|
||||
filtered := make(map[string]bool)
|
||||
|
||||
for version, files := range releases {
|
||||
|
|
@ -260,7 +262,7 @@ func (h *PyPIHandler) rewriteJSONMetadata(body []byte) ([]byte, error) {
|
|||
packageName, _ := extractPyPIName(metadata)
|
||||
packagePURL := ""
|
||||
if packageName != "" {
|
||||
packagePURL = canonicalPackagePURL("pypi", packageName)
|
||||
packagePURL = purl.MakePURLString("pypi", packageName, "")
|
||||
}
|
||||
|
||||
h.filterAndRewriteReleases(metadata, packageName, packagePURL)
|
||||
|
|
@ -426,7 +428,8 @@ func (h *PyPIHandler) handleDownload(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
result, err := h.proxy.GetOrFetchArtifactFromURL(r.Context(), "pypi", name, version, filename, upstreamURL)
|
||||
if err != nil {
|
||||
h.proxy.serveArtifactError(w, err, "failed to fetch package")
|
||||
h.proxy.Logger.Error("failed to get artifact", "error", err)
|
||||
http.Error(w, "failed to fetch package", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -83,7 +83,8 @@ func (h *RPMHandler) handlePackageDownload(w http.ResponseWriter, r *http.Reques
|
|||
result, err := h.proxy.GetOrFetchArtifactFromURL(
|
||||
r.Context(), "rpm", name, version, filename, downloadURL)
|
||||
if err != nil {
|
||||
h.proxy.serveArtifactError(w, err, "failed to fetch package")
|
||||
h.proxy.Logger.Error("failed to get rpm package", "error", err)
|
||||
http.Error(w, "failed to fetch package", http.StatusBadGateway)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -11,22 +10,29 @@ import (
|
|||
|
||||
"github.com/git-pkgs/archives"
|
||||
"github.com/git-pkgs/archives/diff"
|
||||
"github.com/git-pkgs/magic"
|
||||
"github.com/git-pkgs/proxy/internal/database"
|
||||
"github.com/git-pkgs/purl"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
const (
|
||||
contentTypePlainText = "text/plain; charset=utf-8"
|
||||
browseSniffSize = 512
|
||||
)
|
||||
const contentTypePlainText = "text/plain; charset=utf-8"
|
||||
|
||||
// maxBrowseArchiveSize caps how much data openArchive will buffer for
|
||||
// prefix detection. Artifacts larger than this are rejected to prevent
|
||||
// memory exhaustion from a single request.
|
||||
const maxBrowseArchiveSize = 512 << 20 // 512 MB
|
||||
|
||||
// archiveFilename returns a filename suitable for archive format detection.
|
||||
// Some ecosystems (e.g. composer) store artifacts with bare hash filenames
|
||||
// that have no extension. This adds .zip when the original has no extension
|
||||
// and the content is likely a zip archive.
|
||||
func archiveFilename(filename string) string {
|
||||
if path.Ext(filename) == "" {
|
||||
return filename + ".zip"
|
||||
}
|
||||
return filename
|
||||
}
|
||||
|
||||
// detectSingleRootDir returns the single top-level directory name if all files
|
||||
// in the archive live under one common directory (e.g. GitHub zipballs use
|
||||
// "repo-hash/"). Returns "" if there's no single root or the archive is flat.
|
||||
|
|
@ -60,6 +66,8 @@ func detectSingleRootDir(reader archives.Reader) string {
|
|||
// and stripping a single top-level directory prefix (like GitHub zipballs).
|
||||
// For npm, the hardcoded "package/" prefix takes precedence.
|
||||
func openArchive(filename string, content io.Reader, ecosystem string) (archives.Reader, error) { //nolint:ireturn // wraps multiple archive implementations
|
||||
fname := archiveFilename(filename)
|
||||
|
||||
limited := io.LimitReader(content, maxBrowseArchiveSize+1)
|
||||
data, err := io.ReadAll(limited)
|
||||
if err != nil {
|
||||
|
|
@ -70,17 +78,17 @@ func openArchive(filename string, content io.Reader, ecosystem string) (archives
|
|||
}
|
||||
|
||||
if ecosystem == "npm" {
|
||||
return archives.OpenBytesWithPrefix(filename, data, "package/")
|
||||
return archives.OpenBytesWithPrefix(fname, data, "package/")
|
||||
}
|
||||
|
||||
probe, err := archives.OpenBytes(filename, data)
|
||||
probe, err := archives.OpenBytes(fname, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prefix := detectSingleRootDir(probe)
|
||||
_ = probe.Close()
|
||||
|
||||
return archives.OpenBytesWithPrefix(filename, data, prefix)
|
||||
return archives.OpenBytesWithPrefix(fname, data, prefix)
|
||||
}
|
||||
|
||||
// BrowseListResponse contains the file listing for a directory in an archives.
|
||||
|
|
@ -353,14 +361,7 @@ func (s *Server) browseFile(w http.ResponseWriter, r *http.Request, ecosystem, n
|
|||
}
|
||||
defer func() { _ = fileReader.Close() }()
|
||||
|
||||
contentType, knownPath := detectContentTypeFromPath(filePath)
|
||||
var content io.Reader = fileReader
|
||||
if !knownPath {
|
||||
bufferedFile := bufio.NewReaderSize(fileReader, browseSniffSize)
|
||||
prefix, _ := bufferedFile.Peek(browseSniffSize)
|
||||
contentType = detectContentTypeFromPrefix(prefix)
|
||||
content = bufferedFile
|
||||
}
|
||||
contentType := detectContentType(filePath)
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.Header().Set("Content-Security-Policy", "sandbox")
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
|
|
@ -369,103 +370,85 @@ func (s *Server) browseFile(w http.ResponseWriter, r *http.Request, ecosystem, n
|
|||
w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=%q", filename))
|
||||
|
||||
// Stream the file
|
||||
_, _ = io.Copy(w, content)
|
||||
_, _ = io.Copy(w, fileReader)
|
||||
}
|
||||
|
||||
func detectContentTypeFromPath(filename string) (string, bool) {
|
||||
// detectContentType returns an appropriate content type based on file extension.
|
||||
func detectContentType(filename string) string {
|
||||
ext := strings.ToLower(path.Ext(filename))
|
||||
|
||||
switch ext {
|
||||
// Text formats
|
||||
case ".txt", ".md", ".markdown":
|
||||
return contentTypePlainText, true
|
||||
return contentTypePlainText
|
||||
case ".html", ".htm", ".xhtml":
|
||||
return contentTypePlainText, true
|
||||
return contentTypePlainText
|
||||
case ".css":
|
||||
return "text/css; charset=utf-8", true
|
||||
return "text/css; charset=utf-8"
|
||||
case ".js", ".mjs":
|
||||
return "application/javascript; charset=utf-8", true
|
||||
return "application/javascript; charset=utf-8"
|
||||
case ".json":
|
||||
return "application/json; charset=utf-8", true
|
||||
return "application/json; charset=utf-8"
|
||||
case ".xml":
|
||||
return "application/xml; charset=utf-8", true
|
||||
return "application/xml; charset=utf-8"
|
||||
case ".yaml", ".yml":
|
||||
return "text/yaml; charset=utf-8", true
|
||||
return "text/yaml; charset=utf-8"
|
||||
case ".toml":
|
||||
return "text/toml; charset=utf-8", true
|
||||
return "text/toml; charset=utf-8"
|
||||
|
||||
// Programming languages
|
||||
case ".go":
|
||||
return "text/x-go; charset=utf-8", true
|
||||
return "text/x-go; charset=utf-8"
|
||||
case ".rs":
|
||||
return "text/x-rust; charset=utf-8", true
|
||||
return "text/x-rust; charset=utf-8"
|
||||
case ".py":
|
||||
return "text/x-python; charset=utf-8", true
|
||||
return "text/x-python; charset=utf-8"
|
||||
case ".rb":
|
||||
return "text/x-ruby; charset=utf-8", true
|
||||
return "text/x-ruby; charset=utf-8"
|
||||
case ".java":
|
||||
return "text/x-java; charset=utf-8", true
|
||||
return "text/x-java; charset=utf-8"
|
||||
case ".c", ".h":
|
||||
return "text/x-c; charset=utf-8", true
|
||||
return "text/x-c; charset=utf-8"
|
||||
case ".cpp", ".cc", ".cxx", ".hpp":
|
||||
return "text/x-c++; charset=utf-8", true
|
||||
return "text/x-c++; charset=utf-8"
|
||||
case ".ts":
|
||||
return "text/typescript; charset=utf-8", true
|
||||
return "text/typescript; charset=utf-8"
|
||||
case ".tsx":
|
||||
return "text/tsx; charset=utf-8", true
|
||||
return "text/tsx; charset=utf-8"
|
||||
case ".jsx":
|
||||
return "text/jsx; charset=utf-8", true
|
||||
return "text/jsx; charset=utf-8"
|
||||
case ".php":
|
||||
return "text/x-php; charset=utf-8", true
|
||||
return "text/x-php; charset=utf-8"
|
||||
|
||||
// Config files
|
||||
case ".conf", ".config", ".ini":
|
||||
return contentTypePlainText, true
|
||||
return contentTypePlainText
|
||||
case ".sh", ".bash":
|
||||
return "text/x-shellscript; charset=utf-8", true
|
||||
return "text/x-shellscript; charset=utf-8"
|
||||
case ".dockerfile":
|
||||
return "text/x-dockerfile; charset=utf-8", true
|
||||
return "text/x-dockerfile; charset=utf-8"
|
||||
|
||||
// Images
|
||||
case ".png":
|
||||
return "image/png", true
|
||||
return "image/png"
|
||||
case ".jpg", ".jpeg":
|
||||
return "image/jpeg", true
|
||||
return "image/jpeg"
|
||||
case ".gif":
|
||||
return "image/gif", true
|
||||
return "image/gif"
|
||||
case ".svg":
|
||||
return contentTypePlainText, true
|
||||
return contentTypePlainText
|
||||
case ".ico":
|
||||
return "image/x-icon", true
|
||||
return "image/x-icon"
|
||||
|
||||
// Archives
|
||||
case ".zip", ".tar", ".gz", ".bz2", ".xz":
|
||||
return "application/octet-stream", true
|
||||
return "application/octet-stream"
|
||||
|
||||
default:
|
||||
// Try to detect if it looks like text
|
||||
if isLikelyText(filename) {
|
||||
return contentTypePlainText, true
|
||||
return contentTypePlainText
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
func detectContentTypeFromPrefix(prefix []byte) string {
|
||||
result := magic.DetectPrefix(prefix)
|
||||
if result.Kind == magic.KindText {
|
||||
return contentTypePlainText
|
||||
}
|
||||
|
||||
switch result.Format {
|
||||
case "png":
|
||||
return "image/png"
|
||||
case "jpeg":
|
||||
return "image/jpeg"
|
||||
case "gif":
|
||||
return "image/gif"
|
||||
case "pdf":
|
||||
return "application/pdf"
|
||||
default:
|
||||
return "application/octet-stream"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,35 +55,3 @@ func BenchmarkOpenArchive(b *testing.B) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDetectContentType(b *testing.B) {
|
||||
cases := []struct {
|
||||
name string
|
||||
filename string
|
||||
prefix []byte
|
||||
knownPath bool
|
||||
}{
|
||||
{"known-path", "README.md", nil, true},
|
||||
{"text-prefix", "artifact", bytes.Repeat([]byte("a"), browseSniffSize), false},
|
||||
{"png-prefix", "artifact", append([]byte("\x89PNG\r\n\x1a\n"), make([]byte, browseSniffSize-8)...), false},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
var contentType string
|
||||
if tc.knownPath {
|
||||
for b.Loop() {
|
||||
contentType, _ = detectContentTypeFromPath(tc.filename)
|
||||
}
|
||||
} else {
|
||||
for b.Loop() {
|
||||
contentType = detectContentTypeFromPrefix(tc.prefix)
|
||||
}
|
||||
}
|
||||
if contentType == "" {
|
||||
b.Fatal("empty content type")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,44 +137,29 @@ func TestHandleBrowseFile(t *testing.T) {
|
|||
t.Fatalf("failed to upsert artifact: %v", err)
|
||||
}
|
||||
|
||||
files := []struct {
|
||||
path string
|
||||
content string
|
||||
contentType string
|
||||
}{
|
||||
{"README.md", "# Test Package\n", contentTypePlainText},
|
||||
{"notes.data", "short text\n", contentTypePlainText},
|
||||
{"logo", "\x89PNG\r\n\x1a\nimage data", "image/png"},
|
||||
{"page", "<!DOCTYPE html><html></html>", contentTypePlainText},
|
||||
{"misleading.txt", "\x89PNG\r\n\x1a\nimage data", contentTypePlainText},
|
||||
}
|
||||
for _, file := range files {
|
||||
t.Run(file.path, func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/ui/api/browse/npm/test-browse/1.0.0/file/"+file.path, nil)
|
||||
w := httptest.NewRecorder()
|
||||
ts.handler.ServeHTTP(w, req)
|
||||
// Test fetching a file
|
||||
req := httptest.NewRequest("GET", "/ui/api/browse/npm/test-browse/1.0.0/file/README.md", nil)
|
||||
w := httptest.NewRecorder()
|
||||
ts.handler.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected status 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
if w.Body.String() != file.content {
|
||||
t.Errorf("unexpected file content: %q", w.Body.String())
|
||||
}
|
||||
if got := w.Header().Get("Content-Type"); got != file.contentType {
|
||||
t.Errorf("Content-Type = %q, want %q", got, file.contentType)
|
||||
}
|
||||
if got := w.Header().Get("Content-Security-Policy"); got != "sandbox" {
|
||||
t.Errorf("Content-Security-Policy = %q, want sandbox", got)
|
||||
}
|
||||
if got := w.Header().Get("X-Content-Type-Options"); got != "nosniff" {
|
||||
t.Errorf("X-Content-Type-Options = %q, want nosniff", got)
|
||||
}
|
||||
})
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected status 200, got %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
|
||||
body := w.Body.String()
|
||||
if body != "# Test Package\n" {
|
||||
t.Errorf("unexpected file content: %q", body)
|
||||
}
|
||||
|
||||
// Check content type
|
||||
contentType := w.Header().Get("Content-Type")
|
||||
if contentType != contentTypePlainText {
|
||||
t.Errorf("expected text/plain content type, got %q", contentType)
|
||||
}
|
||||
|
||||
// Test fetching non-existent file
|
||||
req := httptest.NewRequest("GET", "/ui/api/browse/npm/test-browse/1.0.0/file/nonexistent.txt", nil)
|
||||
w := httptest.NewRecorder()
|
||||
req = httptest.NewRequest("GET", "/ui/api/browse/npm/test-browse/1.0.0/file/nonexistent.txt", nil)
|
||||
w = httptest.NewRecorder()
|
||||
ts.handler.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusNotFound {
|
||||
|
|
@ -182,52 +167,36 @@ func TestHandleBrowseFile(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBrowseContentTypePolicy(t *testing.T) {
|
||||
func TestDetectContentType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
filename string
|
||||
prefix []byte
|
||||
expectedCT string
|
||||
}{
|
||||
{"text extension", "file.txt", nil, contentTypePlainText},
|
||||
{"markdown extension", "file.md", nil, contentTypePlainText},
|
||||
{"JSON extension", "file.json", nil, "application/json; charset=utf-8"},
|
||||
{"JavaScript extension", "file.js", nil, "application/javascript; charset=utf-8"},
|
||||
{"Go extension", "file.go", nil, "text/x-go; charset=utf-8"},
|
||||
{"Python extension", "file.py", nil, "text/x-python; charset=utf-8"},
|
||||
{"Rust extension", "file.rs", nil, "text/x-rust; charset=utf-8"},
|
||||
{"HTML extension", "file.html", nil, contentTypePlainText},
|
||||
{"HTM extension", "file.htm", nil, contentTypePlainText},
|
||||
{"XHTML extension", "file.xhtml", nil, contentTypePlainText},
|
||||
{"SVG extension", "file.svg", nil, contentTypePlainText},
|
||||
{"PNG extension", "file.png", nil, "image/png"},
|
||||
{"JPEG extension", "file.jpg", nil, "image/jpeg"},
|
||||
{"README", "README", nil, contentTypePlainText},
|
||||
{"LICENSE", "LICENSE", nil, contentTypePlainText},
|
||||
{"Makefile", "Makefile", nil, contentTypePlainText},
|
||||
{"gitignore", ".gitignore", nil, contentTypePlainText},
|
||||
{"unknown empty", "file.bin", nil, "application/octet-stream"},
|
||||
{"extensionless PNG", "asset", []byte("\x89PNG\r\n\x1a\n"), "image/png"},
|
||||
{"extensionless JPEG", "asset", []byte("\xff\xd8\xff"), "image/jpeg"},
|
||||
{"extensionless GIF", "asset", []byte("GIF89a"), "image/gif"},
|
||||
{"extensionless PDF", "asset", []byte("%PDF-1.7"), "application/pdf"},
|
||||
{"extensionless text", "asset", []byte("plain text\n"), contentTypePlainText},
|
||||
{"extensionless HTML", "asset", []byte("<!DOCTYPE html><html></html>"), contentTypePlainText},
|
||||
{"extensionless XML", "asset", []byte("<?xml version=\"1.0\"?><root/>"), contentTypePlainText},
|
||||
{"extensionless SVG", "asset", []byte("<svg xmlns=\"http://www.w3.org/2000/svg\"></svg>"), contentTypePlainText},
|
||||
{"extensionless ZIP", "asset", []byte("PK\x03\x04"), "application/octet-stream"},
|
||||
{"extensionless binary", "asset", []byte{0, 1, 2}, "application/octet-stream"},
|
||||
{"known path wins", "file.txt", []byte("\x89PNG\r\n\x1a\n"), contentTypePlainText},
|
||||
{"file.txt", contentTypePlainText},
|
||||
{"file.md", contentTypePlainText},
|
||||
{"file.json", "application/json; charset=utf-8"},
|
||||
{"file.js", "application/javascript; charset=utf-8"},
|
||||
{"file.go", "text/x-go; charset=utf-8"},
|
||||
{"file.py", "text/x-python; charset=utf-8"},
|
||||
{"file.rs", "text/x-rust; charset=utf-8"},
|
||||
{"file.html", contentTypePlainText},
|
||||
{"file.htm", contentTypePlainText},
|
||||
{"file.xhtml", contentTypePlainText},
|
||||
{"file.svg", contentTypePlainText},
|
||||
{"file.png", "image/png"},
|
||||
{"file.jpg", "image/jpeg"},
|
||||
{"README", contentTypePlainText},
|
||||
{"LICENSE", contentTypePlainText},
|
||||
{"Makefile", contentTypePlainText},
|
||||
{".gitignore", contentTypePlainText},
|
||||
{"file.bin", "application/octet-stream"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, knownPath := detectContentTypeFromPath(tt.filename)
|
||||
if !knownPath {
|
||||
got = detectContentTypeFromPrefix(tt.prefix)
|
||||
}
|
||||
t.Run(tt.filename, func(t *testing.T) {
|
||||
got := detectContentType(tt.filename)
|
||||
if got != tt.expectedCT {
|
||||
t.Errorf("content type for %q with prefix %q = %q, want %q", tt.filename, tt.prefix, got, tt.expectedCT)
|
||||
t.Errorf("detectContentType(%q) = %q, want %q", tt.filename, got, tt.expectedCT)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -286,10 +255,6 @@ func createTestArchive(t *testing.T) []byte {
|
|||
"package/lib/index.js": "module.exports = {};",
|
||||
"package/lib/helper.js": "module.exports.help = () => {};",
|
||||
"package/test/index.test.js": "// tests",
|
||||
"package/notes.data": "short text\n",
|
||||
"package/logo": "\x89PNG\r\n\x1a\nimage data",
|
||||
"package/page": "<!DOCTYPE html><html></html>",
|
||||
"package/misleading.txt": "\x89PNG\r\n\x1a\nimage data",
|
||||
}
|
||||
|
||||
for path, content := range files {
|
||||
|
|
@ -644,19 +609,25 @@ func TestHandleComparePage(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestOpenArchiveDetectsExtensionlessTarGz(t *testing.T) {
|
||||
reader, err := openArchive("artifact", bytes.NewReader(createTestArchive(t)), "npm")
|
||||
if err != nil {
|
||||
t.Fatalf("openArchive failed: %v", err)
|
||||
func TestArchiveFilename(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{"package.tar.gz", "package.tar.gz"},
|
||||
{"d2e2f014ccd6ec9fae8dbe6336a4164346a2a856", "d2e2f014ccd6ec9fae8dbe6336a4164346a2a856.zip"},
|
||||
{"file.zip", "file.zip"},
|
||||
{"archive.tgz", "archive.tgz"},
|
||||
{"noext", "noext.zip"},
|
||||
}
|
||||
defer func() { _ = reader.Close() }()
|
||||
|
||||
files, err := reader.List()
|
||||
if err != nil {
|
||||
t.Fatalf("List failed: %v", err)
|
||||
}
|
||||
if len(files) == 0 {
|
||||
t.Fatal("expected files in extensionless archive")
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
got := archiveFilename(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("archiveFilename(%q) = %q, want %q", tt.input, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -163,11 +163,9 @@ func (s *Server) Start() error {
|
|||
cd := &cooldown.Config{
|
||||
Default: s.cfg.Cooldown.Default,
|
||||
Ecosystems: s.cfg.Cooldown.Ecosystems,
|
||||
Packages: s.cfg.Cooldown.NormalizedPackages(),
|
||||
Packages: s.cfg.Cooldown.Packages,
|
||||
}
|
||||
proxy := handler.NewProxy(s.db, s.storage, fetcher, resolver, s.logger)
|
||||
proxy.HTTPClient.Timeout = s.cfg.ParseHTTPTimeout()
|
||||
proxy.AuthForURL = s.authForURL
|
||||
proxy.Cooldown = cd
|
||||
proxy.CacheMetadata = s.cfg.CacheMetadata
|
||||
proxy.MetadataTTL = s.cfg.ParseMetadataTTL()
|
||||
|
|
@ -197,13 +195,8 @@ func (s *Server) Start() error {
|
|||
})
|
||||
|
||||
// Mount protocol handlers
|
||||
npmHandler := handler.NewNPMHandler(proxy, s.cfg.BaseURL, s.cfg.Upstream.NPM)
|
||||
cargoHandler := handler.NewCargoHandler(
|
||||
proxy,
|
||||
s.cfg.BaseURL,
|
||||
s.cfg.Upstream.Cargo,
|
||||
s.cfg.Upstream.CargoDownload,
|
||||
)
|
||||
npmHandler := handler.NewNPMHandler(proxy, s.cfg.BaseURL)
|
||||
cargoHandler := handler.NewCargoHandler(proxy, s.cfg.BaseURL)
|
||||
gemHandler := handler.NewGemHandler(proxy, s.cfg.BaseURL)
|
||||
goHandler := handler.NewGoHandler(proxy, s.cfg.BaseURL)
|
||||
hexHandler := handler.NewHexHandler(proxy, s.cfg.BaseURL)
|
||||
|
|
@ -309,7 +302,7 @@ func (s *Server) Start() error {
|
|||
"base_url", s.cfg.BaseURL,
|
||||
"ui_url", s.cfg.UIBaseURL,
|
||||
"storage", s.storage.URL(),
|
||||
"database", s.cfg.Database.String())
|
||||
"database", s.cfg.Database.Path)
|
||||
go s.updateCacheStatsMetrics()
|
||||
go s.startEvictionLoop(bgCtx)
|
||||
|
||||
|
|
@ -934,7 +927,7 @@ func (s *Server) handleStats(w http.ResponseWriter, r *http.Request) {
|
|||
TotalSize: size,
|
||||
TotalSizeHuman: formatSize(size),
|
||||
StorageURL: s.storage.URL(),
|
||||
DatabasePath: s.cfg.Database.String(),
|
||||
DatabasePath: s.cfg.Database.Path,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
|
|
|||
|
|
@ -67,13 +67,8 @@ func newTestServer(t *testing.T) *testServer {
|
|||
r := chi.NewRouter()
|
||||
|
||||
// Mount handlers
|
||||
npmHandler := handler.NewNPMHandler(proxy, cfg.BaseURL, cfg.Upstream.NPM)
|
||||
cargoHandler := handler.NewCargoHandler(
|
||||
proxy,
|
||||
cfg.BaseURL,
|
||||
cfg.Upstream.Cargo,
|
||||
cfg.Upstream.CargoDownload,
|
||||
)
|
||||
npmHandler := handler.NewNPMHandler(proxy, cfg.BaseURL)
|
||||
cargoHandler := handler.NewCargoHandler(proxy, cfg.BaseURL)
|
||||
gemHandler := handler.NewGemHandler(proxy, cfg.BaseURL)
|
||||
goHandler := handler.NewGoHandler(proxy, cfg.BaseURL)
|
||||
pypiHandler := handler.NewPyPIHandler(proxy, cfg.BaseURL)
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ document.getElementById('ecosystem-filter').addEventListener('change', function(
|
|||
const params = new URLSearchParams();
|
||||
if (ecosystem) params.set('ecosystem', ecosystem);
|
||||
if (sortBy) params.set('sort', sortBy);
|
||||
window.location.href = '/ui/packages?' + params.toString();
|
||||
window.location.href = '/packages?' + params.toString();
|
||||
});
|
||||
|
||||
document.getElementById('sort-by').addEventListener('change', function(e) {
|
||||
|
|
@ -109,7 +109,7 @@ document.getElementById('sort-by').addEventListener('change', function(e) {
|
|||
const params = new URLSearchParams();
|
||||
if (ecosystem) params.set('ecosystem', ecosystem);
|
||||
if (sortBy) params.set('sort', sortBy);
|
||||
window.location.href = '/ui/packages?' + params.toString();
|
||||
window.location.href = '/packages?' + params.toString();
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -34,31 +34,11 @@ func NewFilesystem(root string) (*Filesystem, error) {
|
|||
}
|
||||
|
||||
func (fs *Filesystem) fullPath(path string) (string, error) {
|
||||
localPath, err := cleanStoragePath(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
full := filepath.Clean(filepath.Join(fs.root, filepath.FromSlash(path)))
|
||||
if full != fs.root && !strings.HasPrefix(full, fs.root+string(filepath.Separator)) {
|
||||
return "", fmt.Errorf("%w: path escapes storage root", ErrNotFound)
|
||||
}
|
||||
return filepath.Join(fs.root, localPath), nil
|
||||
}
|
||||
|
||||
func (fs *Filesystem) prefixPath(prefix string) (string, error) {
|
||||
if prefix == "" {
|
||||
return fs.root, nil
|
||||
}
|
||||
return fs.fullPath(prefix)
|
||||
}
|
||||
|
||||
func cleanStoragePath(path string) (string, error) {
|
||||
if path == "." || strings.Contains(path, `\`) || !filepath.IsLocal(path) {
|
||||
return "", fmt.Errorf("%w: invalid storage path", ErrNotFound)
|
||||
}
|
||||
|
||||
localPath, err := filepath.Localize(path)
|
||||
if err != nil || localPath == "." || !filepath.IsLocal(localPath) {
|
||||
return "", fmt.Errorf("%w: invalid storage path", ErrNotFound)
|
||||
}
|
||||
|
||||
return localPath, nil
|
||||
return full, nil
|
||||
}
|
||||
|
||||
func (fs *Filesystem) Store(ctx context.Context, path string, r io.Reader) (int64, string, error) {
|
||||
|
|
@ -210,7 +190,7 @@ func (fs *Filesystem) UsedSpace(ctx context.Context) (int64, error) {
|
|||
|
||||
// ListPrefix returns object metadata for paths under a prefix.
|
||||
func (fs *Filesystem) ListPrefix(ctx context.Context, prefix string) ([]ObjectInfo, error) {
|
||||
searchRoot, err := fs.prefixPath(prefix)
|
||||
searchRoot, err := fs.fullPath(prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -243,70 +243,19 @@ func TestFilesystemLargeFile(t *testing.T) {
|
|||
assertLargeFileRoundTrip(t, createTestFilesystem(t))
|
||||
}
|
||||
|
||||
func TestFilesystemRejectsInvalidPaths(t *testing.T) {
|
||||
func TestFilesystemRejectsTraversal(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
fs, err := NewFilesystem(tmp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, p := range []string{
|
||||
"",
|
||||
".",
|
||||
"../etc/passwd",
|
||||
"../../etc/passwd",
|
||||
"a/../../etc/passwd",
|
||||
"/etc/passwd",
|
||||
"test//file.txt",
|
||||
"test/./file.txt",
|
||||
"test/../file.txt",
|
||||
`test\..\file.txt`,
|
||||
} {
|
||||
name := p
|
||||
if name == "" {
|
||||
name = "empty"
|
||||
for _, p := range []string{"../etc/passwd", "../../etc/passwd", "a/../../etc/passwd"} {
|
||||
if _, err := fs.Open(context.Background(), p); err == nil {
|
||||
t.Errorf("Open(%q) should reject traversal", p)
|
||||
}
|
||||
if _, _, err := fs.Store(context.Background(), p, strings.NewReader("x")); err == nil {
|
||||
t.Errorf("Store(%q) should reject traversal", p)
|
||||
}
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
if _, err := fs.FullPath(p); !errors.Is(err, ErrNotFound) {
|
||||
t.Errorf("FullPath(%q) = %v, want ErrNotFound", p, err)
|
||||
}
|
||||
if _, err := fs.Open(ctx, p); err == nil {
|
||||
t.Errorf("Open(%q) should reject invalid path", p)
|
||||
}
|
||||
if _, _, err := fs.Store(ctx, p, strings.NewReader("x")); err == nil {
|
||||
t.Errorf("Store(%q) should reject invalid path", p)
|
||||
}
|
||||
if _, err := fs.Exists(ctx, p); err == nil {
|
||||
t.Errorf("Exists(%q) should reject invalid path", p)
|
||||
}
|
||||
if err := fs.Delete(ctx, p); err == nil {
|
||||
t.Errorf("Delete(%q) should reject invalid path", p)
|
||||
}
|
||||
if _, err := fs.Size(ctx, p); err == nil {
|
||||
t.Errorf("Size(%q) should reject invalid path", p)
|
||||
}
|
||||
if _, err := fs.ListPrefix(ctx, p); p != "" && err == nil {
|
||||
t.Errorf("ListPrefix(%q) should reject invalid path", p)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilesystemListPrefixAllowsEmptyPrefix(t *testing.T) {
|
||||
fs := createTestFilesystem(t)
|
||||
ctx := context.Background()
|
||||
|
||||
_, _, _ = fs.Store(ctx, "a.txt", strings.NewReader("aaaa"))
|
||||
_, _, _ = fs.Store(ctx, "c/d.txt", strings.NewReader("ccccc"))
|
||||
|
||||
objects, err := fs.ListPrefix(ctx, "")
|
||||
if err != nil {
|
||||
t.Fatalf("ListPrefix empty prefix failed: %v", err)
|
||||
}
|
||||
if len(objects) != 2 {
|
||||
t.Fatalf("ListPrefix empty prefix returned %d objects, want 2", len(objects))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue