Merge pull request #3035 from greenart7c3/claude/epic-newton-OZLCC

Use URL SHA for Blossom bridge, not imeta hash
This commit is contained in:
Vitor Pamplona
2026-05-22 14:53:00 -04:00
committed by GitHub
2 changed files with 22 additions and 13 deletions
@@ -42,7 +42,6 @@ fun MediaUrlContent.toCoilModel(useLocalBlossomBridge: Boolean): String =
bridgeUrl(
url = url,
useBridge = useLocalBlossomBridge,
explicitHash = hash,
mimeType = mimeType,
authorPubKey = authorPubKey,
skipBridge = this is MediaUrlVideo && isLiveStream,
@@ -95,7 +94,6 @@ const val DEFAULT_LOCAL_CACHE_BASE = "http://127.0.0.1:24242"
private fun bridgeUrl(
url: String,
useBridge: Boolean,
explicitHash: String?,
mimeType: String?,
authorPubKey: String?,
skipBridge: Boolean,
@@ -105,19 +103,15 @@ private fun bridgeUrl(
if (!url.startsWith("http://", ignoreCase = true) && !url.startsWith("https://", ignoreCase = true)) 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
// which only works when the upstream URL is itself BUD-01 layout — the
// file at `<xs>/<sha>.<ext>` is the one named in the URL path, not the
// imeta `x` hash (which on resizing CDNs may identify a different blob
// than the URL filename, e.g. `x` = post-resize, `ox` = original).
// Always use the URL's sha; never trust the imeta override.
val sha = extractSha256FromUrlPath(url) ?: return url
val ext = guessExtension(url, mimeType)
val serverBase = extractServerBase(url, urlSha) ?: return url
val serverBase = extractServerBase(url, sha) ?: return url
val authors =
authorPubKey
@@ -255,4 +255,19 @@ class MediaUrlContentExtTest {
val image = MediaUrlImage(url = url, hash = null)
assertEquals(url, image.toCoilModel(useLocalBlossomBridge = true))
}
@Test
fun bridgeOnUsesUrlShaNotImetaWhenTheyDiffer() {
// On resizing CDNs the imeta `x` (post-resize) can differ from the
// `ox` (original) embedded in the URL. The upstream file is named
// after the URL's sha, so the cache request must use that — using
// the imeta `x` would point xs= at a non-existent path on miss.
val urlSha = "f24026b7281e598973a775adefb1b9a13b9f037a94ac98dd48ccc91b83f4b7b3"
val imetaX = "6932a918de1bfae3bf6611794ff54dd677013d22b760a9212117a0bd9079badf"
val image = MediaUrlImage(url = "https://image.nostr.build/$urlSha.png", hash = imetaX)
assertEquals(
"blossom:$urlSha.png?xs=https://image.nostr.build",
image.toCoilModel(useLocalBlossomBridge = true),
)
}
}