diff --git a/internal/handler/download_test.go b/internal/handler/download_test.go index c53585e2..d0582366 100644 --- a/internal/handler/download_test.go +++ b/internal/handler/download_test.go @@ -960,6 +960,10 @@ func TestMavenHandler_NonArtifactPathsProxied(t *testing.T) { "/com/google/protobuf/protoc/3.21.0/", "/com/google/protobuf/protoc/", "/archetype-catalog.xml", + // Checksums of files that aren't artifacts, too short to parse. + "/archetype-catalog.xml.sha1", + "/archetype-catalog.xml.md5", + "/.index/nexus-maven-repository-index.gz.sha1", } for _, path := range paths { w := httptest.NewRecorder() diff --git a/internal/handler/maven.go b/internal/handler/maven.go index 50d30467..39af4407 100644 --- a/internal/handler/maven.go +++ b/internal/handler/maven.go @@ -241,10 +241,12 @@ func (h *MavenHandler) handleDownload(w http.ResponseWriter, r *http.Request, so // e.g., com/google/guava/guava/32.1.3-jre/guava-32.1.3-jre.jar group, artifact, version, filename := h.parsePath(urlPath) if artifact == "" { - if strings.Count(urlPath, "/") >= minMavenParts-1 { - // A dot or colon in a group directory, or a colon in the - // artifact, which the Maven layout never has. Proxied, the - // file would skip the denylist, scan and cooldown. + // Too few segments to hold a coordinate, or a dot or colon in a + // group directory, or a colon in the artifact, which the Maven + // layout never has. Proxied, an artifact would skip the denylist, + // scan and cooldown. A checksum of another file, such as + // archetype-catalog.xml.sha1, is proxied like that file. + if h.isArtifactFile(h.checksummedFile(path.Base(urlPath))) || h.isCoordinateFile(urlPath) { http.NotFound(w, r) return } @@ -384,12 +386,26 @@ func (h *MavenHandler) isVersionListingFile(filename string) bool { // an artifact. They are as immutable as the artifact, so they are cached // with it rather than refetched on every request. func (h *MavenHandler) isChecksumFile(filename string) bool { - for _, ext := range []string{".sha1", ".sha256", ".sha512", ".md5", ".asc"} { - if strings.HasSuffix(filename, ext) { - return true + return h.checksummedFile(filename) != filename +} + +// checksummedFile is the file a checksum or signature file is for +// (bar-1.0.jar for bar-1.0.jar.sha1 and bar-1.0.jar.asc.sha1), or filename +// itself if it is neither. +func (h *MavenHandler) checksummedFile(filename string) string { + for { + trimmed := filename + for _, ext := range []string{".sha1", ".sha256", ".sha512", ".md5", ".asc"} { + if name, ok := strings.CutSuffix(trimmed, ext); ok { + trimmed = name + break + } + } + if trimmed == filename { + return filename } + filename = trimmed } - return false } // mavenForwardHeaders lets clients resume artifacts proxied without caching. diff --git a/internal/handler/maven_repositories_test.go b/internal/handler/maven_repositories_test.go index e9b2fcf4..4249847d 100644 --- a/internal/handler/maven_repositories_test.go +++ b/internal/handler/maven_repositories_test.go @@ -139,15 +139,18 @@ func TestMavenRepositoryRepointedServesTheNewUpstream(t *testing.T) { } } -// TestMavenDottedGroupDirectoryNotCachedAsGroup checks that a file whose -// group directories contain dots or colons, which the Maven layout never -// has, is not served: it would either share the cache entry of the group -// the dots spell or be proxied past the denylist, scan and cooldown. +// TestMavenDottedGroupDirectoryNotCachedAsGroup checks that a file outside +// the Maven layout is not served: a group directory with a dot or a colon, +// which the layout never has and which would share the cache entry of the +// group the dots spell, and a path too short to hold a coordinate. Proxied, +// they would skip the denylist, scan and cooldown. func TestMavenDottedGroupDirectoryNotCachedAsGroup(t *testing.T) { const standard = "/org/foo/bar/1.0/bar-1.0.jar" odd := []string{ "/org.foo/bar/1.0/bar-1.0.jar", "/org.foo/bar/1.0/bar-1.0-linux.exe", "/org/foo:x/bar/1.0/bar-1.0.jar", "/org/foo/b:ar/1.0/b:ar-1.0.jar", + "/bar/1.0/bar-1.0.jar", "/bar-1.0.jar", "/bar/1.0/bar-1.0.jar.sha1", "/bar-1.0.pom.asc", + "/bar/1.0/bar-1.0.jar.asc.sha1", } central, requests := mavenRepoUpstream(t, "central", append([]string{standard}, odd...)...) portal, _ := mavenRepoUpstream(t, "portal")