/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

//
// This source code implements specifications defined by the Java
// Community Process. In order to remain compliant with the specification
// DO NOT add / change / or delete method signatures!
//

package javax.security.jacc;

import java.util.Iterator;
import java.util.LinkedList;

import javax.servlet.http.HttpServletRequest;

/**
 * @version $Rev: 788107 $ $Date: 2009-06-24 20:24:35 +0200 (Wed, 24 Jun 2009) $
 */
final class URLPatternSpec {

    private final String pattern;
    private final URLPattern first;
    private final LinkedList qualifiers = new LinkedList();

    public URLPatternSpec(String name) {
        if (name == null) throw new java.lang.IllegalArgumentException("URLPatternSpec cannot be null");
        if (name.length() == 0) name = "/";

        pattern = name;

        String[] tokens = pattern.split(":", -1);
        first = new URLPattern(tokens[0]);

        URLPattern candidate;
        for (int i = 1; i < tokens.length; i++) {
            candidate = new URLPattern(tokens[i]);

            // No pattern may exist in the URLPatternList that matches the first pattern.
            if (candidate.matches(first)) {
                throw new java.lang.IllegalArgumentException("Qualifier patterns in the URLPatternSpec cannot match the first URLPattern");
            }

            if (first.type == URLPattern.PATH_PREFIX) {

                // If the first pattern is a path-prefix pattern, only exact patterns
                // matched by the first pattern and path-prefix patterns matched by,
                // but different from, the first pattern may occur in the URLPatternList.

                if (candidate.type == URLPattern.EXACT && !first.matches(candidate)) {
                    throw new java.lang.IllegalArgumentException("Exact qualifier patterns in the URLPatternSpec must be matched by the first URLPattern");
                } else if (candidate.type == URLPattern.PATH_PREFIX
                        && !(first.matches(candidate)
                        && first.pattern.length() < candidate.pattern.length())) {
                    throw new java.lang.IllegalArgumentException("path-prefix qualifier patterns in the URLPatternSpec must be matched by, but different from, the first URLPattern");
                } else if (candidate.type == URLPattern.EXTENSION) {
                    throw new java.lang.IllegalArgumentException("extension qualifier patterns in the URLPatternSpec are not allowed when the first URLPattern is path-prefix");
                }
            } else if (first.type == URLPattern.EXTENSION) {

                // If the first pattern is an extension pattern, only exact patterns
                // that are matched by the first pattern and path-prefix patterns may
                // occur in the URLPatternList.

                if (candidate.type == URLPattern.EXACT) {
                    if (!first.matches(candidate)) {
                        throw new java.lang.IllegalArgumentException("Exact qualifier patterns in the URLPatternSpec must be matched when first URLPattern is an extension pattern");
                    }
                } else if (candidate.type != URLPattern.PATH_PREFIX) {
                    throw new java.lang.IllegalArgumentException("Only exact and path-prefix qualifiers in the URLPatternSpec are allowed when first URLPattern is an extension pattern");
                }
            } else if (first.type == URLPattern.DEFAULT) {

                // If the first pattern is the default pattern, "/", any pattern
                // except the default pattern may occur in the URLPatternList.

                if (candidate.type == URLPattern.DEFAULT) {
                    //This is actually tested for by the "qualifier must not match first" rule
                    throw new java.lang.IllegalArgumentException("Qualifier patterns must not be default when first URLPattern is a default pattern");
                }
            } else if (first.type == URLPattern.EXACT) {

                // If the first pattern is an exact pattern a URLPatternList
                // must not be present in the URLPatternSpec

                throw new java.lang.IllegalArgumentException("Qualifier patterns must not be present when first URLPattern is an exact pattern");
            }

            qualifiers.add(candidate);
        }
    }

    public boolean equals(URLPatternSpec o) {
        return implies(o) && o.implies(this);
    }

    public int hashCode() {
        return pattern.hashCode();
    }

    public String getPatternSpec() {
        return pattern;
    }

    public String toString() {
        return pattern;
    }

    public boolean implies(URLPatternSpec p) {

        // The first URLPattern in the name of the argument permission is
        // matched by the first URLPattern in the name of this permission.
        if (!first.matches(p.first)) return false;

        // The first URLPattern in the name of the argument permission is NOT
        // matched by any URLPattern in the URLPatternList of the URLPatternSpec
        // of this permission.
        Iterator iter1 = qualifiers.iterator();
        while (iter1.hasNext()) {
            if (((URLPattern) iter1.next()).matches(p.first)) return false;
        }

        // If the first URLPattern in the name of the argument permission
        // matches the first URLPattern in the URLPatternSpec of this
        // permission, then every URLPattern in the URLPatternList of the
        // URLPatternSpec of this permission is matched by a URLPattern in
        // the URLPatternList of the argument permission.
        if (p.first.matches(first)) {
            Iterator iter2 = p.qualifiers.iterator();

            while (iter2.hasNext()) {
                Iterator iter3 = qualifiers.iterator();
                URLPattern test = (URLPattern) iter2.next();
                boolean found = false;

                while (iter3.hasNext()) {
                    if (test.matches((URLPattern) iter3.next())) {
                        found = true;
                        break;
                    }
                }
                if (!found) return false;
            }
        }

        return true;
    }

    static String encodeColons(HttpServletRequest request) {
        String result = request.getServletPath() + (request.getPathInfo() == null ? "" : request.getPathInfo());

        if (result.indexOf(":") > -1) result = result.replaceAll(":", "%3A");

        return result;
    }

    private class URLPattern {

        public final static int EXACT = 0x0;
        public final static int PATH_PREFIX = 0x1;
        public final static int EXTENSION = 0x2;
        public final static int DEFAULT = 0x4;

        public int type;
        public String pattern;

        public URLPattern(String pat) {
            if (pat == null) throw new java.lang.IllegalArgumentException("URLPattern cannot be null");
            if (pat.length() == 0) throw new java.lang.IllegalArgumentException("URLPattern cannot be empty");

            if (pat.equals("/") || pat.equals("/*")) {
                type = DEFAULT;
            } else if (pat.charAt(0) == '/' && pat.endsWith("/*")) {
                type = PATH_PREFIX;
            } else if (pat.charAt(0) == '*') {
                type = EXTENSION;
            } else {
                type = EXACT;
            }
            pattern = pat;
        }

        public boolean matches(URLPattern p) {

            String test = p.pattern;

            // their pattern values are String equivalent
            if (pattern.equals(test)) return true;

            switch (type) {

                // this pattern is a path-prefix pattern (that is, it starts
                // with "/" and ends with "/*") and the argument pattern
                // starts with the substring of this pattern, minus its last
                // 2 characters, and the next character of the argument pattern,
                // if there is one, is "/"
                case PATH_PREFIX: {
                    int length = pattern.length() - 2;
                    if (length > test.length()) return false;

                    for (int i = 0; i < length; i++) {
                        if (pattern.charAt(i) != test.charAt(i)) return false;
                    }

                    if (test.length() == length) return true;
                    else if (test.charAt(length) != '/') return false;

                    return true;
                }

                // this pattern is an extension pattern (that is, it starts
                // with "*.") and the argument pattern ends with this pattern
                case EXTENSION: {
                    return test.endsWith(pattern.substring(1));
                }

                // this pattern is the path-prefix pattern "/*" or
                // the reference pattern is the special default pattern,
                // "/", which matches all argument patterns
                case DEFAULT: {
                    return true;
                }
            }
            return false;
        }
    }
}
