From af637de3ccaa77ffac96c884f48125c4cbfba739 Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Mon, 20 Jul 2026 22:12:05 +0300 Subject: [PATCH 1/3] docs: add a Why kuri? section to the README Add a section after the intro explaining the motivation and value: URL parsing is subtle and the details ad-hoc parsers miss (percent-encoding, IDNA, dot-segment resolution, port elision) are the ones that cause interop and security bugs; RFC 3986 and the WHATWG URL Standard genuinely differ; the platform primitives (java.net.URI/URL) are lenient, quirky, and JVM-only, leaving Kotlin Multiplatform without a standards-faithful, dependency-free option. States kuri's priorities in order: measured correctness, two explicit models, safety on untrusted input, and one API across every target. --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index 6d796a9..d1e92ff 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,36 @@ Standard, and UTS #46 for internationalized hosts. Parsing returns a result inst immutable, and the whole API reads naturally from both Kotlin and Java. It runs everywhere Kotlin does — JVM, Android, JS, Wasm, and native. +## Why kuri? + +URL parsing looks trivial and almost never is. RFC 3986 (generic URIs) and the WHATWG URL Standard (web URLs) +are separate specifications with genuinely different rules for validity, host handling, and normalization — +and the details that ad-hoc parsers gloss over are exactly the hard ones: percent-encoding, IDNA/UTS-46 host +processing, dot-segment resolution, default-port elision, IPv4 shorthand. When two components in a system +disagree about what a URL means, the result is interop breakage and a well-known class of security bugs — +origin confusion, SSRF filter bypasses, cache-key mismatches. A regex or a `split('/')` will not get this +right, and neither will a parser that quietly picks one interpretation for you. + +The platform primitives don't close the gap. `java.net.URI` follows the older RFC 2396 and is lenient about +validation; `java.net.URL` predates the WHATWG model and carries surprising behavior (its `equals` can make a +DNS query); and neither exists off the JVM. Kotlin Multiplatform had no standards-faithful, dependency-free +URL library that behaves identically across JVM, Android, JS, Wasm, and native — so a shared-code project +could not parse a URL the same way on every target. + +kuri is built to be that library. Its priorities, in order: + +- **Correctness, measured.** Behavior is verified against the standards' own conformance corpora — WHATWG + `urltestdata.json`, Unicode `IdnaTestV2`, `NormalizationTest.txt` — with a ratcheting baseline so a passing + case can never silently regress. +- **The right semantics, chosen explicitly.** Two models — `Uri` (RFC 3986, preserve-and-normalize) and + `Url` (WHATWG, canonicalize-eagerly) — so you opt into the rules you want instead of trusting one type to + guess which specification applies. +- **Safe on untrusted input.** Parsing yields errors as values rather than exceptions, and every parse is + bounded by configurable resource limits, so hostile input fails cleanly instead of exhausting memory or the + stack. +- **One API, every target.** The full surface lives in common Kotlin, reads idiomatically from Java, and adds + no runtime dependencies beyond the standard library. + ## Installation ```kotlin From 552d44887a68d8f3c320cdcce78aa9eb3c33c639 Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Mon, 20 Jul 2026 22:18:51 +0300 Subject: [PATCH 2/3] docs: tighten the Why kuri? section to a single paragraph Cut the Why section from three paragraphs plus a list down to one paragraph. Lead with the concrete stakes: parser disagreement turns into SSRF bypasses, origin confusion, and poisoned cache keys, and the JVM built-ins are lenient, quirky (URL.equals can hit DNS), and JVM-only. Keep the argument specific and readable rather than exhaustive. --- README.md | 35 ++++++++--------------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index d1e92ff..9ad3c14 100644 --- a/README.md +++ b/README.md @@ -23,33 +23,14 @@ Android, JS, Wasm, and native. ## Why kuri? -URL parsing looks trivial and almost never is. RFC 3986 (generic URIs) and the WHATWG URL Standard (web URLs) -are separate specifications with genuinely different rules for validity, host handling, and normalization — -and the details that ad-hoc parsers gloss over are exactly the hard ones: percent-encoding, IDNA/UTS-46 host -processing, dot-segment resolution, default-port elision, IPv4 shorthand. When two components in a system -disagree about what a URL means, the result is interop breakage and a well-known class of security bugs — -origin confusion, SSRF filter bypasses, cache-key mismatches. A regex or a `split('/')` will not get this -right, and neither will a parser that quietly picks one interpretation for you. - -The platform primitives don't close the gap. `java.net.URI` follows the older RFC 2396 and is lenient about -validation; `java.net.URL` predates the WHATWG model and carries surprising behavior (its `equals` can make a -DNS query); and neither exists off the JVM. Kotlin Multiplatform had no standards-faithful, dependency-free -URL library that behaves identically across JVM, Android, JS, Wasm, and native — so a shared-code project -could not parse a URL the same way on every target. - -kuri is built to be that library. Its priorities, in order: - -- **Correctness, measured.** Behavior is verified against the standards' own conformance corpora — WHATWG - `urltestdata.json`, Unicode `IdnaTestV2`, `NormalizationTest.txt` — with a ratcheting baseline so a passing - case can never silently regress. -- **The right semantics, chosen explicitly.** Two models — `Uri` (RFC 3986, preserve-and-normalize) and - `Url` (WHATWG, canonicalize-eagerly) — so you opt into the rules you want instead of trusting one type to - guess which specification applies. -- **Safe on untrusted input.** Parsing yields errors as values rather than exceptions, and every parse is - bounded by configurable resource limits, so hostile input fails cleanly instead of exhausting memory or the - stack. -- **One API, every target.** The full surface lives in common Kotlin, reads idiomatically from Java, and adds - no runtime dependencies beyond the standard library. +URL parsing looks trivial and almost never is. RFC 3986 and the WHATWG URL Standard are different specs with +different rules, and the parts most parsers skip (percent-encoding, IDNA hosts, dot-segment resolution, port +elision) are exactly where the bugs hide. When two parts of a system read the same URL differently, that gap +becomes an SSRF filter bypass, an origin mix-up, a poisoned cache key. The JVM's built-ins don't save you: +`java.net.URI` is lenient and predates RFC 3986, `java.net.URL` can make a DNS call just to compare two +values, and neither exists off the JVM. kuri is one parser, checked against the standards' own conformance +suites, that gives the same answer on every Kotlin target, hands you errors as return values instead of +exceptions, and lets you choose which spec's rules apply. ## Installation From fb86a2e95ac2342c0c2758bbff314506b998418a Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Mon, 20 Jul 2026 22:23:13 +0300 Subject: [PATCH 3/3] docs: make Why kuri? a bullet list and add the Kotlin Multiplatform gap Convert the Why section from a paragraph to points, and add the Kotlin Multiplatform angle: the standard library has no URL type, so shared code either drops to a JVM-only parser or reimplements parsing per platform. Tighten the platform-primitive claims to what the JDK actually documents: java.net.URI is specified against the superseded RFC 2396, and java.net.URL.equals() makes a blocking DNS call. --- README.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 9ad3c14..b4a7641 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,17 @@ Android, JS, Wasm, and native. ## Why kuri? -URL parsing looks trivial and almost never is. RFC 3986 and the WHATWG URL Standard are different specs with -different rules, and the parts most parsers skip (percent-encoding, IDNA hosts, dot-segment resolution, port -elision) are exactly where the bugs hide. When two parts of a system read the same URL differently, that gap -becomes an SSRF filter bypass, an origin mix-up, a poisoned cache key. The JVM's built-ins don't save you: -`java.net.URI` is lenient and predates RFC 3986, `java.net.URL` can make a DNS call just to compare two -values, and neither exists off the JVM. kuri is one parser, checked against the standards' own conformance -suites, that gives the same answer on every Kotlin target, hands you errors as return values instead of -exceptions, and lets you choose which spec's rules apply. +- URL parsing looks trivial and almost never is. The parts most parsers skip (percent-encoding, IDNA hosts, + dot-segment resolution, port elision) are where the bugs hide. +- RFC 3986 and the WHATWG URL Standard are different specs with different rules. When two parts of a system + read the same URL differently, that gap becomes an SSRF filter bypass, an origin mix-up, a poisoned cache key. +- The JVM's built-ins don't help: `java.net.URI` is specified against the superseded RFC 2396, `java.net.URL` + barely validates and its `equals()` makes a blocking DNS call to compare two values, and neither exists off + the JVM. +- That leaves a gap on Kotlin Multiplatform: the standard library has no URL type, so shared code either drops + to a JVM-only parser and loses the other targets, or reimplements parsing per platform and drifts. +- kuri closes it: one parser in common Kotlin, checked against the standards' own conformance suites, so you + get the same result on JVM, Android, JS, Wasm, and native, with errors as values and your choice of spec. ## Installation