001    /**
002     *
003     * Copyright 2003-2004 The Apache Software Foundation
004     *
005     *  Licensed under the Apache License, Version 2.0 (the "License");
006     *  you may not use this file except in compliance with the License.
007     *  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    
018    package org.apache.geronimo.security.util;
019    
020    import java.util.Collection;
021    import java.util.HashSet;
022    import java.util.Iterator;
023    import java.util.Set;
024    
025    
026    /**
027     * Utility class for <code>ModuleConfiguration</code>.  This class is used to generate qualified patterns, HTTP
028     * method sets, complements of HTTP method sets, and HTTP method sets w/ transport restrictions for URL patterns that
029     * are found in the web deployment descriptor.
030     * @version $Rev: 355877 $ $Date: 2005-12-10 18:48:27 -0800 (Sat, 10 Dec 2005) $
031     */
032    public class URLPattern {
033        private final static String[] HTTP_METHODS = {"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "TRACE"};
034        private final static int[] HTTP_MASKS = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40};
035        private final static int NA = 0x00;
036        private final static int INTEGRAL = 0x01;
037        private final static int CONFIDENTIAL = 0x02;
038    
039        private final URLPatternCheck type;
040        private final String pattern;
041        private int httpMethodsMask;
042        private int transport;
043        private final HashSet roles = new HashSet();
044    
045        /**
046         * Construct an instance of the utility class for <code>WebModuleConfiguration</code>.
047         * @param pat the URL pattern that this instance is to collect information on
048         * @see "JSR 115, section 3.1.3" Translating Servlet Deployment Descriptors
049         */
050        public URLPattern(String pat) {
051            if (pat == null) throw new java.lang.IllegalArgumentException("URL pattern cannot be null");
052            if (pat.length() == 0) throw new java.lang.IllegalArgumentException("URL pattern cannot be empty");
053    
054            if (pat.equals("/") || pat.equals("/*")) {
055                type = DEFAULT;
056            } else if (pat.charAt(0) == '/' && pat.endsWith("/*")) {
057                type = PATH_PREFIX;
058            } else if (pat.charAt(0) == '*') {
059                type = EXTENSION;
060            } else {
061                type = EXACT;
062            }
063            pattern = pat;
064        }
065    
066        /**
067         * Get a qualifed URL pattern relative to a particular set of URL patterns.  This algorithm is described in
068         * JSR 115, section 3.1.3.1 "Qualified URL Pattern Names".
069         * @param patterns the set of possible URL patterns that could be used to qualify this pattern
070         * @return a qualifed URL pattern
071         */
072        public String getQualifiedPattern(Set patterns) {
073            if (type == EXACT) {
074                return pattern;
075            } else {
076                HashSet bucket = new HashSet();
077                StringBuffer result = new StringBuffer(pattern);
078                Iterator iter = patterns.iterator();
079    
080                // Collect a set of qualifying patterns, depending on the type of this pattern.
081                while (iter.hasNext()) {
082                    URLPattern p = (URLPattern) iter.next();
083                    if (type.check(this, p)) {
084                        bucket.add(p.pattern);
085                    }
086                }
087    
088                // append the set of qualifying patterns
089                iter = bucket.iterator();
090                while (iter.hasNext()) {
091                    result.append(':');
092                    result.append((String) iter.next());
093                }
094                return result.toString();
095            }
096        }
097    
098        /**
099         * Add a method to the union of HTTP methods associated with this URL pattern.  An empty string is short hand for
100         * the set of all HTTP methods.
101         * @param method the HTTP method to be added to the set.
102         */
103        public void addMethod(String method) {
104            if (method.length() == 0) {
105                httpMethodsMask = 0xFF;
106                return;
107            }
108    
109            boolean found = false;
110            for (int j = 0; j < HTTP_METHODS.length; j++) {
111                if (method.equals(HTTP_METHODS[j])) {
112                    httpMethodsMask |= HTTP_MASKS[j];
113                    found = true;
114    
115                    break;
116                }
117            }
118            if (!found) throw new IllegalArgumentException("Invalid HTTP method");
119        }
120    
121        /**
122         * Return the set of HTTP methods that have been associated with this URL pattern.
123         * @return a set of HTTP methods
124         */
125        public String getMethods() {
126            StringBuffer buffer = null;
127    
128            for (int i = 0; i < HTTP_MASKS.length; i++) {
129                if ((httpMethodsMask & HTTP_MASKS[i]) > 0) {
130                    if (buffer == null) {
131                        buffer = new StringBuffer();
132                    } else {
133                        buffer.append(",");
134                    }
135                    buffer.append(HTTP_METHODS[i]);
136                }
137            }
138    
139            return (buffer == null ? "" : buffer.toString());
140        }
141    
142        public String getComplementedMethods() {
143            StringBuffer buffer = null;
144    
145            for (int i = 0; i < HTTP_MASKS.length; i++) {
146                if ((httpMethodsMask & HTTP_MASKS[i]) == 0) {
147                    if (buffer == null) {
148                        buffer = new StringBuffer();
149                    } else {
150                        buffer.append(",");
151                    }
152                    buffer.append(HTTP_METHODS[i]);
153                }
154            }
155    
156            return (buffer == null ? "" : buffer.toString());
157        }
158    
159        public String getMethodsWithTransport() {
160            StringBuffer buffer = new StringBuffer(getMethods());
161    
162    
163            if (transport != NA) {
164                buffer.append(":");
165    
166                if (transport != 0x03) {
167                    if (transport == INTEGRAL) {
168                        buffer.append("INTEGRAL");
169                    } else {
170                        buffer.append("CONFIDENTIAL");
171                    }
172                }
173            }
174    
175            return buffer.toString();
176        }
177    
178        public void setTransport(String trans) {
179            switch (transport) {
180                case NA:
181                    {
182                        if ("INTEGRAL".equals(trans)) {
183                            transport = INTEGRAL;
184                        } else if ("CONFIDENTIAL".equals(trans)) {
185                            transport = CONFIDENTIAL;
186                        }
187                        break;
188                    }
189    
190                case INTEGRAL:
191                    {
192                        if ("CONFIDENTIAL".equals(trans)) {
193                            transport = CONFIDENTIAL;
194                        }
195                        break;
196                    }
197            }
198        }
199    
200        public void addRole(String role) {
201            roles.add(role);
202        }
203    
204        public void addAllRoles(Collection collection) {
205            roles.addAll(collection);
206        }
207    
208        public HashSet getRoles() {
209            return roles;
210        }
211    
212        public boolean equals(Object obj) {
213            if (!(obj instanceof URLPattern)) return false;
214    
215            URLPattern test = (URLPattern) obj;
216    
217            return pattern.equals(test.pattern);
218        }
219    
220        public int hashCode() {
221            return pattern.hashCode();
222        }
223    
224        boolean matches(URLPattern p) {
225            String test = p.pattern;
226    
227            // their pattern values are String equivalent
228            if (pattern.equals(test)) return true;
229    
230            return type.matches(pattern, test);
231        }
232    
233        private final static URLPatternCheck EXACT = new URLPatternCheck() {
234            public boolean check(URLPattern base, URLPattern test) {
235                return matches(base.pattern, test.pattern);
236            }
237    
238            public boolean matches(String base, String test) {
239                return base.equals(test);
240            }
241        };
242    
243        private final static URLPatternCheck PATH_PREFIX = new URLPatternCheck() {
244            public boolean check(URLPattern base, URLPattern test) {
245                return ((test.type == PATH_PREFIX || test.type == EXACT)
246                        && base.matches(test)
247                        && !base.equals(test));
248            }
249    
250            /**
251             * This pattern is a path-prefix pattern (that is, it starts with "/" and ends with "/*") and the argument
252             * pattern starts with the substring of this pattern, minus its last 2 characters, and the next character of
253             * the argument pattern, if there is one, is "/"
254             * @param base the base pattern
255             * @param test the pattern to be tested
256             * @return <code>true</code> if <code>test</code> is matched by <code>base</code>
257             */
258            public boolean matches(String base, String test) {
259                int length = base.length() - 2;
260                if (length > test.length()) return false;
261    
262                for (int i = 0; i < length; i++) {
263                    if (base.charAt(i) != test.charAt(i)) return false;
264                }
265    
266                if (test.length() == length)
267                    return true;
268                else if (test.charAt(length) != '/') return false;
269    
270                return true;
271            }
272        };
273    
274        private final static URLPatternCheck EXTENSION = new URLPatternCheck() {
275            public boolean check(URLPattern base, URLPattern test) {
276                if (test.type == PATH_PREFIX) return true;
277    
278                if (test.type == EXACT) return matches(base.pattern, test.pattern);
279    
280                return false;
281            }
282    
283            /**
284             * This pattern is an extension pattern (that is, it startswith "*.") and the argument pattern ends with
285             * this pattern.
286             * @param base the base pattern
287             * @param test the pattern to be tested
288             * @return <code>true</code> if <code>test</code> is matched by <code>base</code>
289             */
290            public boolean matches(String base, String test) {
291                return test.endsWith(base.substring(1));
292            }
293        };
294    
295        private final static URLPatternCheck DEFAULT = new URLPatternCheck() {
296            public boolean check(URLPattern base, URLPattern test) {
297                return base.matches(test) && !base.equals(test);
298            }
299    
300            /**
301             * This pattern is the path-prefix pattern "/*" or the reference pattern is the special default pattern,
302             * "/", which matches all argument patterns.
303             * @param base the base pattern
304             * @param test the pattern to be tested
305             * @return <code>true</code> if <code>test</code> is matched by <code>base</code>
306             * @see "JSR 115"
307             */
308            public boolean matches(String base, String test) {
309                return true;
310            }
311        };
312    }