From defb89898785587a43a1acd03fc53d705e209973 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 29 Jul 2026 00:35:53 +0000 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_01Ao9w26c2gAm4gjJdhgvLyp --- .../service/okhttp/BlossomReadAuthInterceptor.kt | 15 +++++++++------ .../okhttp/BlossomReadAuthInterceptorTest.kt | 2 ++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/BlossomReadAuthInterceptor.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/BlossomReadAuthInterceptor.kt index 78aa21c415..8356ca7e55 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/BlossomReadAuthInterceptor.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/okhttp/BlossomReadAuthInterceptor.kt @@ -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 `.png` and the * derived `.thumb.jpg` resolve to ``. 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 } } } diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/okhttp/BlossomReadAuthInterceptorTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/okhttp/BlossomReadAuthInterceptorTest.kt index e3ea88bf47..233df8338b 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/okhttp/BlossomReadAuthInterceptorTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/okhttp/BlossomReadAuthInterceptorTest.kt @@ -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("/")) }