All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.http.pathmap.UriTemplatePathSpec Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
// 
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// 
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// 
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// 
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
// 
package org.eclipse.jetty.http.pathmap;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;

/**
 *  PathSpec for URI Template based declarations
 *
 *  @see URI Templates (Level 1)
 *
 * @deprecated The Eclipse Jetty and Apache Felix Http Jetty packages are no longer supported.
 */
@Deprecated(since = "2021-05-27")
public class UriTemplatePathSpec extends AbstractPathSpec {

    private static final Logger LOG = Log.getLogger(UriTemplatePathSpec.class);

    private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{(.*)\\}");

    /**
     * Reserved Symbols in URI Template variable
     */
    private static final String VARIABLE_RESERVED = // gen-delims
    ":/?#[]@" + // sub-delims
    "!$&'()*+,;=";

    /**
     * Allowed Symbols in a URI Template variable
     */
    private static final String VARIABLE_SYMBOLS = "-._";

    private static final Set FORBIDDEN_SEGMENTS;

    static {
        FORBIDDEN_SEGMENTS = new HashSet<>();
        FORBIDDEN_SEGMENTS.add("/./");
        FORBIDDEN_SEGMENTS.add("/../");
        FORBIDDEN_SEGMENTS.add("//");
    }

    private final String _declaration;

    private final PathSpecGroup _group;

    private final int _pathDepth;

    private final int _specLength;

    private final Pattern _pattern;

    private final String[] _variables;

    /**
     * The logical (simplified) declaration
     */
    private final String _logicalDeclaration;

    public UriTemplatePathSpec(String rawSpec) {
        Objects.requireNonNull(rawSpec, "Path Param Spec cannot be null");
        if ("".equals(rawSpec) || "/".equals(rawSpec)) {
            _declaration = "/";
            _group = PathSpecGroup.EXACT;
            _pathDepth = 1;
            _specLength = 1;
            _pattern = Pattern.compile("^/$");
            _variables = new String[0];
            _logicalDeclaration = "/";
            return;
        }
        if (rawSpec.charAt(0) != '/') {
            // path specs must start with '/'
            throw new IllegalArgumentException("Syntax Error: path spec \"" + rawSpec + "\" must start with '/'");
        }
        for (String forbidden : FORBIDDEN_SEGMENTS) {
            if (rawSpec.contains(forbidden))
                throw new IllegalArgumentException("Syntax Error: segment " + forbidden + " is forbidden in path spec: " + rawSpec);
        }
        String declaration = rawSpec;
        StringBuilder regex = new StringBuilder();
        regex.append('^');
        List varNames = new ArrayList<>();
        // split up into path segments (ignoring the first slash that will always be empty)
        String[] segments = rawSpec.substring(1).split("/");
        char[] segmentSignature = new char[segments.length];
        StringBuilder logicalSignature = new StringBuilder();
        int pathDepth = segments.length;
        for (int i = 0; i < segments.length; i++) {
            String segment = segments[i];
            Matcher mat = VARIABLE_PATTERN.matcher(segment);
            if (mat.matches()) {
                // entire path segment is a variable.
                String variable = mat.group(1);
                if (varNames.contains(variable)) {
                    // duplicate variable names
                    throw new IllegalArgumentException("Syntax Error: variable " + variable + " is duplicated in path spec: " + rawSpec);
                }
                assertIsValidVariableLiteral(variable, declaration);
                // variable
                segmentSignature[i] = 'v';
                logicalSignature.append("/*");
                // valid variable name
                varNames.add(variable);
                // build regex
                regex.append("/([^/]+)");
            } else if (mat.find(0)) {
                // variable exists as partial segment
                throw new IllegalArgumentException("Syntax Error: variable " + mat.group() + " must exist as entire path segment: " + rawSpec);
            } else if ((segment.indexOf('{') >= 0) || (segment.indexOf('}') >= 0)) {
                // variable is split with a path separator
                throw new IllegalArgumentException("Syntax Error: invalid path segment /" + segment + "/ variable declaration incomplete: " + rawSpec);
            } else if (segment.indexOf('*') >= 0) {
                // glob segment
                throw new IllegalArgumentException("Syntax Error: path segment /" + segment + "/ contains a wildcard symbol (not supported by this uri-template implementation): " + rawSpec);
            } else {
                // valid path segment
                // exact
                segmentSignature[i] = 'e';
                logicalSignature.append('/').append(segment);
                // build regex
                regex.append('/');
                // escape regex special characters
                for (int j = 0; j < segment.length(); j++) {
                    char c = segment.charAt(j);
                    if ((c == '.') || (c == '[') || (c == ']') || (c == '\\'))
                        regex.append('\\');
                    regex.append(c);
                }
            }
        }
        // Handle trailing slash (which is not picked up during split)
        if (rawSpec.charAt(rawSpec.length() - 1) == '/') {
            regex.append('/');
            logicalSignature.append('/');
        }
        regex.append('$');
        Pattern pattern = Pattern.compile(regex.toString());
        int varcount = varNames.size();
        String[] variables = varNames.toArray(new String[varcount]);
        // Convert signature to group
        String sig = String.valueOf(segmentSignature);
        PathSpecGroup group;
        if (Pattern.matches("^e*$", sig))
            group = PathSpecGroup.EXACT;
        else if (Pattern.matches("^e*v+", sig))
            group = PathSpecGroup.PREFIX_GLOB;
        else if (Pattern.matches("^v+e+", sig))
            group = PathSpecGroup.SUFFIX_GLOB;
        else
            group = PathSpecGroup.MIDDLE_GLOB;
        _declaration = declaration;
        _group = group;
        _pathDepth = pathDepth;
        _specLength = declaration.length();
        _pattern = pattern;
        _variables = variables;
        _logicalDeclaration = logicalSignature.toString();
        if (LOG.isDebugEnabled()) {
            LOG.debug("Creating UriTemplatePathSpec[{}] (regex: \"{}\", signature: [{}], group: {}, variables: [{}])", _declaration, regex, sig, _group, String.join(", ", _variables));
        }
    }

    /**
     * Validate variable literal name, per RFC6570, Section 2.1 Literals
     */
    private static void assertIsValidVariableLiteral(String variable, String declaration) {
        int len = variable.length();
        int i = 0;
        int codepoint;
        // must not be zero length
        boolean valid = (len > 0);
        while (valid && i < len) {
            codepoint = variable.codePointAt(i);
            i += Character.charCount(codepoint);
            // basic letters, digits, or symbols
            if (isValidBasicLiteralCodepoint(codepoint, declaration))
                continue;
            // The ucschar and iprivate pieces
            if (Character.isSupplementaryCodePoint(codepoint))
                continue;
            // pct-encoded
            if (codepoint == '%') {
                if (i + 2 > len) {
                    // invalid percent encoding, missing extra 2 chars
                    valid = false;
                    continue;
                }
                codepoint = TypeUtil.convertHexDigit(variable.codePointAt(i++)) << 4;
                codepoint |= TypeUtil.convertHexDigit(variable.codePointAt(i++));
                // validate basic literal
                if (isValidBasicLiteralCodepoint(codepoint, declaration))
                    continue;
            }
            valid = false;
        }
        if (!valid) {
            // invalid variable name
            throw new IllegalArgumentException("Syntax Error: variable {" + variable + "} an invalid variable name: " + declaration);
        }
    }

    private static boolean isValidBasicLiteralCodepoint(int codepoint, String declaration) {
        // basic letters or digits
        if ((codepoint >= 'a' && codepoint <= 'z') || (codepoint >= 'A' && codepoint <= 'Z') || (codepoint >= '0' && codepoint <= '9'))
            return true;
        // basic allowed symbols
        if (VARIABLE_SYMBOLS.indexOf(codepoint) >= 0)
            // valid simple value
            return true;
        // basic reserved symbols
        if (VARIABLE_RESERVED.indexOf(codepoint) >= 0) {
            LOG.warn("Detected URI Template reserved symbol [{}] in path spec \"{}\"", (char) codepoint, declaration);
            // valid simple value
            return false;
        }
        return false;
    }

    @Override
    public int compareTo(PathSpec other) {
        if (other instanceof UriTemplatePathSpec) {
            UriTemplatePathSpec otherUriPathSpec = (UriTemplatePathSpec) other;
            return otherUriPathSpec._logicalDeclaration.compareTo(this._logicalDeclaration);
        } else {
            return super.compareTo(other);
        }
    }

    public Map getPathParams(String path) {
        Matcher matcher = getMatcher(path);
        if (matcher.matches()) {
            if (_group == PathSpecGroup.EXACT)
                return Collections.emptyMap();
            Map ret = new HashMap<>();
            int groupCount = matcher.groupCount();
            for (int i = 1; i <= groupCount; i++) {
                ret.put(_variables[i - 1], matcher.group(i));
            }
            return ret;
        }
        return null;
    }

    protected Matcher getMatcher(String path) {
        int idx = path.indexOf('?');
        if (idx >= 0) {
            // match only non-query part
            return _pattern.matcher(path.substring(0, idx));
        } else {
            // match entire path
            return _pattern.matcher(path);
        }
    }

    @Override
    public int getSpecLength() {
        return _specLength;
    }

    @Override
    public PathSpecGroup getGroup() {
        return _group;
    }

    @Override
    public int getPathDepth() {
        return _pathDepth;
    }

    @Override
    public String getPathInfo(String path) {
        // Path Info only valid for PREFIX_GLOB types
        if (_group == PathSpecGroup.PREFIX_GLOB) {
            Matcher matcher = getMatcher(path);
            if (matcher.matches()) {
                if (matcher.groupCount() >= 1) {
                    String pathInfo = matcher.group(1);
                    if ("".equals(pathInfo))
                        return "/";
                    else
                        return pathInfo;
                }
            }
        }
        return null;
    }

    @Override
    public String getPathMatch(String path) {
        Matcher matcher = getMatcher(path);
        if (matcher.matches()) {
            if (_group == PathSpecGroup.PREFIX_GLOB && matcher.groupCount() >= 1) {
                int idx = matcher.start(1);
                if (idx > 0) {
                    if (path.charAt(idx - 1) == '/')
                        idx--;
                    return path.substring(0, idx);
                }
            }
            return path;
        }
        return null;
    }

    @Override
    public String getDeclaration() {
        return _declaration;
    }

    @Override
    public String getPrefix() {
        return null;
    }

    @Override
    public String getSuffix() {
        return null;
    }

    public Pattern getPattern() {
        return _pattern;
    }

    @Override
    public boolean matches(final String path) {
        return getMatcher(path).matches();
    }

    @Override
    public MatchedPath matched(String path) {
        Matcher matcher = getMatcher(path);
        if (matcher.matches()) {
            return new UriTemplatePathSpec.UriTemplateMatchedPath(this, path, matcher);
        }
        return null;
    }

    public int getVariableCount() {
        return _variables.length;
    }

    public String[] getVariables() {
        return _variables;
    }

    // @deprecated The Eclipse Jetty and Apache Felix Http Jetty packages are no longer supported.
    @Deprecated(since = "2021-05-27")
    private static class UriTemplateMatchedPath implements MatchedPath {

        private final UriTemplatePathSpec pathSpec;

        private final String path;

        private final Matcher matcher;

        public UriTemplateMatchedPath(UriTemplatePathSpec uriTemplatePathSpec, String path, Matcher matcher) {
            this.pathSpec = uriTemplatePathSpec;
            this.path = path;
            this.matcher = matcher;
        }

        @Override
        public String getPathMatch() {
            // TODO: UriTemplatePathSpec has no concept of prefix/suffix, this should be simplified
            // TODO: Treat all UriTemplatePathSpec matches as exact when it comes to pathMatch/pathInfo
            if (pathSpec.getGroup() == PathSpecGroup.PREFIX_GLOB && matcher.groupCount() >= 1) {
                int idx = matcher.start(1);
                if (idx > 0) {
                    if (path.charAt(idx - 1) == '/')
                        idx--;
                    return path.substring(0, idx);
                }
            }
            return path;
        }

        @Override
        public String getPathInfo() {
            // TODO: UriTemplatePathSpec has no concept of prefix/suffix, this should be simplified
            // TODO: Treat all UriTemplatePathSpec matches as exact when it comes to pathMatch/pathInfo
            if (pathSpec.getGroup() == PathSpecGroup.PREFIX_GLOB && matcher.groupCount() >= 1) {
                String pathInfo = matcher.group(1);
                if ("".equals(pathInfo))
                    return "/";
                else
                    return pathInfo;
            }
            return null;
        }

        @Override
        public String toString() {
            return getClass().getSimpleName() + "[" + "pathSpec=" + pathSpec + ", path=\"" + path + "\"" + ", matcher=" + matcher + ']';
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy