fix(blossom): preserve upstream path prefix in xs= hint
The bridge was emitting `xs=https://cdn.nostr.build` for URLs like `https://cdn.nostr.build/i/<sha>.jpg`, dropping the `/i/` prefix. The local cache appends `/<sha>` to the xs server hint per spec, so it would try to fetch `https://cdn.nostr.build/<sha>` — a 404 on nostr.build's non-Blossom CDN scheme. Anchor the server-base extraction on the slash immediately preceding the sha-bearing path segment so we emit `xs=https://cdn.nostr.build/i`. The cache then appends `/<sha>` and reaches the original blob. Affects all three bridge entry points: - MediaUrlContent.toCoilModel (note media) - bridgeProfilePictureUrl (profile pictures) - LocalBlossomCacheRedirectInterceptor (everything else via OkHttp) Flat Blossom paths (`<host>/<sha>`) keep emitting `xs=<host>` unchanged.
This commit is contained in:
+23
-7
@@ -61,26 +61,42 @@ class LocalBlossomCacheRedirectInterceptor(
|
||||
val host = url.host
|
||||
if (host == LOCAL_CACHE_HOST || host.equals("localhost", ignoreCase = true)) return null
|
||||
|
||||
val (sha, ext) = findSha256AndExtensionInPath(url) ?: return null
|
||||
val originalHostBase = "${url.scheme}://$host" + if (url.port != HttpUrl.defaultPort(url.scheme)) ":${url.port}" else ""
|
||||
val (shaSegmentIndex, sha, ext) = findSha256AndExtensionInPath(url) ?: return null
|
||||
val serverBase = buildServerBase(url, shaSegmentIndex)
|
||||
|
||||
return "$LOCAL_CACHE_BASE/$sha.$ext"
|
||||
.toHttpUrl()
|
||||
.newBuilder()
|
||||
.addQueryParameter("xs", originalHostBase)
|
||||
.addQueryParameter("xs", serverBase)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun findSha256AndExtensionInPath(url: HttpUrl): Pair<String, String>? {
|
||||
for (segment in url.pathSegments) {
|
||||
val match = SHA256_SEGMENT_REGEX.find(segment) ?: continue
|
||||
private fun findSha256AndExtensionInPath(url: HttpUrl): Triple<Int, String, String>? {
|
||||
url.pathSegments.forEachIndexed { index, segment ->
|
||||
val match = SHA256_SEGMENT_REGEX.find(segment) ?: return@forEachIndexed
|
||||
val sha = match.value.lowercase()
|
||||
val ext = guessExtensionFrom(segment, sha) ?: "bin"
|
||||
return sha to ext
|
||||
return Triple(index, sha, ext)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the URL prefix that the local cache should append `/<sha>` to.
|
||||
* Preserves any path prefix the upstream CDN uses (e.g. `/i` for
|
||||
* `https://cdn.nostr.build/i/<sha>`) so the cache can fetch the blob
|
||||
* from its actual location on miss.
|
||||
*/
|
||||
private fun buildServerBase(
|
||||
url: HttpUrl,
|
||||
shaSegmentIndex: Int,
|
||||
): String {
|
||||
val origin = "${url.scheme}://${url.host}" + if (url.port != HttpUrl.defaultPort(url.scheme)) ":${url.port}" else ""
|
||||
if (shaSegmentIndex == 0) return origin
|
||||
val prefixSegments = url.pathSegments.subList(0, shaSegmentIndex)
|
||||
return "$origin/" + prefixSegments.joinToString("/")
|
||||
}
|
||||
|
||||
private fun guessExtensionFrom(
|
||||
segment: String,
|
||||
sha: String,
|
||||
|
||||
+13
-1
@@ -114,7 +114,19 @@ class LocalBlossomCacheRedirectInterceptorTest {
|
||||
val captured = mutableListOf<String>()
|
||||
val response = interceptor.intercept(fakeChain("https://nostr.build/i/cache/$sha.webp", captured))
|
||||
assertEquals(
|
||||
"http://127.0.0.1:24242/$sha.webp?xs=https%3A%2F%2Fnostr.build",
|
||||
"http://127.0.0.1:24242/$sha.webp?xs=https%3A%2F%2Fnostr.build%2Fi%2Fcache",
|
||||
captured.single(),
|
||||
)
|
||||
response.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun bridgeOnPreservesNostrBuildPathPrefix() {
|
||||
val interceptor = LocalBlossomCacheRedirectInterceptor { true }
|
||||
val captured = mutableListOf<String>()
|
||||
val response = interceptor.intercept(fakeChain("https://cdn.nostr.build/i/$sha.jpg", captured))
|
||||
assertEquals(
|
||||
"http://127.0.0.1:24242/$sha.jpg?xs=https%3A%2F%2Fcdn.nostr.build%2Fi",
|
||||
captured.single(),
|
||||
)
|
||||
response.close()
|
||||
|
||||
Reference in New Issue
Block a user