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("blossom:", ignoreCase = true)) return url
|
||||||
if (!url.startsWith("http://", ignoreCase = true) && !url.startsWith("https://", ignoreCase = true)) return url
|
if (!url.startsWith("http://", ignoreCase = true) && !url.startsWith("https://", ignoreCase = true)) return url
|
||||||
|
|
||||||
val sha =
|
// The local Blossom cache fetches `<xs>/<sha>.<ext>` on miss per BUD-01,
|
||||||
explicitHash?.lowercase()?.takeIf { sha256HexRegex.matches(it) }
|
// which only works when the upstream URL is itself BUD-01 layout. For
|
||||||
?: extractSha256FromUrlPath(url)
|
// non-BUD-01 URLs (e.g. https://i.nostr.build/M5AwJ.gif) the imeta `x`
|
||||||
?: return url
|
// 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 ext = guessExtension(url, mimeType)
|
||||||
val serverBase = extractServerBase(url, sha) ?: return url
|
val serverBase = extractServerBase(url, urlSha) ?: return url
|
||||||
|
|
||||||
val authors =
|
val authors =
|
||||||
authorPubKey
|
authorPubKey
|
||||||
|
|||||||
+24
-5
@@ -80,14 +80,15 @@ class MediaUrlContentExtTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun bridgeOnInfersExtensionFromMimeType() {
|
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)
|
val result = image.toCoilModel(useLocalBlossomBridge = true)
|
||||||
assertTrue(result.startsWith("blossom:$sha.png?xs="), "expected png extension from mime, got $result")
|
assertTrue(result.startsWith("blossom:$sha.png?xs="), "expected png extension from mime, got $result")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun bridgeOnFallsBackToBinExtension() {
|
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)
|
val result = image.toCoilModel(useLocalBlossomBridge = true)
|
||||||
assertTrue(result.startsWith("blossom:$sha.bin?xs="), "expected bin extension fallback, got $result")
|
assertTrue(result.startsWith("blossom:$sha.bin?xs="), "expected bin extension fallback, got $result")
|
||||||
}
|
}
|
||||||
@@ -100,7 +101,7 @@ class MediaUrlContentExtTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun uppercaseHashNormalisedToLowercase() {
|
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)
|
val result = image.toCoilModel(useLocalBlossomBridge = true)
|
||||||
assertTrue(result.startsWith("blossom:$sha.jpg?xs="), "expected lowercase sha, got $result")
|
assertTrue(result.startsWith("blossom:$sha.jpg?xs="), "expected lowercase sha, got $result")
|
||||||
}
|
}
|
||||||
@@ -110,7 +111,7 @@ class MediaUrlContentExtTest {
|
|||||||
val authorPub = "a8f3721a0dc1b4d5c12f4cc7c54ae14071eb9c1b4f9b2cf0d4ab22c0e9f0c7e5"
|
val authorPub = "a8f3721a0dc1b4d5c12f4cc7c54ae14071eb9c1b4f9b2cf0d4ab22c0e9f0c7e5"
|
||||||
val image =
|
val image =
|
||||||
MediaUrlImage(
|
MediaUrlImage(
|
||||||
url = "https://cdn.example.com/foo.jpg",
|
url = "https://cdn.example.com/$sha.jpg",
|
||||||
hash = sha,
|
hash = sha,
|
||||||
authorPubKey = authorPub,
|
authorPubKey = authorPub,
|
||||||
)
|
)
|
||||||
@@ -122,7 +123,7 @@ class MediaUrlContentExtTest {
|
|||||||
fun invalidAuthorPubKeyDropped() {
|
fun invalidAuthorPubKeyDropped() {
|
||||||
val image =
|
val image =
|
||||||
MediaUrlImage(
|
MediaUrlImage(
|
||||||
url = "https://cdn.example.com/foo.jpg",
|
url = "https://cdn.example.com/$sha.jpg",
|
||||||
hash = sha,
|
hash = sha,
|
||||||
authorPubKey = "not-a-pubkey",
|
authorPubKey = "not-a-pubkey",
|
||||||
)
|
)
|
||||||
@@ -130,6 +131,24 @@ class MediaUrlContentExtTest {
|
|||||||
assertEquals("blossom:$sha.jpg?xs=https://cdn.example.com", result)
|
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
|
@Test
|
||||||
fun bridgeProfilePictureUrlNullReturnsNull() {
|
fun bridgeProfilePictureUrlNullReturnsNull() {
|
||||||
assertEquals(null, bridgeProfilePictureUrl(null, useBridge = true))
|
assertEquals(null, bridgeProfilePictureUrl(null, useBridge = true))
|
||||||
|
|||||||
Reference in New Issue
Block a user