mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 03:38:23 +00:00
fix(quartz): NIP-62 vanish compares relay urls as NormalizedRelayUrl
RelayTag.shouldVanishFrom compared the tag's url to the relay's normalized url as a string, so a vanish naming `wss://relay.example` never matched the relay whose normalized url is `wss://relay.example/`. The request was accepted with OK true and deleted nothing; the author could republish the "vanished" events. That is the form most clients write. The tag value is now normalized and compared as a NormalizedRelayUrl, with the everywhere marker and an already-normalized tag kept as fast paths. Found by an over-the-wire contract test of a relay (vespa-relay) built on quartz's relay server: `["relay","ws://localhost:7777"]` vanished nothing, `["relay","ws://localhost:7777/"]` worked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rtkpq5k2zy2hGP44kXLtq9
This commit is contained in:
+14
-1
@@ -37,10 +37,23 @@ class RelayTag {
|
||||
|
||||
fun shouldVanishFromEverywhere(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == EVERYWHERE
|
||||
|
||||
/**
|
||||
* Whether this tag asks [relay] to vanish the author. The tag carries whatever url the
|
||||
* client typed, so it is compared as a [NormalizedRelayUrl], not as a string: a
|
||||
* `wss://relay.example` tag must reach the relay whose normalized url is
|
||||
* `wss://relay.example/`. A raw string match quietly accepted the request and deleted
|
||||
* nothing.
|
||||
*/
|
||||
fun shouldVanishFrom(
|
||||
tag: Array<String>,
|
||||
relay: NormalizedRelayUrl,
|
||||
) = tag.has(1) && tag[0] == TAG_NAME && (tag[1] == relay.url || tag[1] == EVERYWHERE)
|
||||
): Boolean {
|
||||
if (!tag.has(1) || tag[0] != TAG_NAME) return false
|
||||
val value = tag[1]
|
||||
// Fast paths first: the everywhere marker, and a tag already in normalized form.
|
||||
if (value == EVERYWHERE || value == relay.url) return true
|
||||
return RelayUrlNormalizer.normalizeOrNull(value) == relay
|
||||
}
|
||||
|
||||
fun parse(tag: Array<String>): NormalizedRelayUrl? {
|
||||
ensure(tag.has(1)) { return null }
|
||||
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip62RequestToVanish.tags
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class RelayTagTest {
|
||||
private val relay = RelayUrlNormalizer.normalize("wss://relay.example.com")
|
||||
|
||||
private fun tag(url: String) = arrayOf(RelayTag.TAG_NAME, url)
|
||||
|
||||
@Test
|
||||
fun matchesTheNormalizedForm() {
|
||||
assertTrue(RelayTag.shouldVanishFrom(tag(relay.url), relay))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun matchesTheFormAClientTypes() {
|
||||
// The form most clients write: no trailing slash. It used to be accepted and ignored.
|
||||
assertTrue(RelayTag.shouldVanishFrom(tag("wss://relay.example.com"), relay))
|
||||
assertTrue(RelayTag.shouldVanishFrom(tag("wss://Relay.Example.com/"), relay))
|
||||
assertTrue(RelayTag.shouldVanishFrom(tag("relay.example.com"), relay))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun matchesALocalRelay() {
|
||||
val local = RelayUrlNormalizer.normalize("ws://localhost:7777")
|
||||
assertTrue(RelayTag.shouldVanishFrom(tag("ws://localhost:7777"), local))
|
||||
assertTrue(RelayTag.shouldVanishFrom(tag("ws://localhost:7777/"), local))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun matchesEverywhere() {
|
||||
assertTrue(RelayTag.shouldVanishFrom(tag(RelayTag.EVERYWHERE), relay))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ignoresOtherRelays() {
|
||||
assertFalse(RelayTag.shouldVanishFrom(tag("wss://other.example.com"), relay))
|
||||
assertFalse(RelayTag.shouldVanishFrom(tag("wss://relay.example.com/other"), relay))
|
||||
assertFalse(RelayTag.shouldVanishFrom(tag("not a url"), relay))
|
||||
assertFalse(RelayTag.shouldVanishFrom(arrayOf("r", relay.url), relay))
|
||||
assertFalse(RelayTag.shouldVanishFrom(arrayOf(RelayTag.TAG_NAME), relay))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun theEventLevelCheckUsesTheSameRule() {
|
||||
val tags = arrayOf(arrayOf("p", "a".repeat(64)), tag("wss://relay.example.com"))
|
||||
assertTrue(tags.shouldVanishFrom(relay))
|
||||
assertFalse(tags.shouldVanishFrom(RelayUrlNormalizer.normalize("wss://other.example.com")))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user