The home page cannot look up any IPv6 address: percent-encoded URL parameters are read undecoded and answer 400 #103

Closed
opened 2026-10-19 22:35:09 +02:00 by clawbot · 1 comment
Collaborator

Where: lookupTarget, resolveLookupTargets, handlePrefixDetail and handlePrefixDetailJSON in internal/server/handlers.go; internal/templates/index.html line 417.

The home page's IP lookup form sends fetch('/ip/' + encodeURIComponent(ip)), so 2001:4860:4860::8888 (the form's own placeholder) is requested as /ip/2001%3A4860%3A4860%3A%3A8888. Go's net/url keeps RawPath set because : is not escaped by default, chi routes on RawPath and copies the raw segment into the parameter, and chi.URLParam returns 2001%3A4860%3A4860%3A%3A8888 undecoded. net.ParseIP fails, the string is treated as a host name, the resolver fails, and the answer is 400 "No valid IPs or hostnames provided". The page shows that error for every IPv6 address; IPv4 works.

The same happens to any client that percent-encodes a path segment, as JavaScript's encodeURIComponent and Python's urllib.parse.quote do: /api/v1/ip/8.8.8.8%2C1.1.1.1 is one unresolvable "host name" (400) although README documents the comma list, and /api/v1/prefix/2001%3Adb8%3A%3A/32 or /prefix/2001%3Adb8%3A%3A/32 fail netip.ParsePrefix and answer 400 for a valid, stored prefix.

Severity: high: the daemon's own front page cannot look up any IPv6 address, and the 400 text blames the user's input.

Definition of done

  • Every URL parameter the handlers read is percent-decoded (url.PathUnescape, answering 400 only for a bad escape), or the router is given the decoded path to match on; fixing index.html alone is not enough, since API clients encode the same way.
  • Tests, failing before the fix, through s.router with a stored 2001:db8::/32 route: GET /ip/2001%3Adb8%3A%3A1 answers 200 with one result; GET /api/v1/prefix/2001%3Adb8%3A%3A/32 and GET /prefix/2001%3Adb8%3A%3A/32 answer 200; GET /api/v1/ip/192.0.2.1%2C192.0.2.2 with routes for both answers two results. All four answer 400 today.
  • make check passes.

Model: fable-5-1

Where: `lookupTarget`, `resolveLookupTargets`, `handlePrefixDetail` and `handlePrefixDetailJSON` in `internal/server/handlers.go`; `internal/templates/index.html` line 417. The home page's IP lookup form sends `fetch('/ip/' + encodeURIComponent(ip))`, so `2001:4860:4860::8888` (the form's own placeholder) is requested as `/ip/2001%3A4860%3A4860%3A%3A8888`. Go's `net/url` keeps `RawPath` set because `:` is not escaped by default, chi routes on `RawPath` and copies the raw segment into the parameter, and `chi.URLParam` returns `2001%3A4860%3A4860%3A%3A8888` undecoded. `net.ParseIP` fails, the string is treated as a host name, the resolver fails, and the answer is 400 "No valid IPs or hostnames provided". The page shows that error for every IPv6 address; IPv4 works. The same happens to any client that percent-encodes a path segment, as JavaScript's `encodeURIComponent` and Python's `urllib.parse.quote` do: `/api/v1/ip/8.8.8.8%2C1.1.1.1` is one unresolvable "host name" (400) although README documents the comma list, and `/api/v1/prefix/2001%3Adb8%3A%3A/32` or `/prefix/2001%3Adb8%3A%3A/32` fail `netip.ParsePrefix` and answer 400 for a valid, stored prefix. Severity: high: the daemon's own front page cannot look up any IPv6 address, and the 400 text blames the user's input. ## Definition of done - Every URL parameter the handlers read is percent-decoded (`url.PathUnescape`, answering 400 only for a bad escape), or the router is given the decoded path to match on; fixing `index.html` alone is not enough, since API clients encode the same way. - Tests, failing before the fix, through `s.router` with a stored `2001:db8::/32` route: `GET /ip/2001%3Adb8%3A%3A1` answers 200 with one result; `GET /api/v1/prefix/2001%3Adb8%3A%3A/32` and `GET /prefix/2001%3Adb8%3A%3A/32` answer 200; `GET /api/v1/ip/192.0.2.1%2C192.0.2.2` with routes for both answers two results. All four answer 400 today. - `make check` passes. Model: fable-5-1
clawbot self-assigned this 2026-10-19 22:35:09 +02:00
Author
Collaborator

Fixed in #123: the router now matches routes against the decoded path, so every handler reads its URL parameters percent-decoded, and the home page's lookup form works without a change.

Model: opus-5-5

Fixed in https://git.eeqj.de/sneak/routewatch/pulls/123: the router now matches routes against the decoded path, so every handler reads its URL parameters percent-decoded, and the home page's lookup form works without a change. Model: opus-5-5
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/routewatch#103