001: /* Copyright 2005 The JA-SIG Collaborative. All rights reserved.
002: * See license distributed with this file and
003: * available online at http://www.uportal.org/license.html
004: */
005:
006: package org.jasig.portal.utils.uri;
007:
008: import java.io.Serializable;
009: import java.net.URI;
010: import java.util.Arrays;
011: import java.util.Collections;
012: import java.util.List;
013:
014: import org.apache.commons.logging.Log;
015: import org.apache.commons.logging.LogFactory;
016: import org.springframework.util.StringUtils;
017:
018: /**
019: * UriScrutinizer implementation matching URIs against allowed prefixes
020: * and disallowed prefixes to determine whether to block the URI.
021: *
022: * <p>The URI will be allowed if it is prefixed by at least one of the allowed
023: * prefixes and it is not prefixed by any of the blocked prefixes.</p>
024: *
025: * <p>Instances of this class are immutable once constructed.</p>
026: * <p>Instances of this class are threadsafe and serializable.</p>
027: * @since uPortal 2.5.1
028: */
029: public final class PrefixUriScrutinizer implements IUriScrutinizer,
030: Serializable {
031:
032: public static PrefixUriScrutinizer instanceFromParameters(
033: String allowPrefixesArg, String denyPrefixesArg) {
034:
035: String[] allowPrefixes;
036: if (!StringUtils.hasText(allowPrefixesArg)) {
037: // if the parameter wasn't specified
038: // or contains only whitespace
039: // default to allowing http and https
040: allowPrefixes = new String[] { "http://", "https://" };
041: } else {
042: // parse the whitespace delimited String into a String array
043: allowPrefixes = allowPrefixesArg.split("\\s");
044: }
045:
046: String[] denyPrefixes;
047: if (!StringUtils.hasText(denyPrefixesArg)) {
048: // if the parameter wasn't specified or contains
049: // only whitespace, default to explicitly denying none.
050: denyPrefixes = new String[0];
051: } else {
052: // parse the whitespace delimited String into a String array
053: denyPrefixes = denyPrefixesArg.split("\\s");
054: }
055:
056: return new PrefixUriScrutinizer(allowPrefixes, denyPrefixes);
057:
058: }
059:
060: private final Log log = LogFactory.getLog(getClass());
061:
062: /**
063: * Allowed prefixes for URIs examined by this scrutinizer instance.
064: * URIs must match at least one of these prefixes.
065: */
066: private final String[] allowPrefixes;
067:
068: /**
069: * Blocked prefixes for URIs examined by this scrutinizer instance.
070: * URIs must not match any of these prefixes.
071: */
072: private final String[] denyPrefixes;
073:
074: /**
075: * Create a new PrefixUriScrutinizer instance specifying the allowed
076: * URI prefixes and the blocked URI prefixes. Both arguments must not be
077: * null or contain null references. This instance will block all URIs if
078: * allowPrefixesArg is empty. This constructor will copy the argument
079: * arrays, normalizing the prefix content to all-lowercase.
080: * @param allowPrefixesArg non-null potentially empty array of Strings
081: * @param denyPrefixesArg non-null potentially empty array of Strings
082: */
083: public PrefixUriScrutinizer(final String[] allowPrefixesArg,
084: final String[] denyPrefixesArg) {
085:
086: // method implementation is relatively long and complex because it
087: // is doing argument checking and normalization.
088:
089: if (allowPrefixesArg == null) {
090: throw new IllegalArgumentException(
091: "Cannot construct "
092: + "PrefixUriScrutinizer with null array of allow prefixes.");
093: }
094:
095: // copy prefixes, normalizing case to lowercase
096:
097: String[] lowercaseAllowPrefixes = new String[allowPrefixesArg.length];
098: for (int i = 0; i < allowPrefixesArg.length; i++) {
099: String allowPrefix = allowPrefixesArg[i];
100: if (allowPrefix == null) {
101: throw new IllegalArgumentException(
102: "Illegal null in allowPrefixesArg: "
103: + allowPrefixesArg);
104: }
105:
106: lowercaseAllowPrefixes[i] = allowPrefix.toLowerCase();
107: }
108:
109: this .allowPrefixes = lowercaseAllowPrefixes;
110:
111: if (denyPrefixesArg == null) {
112: throw new IllegalArgumentException(
113: "Cannot construct "
114: + "PrefixUriScrutinizer with null array of deny prefixes.");
115: }
116:
117: String[] lowercaseDenyPrefixes = new String[denyPrefixesArg.length];
118: for (int i = 0; i < denyPrefixesArg.length; i++) {
119: String denyPrefix = denyPrefixesArg[i];
120: if (denyPrefix == null) {
121: throw new IllegalArgumentException(
122: "Illegal null in denyPrefixesArg array.");
123: }
124: lowercaseDenyPrefixes[i] = denyPrefix.toLowerCase();
125: }
126:
127: this .denyPrefixes = lowercaseDenyPrefixes;
128: }
129:
130: public void scrutinize(final URI uriArg) throws BlockedUriException {
131:
132: if (log.isTraceEnabled()) {
133: log.trace("Examinging [" + uriArg + "] with scrutinizer "
134: + this );
135: }
136:
137: if (uriArg == null) {
138: throw new IllegalArgumentException(
139: "Cannot scrutinize a null URI.");
140: }
141:
142: // normalize to block devious URIs -- see testcase
143: URI normalizedUri = uriArg.normalize();
144:
145: String uriString = normalizedUri.toString();
146: String lowercaseUriString = uriString.toLowerCase();
147:
148: // default to not accepting the parameter value
149: boolean acceptParamValue = false;
150:
151: // for each allowable prefix, check for match.
152:
153: for (int allowablePrefixNum = 0; allowablePrefixNum < this .allowPrefixes.length; allowablePrefixNum++) {
154:
155: String allowablePrefix = this .allowPrefixes[allowablePrefixNum];
156: if (lowercaseUriString.startsWith(allowablePrefix)) {
157: acceptParamValue = true;
158:
159: // break out of the for loop.
160: // Only need one allowable prefix match.
161: break;
162: }
163: }
164:
165: // if no match, fail
166: if (!acceptParamValue) {
167: throw new BlockedUriException(uriArg,
168: "URI not prefixed by any of the allowed prefixes ("
169: + Arrays.asList(this .allowPrefixes) + ")");
170: }
171:
172: for (int blockedPrefixNum = 0; blockedPrefixNum < this .denyPrefixes.length; blockedPrefixNum++) {
173:
174: String blockedPrefix = this .denyPrefixes[blockedPrefixNum];
175: if (lowercaseUriString.startsWith(blockedPrefix)) {
176:
177: throw new BlockedUriException(uriArg,
178: "URI matched blocked prefix: " + blockedPrefix);
179:
180: }
181: }
182:
183: }
184:
185: /**
186: * Get an unmodifiable List of the Strings allowed as prefixes to URIs
187: * scrutinized by this PrefixUriScrutinizer.
188: * @return an unmodifiable List of Strings
189: */
190: public List getAllowPrefixes() {
191: return Collections.unmodifiableList(Arrays
192: .asList(this .allowPrefixes));
193: }
194:
195: /**
196: * Get an unmodifiable List of the Strings explicitly denied as prefixes to
197: * URIs scrutinized by this PrefixUriScrutinizer.
198: * @return an unmodifiable potentially empty list of Strings
199: */
200: public List getDenyPrefixes() {
201: return Collections.unmodifiableList(Arrays
202: .asList(this .denyPrefixes));
203: }
204:
205: public String toString() {
206: return "PrefixUriScrutinizer allow:" + this .allowPrefixes
207: + " deny:" + this.denyPrefixes;
208: }
209:
210: }
|