In HTTP(S) URLs, Rust treats \ as a valid delimiter between the scheme and host, or the host and path, and normalizes it to /.
Rust normalizes fullwidth Unicode characters to their simpler forms. For example, . (full-width dot) is converted to .(normal dot). This does not apply to URL delimiters: a full-width slash (/) is not treated as a valid URL delimiter.
Rust converts many integer-form hosts to dotted IPv4: http://127.1 → 127.0.0.1, http://2130706433 → 127.0.0.1, http://0x7f000001 → 127.0.0.1, http://017700000001 → 127.0.0.1, http://0177.0.0.1 → 127.0.0.1, and http://1234567890 → 73.150.2.210.
Rust does not drop Windows drive segments* while normalizing dot segments, as in file://hello/c:/...
Rust drops any hostname when the path starts with a Windows drive segment*. For example, after normalizing file://hello/c:/.., the host is empty.
Rust converts non-ASCII characters in a hostname to their Punycode equivalents. For example, https://bücher.com/ has the hostname xn--bcher-kva.com.
Rust normalizes dot segments through one level of URL encoding in the path.
01 / LANGUAGE
Go
NOTE: Unlike the other languages tested, Go recognizes two types of URLs: opaque URLs (mailto:hello) and host-based URLs (http://example.com). A URL cannot be both at once. This distinction is the main source of divergence between Go and the other languages tested.
Scheme host segmentation handling:
If there is no / (slash) directly after the : Go will consider the URL as an opaque URL. This is especially interesting because many languages would normalize \ into / and consider evil.com the hostname in http:\example.com, but in Go the hostname is empty. Depending on the application logic, this may trick the server into treating it as an absolute-path URL.
If there is a single / (slash) after :, Go treats it as a host-based URL with an empty hostname. For example, in http:/evil.com, /evil.com is considered the path rather than the hostname.
If there are two slashes after the :, it is handled as expected. For example, in http://example.com, example.com is considered the hostname.
Go allows CR/LF/CRLF, and any other character, in the fragment and query without encoding. This may allow CRLF injection.
Go percent-decodes URL-encoded values in the path, fragment, and query ONLY ONCE, even when values are encoded multiple times. The decoded bytes do not have to be valid UTF-8 strings, which can cause crashes in other languages.
Go URL-encodes non-ASCII characters only in the path.
01 / LANGUAGE
Node
In HTTP(S) URLs, Node also treats \ as a valid delimiter between the scheme and host, or the host and path, and normalizes it to /.
Node normalizes fullwidth Unicode characters to their simpler forms. For example, . is converted to .. This does not apply to URL delimiters: a fullwidth slash (/) is not treated as a valid URL delimiter.
Node converts many integer-form hosts to dotted IPv4: http://127.1 → 127.0.0.1, http://2130706433 → 127.0.0.1, http://0x7f000001 → 127.0.0.1, http://017700000001 → 127.0.0.1, http://0177.0.0.1 → 127.0.0.1, and http://1234567890 → 73.150.2.210.
Node converts non-ASCII characters in a hostname to their Punycode equivalents. For example, https://bücher.com/ has the hostname xn--bcher-kva.com.
In the file scheme, Node specifically drops the localhost hostname when the path starts with [a-zA-Z]:/. For example, the hostname in file://localhost/c:/hello is empty.
For most schemes, Node treats everything as the path if there is no slash (/) or backslash (\) character after the :. For example, in unknown:hello/world or file:hello/world, Node treats hello/world as the path, however with http and https schemes, the first segment is considered the host (host: hello, path: /world).
Node strips default ports for known schemes — not just http/https but also ftp (port 21). http://ex:80/, https://ex:443/ and ftp://ex:21/ all yield port=''. Non-default ports are kept.
Node percent-encodes raw spaces in fragment/query: #frag bar → frag%20bar; ?query bar → query%20bar. Raw CR/LF in the fragment/query are stripped (along with the previous rule, #frag\r\nFoo → fragFoo / frag%20Foo depending on surrounding chars).
Node normalizes dot segments (/./, /../) through one level of URL encoding in the path: /hello/../world → /world, /./a/./b/./c → /a/b/c, /.%2e/ (case mixed) → /, /./../.a → /.a. It also collapses // after a . segment (http://ex/.// → //).
Node escapes backslash in the host boundary: http://ex\ample.com/a → host=ex, path=/ample.com/a. That is, the \ terminates the host the same way / would, and the remainder is path.
Node treats http:///example (three slashes) as if the empty authority fell through: host=example, path=/.
01 / LANGUAGE
Python
Python treats single-slash URLs (http:/foo) as a scheme and path with an empty host.
Python treats URLs without a slash after the colon (http:foo) as a scheme and path.
Python does not Punycode-encode the host. https://bücher.example/café?q=✓#résumé → host=bücher.example
Python does not apply IDNA's NFKC compatibility normalization to hosts: http://exa,mple.com/ keeps the fullwidth comma raw (host=exa,mple.com).
Python does not normalize dot-segments in the path: /a/../.. stays /a/../.., /.// stays /.//, /a/./b/./c stays as-is, /.%2e/ stays /.%2e/.
Python strips raw CR/LF/TAB silently from the URL while tokenizing http://ex\tam.com/a → host=exam.com. The same is true for fragments or queries containing raw CR/LF.