blob: f9cd674e10867008611d56622c7425b7999784d1 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2017 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;
/**
* 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>
* <dt>Legacy format</dt><dd>The legacy format used by {@link IPAddressMap} for IPv4 only.
* eg. "10.10.10-14.0-128"</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;
}
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[] raw_min = min.getAddress();
byte[] raw_max = max.getAddress();
if (raw_min.length!=raw_max.length)
throw new IllegalArgumentException("Cannot mix IPv4 and IPv6: "+pattern);
if (raw_min.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[raw_min.length];
_max = new int[raw_min.length];
for (int i=0;i<_min.length;i++)
{
_min[i]=0xff&raw_min[i];
_max[i]=0xff&raw_max[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;
}
}
public boolean test(InetAddress item, byte[] raw)
{
if (raw.length!=_min.length)
return false;
boolean min_ok = false;
boolean max_ok = false;
for (int i=0;i<_min.length;i++)
{
int r = 0xff&raw[i];
if (!min_ok)
{
if (r<_min[i])
return false;
if (r>_min[i])
min_ok=true;
}
if (!max_ok)
{
if (r>_max[i])
return false;
if (r<_max[i])
max_ok=true;
}
if (min_ok && max_ok)
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 && _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);
}
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);
}
}
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;
}
}
}