refactor: use Hex.isHex64 instead of a regex for the blob-id check

blossomHashOrNull runs on every media URL the feed loads. Quartz's unrolled
Hex.isHex64 is the fast path for validating a 32-byte hex id (~30% faster
than isHex, far faster than a regex match). It only checks the first 64
chars and doesn't verify length, so keep an explicit length == 64 guard to
reject longer segments.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ao9w26c2gAm4gjJdhgvLyp
This commit is contained in:
Claude
2026-07-29 00:35:53 +00:00
parent 7d1c45607b
commit defb898987
2 changed files with 11 additions and 6 deletions
@@ -21,6 +21,7 @@
package com.vitorpamplona.amethyst.service.okhttp
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.utils.Hex
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
@@ -112,18 +113,20 @@ class BlossomReadAuthInterceptor(
.build()
companion object {
private val SHA256_HEX = Regex("^[0-9a-f]{64}$")
/**
* Extracts the sha256 blob id from a Blossom URL path. The blob is the
* last path segment, up to its first `.` — so both `<hash>.png` and the
* derived `<hash>.thumb.jpg` resolve to `<hash>`. Returns `null` when the
* segment isn't a lowercase 64-char hex string.
* segment isn't a 64-char hex string.
*
* Uses Quartz's unrolled [Hex.isHex64] rather than a regex — this runs on
* every media URL the feed loads. [Hex.isHex64] only checks the first 64
* chars and doesn't verify total length, so the `length == 64` guard is
* what rejects longer segments.
*/
fun blossomHashOrNull(encodedPath: String): HexKey? {
val lastSegment = encodedPath.substringAfterLast('/')
val base = lastSegment.substringBefore('.').lowercase()
return if (SHA256_HEX.matches(base)) base else null
val base = encodedPath.substringAfterLast('/').substringBefore('.').lowercase()
return if (base.length == 64 && Hex.isHex64(base)) base else null
}
}
}
@@ -66,6 +66,8 @@ class BlossomReadAuthInterceptorTest {
assertNull(BlossomReadAuthInterceptor.blossomHashOrNull("/media/avatar.png"))
assertNull(BlossomReadAuthInterceptor.blossomHashOrNull("/media/nostr.build_$sha.jpg"))
assertNull(BlossomReadAuthInterceptor.blossomHashOrNull("/media/${sha}_thumb.jpg"))
// 65 hex chars: isHex64 checks only the first 64, so the length guard must reject it.
assertNull(BlossomReadAuthInterceptor.blossomHashOrNull("/media/${sha}a.png"))
assertNull(BlossomReadAuthInterceptor.blossomHashOrNull("/"))
}