//
// ========================================================================
// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.util;
import java.net.InetAddress;
import java.util.AbstractSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
A set of InetAddress patterns.
This is a Set
of String patterns that are used to match a Predicate
over InetAddress for containment semantics. The patterns that may be set are:
- InetAddress
- A single InetAddress either in hostname or address format. All formats supported by
InetAddress
are accepted. Not ethat using hostname matches may force domain lookups. eg. "[::1]", "1.2.3.4", "::ffff:127.0.0.1"
- InetAddress/CIDR
- An InetAddress with a integer number of bits to indicate
the significant prefix. eg. "192.168.0.0/16" will match from "192.168.0.0" to
"192.168.255.255"
- InetAddress-InetAddress
- An inclusive range of InetAddresses.
eg. "[a000::1]-[afff::]", "192.168.128.0-192.168.128.255"
This class is designed to work with IncludeExcludeSet
See Also:
/**
* A set of InetAddress patterns.
* <p>This is a {@link Set} of String patterns that are used to match
* a {@link Predicate} over InetAddress for containment semantics.
* The patterns that may be set are:
* </p>
* <dl>
* <dt>InetAddress</dt><dd>A single InetAddress either in hostname or address format.
* All formats supported by {@link InetAddress} are accepted. Not ethat using hostname
* matches may force domain lookups. eg. "[::1]", "1.2.3.4", "::ffff:127.0.0.1"</dd>
* <dt>InetAddress/CIDR</dt><dd>An InetAddress with a integer number of bits to indicate
* the significant prefix. eg. "192.168.0.0/16" will match from "192.168.0.0" to
* "192.168.255.255" </dd>
* <dt>InetAddress-InetAddress</dt><dd>An inclusive range of InetAddresses.
* eg. "[a000::1]-[afff::]", "192.168.128.0-192.168.128.255"</dd>
* </dl>
* <p>This class is designed to work with {@link IncludeExcludeSet}</p>
*
* @see IncludeExcludeSet
*/
public class InetAddressSet extends AbstractSet<String> implements Set<String>, Predicate<InetAddress>
{
private Map<String, InetPattern> _patterns = new HashMap<>();
@Override
public boolean add(String pattern)
{
return _patterns.put(pattern, newInetRange(pattern)) == null;
}
protected InetPattern newInetRange(String pattern)
{
if (pattern == null)
return null;
int slash = pattern.lastIndexOf('/');
int dash = pattern.lastIndexOf('-');
try
{
if (slash >= 0)
return new CidrInetRange(pattern, InetAddress.getByName(pattern.substring(0, slash).trim()), StringUtil.toInt(pattern, slash + 1));
if (dash >= 0)
return new MinMaxInetRange(pattern, InetAddress.getByName(pattern.substring(0, dash).trim()), InetAddress.getByName(pattern.substring(dash + 1).trim()));
return new SingletonInetRange(pattern, InetAddress.getByName(pattern));
}
catch (Exception e)
{
try
{
if (slash < 0 && dash > 0)
return new LegacyInetRange(pattern);
}
catch (Exception e2)
{
e.addSuppressed(e2);
}
throw new IllegalArgumentException("Bad pattern: " + pattern, e);
}
}
@Override
public boolean remove(Object pattern)
{
return _patterns.remove(pattern) != null;
}
@Override
public Iterator<String> iterator()
{
return _patterns.keySet().iterator();
}
@Override
public int size()
{
return _patterns.size();
}
@Override
public boolean test(InetAddress address)
{
if (address == null)
return false;
byte[] raw = address.getAddress();
for (InetPattern pattern : _patterns.values())
{
if (pattern.test(address, raw))
return true;
}
return false;
}
abstract static class InetPattern
{
final String _pattern;
InetPattern(String pattern)
{
_pattern = pattern;
}
abstract boolean test(InetAddress address, byte[] raw);
@Override
public String toString()
{
return _pattern;
}
}
static class SingletonInetRange extends InetPattern
{
final InetAddress _address;
public SingletonInetRange(String pattern, InetAddress address)
{
super(pattern);
_address = address;
}
@Override
public boolean test(InetAddress address, byte[] raw)
{
return _address.equals(address);
}
}
static class MinMaxInetRange extends InetPattern
{
final int[] _min;
final int[] _max;
public MinMaxInetRange(String pattern, InetAddress min, InetAddress max)
{
super(pattern);
byte[] rawMin = min.getAddress();
byte[] rawMax = max.getAddress();
if (rawMin.length != rawMax.length)
throw new IllegalArgumentException("Cannot mix IPv4 and IPv6: " + pattern);
if (rawMin.length == 4)
{
// there must be 6 '.' or this is likely to be a legacy pattern
int count = 0;
for (char c : pattern.toCharArray())
{
if (c == '.')
count++;
}
if (count != 6)
throw new IllegalArgumentException("Legacy pattern: " + pattern);
}
_min = new int[rawMin.length];
_max = new int[rawMin.length];
for (int i = 0; i < _min.length; i++)
{
_min[i] = 0xff & rawMin[i];
_max[i] = 0xff & rawMax[i];
}
for (int i = 0; i < _min.length; i++)
{
if (_min[i] > _max[i])
throw new IllegalArgumentException("min is greater than max: " + pattern);
if (_min[i] < _max[i])
break;
}
}
@Override
public boolean test(InetAddress item, byte[] raw)
{
if (raw.length != _min.length)
return false;
boolean minOk = false;
boolean maxOk = false;
for (int i = 0; i < _min.length; i++)
{
int r = 0xff & raw[i];
if (!minOk)
{
if (r < _min[i])
return false;
if (r > _min[i])
minOk = true;
}
if (!maxOk)
{
if (r > _max[i])
return false;
if (r < _max[i])
maxOk = true;
}
if (minOk && maxOk)
break;
}
return true;
}
}
static class CidrInetRange extends InetPattern
{
final byte[] _raw;
final int _octets;
final int _mask;
final int _masked;
public CidrInetRange(String pattern, InetAddress address, int cidr)
{
super(pattern);
_raw = address.getAddress();
_octets = cidr / 8;
_mask = 0xff & (0xff << (8 - cidr % 8));
_masked = _mask == 0 ? 0 : _raw[_octets] & _mask;
if (cidr > (_raw.length * 8))
throw new IllegalArgumentException("CIDR too large: " + pattern);
if (_mask != 0 && (0xff & _raw[_octets]) != _masked)
throw new IllegalArgumentException("CIDR bits non zero: " + pattern);
for (int o = _octets + (_mask == 0 ? 0 : 1); o < _raw.length; o++)
{
if (_raw[o] != 0)
throw new IllegalArgumentException("CIDR bits non zero: " + pattern);
}
}
@Override
public boolean test(InetAddress item, byte[] raw)
{
if (raw.length != _raw.length)
return false;
for (int o = 0; o < _octets; o++)
{
if (_raw[o] != raw[o])
return false;
}
if (_mask != 0 && (raw[_octets] & _mask) != _masked)
return false;
return true;
}
}
static class LegacyInetRange extends InetPattern
{
int[] _min = new int[4];
int[] _max = new int[4];
public LegacyInetRange(String pattern)
{
super(pattern);
String[] parts = pattern.split("\\.");
if (parts.length != 4)
throw new IllegalArgumentException("Bad legacy pattern: " + pattern);
for (int i = 0; i < 4; i++)
{
String part = parts[i].trim();
int dash = part.indexOf('-');
if (dash < 0)
_min[i] = _max[i] = Integer.parseInt(part);
else
{
_min[i] = (dash == 0) ? 0 : StringUtil.toInt(part, 0);
_max[i] = (dash == part.length() - 1) ? 255 : StringUtil.toInt(part, dash + 1);
}
if (_min[i] < 0 || _min[i] > _max[i] || _max[i] > 255)
throw new IllegalArgumentException("Bad legacy pattern: " + pattern);
}
}
@Override
public boolean test(InetAddress item, byte[] raw)
{
if (raw.length != 4)
return false;
for (int i = 0; i < 4; i++)
{
if ((0xff & raw[i]) < _min[i] || (0xff & raw[i]) > _max[i])
return false;
}
return true;
}
}
}