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:
+6
-4
@@ -144,11 +144,13 @@ private fun percentEncode(input: String): String {
|
||||
}
|
||||
|
||||
private fun extractSha256FromUrlPath(url: String): String? {
|
||||
// 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).
|
||||
val pathPart = url.substringBefore('?').substringBefore('#')
|
||||
// Prefer the rightmost 64-char hex segment so CDNs that put a cache prefix
|
||||
// (itself a 64-char hex) ahead of the blob hash — e.g.
|
||||
// `https://share.yabu.me/<prefix>/<sha>.webp` — still resolve to the blob.
|
||||
val match = sha256InPathRegex.findAll(pathPart).lastOrNull() ?: return null
|
||||
val lastSegment = pathPart.substringAfterLast('/')
|
||||
val match = sha256InPathRegex.find(lastSegment) ?: return null
|
||||
return match.value.lowercase()
|
||||
}
|
||||
|
||||
|
||||
+17
-2
@@ -175,7 +175,7 @@ class MediaUrlContentExtTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bridgeOnPicksRightmostShaWhenPathHasTwoHashes() {
|
||||
fun bridgeOnRewritesShaInLastPathSegmentWithHexPrefix() {
|
||||
// share.yabu.me layout: <cache-prefix-sha>/<blob-sha>.<ext>
|
||||
val image =
|
||||
MediaUrlImage(
|
||||
@@ -189,7 +189,7 @@ class MediaUrlContentExtTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bridgeProfilePictureUrlPicksRightmostShaWhenPathHasTwoHashes() {
|
||||
fun bridgeProfilePictureUrlRewritesShaInLastPathSegmentWithHexPrefix() {
|
||||
assertEquals(
|
||||
"http://127.0.0.1:24242/28fa4d999af6ae3e4e11bfc2727130ef1b3a13cc0f981e5a93c3996cb2f524e5.webp?xs=https%3A%2F%2Fshare.yabu.me%2F84b0c46ab699ac35eb2ca286470b85e081db2087cdef63932236c397417782f5",
|
||||
bridgeProfilePictureUrl(
|
||||
@@ -198,4 +198,19 @@ class MediaUrlContentExtTest {
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@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 url = "https://example.com/$sha/avatar.jpg"
|
||||
val image = MediaUrlImage(url = url, hash = null)
|
||||
assertEquals(url, image.toCoilModel(useLocalBlossomBridge = true))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bridgeProfilePictureUrlSkipsWhenLastSegmentIsNotSha() {
|
||||
val url = "https://example.com/$sha/avatar.jpg"
|
||||
assertEquals(url, bridgeProfilePictureUrl(url, useBridge = true))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user