Merge pull request #2959 from greenart7c3/claude/fix-blossom-cache-url-8zolt
fix(blossom): only bridge to local cache when URL is BUD-01 layout
This commit is contained in:
+12
-5
@@ -104,13 +104,20 @@ private fun bridgeUrl(
|
||||
if (url.startsWith("blossom:", ignoreCase = true)) return url
|
||||
if (!url.startsWith("http://", ignoreCase = true) && !url.startsWith("https://", ignoreCase = true)) return url
|
||||
|
||||
val sha =
|
||||
explicitHash?.lowercase()?.takeIf { sha256HexRegex.matches(it) }
|
||||
?: extractSha256FromUrlPath(url)
|
||||
?: return url
|
||||
// The local Blossom cache fetches `<xs>/<sha>.<ext>` on miss per BUD-01,
|
||||
// which only works when the upstream URL is itself BUD-01 layout. For
|
||||
// non-BUD-01 URLs (e.g. https://i.nostr.build/M5AwJ.gif) the imeta `x`
|
||||
// hash identifies the blob but the upstream server doesn't host it at
|
||||
// /<sha>.<ext>, so trusting only `explicitHash` would point the cache
|
||||
// at a 404. Require the sha to be in the URL path before bridging.
|
||||
val urlSha = extractSha256FromUrlPath(url) ?: return url
|
||||
|
||||
// Prefer the imeta hash when it's a valid sha256 (authoritative casing),
|
||||
// otherwise fall back to what was parsed from the URL.
|
||||
val sha = explicitHash?.lowercase()?.takeIf { sha256HexRegex.matches(it) } ?: urlSha
|
||||
|
||||
val ext = guessExtension(url, mimeType)
|
||||
val serverBase = extractServerBase(url, sha) ?: return url
|
||||
val serverBase = extractServerBase(url, urlSha) ?: return url
|
||||
|
||||
val authors =
|
||||
authorPubKey
|
||||
|
||||
+24
-5
@@ -80,14 +80,15 @@ class MediaUrlContentExtTest {
|
||||
|
||||
@Test
|
||||
fun bridgeOnInfersExtensionFromMimeType() {
|
||||
val image = MediaUrlImage(url = "https://nostr.build/i/abc", hash = sha, mimeType = "image/png")
|
||||
// BUD-01 allows `<sha>` without an extension; mimeType supplies one.
|
||||
val image = MediaUrlImage(url = "https://nostr.build/i/$sha", hash = sha, mimeType = "image/png")
|
||||
val result = image.toCoilModel(useLocalBlossomBridge = true)
|
||||
assertTrue(result.startsWith("blossom:$sha.png?xs="), "expected png extension from mime, got $result")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bridgeOnFallsBackToBinExtension() {
|
||||
val image = MediaUrlImage(url = "https://nostr.build/i/abc", hash = sha)
|
||||
val image = MediaUrlImage(url = "https://nostr.build/i/$sha", hash = sha)
|
||||
val result = image.toCoilModel(useLocalBlossomBridge = true)
|
||||
assertTrue(result.startsWith("blossom:$sha.bin?xs="), "expected bin extension fallback, got $result")
|
||||
}
|
||||
@@ -100,7 +101,7 @@ class MediaUrlContentExtTest {
|
||||
|
||||
@Test
|
||||
fun uppercaseHashNormalisedToLowercase() {
|
||||
val image = MediaUrlImage(url = "https://cdn.example.com/foo.jpg", hash = sha.uppercase())
|
||||
val image = MediaUrlImage(url = "https://cdn.example.com/${sha.uppercase()}.jpg", hash = sha.uppercase())
|
||||
val result = image.toCoilModel(useLocalBlossomBridge = true)
|
||||
assertTrue(result.startsWith("blossom:$sha.jpg?xs="), "expected lowercase sha, got $result")
|
||||
}
|
||||
@@ -110,7 +111,7 @@ class MediaUrlContentExtTest {
|
||||
val authorPub = "a8f3721a0dc1b4d5c12f4cc7c54ae14071eb9c1b4f9b2cf0d4ab22c0e9f0c7e5"
|
||||
val image =
|
||||
MediaUrlImage(
|
||||
url = "https://cdn.example.com/foo.jpg",
|
||||
url = "https://cdn.example.com/$sha.jpg",
|
||||
hash = sha,
|
||||
authorPubKey = authorPub,
|
||||
)
|
||||
@@ -122,7 +123,7 @@ class MediaUrlContentExtTest {
|
||||
fun invalidAuthorPubKeyDropped() {
|
||||
val image =
|
||||
MediaUrlImage(
|
||||
url = "https://cdn.example.com/foo.jpg",
|
||||
url = "https://cdn.example.com/$sha.jpg",
|
||||
hash = sha,
|
||||
authorPubKey = "not-a-pubkey",
|
||||
)
|
||||
@@ -130,6 +131,24 @@ class MediaUrlContentExtTest {
|
||||
assertEquals("blossom:$sha.jpg?xs=https://cdn.example.com", result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bridgeOnSkipsNonBud01UrlEvenWithImetaHash() {
|
||||
// The imeta `x` hash refers to a blob whose canonical Blossom location
|
||||
// is /<sha>.<ext>, but the upstream URL serves it under a different
|
||||
// path (https://i.nostr.build/M5AwJ.gif). Routing this through the
|
||||
// local cache would set xs=https://i.nostr.build, and the cache would
|
||||
// fetch https://i.nostr.build/<sha>.gif on miss, which 404s.
|
||||
val url = "https://i.nostr.build/M5AwJ.gif"
|
||||
val image = MediaUrlImage(url = url, hash = sha)
|
||||
assertEquals(url, image.toCoilModel(useLocalBlossomBridge = true))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bridgeProfilePictureUrlSkipsNonBud01UrlEvenWithImetaHash() {
|
||||
val url = "https://i.nostr.build/M5AwJ.gif"
|
||||
assertEquals(url, bridgeProfilePictureUrl(url, useBridge = true))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bridgeProfilePictureUrlNullReturnsNull() {
|
||||
assertEquals(null, bridgeProfilePictureUrl(null, useBridge = true))
|
||||
|
||||
Reference in New Issue
Block a user