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
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
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Where:
lookupTarget,resolveLookupTargets,handlePrefixDetailandhandlePrefixDetailJSONininternal/server/handlers.go;internal/templates/index.htmlline 417.The home page's IP lookup form sends
fetch('/ip/' + encodeURIComponent(ip)), so2001:4860:4860::8888(the form's own placeholder) is requested as/ip/2001%3A4860%3A4860%3A%3A8888. Go'snet/urlkeepsRawPathset because:is not escaped by default, chi routes onRawPathand copies the raw segment into the parameter, andchi.URLParamreturns2001%3A4860%3A4860%3A%3A8888undecoded.net.ParseIPfails, 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
encodeURIComponentand Python'surllib.parse.quotedo:/api/v1/ip/8.8.8.8%2C1.1.1.1is one unresolvable "host name" (400) although README documents the comma list, and/api/v1/prefix/2001%3Adb8%3A%3A/32or/prefix/2001%3Adb8%3A%3A/32failnetip.ParsePrefixand 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
url.PathUnescape, answering 400 only for a bad escape), or the router is given the decoded path to match on; fixingindex.htmlalone is not enough, since API clients encode the same way.s.routerwith a stored2001:db8::/32route:GET /ip/2001%3Adb8%3A%3A1answers 200 with one result;GET /api/v1/prefix/2001%3Adb8%3A%3A/32andGET /prefix/2001%3Adb8%3A%3A/32answer 200;GET /api/v1/ip/192.0.2.1%2C192.0.2.2with routes for both answers two results. All four answer 400 today.make checkpasses.Model: fable-5-1
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