Watch
1
0
Fork
You've already forked pkg-proxy
1
mirror of https://github.com/git-pkgs/proxy.git synced 2026-11-09 07:39:19 -05:00
Commit graph pkg-proxy/internal/handler
Author SHA1 Message Date
Andrew Nesbitt
b6e71f4d7a
Merge pull request #684 from simonchrz/perf/jsonstream-jsontext
Read jsonstream objects with jsontext
2026-11-09 09:32:19 +00:00
Simon Chrzanowski
4ae55605b1 Send Maven coordinate files under odd groups to the 404
A file named {artifact}-{version} under a dotted group directory, such as
org.foo/bar/1.0/bar-1.0-linux.exe, was not recognised as a coordinate file
and so was proxied past the checks. isCoordinateFile no longer checks the
group, so handleDownload answers 404 for it. The docs say which files get
the 404, and the Range test covers a stale If-Range.
2026-11-08 22:18:14 +01:00
Simon Chrzanowski
b61e35205c Answer 404 for Maven files outside the layout
A file whose group directories hold a dot or colon was proxied without
caching, which skipped the denylist, scan and cooldown. Such paths aren't
in the Maven layout, so they now get 404. Directory listings and catalogs
still go through proxyUpstream, which shares its error handling with
Proxy.ProxyUpstream in relayUpstream.
2026-11-08 21:49:48 +01:00
Simon Chrzanowski
2ac80bf76b Merge remote-tracking branch 'origin/main' into fix/cache-key-collisions
# Conflicts:
#	internal/gitcache/cache_test.go
2026-11-08 21:47:56 +01:00
Andrew Nesbitt
1c9509d125
Merge pull request #679 from simonchrz/fix/missing-routes
Fix missing routes and protocol gaps
2026-11-08 19:57:38 +00:00
Simon Chrzanowski
44afe6997a Read jsonstream objects with jsontext
CopyObject takes the reader and decodes it with jsontext, accepting
repeated names and invalid UTF-8 as encoding/json did. Keys are written
with the bytes they were read with instead of being re-encoded. Member
gets Skip, which reads past a value without keeping it, and npm's
RewriteStream skips dropped versions and duplicates with it.

conda-forge noarch repodata.json (195 MB): filtering 0.89 to 0.62 s and
379 to 77 MB allocated, the timestamp index 0.84 to 0.61 s. The next
packument (32 MB): RewriteStream 69 to 34 ms and 38 to 3.5 MB.
2026-11-08 19:03:21 +01:00
Simon Chrzanowski
3fdbe51ad5 Merge remote-tracking branch 'origin/main' into feat/mirror-git-tags-submodules 2026-11-08 18:56:36 +01:00
Simon Chrzanowski
140b70233a Move the PyPI root index replacement loop into its own method 2026-11-08 18:51:46 +01:00
Simon Chrzanowski
2ecdcced4c Search again only for the replacement just made in the PyPI root index
Each match searched the rest of the buffer for every replacement. The
root index has no single-quoted links, so after every double-quoted one
the other pattern scanned to the end again, making each chunk quadratic
in its links. Each replacement's next match is now kept and only
searched for again once a replacement passes it (50,000 links: 18.6 to
818 MB/s).
2026-11-08 18:51:00 +01:00
Andrew Nesbitt
43bd67fc13
Merge pull request #681 from simonchrz/fix/cooldown-denylist-gaps
Fix cooldown and denylist gaps
2026-11-08 17:31:53 +00:00
Andrew Nesbitt
1b22f79be2
Fail the conda timestamp test when the index is empty 2026-11-08 17:23:11 +00:00
Andrew Nesbitt
53c0fd9c51
Merge pull request #676 from simonchrz/perf/npm-jsontext
Skip discarded npm packument values with jsontext
2026-11-08 17:02:31 +00:00
Andrew Nesbitt
9fb3119b87
Merge pull request #672 from milas/milas/deb-caching
fix(debian): route packages by filename rather than `/pool` prefix
2026-11-08 15:55:01 +00:00
Simon Chrzanowski
da090443ec Forward Range to upstream for Maven paths proxied uncached
Paths proxied without caching sent no request headers, so a resumed
download got the whole file back instead of the requested bytes.
2026-11-08 12:19:19 +01:00
Simon Chrzanowski
0ff118b642 Try the Plugin Portal for Maven paths proxied uncached
Paths the proxy doesn't cache, such as ones with a dotted group
directory, went to the primary upstream only, so a file available only on
the Gradle Plugin Portal returned 404. They now try the Plugin Portal on a
404, as cached files do.
2026-11-08 10:57:38 +01:00
Simon Chrzanowski
fede2463f7 Describe conda shard timestamps as condaTime reads them 2026-11-08 10:10:43 +01:00
Simon Chrzanowski
7fdfd9908f Refetch npm packuments only when a cached copy lacks the version
Under cooldown, a tarball for a version the packument doesn't list is
decided on a fresh fetch of the packument. That check had gaps:

- A stale copy served because upstream failed counted as fetched, so
  the version went through with no publish time. It now triggers the
  refetch like a copy within its TTL.
- A packument just stored from upstream counted as cached, so every
  request for an unlisted version fetched it twice. Only a stored row
  read within its TTL counts now.
- A stored refetch that joined another caller's fetch could get that
  caller's stale fallback and take it as upstream's answer. The stored
  lookup now reports stale results, and a stale refetch is refused.
- A refused refetch dropped the upstream error, so a rate-limit hold
  answered 502 instead of 503 with Retry-After, and a 404 was lost.
  Stale results now carry the upstream error, and the refusal wraps it.
2026-11-08 10:10:43 +01:00
Simon Chrzanowski
c7031e4897 Track mirrored manifests by sha256 whatever digest names them
Mirroring img:v1@sha512:<X> compared the sha512 digest with the sha256
digest the tag resolved to, so an unmoved tag was reported as moved
and its manifest walked twice. Key seen manifests and the returned
digest by sha256, as tags are.
2026-11-08 10:09:23 +01:00
Simon Chrzanowski
39d6b8c7b0 Rewrite PyPI root index links under the upstream's path
An upstream such as Nexus served from /repository/pypi links projects
as /repository/pypi/simple/<name>/, which the rewrite missed. Match
links under the configured upstream's path, in single-quoted hrefs as
well as double-quoted ones, and drop the upstream's ETag, which names
the body before the rewrite.
2026-11-08 10:07:24 +01:00
Simon Chrzanowski
5c798069a1 Treat a null notification-url as absent for routed packages
Composer falls back to the default repository's notify-batch when a
version's notification-url is null, which would report installs of a
routed private package to Packagist. Replace null with an empty string
as is already done for pushed versions.

Also drop the pushed-version check's local url variable, which shadowed
net/url, and test that a pushed manifest's own URL is kept.
2026-11-08 10:06:20 +01:00
Simon Chrzanowski
8233ce3339 Clean RPM package paths before caching
The cache key for an RPM package is its request path, so a path with
an empty or "." segment made a second entry for the same upstream
file. The path is now cleaned before it is used as the key and the
upstream URL.
2026-11-08 10:05:58 +01:00
Simon Chrzanowski
de280a1694 Don't cache Maven paths with a colon in the group or artifact
The cache keys Maven artifacts as group:artifact, so org:foo/bar and
org/foo:bar both came out as org:foo:bar and shared one entry. Such
paths are now proxied without caching, like dotted group directories.
2026-11-08 10:05:23 +01:00
Simon Chrzanowski
61947c9c8c Treat a null Composer notification-url as absent
Composer reads "notification-url": null as missing and reports installs
to notify-batch, so a pushed manifest with null now gets an empty one too.
The docs say a pushed manifest's own notification-url is kept.
2026-11-08 08:51:39 +01:00
Simon Chrzanowski
4f7a8b1b08 Read Conda timestamps in seconds or milliseconds as conda does
The Conda handler divided every repodata timestamp by 1000, assuming
milliseconds. Some entries (a few in conda-forge noarch) use seconds,
so their publish time came out in 1970 and they were never held back
by cooldown. Read a timestamp as milliseconds only when it is above
253402300799, as conda does, in one helper used by the repodata
filter, the shard filter and the download index.
2026-11-08 08:04:09 +01:00
Simon Chrzanowski
20cbeeff22 Disable install notifications for pushed Composer versions
Composer reports installs to the repository's notify-batch URL unless a
version has its own notification-url, and the proxy's packages.json sets
notify-batch to the upstream. Routed packages get an empty
notification-url for this reason, but pushed and hosted-only versions
did not, so installs of private packages were reported to packagist.org.
Give pushed versions the same empty notification-url.
2026-11-08 08:01:34 +01:00
Simon Chrzanowski
1fbcdc95c8 Rewrite project links in the PyPI root simple index
pypi.org's HTML root index links each project as /simple/<name>/. The
proxy relayed it unchanged, but it is mounted at /pypi, so following a
link went to /simple/<name>/ on the proxy and got 404. Rewrite those
hrefs to the proxy's /pypi/simple/ while streaming the response, since
the index is tens of megabytes. The JSON index has no links and is left
alone.
2026-11-08 08:01:34 +01:00
Simon Chrzanowski
3860a6cae5 Don't cache Maven paths with dotted group directories
parsePath joins the group directories with dots, and the cache filename
is the file's base name, so org.foo/bar/1.0/bar-1.0.jar and
org/foo/bar/1.0/bar-1.0.jar shared one cache entry. Only upstreams that
serve content at such directories, such as raw or hosted repositories,
are affected.

The Maven layout splits a group at its dots, so a group directory never
contains a dot or is empty. Treat such paths as outside the layout and
proxy them uncached, which keeps every existing cache entry valid.
2026-11-08 08:00:50 +01:00
Simon Chrzanowski
d073fdcd52 Close npm, PyPI and pub cooldown gaps in metadata and downloads
npm: a tarball request for a version the cached packument did not list
yet (cached within metadata_ttl before the release) found no publish
time and was let through cooldown, and fetched without integrity. When
cooldown applies and the cached packument lacks the version, fetch it
again and decide on what upstream lists now; if it can't be fetched,
fail as when a publish time can't be read.

npm: cooldown only moved dist-tags.latest. Other tags kept naming
versions in cooldown, while the denylist removes such tags. Cooldown
now removes them too, and drops latest when no version is left, in
both the in-memory and streamed rewrites.

PyPI: the simple JSON rewrite removed filtered files but left their
versions in the PEP 700 versions list. Remove them there too.

pub: latest was never rewritten, so its archive_url pointed at pub.dev,
it kept naming a version in cooldown when no version was left, and the
replacement was the last list entry, which may be a prerelease. Point
latest's archive_url at the proxy, replace a held back latest with the
newest stable version left (or the newest prerelease), and drop it
when nothing is left.
2026-11-08 08:00:14 +01:00
Simon Chrzanowski
0b399c248d Accept sha512 OCI blob digests and check HEAD digests
The blob route and blobPathPattern only matched sha256 digests, so a
sha512 blob got 404 although the fetch path can verify sha512. The image
mirror rejected sha512 digests in index entries, configs and layers for
the same reason. Accept sha512 in both, with exact digest lengths.

A HEAD for an uncached manifest by digest passed on whatever digest the
upstream named. GET checks the body against the requested digest; HEAD
now answers DIGEST_INVALID when the upstream names a different digest
of the same algorithm.
2026-11-08 07:57:26 +01:00
Simon Chrzanowski
cbbeed423b Cache RPM packages under their full path
RPM packages were cached under their bare filename. With upstream.rpm
pointing at a mirror root that serves several distributions, Rocky and
Alma both publish bash-5.1.8-9.el9.x86_64.rpm with different bytes, and
whichever was fetched first was served for both, so dnf failed the
checksum or signature check.

Use the path under the upstream as the cache filename, as the APK and
Arch handlers do. Packages cached before this change are no longer
found and are fetched again once; retention still matches them by
package name.
2026-11-08 07:56:52 +01:00
Simon Chrzanowski
70d9db2906 Add CRAN routes for macOS arch layout and Meta files
R 4.3 and later on macOS fetch binaries from a two-segment platform path
such as bin/macosx/big-sur-arm64/contrib/4.4/. The router only matched a
single platform segment, so both the PACKAGES indexes and the .tgz
downloads returned 404. Add routes for that layout and include the arch
in the stored version so arm64 and x86_64 builds stay apart.

src/contrib/Meta/archive.rds and current.rds, which remotes and pak use
to install archived versions, had no route either. Serve .rds files under
Meta as cached metadata.
2026-11-08 07:55:24 +01:00
Milas Bowman
7d84bf7682
chore: remove deb source pkg logic / fix root-level pkg caching 2026-11-07 15:31:42 -05:00
Simon Chrzanowski
d58b07b2a7 Skip discarded npm packument values with jsontext
ScanPackument and LookupVersion read with a jsontext.Decoder, skipping
values they don't keep with SkipValue and reading the one LookupVersion
returns with ReadValue, instead of decoding each into a json.RawMessage.
Repeated names and invalid UTF-8 are still accepted as encoding/json
accepts them. Go 1.27 is now required, for encoding/json/jsontext.

Benchmarks for both use the 5,000-version packument the handler benchmarks
build, now shared in internal/metadata/npm/npmbench.
2026-11-07 18:02:59 +01:00
Simon Chrzanowski
e9aa078a85 Mirror pkg:git tags and the submodules a commit pins
A pkg:git version can now be a tag as well as a full commit ID. With a
version, the submodules the commit records are mirrored at the commits it
pins, from the upstream.git hosts serving them; one no host serves is
skipped with a warning.
2026-11-07 17:29:03 +01:00
Andrew Nesbitt
72aa8fdc43
Merge pull request #667 from simonchrz/feat/mirror-git
Mirror pkg:git commits into the git cache
2026-11-07 14:58:02 +00:00
Andrew Nesbitt
7e18c6a020
Merge pull request #664 from simonchrz/fix/retention-mirrored-images
Keep blobs of scheduled images from retention
2026-11-07 14:43:06 +00:00
Milas Bowman
611feb003a
fix(debian): route packages by filename rather than /pool prefix 2026-11-06 20:12:14 -05:00
Simon Chrzanowski
abe94017a1 Renew wanted refs of mirrored commits and count refreshes that fetched
Mirroring a commit on no branch or tag again writes a fresh wanted ref, so
its 7 days count from the last mirror run. A cached repository whose refresh
fetched new commits is no longer reported as cached.
2026-11-06 11:36:42 +01:00
Andrew Nesbitt
c2dd16903a
Share publish indexes across cooldown profiles 2026-11-06 09:38:07 +00:00
Simon Chrzanowski
03be03b8fe Mirror pkg:git commits into the git cache
proxy mirror, mirror API jobs and SBOMs take pkg:git/{host}/{path}@{commit}
and cache the repository and commit in the cache /git serves, so a later
clone is a cache hit, also with the upstream down. Part of #403.
2026-11-06 07:48:51 +01:00
Andrew Nesbitt
79be33049a
Add cooldown profiles selected by registry URL 2026-11-05 21:42:44 +00:00
Andrew Nesbitt
61ac273862
Check primary metadata before allowing untimed downloads 2026-11-05 20:00:19 +00:00
Andrew Nesbitt
b9dcbd27fc
Handle cooldown lookup failures and reuse download indexes 2026-11-05 18:41:03 +00:00
Christian Heim
5db16cbb99
Conda reads repodata as a stream for the download check, under the same bound as the repodata filter, so a repodata.json over metadata_max_size no longer lets a package in cooldown through. If even the compressed copy is too large to cache, the check streams it from upstream. 2026-11-05 18:30:44 +00:00
Christian Heim
89c60abd1a
Conda now checks each package file against its own timestamp, because builds of one version differ. Conda repodata and helm indexes are parsed once per cached document instead of on every download. 2026-11-05 18:30:43 +00:00
Christian Heim
3456b394be
Cargo, gem, hex, pub, conda and helm now check cooldown on artifact downloads too, so a version inside the window is withheld even when it was cached before. Publish times are stored in UTC. Fixes #521. 2026-11-05 18:30:42 +00:00
Simon Chrzanowski
0f26841f56 Keep blobs of scheduled images from retention
A mirror run that finds a blob cached records it in mirrored_at, which
retention counts as use. LRU eviction keeps using last_accessed_at, so
#508's decision not to count mirror runs as cache hits stands.

Fixes #648.
2026-11-05 17:39:19 +01:00
Simon Chrzanowski
3e2a84a88a Mirror Alpine packages
The PURL namespace names the upstream.apk repository, and the distro and
arch qualifiers syft writes give the release and architecture. The package
is looked up in the main and community indexes of that release and cached
under the path the APK handler serves it from.
2026-11-04 15:44:01 +01:00
Simon Chrzanowski
82b45cbb1b Merge remote-tracking branch 'origin/main' into feat/composer-advisories
# Conflicts:
#	README.md
2026-11-04 13:05:10 +01:00
Simon Chrzanowski
cb330a5cbf Answer 502 when a Composer download's metadata lookup fails upstream
The lookup took any non-200 metadata response for a version that isn't
listed, so a Packagist outage or rate limit answered the archive request
with 404. Composer treats that as the version being gone.

Only 404 and 410 now mean the document is missing. A 429 is passed on as
503 with Retry-After while the upstream is held, and any other status
answers 502.
2026-11-03 20:10:24 +01:00