001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.net.ipsettings;
020
021
022import java.net.Inet4Address;
023import java.net.Inet6Address;
024import java.net.InetAddress;
025import java.net.UnknownHostException;
026import java.util.ArrayList;
027import java.util.Collections;
028import java.util.List;
029import java.util.Map;
030import java.util.TreeMap;
031
032import lucee.commons.lang.ExceptionUtil;
033
034
035/**
036 * an efficient data structure for IP-range based settings
037 */
038public class IPSettings {
039
040
041        public static final Map EMPTY = Collections.EMPTY_MAP;
042
043
044        private IPRangeNode<Map> root, ipv4, ipv6;
045        private boolean isSorted;
046        private int version;
047
048
049        public IPSettings() {
050
051                try {
052
053                        root = new IPRangeNodeRoot();
054
055                        root.addChild( ipv4 = new IPRangeNode( "0.0.0.0", "255.255.255.255" ) );
056                        root.addChild( ipv6 = new IPRangeNode( "0:0:0:0:0:0:0:0", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" ) );
057                }
058                catch (Throwable t) {
059                        ExceptionUtil.rethrowIfNecessary(t);
060                }                                                  // all valid addresses, should never happen
061        }
062
063
064        /**
065         * all added data should go through this method
066         *
067         * @param ipr
068         * @param doCheck
069         */
070        public synchronized void put( IPRangeNode<Map> ipr, boolean doCheck ) {
071
072                IPRangeNode parent = ipr.isV4() ? ipv4 : ipv6;
073
074                parent.addChild( ipr, doCheck );
075
076                version++;
077                isSorted = false;
078        }
079
080
081        /** calls put( IPRangeNode ipr ) */
082        public void put( IPRangeNode<Map> ipr ) {
083
084                this.put( ipr, true );
085        }
086
087
088        /**
089         * puts all the children at the IPv4 or IPv6 nodes for fast insertion.
090         * this method does not look for a more accurate insertion point and is useful
091         * when adding many items at once, e.g. for Country Codes of all known IP ranges
092         *
093         * @param children
094         */
095        public void putAll( List<IPRangeNode<Map>> children ) {
096
097                for ( IPRangeNode child : children ) {
098
099                        this.put( child, false );                                           // pass false for optimized insertion performance
100                }
101        }
102
103
104        public void putSettings( String lower, String upper, Map settings ) throws UnknownHostException {
105
106                IPRangeNode<Map> ipr = new IPRangeNode(lower, upper);
107                ipr.setData( settings );
108
109                this.put( ipr );
110        }
111
112
113        public void putSettings( String addr, Map settings ) throws UnknownHostException {
114
115                if ( addr.equals( "*" ) ) {
116
117                        root.setData( settings );
118                        return;
119                }
120
121                IPRangeNode<Map> ipr = new IPRangeNode(addr);
122                ipr.setData( settings );
123
124                this.put( ipr );
125        }
126
127
128        /**
129         * returns a single, best matching node for the given address
130         *
131         * @param addr
132         * @return
133         */
134        public IPRangeNode get( InetAddress addr ) {
135
136                if ( version == 0 )                                                     // no data was added
137                        return null;
138
139                IPRangeNode node = isV4( addr ) ? ipv4 : ipv6;
140
141                if ( !this.isSorted )
142                        this.optimize();
143
144                return node.findFast( addr );
145        }
146
147
148        /**
149         * returns a List of all the nodes (from root to best matching) for the given address
150         *
151         * @param iaddr
152         * @return
153         */
154        public List<IPRangeNode> getChain( InetAddress iaddr ) {
155
156                List<IPRangeNode> result = new ArrayList();
157
158                result.add( root );
159
160                IPRangeNode node = isV4( iaddr ) ? ipv4 : ipv6;
161                node.findFast( iaddr, result );
162
163                return result;
164        }
165
166
167        /**
168         * returns the cumulative settings for a given address
169         *
170         * @param iaddr
171         * @return
172         */
173        public Map getSettings( InetAddress iaddr ) {
174
175                Map result = new TreeMap(String.CASE_INSENSITIVE_ORDER);
176
177                List<IPRangeNode> chain = getChain( iaddr );
178
179                for ( IPRangeNode<Map> ipr : chain ) {
180
181                        Map m = ipr.getData();
182                        if ( m != null )
183                                result.putAll( m );
184                }
185
186                return result;
187        }
188
189
190        /**
191         * returns the cumulative settings for a given address
192         *
193         * @param addr
194         * @return
195         */
196        public Map getSettings( String addr ) {
197
198                try {
199
200                        return this.getSettings( InetAddress.getByName(addr) );
201                }
202                catch (Throwable t) {
203                        ExceptionUtil.rethrowIfNecessary(t);
204                }
205
206                return EMPTY;
207        }
208
209
210        /**
211         * returns the settings for a single (non-cumulative) node that best matches the given address
212         *
213         * @param addr
214         * @return
215         */
216        public Map getNodeSettings( InetAddress addr ) {
217
218                IPRangeNode<Map> ipr = this.get( addr );
219
220                if ( ipr != null ) {
221
222                        Map result = ipr.getData();
223
224                        if ( result != null )
225                                return result;
226                }
227
228                return EMPTY;
229        }
230
231
232        /**
233         * returns the settings for a single (non-cumulative) node that best matches the given address
234         *
235         * @param addr
236         * @return
237         */
238        public Map getNodeSettings( String addr ) {
239
240                try {
241
242                        return this.getNodeSettings( InetAddress.getByName(addr) );
243                }
244                catch (Throwable t) {
245                        ExceptionUtil.rethrowIfNecessary(t);
246                }
247
248                return EMPTY;
249        }
250
251
252        public int getVersion() {
253
254                return version;
255        }
256
257
258        /** sorts the data for fast binary search */
259        private void optimize() {
260
261                root.getChildren().sortChildren();
262
263                isSorted = true;
264        }
265
266
267        /** returns true if the value is an IPv4 address */
268        public static boolean isV4(InetAddress addr) {
269
270                return addr instanceof Inet4Address;
271        }
272
273        /** returns true if the value is an IPv6 address */
274        public static boolean isV6(InetAddress addr) {
275
276                return addr instanceof Inet6Address;
277        }
278
279}