# URL Parser

> Split a URL into host, port, path segments and ordered query parameters, exactly as browsers do.

Category: Inspect & Identify · Page: https://util.caruana.me/tools/url-parse/

## API, MCP and A2A

### Parse a URL (`url_parse`)

`GET|POST https://util.caruana.me/api/url/parse` · MCP tool `url_parse` · A2A skill `url_parse` · [reference](https://util.caruana.me/reference/url_parse/)

Parses a URL exactly as browsers and Cloudflare Workers do (WHATWG URL Standard) and returns every component: scheme, username, host in ASCII and Unicode forms, port and default port, origin, path segments decoded, query parameters as an ordered list (duplicates kept), and fragment. It classifies the host (domain, IPv4, IPv6, localhost, private or other special-purpose range) without resolving DNS. A password, if present, is never returned: `hasPassword` is true and `href` shows `***` instead. With `base`, a relative reference is resolved first.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `input` | string (≤ 16384 chars) | yes | The URL, absolute (`https://…`), or relative (`../a?b=1`) when `base` is given. |
| `base` | string (≤ 16384 chars) | no | Absolute base URL for resolving a relative `input` (RFC 3986 §5 as refined by WHATWG). Ignored when `input` is absolute. |

Privacy: The server receives the URL (and base), parses it in memory, and returns its parts. Your input is not stored or logged; only the per-call metadata row (operation, time, status, sizes) is recorded. Passwords in the URL are never echoed back: they are replaced by `***`. The server does not resolve the host or contact it.


## User guide

The tool sits in a window that works like a desktop app: menus on the top row and the **full-screen** icon at its right (**Esc** leaves full screen). The set-up options described below are in **Settings → Options…**, an overlay, and the **Settings** menu also lists the common choices directly. To keep the options in view instead, choose **View → Inline config**; the page remembers that choice. **Help → Keyboard shortcuts…** lists the keys.

1. **Paste a URL.** Leading and trailing spaces are tolerated, as browsers tolerate them.
2. To resolve a **relative reference** such as `../img/a.png` or `?page=2`, also enter the **base URL** it appears on.
3. Read the cards:
   - **Overview:** the normalized URL, scheme, host, port and origin.
   - **Path segments**, percent-decoded.
   - **Query parameters**, in order, with repeated names kept.
   - **Notes:** anything the parser silently changed or that deserves attention.

**Why use a parser at all?** URLs have many edge cases that regular expressions and by-eye reading get wrong:

- Host names are case-insensitive, and default ports disappear (`https://Example.COM:443/` is `https://example.com/`).
- `http://0x7f.1/` is `127.0.0.1`.
- Backslashes count as slashes in `http:` URLs.
- `+` means a space in a query string but not in a path.

This tool shows you what a browser, Node.js or a Cloudflare Worker will actually see.

**Passwords are never shown.** If the URL contains `user:password@`, the password is replaced by `***` in the output and reported only as _present_. Credentials in URLs end up in logs, browser history and `Referer` headers. Remove them.

## Specification

**Parser.** The [WHATWG URL Standard](https://url.spec.whatwg.org/), via the runtime's `URL` class. The API runs it in Cloudflare Workers (V8) and the page runs it in your browser. Results match whenever both implement the current standard.

**Normalization performed by the parser** (reported in `warnings` when it changes the input):

- **Stripping:** leading and trailing C0 control characters and spaces are removed, and so are tab and newline characters anywhere.
- **Special schemes** (`http`, `https`, `ws`, `wss`, `ftp`, `file`): the scheme and host are lowercased, a default port is dropped, `\` is treated as `/`, and `.` and `..` path segments are resolved.
- **IPv4:** hosts written in hex, octal or shorthand (`0x7f.1`, `2130706433`) become dotted decimal.
- **IDN:** internationalized host names become Punycode (`münchen.de` → `xn--mnchen-3ya.de`), per UTS #46.
- **Percent-encoding:** characters outside the allowed sets are percent-encoded. Existing escapes are kept as written, even invalid ones such as `%zz`.

**Components.**

| Field                                  | Meaning                                                                                                                     |
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `href`                                 | Normalized URL; a password is replaced by `***` and `hrefRedacted` is true                                                  |
| `username`                             | `raw` (as serialized) and `decoded` (percent-decoded UTF-8, or null if invalid)                                             |
| `hasPassword`                          | Whether a non-empty password was present. The password itself is never returned                                             |
| `hostname` / `hostnameUnicode`         | ASCII form, and the same with xn-- labels decoded                                                                           |
| `hostType`                             | `domain`, `ipv4`, `ipv6`, `opaque` (host of a non-special scheme), `empty` (`file:///…`) or `none` (`mailto:`, `data:`)     |
| `port`, `defaultPort`, `effectivePort` | `port` is null when absent or equal to the default; `effectivePort` is `port ?? defaultPort`                                |
| `origin`                               | `scheme://host[:port]`, or null for opaque origins                                                                          |
| `pathSegments`                         | The path split on `/` after the leading slash; `raw` and `decoded` per segment. Empty for opaque paths such as `mailto:a@b` |
| `query`                                | Ordered `{name, value, rawName, rawValue}`; see below                                                                       |
| `fragment`                             | The part after `#`, raw and decoded                                                                                         |

**Query parameters** are parsed as `application/x-www-form-urlencoded` (WHATWG URL §5.1), the same rules as `URLSearchParams`:

- **Splitting:** on `&`; empty pieces are skipped, and the first `=` separates name and value.
- **Decoding:** `+` becomes a space, and `%XX` escapes are decoded as UTF-8. Invalid UTF-8 becomes U+FFFD, and a `%` not followed by two hex digits is kept literally.
- **Order and duplicates:** order is preserved and duplicate names are kept.
- **No `=`:** `value` is the empty string and `rawValue` is null.

**Host classification.** IP hosts are matched against the IANA special-purpose address registries:

| Range                      | IPv4                                                     | IPv6                                                              |
| -------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------- |
| `loopback`                 | 127.0.0.0/8                                              | ::1                                                               |
| `private` / `unique-local` | 10/8, 172.16/12, 192.168/16                              | fc00::/7                                                          |
| `shared` (CGNAT)           | 100.64.0.0/10                                            |                                                                   |
| `link-local`               | 169.254.0.0/16 (includes cloud metadata 169.254.169.254) | fe80::/10                                                         |
| `unspecified`              | 0.0.0.0/8                                                | ::                                                                |
| `documentation`            | 192.0.2/24, 198.51.100/24, 203.0.113/24                  | 2001:db8::/32                                                     |
| `benchmarking`             | 198.18.0.0/15                                            |                                                                   |
| `multicast`                | 224.0.0.0/4                                              | ff00::/8                                                          |
| `broadcast` / `reserved`   | 255.255.255.255; 240/4, 192.0.0/24                       | other non-global space                                            |
| `ipv4-mapped`, `nat64`     |                                                          | ::ffff:0:0/96, 64:ff9b::/96 (the embedded IPv4 is classified too) |
| `site-local`               |                                                          | fec0::/10 (deprecated)                                            |
| `public`                   | everything else                                          | 2000::/3 outside the ranges above                                 |

**Flags.**

- **`isLocalhost`:** `localhost` or `*.localhost` (RFC 6761), or any loopback address.
- **`isPrivate`:** RFC 1918, shared (100.64/10), unique-local or site-local, including IPv4-mapped forms.
- **`isPublic`:** true only for a public IP, false for special-purpose IPs and localhost, and null for domain names, which are not resolved.

> **Not an SSRF filter on its own.** A domain name can resolve to a private address, and DNS answers can change between check and use. Validate the resolved address at connection time.

**Relative references.** With `base`, the input is resolved against it (RFC 3986 §5 as refined by WHATWG). `resolvedAgainstBase` is false when the input was already absolute, in which case `base` is ignored.

**Errors.** An invalid URL is reported with a likely cause: a missing scheme, a forbidden host character, a bad port, or a malformed IPv6 literal. The input is not echoed back, so a password in a malformed URL is not repeated either.

**Limits.** API `input` and `base`: at most 16,384 characters each.
