public static function parse(string $url, string $default_scheme = null)
    {
        if (substr($url, 0, 4) === "www." && !empty($default_scheme))
            $url = $default_scheme . '://' . $url;
        elseif (substr($url, 0, 2) === '//' && !empty($_SERVER['REQUEST_SCHEME']))
            $url = $_SERVER['REQUEST_SCHEME'] . '://' . substr($url, 2);

        $parts = parse_url($url);

        if ($parts === false)
            throw new URLException("Invalid URL: " . $url);

        $scheme   = $parts['scheme'] ?? null;
        $username = $parts['user'] ?? null;
        $password = $parts['pass'] ?? null;
        $host     = $parts['host'] ?? null;
        $port     = $parts['port'] ?? null;
        $path     = $parts['path'] ?? '/';
        $query    = $parts['query'] ?? null;
        $fragment = $parts['fragment'] ?? null;

        $query = self::parseQuery($query);

        // Scheme is case insensitive, to normalize to lowercase
        if ($scheme !== null)
            $scheme = strtolower($scheme);

        // Hostnames are case insensitive, to normalize to lowercase
        if ($host !== null)
            $host = strtolower($host);

        if (!empty($scheme) && !in_array($scheme, array('http', 'https', 'ftp')))
            throw new URLException("Unsupported scheme: '" . $scheme . "'");

        return array(
            'scheme' => $scheme,
            'username' => $username,
            'password' => $password,
            'host' => $host,
            'port' => $port,
            'path' => $path,
            'query' => $query,
            'fragment' => $fragment
        );
    }