/**
*
* Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
**/
package lucee.runtime.net.ipsettings;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import lucee.commons.lang.ExceptionUtil;
/**
* an efficient data structure for IP-range based settings
*/
public class IPSettings {
public static final Map EMPTY = Collections.EMPTY_MAP;
private IPRangeNode<Map> root, ipv4, ipv6;
private boolean isSorted;
private int version;
public IPSettings() {
try {
root = new IPRangeNodeRoot();
root.addChild( ipv4 = new IPRangeNode( "0.0.0.0", "255.255.255.255" ) );
root.addChild( ipv6 = new IPRangeNode( "0:0:0:0:0:0:0:0", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" ) );
}
catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);} // all valid addresses, should never happen
}
/**
* all added data should go through this method
*
* @param ipr
* @param doCheck
*/
public synchronized void put( IPRangeNode<Map> ipr, boolean doCheck ) {
IPRangeNode parent = ipr.isV4() ? ipv4 : ipv6;
parent.addChild( ipr, doCheck );
version++;
isSorted = false;
}
/** calls put( IPRangeNode ipr ) */
public void put( IPRangeNode<Map> ipr ) {
this.put( ipr, true );
}
/**
* puts all the children at the IPv4 or IPv6 nodes for fast insertion.
* this method does not look for a more accurate insertion point and is useful
* when adding many items at once, e.g. for Country Codes of all known IP ranges
*
* @param children
*/
public void putAll( List<IPRangeNode<Map>> children ) {
for ( IPRangeNode child : children ) {
this.put( child, false ); // pass false for optimized insertion performance
}
}
public void putSettings( String lower, String upper, Map settings ) throws UnknownHostException {
IPRangeNode<Map> ipr = new IPRangeNode(lower, upper);
ipr.setData( settings );
this.put( ipr );
}
public void putSettings( String addr, Map settings ) throws UnknownHostException {
if ( addr.equals( "*" ) ) {
root.setData( settings );
return;
}
IPRangeNode<Map> ipr = new IPRangeNode(addr);
ipr.setData( settings );
this.put( ipr );
}
/**
* returns a single, best matching node for the given address
*
* @param addr
* @return
*/
public IPRangeNode get( InetAddress addr ) {
if ( version == 0 ) // no data was added
return null;
IPRangeNode node = isV4( addr ) ? ipv4 : ipv6;
if ( !this.isSorted )
this.optimize();
return node.findFast( addr );
}
/**
* returns a List of all the nodes (from root to best matching) for the given address
*
* @param iaddr
* @return
*/
public List<IPRangeNode> getChain( InetAddress iaddr ) {
List<IPRangeNode> result = new ArrayList();
result.add( root );
IPRangeNode node = isV4( iaddr ) ? ipv4 : ipv6;
node.findFast( iaddr, result );
return result;
}
/**
* returns the cumulative settings for a given address
*
* @param iaddr
* @return
*/
public Map getSettings( InetAddress iaddr ) {
Map result = new TreeMap(String.CASE_INSENSITIVE_ORDER);
List<IPRangeNode> chain = getChain( iaddr );
for ( IPRangeNode<Map> ipr : chain ) {
Map m = ipr.getData();
if ( m != null )
result.putAll( m );
}
return result;
}
/**
* returns the cumulative settings for a given address
*
* @param addr
* @return
*/
public Map getSettings( String addr ) {
try {
return this.getSettings( InetAddress.getByName(addr) );
}
catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);}
return EMPTY;
}
/**
* returns the settings for a single (non-cumulative) node that best matches the given address
*
* @param addr
* @return
*/
public Map getNodeSettings( InetAddress addr ) {
IPRangeNode<Map> ipr = this.get( addr );
if ( ipr != null ) {
Map result = ipr.getData();
if ( result != null )
return result;
}
return EMPTY;
}
/**
* returns the settings for a single (non-cumulative) node that best matches the given address
*
* @param addr
* @return
*/
public Map getNodeSettings( String addr ) {
try {
return this.getNodeSettings( InetAddress.getByName(addr) );
}
catch(Throwable t) {ExceptionUtil.rethrowIfNecessary(t);}
return EMPTY;
}
public int getVersion() {
return version;
}
/** sorts the data for fast binary search */
private void optimize() {
root.getChildren().sortChildren();
isSorted = true;
}
/** returns true if the value is an IPv4 address */
public static boolean isV4(InetAddress addr) {
return addr instanceof Inet4Address;
}
/** returns true if the value is an IPv6 address */
public static boolean isV6(InetAddress addr) {
return addr instanceof Inet6Address;
}
}