org.eclipse.jetty.util.HostPort Maven / Gradle / Ivy
Show all versions of jetty-util Show documentation
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.util;
import java.net.InetAddress;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Parse an authority string (in the form {@code host:port}) into
* {@code host} and {@code port}, handling IPv4 and IPv6 host formats
* as defined in RFC 2732
*/
public class HostPort
{
private static final Logger LOG = LoggerFactory.getLogger(HostPort.class);
/**
* Indicator that the provided port string was bad see unsafe mode at {@link #parsePort(String, boolean)}
*/
private static final int BAD_PORT = -2;
private final String _host;
private final int _port;
/**
* Create a HostPort from an unsafe (and not validated) authority.
*
*
* There are no validations performed against the provided authority.
* It is quite possible to end up with HostPort that cannot be used
* to generate valid URL, URI, InetSocketAddress, Location header, etc.
*
*
* @param authority raw authority
* @return the HostPort
*/
public static HostPort unsafe(String authority)
{
return new HostPort(authority, true);
}
public HostPort(String host, int port)
{
_host = normalizeHost(host);
_port = (port > 0) ? port : URIUtil.UNDEFINED_PORT;
}
public HostPort(String authority) throws IllegalArgumentException
{
this(authority, false);
}
@SuppressWarnings({"ReassignedVariable", "DataFlowIssue"})
private HostPort(String authority, boolean unsafe)
{
String host;
int port = URIUtil.UNDEFINED_PORT;
if (authority == null)
{
LOG.warn("Bad Authority []");
if (!unsafe)
throw new IllegalArgumentException("No Authority");
_host = "";
_port = URIUtil.UNDEFINED_PORT;
return;
}
if (authority.isEmpty())
{
_host = authority;
_port = URIUtil.UNDEFINED_PORT;
return;
}
try
{
if (authority.charAt(0) == '[')
{
// ipv6reference
int close = authority.lastIndexOf(']');
if (close < 0)
{
LOG.warn("Bad IPv6 host: [{}]", authority);
if (!unsafe)
throw new IllegalArgumentException("Bad IPv6 host");
host = authority;
}
else
{
host = authority.substring(0, close + 1);
}
if (!isValidIpAddress(host))
{
LOG.warn("Bad IPv6 host: [{}]", host);
if (!unsafe)
throw new IllegalArgumentException("Bad IPv6 host");
}
if (authority.length() > close + 1)
{
// ipv6 with port
if (authority.charAt(close + 1) != ':')
{
LOG.warn("Bad IPv6 port: [{}]", authority);
if (!unsafe)
throw new IllegalArgumentException("Bad IPv6 port");
host = authority; // whole authority (no substring)
port = URIUtil.UNDEFINED_PORT; // no port
}
else
{
port = parsePort(authority.substring(close + 2), unsafe);
// horribly bad port during unsafe (eg: "Host:xxx")
if (unsafe && port == BAD_PORT)
{
host = authority; // whole authority (no substring)
port = URIUtil.UNDEFINED_PORT;
}
}
}
else
{
port = URIUtil.UNDEFINED_PORT;
}
}
else
{
// ipv6address or ipv4address or hostname
int c = authority.lastIndexOf(':');
if (c >= 0)
{
if (c != authority.indexOf(':'))
{
// ipv6address no port
port = URIUtil.UNDEFINED_PORT;
host = "[" + authority + "]";
if (!isValidIpAddress(host))
{
LOG.warn("Bad IPv6Address: [{}]", host);
if (!unsafe)
throw new IllegalArgumentException("Bad IPv6 host");
host = authority; // whole authority (no substring)
}
}
else
{
// host/ipv4 with port
host = authority.substring(0, c);
if (StringUtil.isBlank(host))
{
LOG.warn("Bad Authority: [{}]", host);
if (!unsafe)
throw new IllegalArgumentException("Bad Authority");
// unsafe - allow host to be empty
host = "";
}
else if (!isValidHostName(host))
{
LOG.warn("Bad Authority: [{}]", host);
if (!unsafe)
throw new IllegalArgumentException("Bad Authority");
// unsafe - bad hostname
host = authority; // whole authority (no substring)
}
port = parsePort(authority.substring(c + 1), unsafe);
// horribly bad port during unsafe (eg: "Host:xxx")
if (unsafe && port == BAD_PORT)
{
host = authority; // whole authority (no substring)
port = URIUtil.UNDEFINED_PORT;
}
}
}
else
{
// host/ipv4 without port
host = authority;
if (StringUtil.isBlank(host) || !isValidHostName(host))
{
LOG.warn("Bad Authority: [{}]", host);
if (!unsafe)
throw new IllegalArgumentException("Bad Authority");
}
port = URIUtil.UNDEFINED_PORT;
}
}
}
catch (IllegalArgumentException iae)
{
if (!unsafe)
throw iae;
host = authority;
port = URIUtil.UNDEFINED_PORT;
}
catch (Exception ex)
{
if (!unsafe)
throw new IllegalArgumentException("Bad HostPort", ex);
host = authority;
port = URIUtil.UNDEFINED_PORT;
}
_host = host;
_port = (port > 0) ? port : URIUtil.UNDEFINED_PORT;
}
protected boolean isValidIpAddress(String ip)
{
try
{
// Per javadoc, If a literal IP address is supplied, only the validity of the
// address format is checked.
InetAddress.getByName(ip);
return true;
}
catch (Throwable ignore)
{
return false;
}
}
protected boolean isValidHostName(String name)
{
return URIUtil.isValidHostRegisteredName(name);
}
/**
* Get the host.
*
* @return the host
*/
@ManagedAttribute("host")
public String getHost()
{
return _host;
}
/**
* Get the port.
*
* @return the port
*/
@ManagedAttribute("port")
public int getPort()
{
return _port;
}
/**
* Get the port or the given default port.
*
* @param defaultPort, the default port to return if a port is not specified
* @return the port
*/
public int getPort(int defaultPort)
{
return _port > 0 ? _port : defaultPort;
}
public boolean hasHost()
{
return StringUtil.isNotBlank(_host);
}
public boolean hasPort()
{
return _port > 0;
}
@Override
public String toString()
{
if (_port > 0)
return _host + ":" + _port;
return _host;
}
/**
* Normalizes IPv6 address as per RFC 2732
* and RFC 6874,
* surrounding with square brackets if they are absent.
*
* @param host a host name, IPv4 address, IPv6 address or IPv6 literal
* @return a host name or an IPv4 address or an IPv6 literal (not an IPv6 address)
*/
public static String normalizeHost(String host)
{
// if it is normalized IPv6 or could not be IPv6, return
if (host == null || host.isEmpty() || host.charAt(0) == '[' || host.indexOf(':') < 0)
return host;
// normalize with [ ]
return "[" + host + "]";
}
/**
* Parse a string representing a port validating it is a valid port value.
*
* @param rawPort the port string.
* @return the integer value for the port.
* @throws IllegalArgumentException if the port is invalid
*/
public static int parsePort(String rawPort) throws IllegalArgumentException
{
if (StringUtil.isEmpty(rawPort))
throw new IllegalArgumentException("Bad port");
int port = Integer.parseInt(rawPort);
if (port <= 0 || port > 65535)
throw new IllegalArgumentException("Bad port");
return port;
}
/**
* Parse a potential port.
*
*
* In safe mode, this will either return a port in the valid range of 0 to 65535, or throw an {@link IllegalArgumentException}.
*
*
* In unsafe mode, this will return a port in the valid range of 0 to 65535, or {@link #BAD_PORT} indicating that the
* port provided was not a number (eg: {@code ":xxx"}) or was not in the valid range (eg: {@code ":-80"} or {@code ":11222333"})
*
*
* In both safe and unsafe, an empty {@code rawPort} will result in a return value of {@link URIUtil#UNDEFINED_PORT}
*
*
* @param rawPort the raw port string to parse
* @param unsafe true for unsafe mode (no exceptions), false for safe mode (with exceptions if there is a problem).
* @return the port
* @throws IllegalArgumentException if unable to parse a valid port and {@code unsafe} is false
*/
private int parsePort(String rawPort, boolean unsafe)
{
if (StringUtil.isEmpty(rawPort))
{
if (!unsafe)
throw new IllegalArgumentException("Blank port");
return URIUtil.UNDEFINED_PORT;
}
try
{
int port = Integer.parseInt(rawPort);
if (port <= 0 || port > 65535)
{
LOG.warn("Bad port [{}]", port);
if (!unsafe)
throw new IllegalArgumentException("Bad port");
return BAD_PORT;
}
return port;
}
catch (NumberFormatException e)
{
LOG.warn("Bad port [{}]", rawPort);
if (!unsafe)
throw new IllegalArgumentException("Bad Port");
return BAD_PORT;
}
}
}