fix(blossom-bridge): only the last path segment is the blob hash
BUD-01 defines a Blossom URL as `<server>/<sha256>[.<ext>]` — the blob hash is always the last path segment. Walking the path right-to-left for any hex match was too permissive: a non-Blossom URL like `https://example.com/<sha>/avatar.jpg` (sha appears in an intermediate segment) would get incorrectly bridged. Both parsers now look at the last path segment only and skip the URL entirely when it isn't a sha256. The earlier hex-prefix case (share.yabu.me's `<cache-prefix>/<blob>.ext`) still works because the blob is still the last segment; the prefix flows into `xs` via the existing `buildServerBase` / `extractServerBase` logic. Adds negative tests covering the sha-in-non-last-segment case in both modules.
This commit is contained in:
+12
-12
@@ -72,18 +72,18 @@ class LocalBlossomCacheRedirectInterceptor(
|
||||
}
|
||||
|
||||
private fun findSha256AndExtensionInPath(url: HttpUrl): Triple<Int, String, String>? {
|
||||
// Walk segments right-to-left so CDNs that put a cache prefix (itself a
|
||||
// 64-char hex segment) ahead of the blob hash — e.g.
|
||||
// `https://share.yabu.me/<prefix>/<sha>.webp` — still resolve to the
|
||||
// blob and leave the prefix in `xs`.
|
||||
for (index in url.pathSegments.indices.reversed()) {
|
||||
val segment = url.pathSegments[index]
|
||||
val match = SHA256_SEGMENT_REGEX.find(segment) ?: continue
|
||||
val sha = match.value.lowercase()
|
||||
val ext = guessExtensionFrom(segment, sha) ?: "bin"
|
||||
return Triple(index, sha, ext)
|
||||
}
|
||||
return null
|
||||
// Per Blossom (BUD-01) the blob is always the last path segment. If the
|
||||
// last segment isn't a sha256, this isn't a Blossom URL and the bridge
|
||||
// must leave it alone — even if an earlier path segment happens to be
|
||||
// a 64-char hex (e.g. a per-user cache prefix). The prefix segments
|
||||
// are preserved verbatim via `buildServerBase`.
|
||||
val lastIndex = url.pathSegments.lastIndex
|
||||
if (lastIndex < 0) return null
|
||||
val segment = url.pathSegments[lastIndex]
|
||||
val match = SHA256_SEGMENT_REGEX.find(segment) ?: return null
|
||||
val sha = match.value.lowercase()
|
||||
val ext = guessExtensionFrom(segment, sha) ?: "bin"
|
||||
return Triple(lastIndex, sha, ext)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+13
-1
@@ -133,7 +133,7 @@ class LocalBlossomCacheRedirectInterceptorTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bridgeOnPicksRightmostShaWhenPathHasTwoHashes() {
|
||||
fun bridgeOnRewritesShaInLastPathSegmentWithHexPrefix() {
|
||||
// share.yabu.me layout: <cache-prefix-sha>/<blob-sha>.<ext>
|
||||
val interceptor = LocalBlossomCacheRedirectInterceptor { true }
|
||||
val captured = mutableListOf<String>()
|
||||
@@ -151,6 +151,18 @@ class LocalBlossomCacheRedirectInterceptorTest {
|
||||
response.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bridgeOnSkipsWhenLastSegmentIsNotSha() {
|
||||
// Per BUD-01 the last segment is the blob; if it isn't a sha256, the
|
||||
// URL isn't a Blossom blob even if an earlier segment is hex.
|
||||
val interceptor = LocalBlossomCacheRedirectInterceptor { true }
|
||||
val captured = mutableListOf<String>()
|
||||
val url = "https://example.com/$sha/avatar.jpg"
|
||||
val response = interceptor.intercept(fakeChain(url, captured))
|
||||
assertEquals(url, captured.single())
|
||||
response.close()
|
||||
}
|
||||
|
||||
private fun fakeChain(
|
||||
url: String,
|
||||
captured: MutableList<String>,
|
||||
|
||||
Reference in New Issue
Block a user