Code review:
- reject ill-formed localpart dots and IP literals in parse - avoid Pair allocation in Nip05Id.parse
This commit is contained in:
+8
-3
@@ -47,8 +47,9 @@ data class Nip05Id(
|
|||||||
fun toDomainUrl(): String = domainUrl(domain)
|
fun toDomainUrl(): String = domainUrl(domain)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// NIP-05 localpart: a-z, 0-9, -, _, .
|
// NIP-05 localpart: dot-separated atoms of [a-z0-9_-]. Forbids leading,
|
||||||
private val LOCAL_PART_REGEX = Regex("^[a-z0-9._-]+$")
|
// trailing, and consecutive dots (NIP-05 + RFC 5321 local-part rules).
|
||||||
|
private val LOCAL_PART_REGEX = Regex("^[a-z0-9_-]+(\\.[a-z0-9_-]+)*$")
|
||||||
|
|
||||||
// Hostname: dot-separated labels of [a-z0-9-], no leading/trailing hyphen,
|
// Hostname: dot-separated labels of [a-z0-9-], no leading/trailing hyphen,
|
||||||
// require at least one dot so single-label garbage (e.g. "s!ayer") is rejected.
|
// require at least one dot so single-label garbage (e.g. "s!ayer") is rejected.
|
||||||
@@ -58,9 +59,13 @@ data class Nip05Id(
|
|||||||
fun parse(nip05address: String): Nip05Id? {
|
fun parse(nip05address: String): Nip05Id? {
|
||||||
val parts = nip05address.trim().lowercase().split("@")
|
val parts = nip05address.trim().lowercase().split("@")
|
||||||
if (parts.size != 2) return null
|
if (parts.size != 2) return null
|
||||||
val (name, domain) = parts[0] to parts[1]
|
val name = parts[0]
|
||||||
|
val domain = parts[1]
|
||||||
if (!LOCAL_PART_REGEX.matches(name)) return null
|
if (!LOCAL_PART_REGEX.matches(name)) return null
|
||||||
if (!DOMAIN_REGEX.matches(domain)) return null
|
if (!DOMAIN_REGEX.matches(domain)) return null
|
||||||
|
// Reject IP literals — NIP-05 expects a hostname, and a digits-only
|
||||||
|
// TLD is the cheapest way to tell them apart from real domains.
|
||||||
|
if (domain.substringAfterLast('.').all { it.isDigit() }) return null
|
||||||
return Nip05Id(name, domain)
|
return Nip05Id(name, domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+21
@@ -133,6 +133,27 @@ class Nip05Test {
|
|||||||
assertNull(Nip05Id.parse("alice@example-.com"))
|
assertNull(Nip05Id.parse("alice@example-.com"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parse rejects localpart with leading trailing or consecutive dots`() {
|
||||||
|
assertNull(Nip05Id.parse(".alice@example.com"))
|
||||||
|
assertNull(Nip05Id.parse("alice.@example.com"))
|
||||||
|
assertNull(Nip05Id.parse("alice..bob@example.com"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parse rejects IPv4 literal as domain`() {
|
||||||
|
assertNull(Nip05Id.parse("alice@192.168.1.1"))
|
||||||
|
assertNull(Nip05Id.parse("alice@8.8.8.8"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parse accepts wildcard underscore localpart`() {
|
||||||
|
val nip05 = Nip05Id.parse("_@example.com")
|
||||||
|
assertNotNull(nip05)
|
||||||
|
assertEquals("_", nip05.name)
|
||||||
|
assertEquals("example.com", nip05.domain)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `execute assemble url with valid value returns nip05 url`() {
|
fun `execute assemble url with valid value returns nip05 url`() {
|
||||||
// given
|
// given
|
||||||
|
|||||||
Reference in New Issue
Block a user