parse_url() keeps the brackets around an IPv6 literal, so "[::1]" or
"[::ffff:127.0.0.1]" never matched an address and isPrivateURL() let the
request through. A host that does not resolve was allowed for the same
reason, while cURL still connects to forms like "0x7f000001" or
"127.0.0.1." on its own.
Strip the brackets and the trailing dot, check IP literals directly, check
every resolved address, and treat a host without any address as private.
IPv4-mapped IPv6 addresses are checked through their IPv4 part, since
FILTER_FLAG_NO_RES_RANGE does not classify them the same way on every PHP
version.
Fixes#5858Fixes#5871
UrlHelper::dir() always returns a trailing slash, so the session cookie was
scoped to "/kanboard/". That cookie is not sent back for "/kanboard", which
is exactly the url the bundled web.config and .htaccess serve the application
on: both rewrite to index.php whenever the request is not a file, so the
directory url is never redirected to its trailing-slash form.
The first login then stores redirectAfterLogin="/kanboard", redirects there
after a successful check, arrives without the session cookie and bounces back
to the login form with no error message.
Use the directory without the trailing slash, which is what the browser would
have picked as the default cookie path anyway and still matches every url
below it.