Merge pull request #2866 from greenart7c3/claude/fix-blossom-cache-url-M9Pzk
Fix Blossom bridge to only rewrite last path segment as SHA256
This commit is contained in:
+12
-7
@@ -72,13 +72,18 @@ class LocalBlossomCacheRedirectInterceptor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun findSha256AndExtensionInPath(url: HttpUrl): Triple<Int, String, String>? {
|
private fun findSha256AndExtensionInPath(url: HttpUrl): Triple<Int, String, String>? {
|
||||||
url.pathSegments.forEachIndexed { index, segment ->
|
// Per Blossom (BUD-01) the blob is always the last path segment. If the
|
||||||
val match = SHA256_SEGMENT_REGEX.find(segment) ?: return@forEachIndexed
|
// last segment isn't a sha256, this isn't a Blossom URL and the bridge
|
||||||
val sha = match.value.lowercase()
|
// must leave it alone — even if an earlier path segment happens to be
|
||||||
val ext = guessExtensionFrom(segment, sha) ?: "bin"
|
// a 64-char hex (e.g. a per-user cache prefix). The prefix segments
|
||||||
return Triple(index, sha, ext)
|
// are preserved verbatim via `buildServerBase`.
|
||||||
}
|
val lastIndex = url.pathSegments.lastIndex
|
||||||
return null
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+31
@@ -132,6 +132,37 @@ class LocalBlossomCacheRedirectInterceptorTest {
|
|||||||
response.close()
|
response.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun bridgeOnRewritesShaInLastPathSegmentWithHexPrefix() {
|
||||||
|
// share.yabu.me layout: <cache-prefix-sha>/<blob-sha>.<ext>
|
||||||
|
val interceptor = LocalBlossomCacheRedirectInterceptor { true }
|
||||||
|
val captured = mutableListOf<String>()
|
||||||
|
val response =
|
||||||
|
interceptor.intercept(
|
||||||
|
fakeChain(
|
||||||
|
"https://share.yabu.me/84b0c46ab699ac35eb2ca286470b85e081db2087cdef63932236c397417782f5/28fa4d999af6ae3e4e11bfc2727130ef1b3a13cc0f981e5a93c3996cb2f524e5.webp",
|
||||||
|
captured,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
"http://127.0.0.1:24242/28fa4d999af6ae3e4e11bfc2727130ef1b3a13cc0f981e5a93c3996cb2f524e5.webp?xs=https%3A%2F%2Fshare.yabu.me%2F84b0c46ab699ac35eb2ca286470b85e081db2087cdef63932236c397417782f5",
|
||||||
|
captured.single(),
|
||||||
|
)
|
||||||
|
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(
|
private fun fakeChain(
|
||||||
url: String,
|
url: String,
|
||||||
captured: MutableList<String>,
|
captured: MutableList<String>,
|
||||||
|
|||||||
+7
-2
@@ -144,8 +144,13 @@ private fun percentEncode(input: String): String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun extractSha256FromUrlPath(url: 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('#')
|
val pathPart = url.substringBefore('?').substringBefore('#')
|
||||||
val match = sha256InPathRegex.find(pathPart) ?: return null
|
val lastSegment = pathPart.substringAfterLast('/')
|
||||||
|
val match = sha256InPathRegex.find(lastSegment) ?: return null
|
||||||
return match.value.lowercase()
|
return match.value.lowercase()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +194,7 @@ private fun extractServerBase(
|
|||||||
val hostStart = schemeEnd + 3
|
val hostStart = schemeEnd + 3
|
||||||
if (hostStart >= pathPart.length) return null
|
if (hostStart >= pathPart.length) return null
|
||||||
|
|
||||||
val shaIndex = pathPart.indexOf(sha, ignoreCase = true)
|
val shaIndex = pathPart.lastIndexOf(sha, ignoreCase = true)
|
||||||
if (shaIndex >= 0) {
|
if (shaIndex >= 0) {
|
||||||
// Anchor on the slash immediately preceding the sha so the cache
|
// Anchor on the slash immediately preceding the sha so the cache
|
||||||
// can append "/<sha>" verbatim per the local-blossom-cache spec.
|
// can append "/<sha>" verbatim per the local-blossom-cache spec.
|
||||||
|
|||||||
+40
@@ -173,4 +173,44 @@ class MediaUrlContentExtTest {
|
|||||||
val uri = "blossom:$sha.jpg?xs=https://nostr.build"
|
val uri = "blossom:$sha.jpg?xs=https://nostr.build"
|
||||||
assertEquals(uri, bridgeProfilePictureUrl(uri, useBridge = true))
|
assertEquals(uri, bridgeProfilePictureUrl(uri, useBridge = true))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun bridgeOnRewritesShaInLastPathSegmentWithHexPrefix() {
|
||||||
|
// share.yabu.me layout: <cache-prefix-sha>/<blob-sha>.<ext>
|
||||||
|
val image =
|
||||||
|
MediaUrlImage(
|
||||||
|
url = "https://share.yabu.me/84b0c46ab699ac35eb2ca286470b85e081db2087cdef63932236c397417782f5/28fa4d999af6ae3e4e11bfc2727130ef1b3a13cc0f981e5a93c3996cb2f524e5.webp",
|
||||||
|
hash = null,
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
"blossom:28fa4d999af6ae3e4e11bfc2727130ef1b3a13cc0f981e5a93c3996cb2f524e5.webp?xs=https://share.yabu.me/84b0c46ab699ac35eb2ca286470b85e081db2087cdef63932236c397417782f5",
|
||||||
|
image.toCoilModel(useLocalBlossomBridge = true),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun bridgeProfilePictureUrlRewritesShaInLastPathSegmentWithHexPrefix() {
|
||||||
|
assertEquals(
|
||||||
|
"http://127.0.0.1:24242/28fa4d999af6ae3e4e11bfc2727130ef1b3a13cc0f981e5a93c3996cb2f524e5.webp?xs=https%3A%2F%2Fshare.yabu.me%2F84b0c46ab699ac35eb2ca286470b85e081db2087cdef63932236c397417782f5",
|
||||||
|
bridgeProfilePictureUrl(
|
||||||
|
"https://share.yabu.me/84b0c46ab699ac35eb2ca286470b85e081db2087cdef63932236c397417782f5/28fa4d999af6ae3e4e11bfc2727130ef1b3a13cc0f981e5a93c3996cb2f524e5.webp",
|
||||||
|
useBridge = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@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