001: /*
002: * $Id: IPFilter.java,v 1.42 2007/03/16 09:55:00 agoubard Exp $
003: *
004: * Copyright 2003-2007 Orange Nederland Breedband B.V.
005: * See the COPYRIGHT file for redistribution and use restrictions.
006: */
007: package org.xins.server;
008:
009: import org.xins.common.MandatoryArgumentChecker;
010: import org.xins.common.net.IPAddressUtils;
011: import org.xins.common.text.ParseException;
012:
013: /**
014: * Filter for IP addresses.
015: *
016: * <a name="format"></a>
017: * <h3>Filter expression format</h3>
018: *
019: * <p>An <code>IPFilter</code> instance is created using a so-called
020: * <em>filter expression</em>. This filter expression specifies the IP address
021: * and mask to use for matching a subject IP address.
022: *
023: * <p>A filter expression must match the following format:
024: *
025: * <blockquote><code>"<em>a</em>.<em>a</em>.<em>a</em>.<em>a</em>"</code>,
026: * optionally followed by: <code>/<em>n</em></code>, where <em>a</em> is a
027: * number between 0 and 255, with no leading zeroes, and <em>n</em> is a
028: * number between 0 and 32, no leading zeroes; if <em>n</em> is not
029: * specified.</blockquote>
030: *
031: * <h3>Example code</h3>
032: *
033: * <p>An <code>IPFilter</code> object is
034: * created and used as follows:
035: *
036: * <blockquote><code>IPFilter filter = IPFilter.parseFilter("10.0.0.0/24");
037: * <br>if (filter.match("10.0.0.1")) {
038: * <br> // IP is granted access
039: * <br>} else {
040: * <br> // IP is denied access
041: * <br>}</code></blockquote>
042: *
043: * @version $Revision: 1.42 $ $Date: 2007/03/16 09:55:00 $
044: * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
045: * @author Peter Troon
046: *
047: * @since XINS 1.0.0
048: */
049: public final class IPFilter {
050:
051: /**
052: * The character that delimits the IP address and the mask of the provided
053: * filter.
054: */
055: private static final char IP_MASK_DELIMETER = '/';
056:
057: /**
058: * The expression of this filter, cannot be <code>null</code>.
059: */
060: private final String _expression;
061:
062: /**
063: * The base IP address, as a <code>String</code>. Never <code>null</code>.
064: */
065: private final String _baseIPString;
066:
067: /**
068: * The base IP address.
069: */
070: private final int _baseIP;
071:
072: /**
073: * The mask of this filter. Can only have a value between 0 and 32.
074: */
075: private final int _mask;
076:
077: /**
078: * The shift value, which equals <code>32 - </code>{@link #_mask}. Always
079: * between 0 and 32.
080: */
081: private final int _shift;
082:
083: /**
084: * Creates an <code>IPFilter</code> object for the specified filter
085: * expression. The expression consists of a base IP address and a bit
086: * count. The bit count indicates how many bits in an IP address must match
087: * the bits in the base IP address.
088: *
089: * @param ipString
090: * the base IP address, as a character string, should not be
091: * <code>null</code>.
092: *
093: * @param ip
094: * the base IP address, as an <code>int</code>.
095: *
096: * @param mask
097: * the mask, between 0 and 32 (inclusive).
098: */
099: private IPFilter(String ipString, int ip, int mask) {
100: _expression = ipString + IP_MASK_DELIMETER + mask;
101: _baseIPString = ipString;
102: _baseIP = ip;
103: _mask = mask;
104: _shift = 32 - _mask;
105: }
106:
107: /**
108: * Creates an <code>IPFilter</code> object for the specified filter
109: * expression. The expression consists of a base IP address and a bit
110: * count. The bit count indicates how many bits in an IP address must match
111: * the bits in the base IP address.
112: *
113: * @param expression
114: * the filter expression, cannot be <code>null</code> and must match
115: * <a href="#format">the format for a filter expression</a>.
116: * then 32 is assumed.
117: *
118: * @return
119: * the constructed <code>IPFilter</code> object, never
120: * <code>null</code>.
121: *
122: * @throws IllegalArgumentException
123: * if <code>expression == null</code>.
124: *
125: * @throws ParseException
126: * if <code>expression</code> does not match the specified format.
127: */
128: public static final IPFilter parseIPFilter(String expression)
129: throws IllegalArgumentException, ParseException {
130:
131: // Check preconditions
132: MandatoryArgumentChecker.check("expression", expression);
133:
134: // Find the slash ('/') character
135: int slashPosition = expression.indexOf(IP_MASK_DELIMETER);
136:
137: String ipString;
138: int mask;
139:
140: // If we have a slash, then it cannot be at the first or last position
141: if (slashPosition >= 0) {
142: if (slashPosition == 0
143: || slashPosition == expression.length() - 1) {
144: throw new ParseException("The string \"" + expression
145: + "\" is not a valid IP filter expression.");
146: }
147:
148: // Split the IP and the mask
149: ipString = expression.substring(0, slashPosition);
150: mask = parseMask(expression.substring(slashPosition + 1));
151:
152: // If we don't have a slash, then parse the IP address only and assume
153: // the mask to be 32 bits
154: } else {
155: ipString = expression;
156: mask = 32;
157: }
158:
159: // Convert the IP string to an int
160: int ip = IPAddressUtils.ipToInt(ipString);
161:
162: // Create and return an IPFilter object
163: return new IPFilter(ipString, ip, mask);
164: }
165:
166: /**
167: * Parses the specified mask.
168: *
169: * @param maskString
170: * the mask string, may not be <code>null</code>.
171: *
172: * @return
173: * an integer representing the value of the mask, between 0 and 32.
174: *
175: * @throws ParseException
176: * if the specified string is not a mask between 0 and 32, with no
177: * leading zeroes.
178: */
179: private static final int parseMask(String maskString)
180: throws ParseException {
181:
182: // Convert to an int
183: int mask;
184: try {
185: mask = Integer.parseInt(maskString);
186:
187: // Catch conversion exception
188: } catch (NumberFormatException nfe) {
189: throw new ParseException("The mask string \"" + maskString
190: + "\" is not a valid number.");
191: }
192:
193: // Number must be between 0 and 32
194: if (mask < 0 || mask > 32) {
195: throw new ParseException("The mask string \"" + maskString
196: + "\" is not a number between 0 and 32.");
197: }
198:
199: // Disallow a leading zero
200: if (maskString.length() >= 2 && maskString.charAt(0) == '0') {
201: throw new ParseException("The mask string \"" + maskString
202: + "\" starts with a leading zero.");
203: }
204:
205: return mask;
206: }
207:
208: /**
209: * Returns the filter expression.
210: *
211: * @return
212: * the original filter expression, never <code>null</code>.
213: */
214: public final String getExpression() {
215: return _expression;
216: }
217:
218: /**
219: * Returns the base IP address.
220: *
221: * @return
222: * the base IP address, in the form
223: * <code><em>a</em>.<em>a</em>.<em>a</em>.<em>a</em>/<em>n</em></code>,
224: * where <em>a</em> is a number between 0 and 255, with no leading
225: * zeroes; never <code>null</code>.
226: */
227: public final String getBaseIP() {
228: return _baseIPString;
229: }
230:
231: /**
232: * Returns the mask.
233: *
234: * @return
235: * the mask, between 0 and 32.
236: */
237: public final int getMask() {
238: return _mask;
239: }
240:
241: /**
242: * Determines if the specified IP address is authorized.
243: *
244: * @param ipString
245: * the IP address of which must be determined if it is authorized,
246: * cannot be <code>null</code> and must match the form:
247: * <code><em>a</em>.<em>a</em>.<em>a</em>.<em>a</em>/<em>n</em></code>,
248: * where <em>a</em> is a number between 0 and 255, with no leading
249: * zeroes.
250: *
251: * @return
252: * <code>true</code> if the IP address is authorized to access the
253: * protected resource, otherwise <code>false</code>.
254: *
255: * @throws IllegalArgumentException
256: * if <code>ipString == null</code>.
257: *
258: * @throws ParseException
259: * if <code>ip</code> does not match the specified format.
260: */
261: public final boolean match(String ipString)
262: throws IllegalArgumentException, ParseException {
263:
264: // Check preconditions
265: MandatoryArgumentChecker.check("ipString", ipString);
266:
267: // Convert the IP string to an 'int'
268: int ip = IPAddressUtils.ipToInt(ipString);
269:
270: // Short-circuit if mask is 0 bits
271: if (_mask == 0) {
272: return true;
273: }
274:
275: // Perform the match
276: boolean match = (ip >> _shift) == (_baseIP >> _shift);
277:
278: return match;
279: }
280:
281: /**
282: * Returns a textual representation of this filter. The implementation of
283: * this method returns the filter expression passed.
284: *
285: * @return
286: * a textual presentation, never <code>null</code>.
287: */
288: public final String toString() {
289: return getExpression();
290: }
291: }
|