Type any URL. Hrefine parses it live in this page, with the same
JavaScript build its tests run on, and your browser's own
URL parses it beside it.
These fields can't hold tabs or newlines, so ␉ and ␊ stand in for them; elsewhere they show as \t and \n.
Hrefine live
Your browser's URLlive
Parts
Part
Hrefine
Your browser
What's wrong with it
checkUrl gives the standard's validation errors,
each with where it is in what you typed, in code points: one,
or a whole host or port. Browsers accept most
of these URLs anyway; the last error, if parsing fails, says
why.
Other parsers
Other languages' parsers can't run in your browser. For the
gallery's URLs and the more disagreements below, this page
shows their answers, recorded
ahead of time. For anything else, only Hrefine and your browser
run live.
The trick gallery
Each of these reads one way to a browser and another to some
parser on a server. Differences like these are a known source of
server-side request forgery (SSRF), open redirects, and allowlist
bypasses, shown at length in Orange Tsai's well-known 2017 talk,
“A New Era of SSRF,” on URL parsers in popular languages. Answers recorded 2026-10-30; see
versions.
✓ same as the browser✗ differs from the browser– rejects it
Short addresses
http://127.1/
Browsers read IPv4 addresses with missing parts, as the old inet_aton did, so 127.1 is 127.0.0.1. A parser that keeps the text lets it past a blocklist of 127.0.0.1, and the system's resolver usually connects to loopback anyway.
4 parsers differ from the browser.
Every parser's answer
Parser
Host
Compared
Reads the URL as
Browsers
127.0.0.1
http://127.0.0.1/
Hrefine
127.0.0.1
✓ same as the browser
http://127.0.0.1/
Python urllib
127.1
✗ differs from the browser
http://127.1/
Java URI
no host
✗ differs from the browser
http://127.1/
Java URL
127.1
✗ differs from the browser
http://127.1/
C# Uri
127.0.0.1
✓ same as the browser
http://127.0.0.1/
Go net/url
127.1
✗ differs from the browser
http://127.1/
curl
127.0.0.1
✓ same as the browser
http://127.0.0.1/
Hex addresses
http://0x7f.1/
Parts in hex (or octal, with a leading 0) are numbers too. A check that looks for internal addresses as text misses them; Hrefine says the host is an IPv4 address, whatever it looks like.
4 parsers differ from the browser.
Every parser's answer
Parser
Host
Compared
Reads the URL as
Browsers
127.0.0.1
http://127.0.0.1/
Hrefine
127.0.0.1
✓ same as the browser
http://127.0.0.1/
Python urllib
0x7f.1
✗ differs from the browser
http://0x7f.1/
Java URI
no host
✗ differs from the browser
http://0x7f.1/
Java URL
0x7f.1
✗ differs from the browser
http://0x7f.1/
C# Uri
127.0.0.1
✓ same as the browser
http://127.0.0.1/
Go net/url
0x7f.1
✗ differs from the browser
http://0x7f.1/
curl
127.0.0.1
✓ same as the browser
http://127.0.0.1/
One big number
http://2130706433/
A whole address as one decimal number, 127 × 2²⁴ + 1. Python, Java, and Go keep it as text, and the browser reads loopback.
4 parsers differ from the browser.
Every parser's answer
Parser
Host
Compared
Reads the URL as
Browsers
127.0.0.1
http://127.0.0.1/
Hrefine
127.0.0.1
✓ same as the browser
http://127.0.0.1/
Python urllib
2130706433
✗ differs from the browser
http://2130706433/
Java URI
2130706433
✗ differs from the browser
http://2130706433/
Java URL
2130706433
✗ differs from the browser
http://2130706433/
C# Uri
127.0.0.1
✓ same as the browser
http://127.0.0.1/
Go net/url
2130706433
✗ differs from the browser
http://2130706433/
curl
127.0.0.1
✓ same as the browser
http://127.0.0.1/
Backslashes
https:\\evil.example\path
Browsers treat \ as / in http and https URLs. Python and Go find no host here at all, so a host check has nothing to stop, and the browser goes to evil.example.
3 parsers differ from the browser, and 3 reject it.
Every parser's answer
Parser
Host
Compared
Reads the URL as
Browsers
evil.example
https://evil.example/path
Hrefine
evil.example
✓ same as the browser
https://evil.example/path
Python urllib
no host
✗ differs from the browser
https:///\\evil.example\path
Java URI
error
– rejects it
URISyntaxException: Illegal character in opaque part at index 6: https:\\evil.example\path
Java URL
empty host
✗ differs from the browser
https:\\evil.example\path
C# Uri
error
– rejects it
UriFormatException: Invalid URI: The hostname could not be parsed.
Go net/url
no host
✗ differs from the browser
https:\\evil.example\path
curl
error
– rejects it
Unsupported number of slashes following scheme
Open redirects
/\evil.example
against https://good.example/
A server checks that a next parameter is a path on its own site, and Python, Java's URL, C#, Go, and curl all say it is. The browser, resolving it against the page, goes to evil.example.
5 parsers differ from the browser, and 1 rejects it.
Every parser's answer
Parser
Host
Compared
Reads the URL as
Browsers
evil.example
https://evil.example/
Hrefine
evil.example
✓ same as the browser
https://evil.example/
Python urllib
good.example
✗ differs from the browser
https://good.example/\evil.example
Java URI
error
– rejects it
URISyntaxException: Illegal character in path at index 1: /\evil.example
Java URL
good.example
✗ differs from the browser
https://good.example/\evil.example
C# Uri
good.example
✗ differs from the browser
https://good.example//evil.example
Go net/url
good.example
✗ differs from the browser
https://good.example/%5Cevil.example
curl
good.example
✗ differs from the browser
https://good.example/\evil.example
Who's the host?
https://good.example@evil.example/
Everything before @ is a user name, so the host is evil.example. Parsers agree; the trap is people, and checks like startsWith("https://good.example").
Every parser here agrees. The trap is checking the text.
Every parser's answer
Parser
Host
Compared
Reads the URL as
Browsers
evil.example
https://good.example@evil.example/
Hrefine
evil.example
✓ same as the browser
https://good.example@evil.example/
Python urllib
evil.example
✓ same as the browser
https://good.example@evil.example/
Java URI
evil.example
✓ same as the browser
https://good.example@evil.example/
Java URL
evil.example
✓ same as the browser
https://good.example@evil.example/
C# Uri
evil.example
✓ same as the browser
https://good.example@evil.example/
Go net/url
evil.example
✓ same as the browser
https://good.example@evil.example/
curl
evil.example
✓ same as the browser
https://good.example@evil.example/
Fragments with @
https://evil.example#@good.example/
Everything after # is the fragment, so the host is evil.example. Parsers agree; a check that takes the text after the last @ reads good.example.
Every parser here agrees. The trap is checking the text.
Every parser's answer
Parser
Host
Compared
Reads the URL as
Browsers
evil.example
https://evil.example/#@good.example/
Hrefine
evil.example
✓ same as the browser
https://evil.example/#@good.example/
Python urllib
evil.example
✓ same as the browser
https://evil.example#@good.example/
Java URI
evil.example
✓ same as the browser
https://evil.example#@good.example/
Java URL
evil.example
✓ same as the browser
https://evil.example#@good.example/
C# Uri
evil.example
✓ same as the browser
https://evil.example/#@good.example/
Go net/url
evil.example
✓ same as the browser
https://evil.example#@good.example/
curl
evil.example
✓ same as the browser
https://evil.example/#@good.example/
Tabs and newlines
https://good.example\t.evil.example/
Browsers drop tabs and newlines anywhere in a URL, so this host ends in .evil.example. Strict parsers refuse it, which is safe only if nothing downstream reads it the browser's way.
5 reject it.
Every parser's answer
Parser
Host
Compared
Reads the URL as
Browsers
good.example.evil.example
https://good.example.evil.example/
Hrefine
good.example.evil.example
✓ same as the browser
https://good.example.evil.example/
Python urllib
good.example.evil.example
✓ same as the browser
https://good.example.evil.example/
Java URI
error
– rejects it
URISyntaxException: Illegal character in authority at index 20: https://good.example .evil.example/
Java URL
error
– rejects it
MalformedURLException: Illegal character found in host: control char (code=9)
C# Uri
error
– rejects it
UriFormatException: Invalid URI: The hostname could not be parsed.
Go net/url
error
– rejects it
parse "https://good.example\t.evil.example/": net/url: invalid control character in URL
curl
error
– rejects it
Malformed input to a URL function
javascript:, hidden
java\tscript:alert(1)
A filter looking for javascript: misses it, but browsers drop the tab and run the script. Check the parsed scheme, never the text.
5 reject it.
Every parser's answer
Parser
Host
Compared
Reads the URL as
Browsers
no host
javascript:alert(1)
Hrefine
no host
✓ same as the browser
javascript:alert(1)
Python urllib
no host
✓ same as the browser
javascript:alert(1)
Java URI
error
– rejects it
URISyntaxException: Illegal character in scheme name at index 4: java script:alert(1)
Java URL
error
– rejects it
MalformedURLException: no protocol: java script:alert(1)
C# Uri
error
– rejects it
UriFormatException: Invalid URI: The URI scheme is not valid.
Go net/url
error
– rejects it
parse "java\tscript:alert(1)": net/url: invalid control character in URL
curl
error
– rejects it
Malformed input to a URL function
Look-alike letters
https://аpple.com/
The first letter is Cyrillic а, so browsers send xn--pple-43d.com, which isn't apple.com. Some parsers keep the Unicode, so a check and a request can see different names. This is the long-known IDN homograph trick.
5 parsers differ from the browser.
Every parser's answer
Parser
Host
Compared
Reads the URL as
Browsers
xn--pple-43d.com
https://xn--pple-43d.com/
Hrefine
xn--pple-43d.com
✓ same as the browser
https://xn--pple-43d.com/
Python urllib
аpple.com
✗ differs from the browser
https://аpple.com/
Java URI
no host
✗ differs from the browser
https://аpple.com/
Java URL
аpple.com
✗ differs from the browser
https://аpple.com/
C# Uri
xn--pple-43d.com
✓ same as the browser
https://аpple.com/
Go net/url
аpple.com
✗ differs from the browser
https://%D0%B0pple.com/
curl
аpple.com
✗ differs from the browser
https://аpple.com/
Look-alike hosts
аррӏе.comshows asxn--80ak6aa92e.com
Every letter is Cyrillic, but each looks Latin, so browsers show the xn-- form instead. Hrefine's display form decides as Chrome does, from Unicode's data.
Other hosts, and why
аррӏе.com→xn--80ak6aa92e.comkept as xn--All Cyrillic, but every letter looks Latin: a whole-script look-alike of apple.com.
аррӏе.рф→аррӏе.рфshown in UnicodeThe same letters under .рф, a Cyrillic top-level domain, where readers expect Cyrillic.
аpple.com→xn--pple-43d.comkept as xn--One Cyrillic а among Latin letters: mixed scripts.
пример.рф→пример.рфshown in UnicodeRussian for “example”, with Cyrillic letters that don't look Latin.
日本語.jp→日本語.jpshown in UnicodeJapanese, in one script.
Anyone can register names like these, so browsers check each label's letters against Unicode's data. Its scripts must go together: one script, or the mixes Chinese, Japanese, and Korean use. And a label whose every letter looks Latin keeps xn-- unless the top-level domain is in its own script. UrlDisplay.addressBar and domainToDisplay follow these rules. Show people that form, but decide, and hand on, with href, which is always ASCII.
Dots that aren't dots
https://good.example。evil.example/
Browsers read the ideographic full stop 。 as a dot, so this is a name under evil.example. Parsers without IDNA see one odd label, and a check for names ending in .evil.example misses it.
5 parsers differ from the browser.
Every parser's answer
Parser
Host
Compared
Reads the URL as
Browsers
good.example.evil.example
https://good.example.evil.example/
Hrefine
good.example.evil.example
✓ same as the browser
https://good.example.evil.example/
Python urllib
good.example。evil.example
✗ differs from the browser
https://good.example。evil.example/
Java URI
no host
✗ differs from the browser
https://good.example。evil.example/
Java URL
good.example。evil.example
✗ differs from the browser
https://good.example。evil.example/
C# Uri
good.example.evil.example
✓ same as the browser
https://good.example。evil.example/
Go net/url
good.example。evil.example
✗ differs from the browser
https://good.example%E3%80%82evil.example/
curl
good.example。evil.example
✗ differs from the browser
https://good.example。evil.example/
Protocol-relative
//evil.example/
against https://good.example/
It starts with /, so it looks like a path on your own site, but // starts a host. Every parser agrees; the trap is checks like startsWith("/").
Every parser here agrees. The trap is checking the text.
Every parser's answer
Parser
Host
Compared
Reads the URL as
Browsers
evil.example
https://evil.example/
Hrefine
evil.example
✓ same as the browser
https://evil.example/
Python urllib
evil.example
✓ same as the browser
https://evil.example/
Java URI
evil.example
✓ same as the browser
https://evil.example/
Java URL
evil.example
✓ same as the browser
https://evil.example/
C# Uri
evil.example
✓ same as the browser
https://evil.example/
Go net/url
evil.example
✓ same as the browser
https://evil.example/
curl
evil.example
✓ same as the browser
https://evil.example/
Too many slashes
https:///evil.example/
Browsers and curl skip extra slashes and find evil.example. Python, Java, and Go see no host, and C# refuses it.
4 parsers differ from the browser, and 1 rejects it.
Every parser's answer
Parser
Host
Compared
Reads the URL as
Browsers
evil.example
https://evil.example/
Hrefine
evil.example
✓ same as the browser
https://evil.example/
Python urllib
no host
✗ differs from the browser
https:///evil.example/
Java URI
no host
✗ differs from the browser
https:///evil.example/
Java URL
empty host
✗ differs from the browser
https:/evil.example/
C# Uri
error
– rejects it
UriFormatException: Invalid URI: The hostname could not be parsed.
Go net/url
empty host
✗ differs from the browser
https:///evil.example/
curl
evil.example
✓ same as the browser
https://evil.example/
48 more disagreements
Smaller tricks, by kind, many from web-platform-tests' cases and
from published research on parser differences. Each mark says
whether that parser reads the same host as browsers, or for
paths, the same path. Pick an input to try it, with every
parser's full answer.
Input
Browsers read
Python urllib
Java URI
Java URL
C# Uri
Go net/url
curl
Backslashes, whitespace, and controls
Browsers end the host at \; Python keeps it in the host.
host good.example
✗
–
–
–
–
–
against https://good.example/Two backslashes start a host, as // does.
host evil.example
✗
–
✗
✓
✗
✗
against https://good.example/The tab goes, leaving //evil.example: an open redirect past a check for //.
host evil.example
✓
–
✗
✗
–
–
One slash and one backslash still start the host.
host evil.example
✗
–
✗
✓
✗
–
Browsers trim leading control characters, so this runs as javascript:.
host no host
✓
–
–
–
–
–
An escaped tab in a host is an error to browsers; Python and Java keep it as text.