sirius.web.http.IPRange Maven / Gradle / Ivy
Show all versions of sirius-web Show documentation
/*
* Made with all the love in the world
* by scireum in Remshalden, Germany
*
* Copyright by scireum GmbH
* http://www.scireum.de - [email protected]
*/
package sirius.web.http;
import com.google.common.collect.Lists;
import sirius.kernel.commons.Explain;
import sirius.kernel.commons.Strings;
import java.math.BigInteger;
import java.net.InetAddress;
import java.util.List;
/**
* Represents a range of IP addresses in CIDR notation.
*
* Valid inputs are x.x.x.x/bits or z:z:z:z:z:z:z:z/bits. If /bits is missing, /32 vor IPv4 or /128 for IPv6 is
* assumed.
*/
public class IPRange {
/**
* Internal bitmask representing the IP range. We use BigInteger internally since primitive types are
* signed and computations get nasty.
*/
private BigInteger baseIP = BigInteger.ZERO;
/**
* Represents the full mask, meaning that all bits of the given baseIP need to be applied
*/
private static final BigInteger COMPLETE_MASK = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16);
/**
* Contains the effective mask. This determines which bits of baseIP are used to check against an incoming IP.
*/
private BigInteger mask = COMPLETE_MASK;
/**
* Contains a string representation of this range
*/
private String stringRepresentation = "*";
/**
* Constant for a "non filtering" range set which accepts all IP addresses
*/
public static final RangeSet NO_FILTER = new RangeSet();
/**
* Constant for a range which only accepts localhost.
*/
public static final RangeSet LOCALHOST = createLocalHostRangeSet();
/**
* Represents the IP address of the localhost in an IPv4 environment
*/
@SuppressWarnings("squid:S1313")
@Explain("We really mean localhost here.")
public static final IPRange IPV4_LOCALHOST = IPRange.parseRange("127.0.0.1");
/**
* Represents the IP address of the localhost in an IPv6 environment
*/
public static final IPRange IPV6_LOCALHOST = IPRange.parseRange("0:0:0:0:0:0:0:1");
/**
* Parses a CIDR expression and returns an IPRange
*
* @param cidr the CIDR expression to parse. This can be either an IP address like 192.168.1.4 or a subnet like
* 192.168.192.0/24 or empty which implies 0.0.0.0/0
* @return an IPRange representing the given cidr expression.
* @throws IllegalArgumentException in case of an invalid expression
*/
public static IPRange parseRange(String cidr) {
IPRange result = new IPRange();
try {
if (Strings.isFilled(cidr)) {
result.stringRepresentation = cidr;
String[] input = cidr.split("/");
boolean ip6 = cidr.contains(":");
result.baseIP = ipToInt(InetAddress.getByName(input[0]));
if (input.length > 1) {
result.mask = COMPLETE_MASK.shiftLeft((ip6 ? 128 : 32) - Integer.parseInt(input[1]));
}
}
} catch (Exception e) {
throw new IllegalArgumentException(Strings.apply("Invalid IP range given: %s - %s", cidr, e.getMessage()));
}
return result;
}
/*
* Converts an IP address to a 32 bit integer - using BigInteger because Java has no unsigned types.
*/
private static BigInteger ipToInt(InetAddress address) {
return new BigInteger(address.getAddress());
}
/**
* Checks if the given IP is within the described ip range.
*
* @param address the address to check
* @return true if the given address is in the described IP range, false otherwise
*/
public boolean matches(InetAddress address) {
if (BigInteger.ZERO.equals(baseIP)) {
return true;
}
BigInteger ip = ipToInt(address);
if (BigInteger.ZERO.equals(ip)) {
return true;
}
return baseIP.and(mask).and(COMPLETE_MASK).equals(ip.and(mask).and(COMPLETE_MASK));
}
/*
* Computes a RangeSet which only accepts localhost
*/
@SuppressWarnings("squid:S1313")
@Explain("We really mean localhost here.")
private static RangeSet createLocalHostRangeSet() {
RangeSet result = new RangeSet();
result.ranges = Lists.newArrayList();
// Do not use the constants defined above, as these might not be initialized yet.
// As the definition of localhost shouldn't change, we live with double defined constants rather than
// using static/lazy initializer magic...
result.ranges.add(IPRange.parseRange("127.0.0.1"));
result.ranges.add(IPRange.parseRange("0:0:0:0:0:0:0:1"));
return result;
}
/**
* Compiles the given list of ranges into a range set.
*
* @param commaSeparatedListOfRanges a string defining a list of ip ranges separated by a ",". Each ip range
* can be an address or a sub net in CIDR notation.
* @return a RangeSet representing the given input. If the input was empty, a "no filter" set is returned, which
* accepts all IPs.
*/
public static RangeSet paraseRangeSet(String commaSeparatedListOfRanges) {
RangeSet result = new RangeSet();
if (Strings.isFilled(commaSeparatedListOfRanges)) {
result.ranges = Lists.newArrayList();
for (String range : commaSeparatedListOfRanges.replace(" ", "").split("[,;]")) {
if (Strings.isFilled(range)) {
result.ranges.add(IPRange.parseRange(range.trim()));
}
}
if (result.ranges.isEmpty()) {
// No rules were parsed -> accept everything with a fast ==
result.ranges = null;
}
}
return result;
}
@Override
public String toString() {
return stringRepresentation;
}
/**
* Represents a set of IP ranges.
*
* Ranges can be specified as a list of CIDR sub nets, separated by a ",".
*/
public static class RangeSet {
/*
* List of accepted ranges. If this is null, everything is accepted
*/
private List ranges;
/*
* Use compile to create a set of ranges.
*/
private RangeSet() {
}
/**
* Checks if the given address if accepted by one of the declared ranges, or if
* the range is completely empty.
*
* @param addr the address to check
* @return true if the given address is accepted, false otherwise
*/
public boolean accepts(InetAddress addr) {
if (addr == null || ranges == null) {
return true;
}
for (IPRange range : ranges) {
if (range.matches(addr)) {
return true;
}
}
return false;
}
/**
* Determines if a filter range is given or not
*
* @return true if no filter range is given. This implies "no filtering" and will accept all IPs. If
* at least one range is given, false will be returned
*/
public boolean isEmpty() {
return ranges == null;
}
@Override
public String toString() {
return Strings.join(ranges, ", ");
}
}
}