X-Git-Url: https://code.wpia.club/?p=gigi.git;a=blobdiff_plain;f=lib%2Fjetty%2Forg%2Feclipse%2Fjetty%2Fhttp%2Fpathmap%2FUriTemplatePathSpec.java;fp=lib%2Fjetty%2Forg%2Feclipse%2Fjetty%2Fhttp%2Fpathmap%2FUriTemplatePathSpec.java;h=208820b6e3ba1117c15d526a87722212f9c328f7;hp=0000000000000000000000000000000000000000;hb=ba4f228fa9f72d50991a2218cfd83987ef5d385e;hpb=875b5e9651498a0cd8e0001c0742ba843e47cad0 diff --git a/lib/jetty/org/eclipse/jetty/http/pathmap/UriTemplatePathSpec.java b/lib/jetty/org/eclipse/jetty/http/pathmap/UriTemplatePathSpec.java new file mode 100644 index 00000000..208820b6 --- /dev/null +++ b/lib/jetty/org/eclipse/jetty/http/pathmap/UriTemplatePathSpec.java @@ -0,0 +1,341 @@ +// +// ======================================================================== +// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// 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) + */ +public class UriTemplatePathSpec extends RegexPathSpec +{ + 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 String variables[]; + + public UriTemplatePathSpec(String rawSpec) + { + super(); + Objects.requireNonNull(rawSpec,"Path Param Spec cannot be null"); + + if ("".equals(rawSpec) || "/".equals(rawSpec)) + { + super.pathSpec = "/"; + super.pattern = Pattern.compile("^/$"); + super.pathDepth = 1; + this.specLength = 1; + this.variables = new String[0]; + this.group = PathSpecGroup.EXACT; + return; + } + + if (rawSpec.charAt(0) != '/') + { + // path specs must start with '/' + StringBuilder err = new StringBuilder(); + err.append("Syntax Error: path spec \""); + err.append(rawSpec); + err.append("\" must start with '/'"); + throw new IllegalArgumentException(err.toString()); + } + + for (String forbidden : FORBIDDEN_SEGMENTS) + { + if (rawSpec.contains(forbidden)) + { + StringBuilder err = new StringBuilder(); + err.append("Syntax Error: segment "); + err.append(forbidden); + err.append(" is forbidden in path spec: "); + err.append(rawSpec); + throw new IllegalArgumentException(err.toString()); + } + } + + this.pathSpec = 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]; + this.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 + StringBuilder err = new StringBuilder(); + err.append("Syntax Error: variable "); + err.append(variable); + err.append(" is duplicated in path spec: "); + err.append(rawSpec); + throw new IllegalArgumentException(err.toString()); + } + + assertIsValidVariableLiteral(variable); + + segmentSignature[i] = 'v'; // variable + // valid variable name + varNames.add(variable); + // build regex + regex.append("/([^/]+)"); + } + else if (mat.find(0)) + { + // variable exists as partial segment + StringBuilder err = new StringBuilder(); + err.append("Syntax Error: variable "); + err.append(mat.group()); + err.append(" must exist as entire path segment: "); + err.append(rawSpec); + throw new IllegalArgumentException(err.toString()); + } + else if ((segment.indexOf('{') >= 0) || (segment.indexOf('}') >= 0)) + { + // variable is split with a path separator + StringBuilder err = new StringBuilder(); + err.append("Syntax Error: invalid path segment /"); + err.append(segment); + err.append("/ variable declaration incomplete: "); + err.append(rawSpec); + throw new IllegalArgumentException(err.toString()); + } + else if (segment.indexOf('*') >= 0) + { + // glob segment + StringBuilder err = new StringBuilder(); + err.append("Syntax Error: path segment /"); + err.append(segment); + err.append("/ contains a wildcard symbol (not supported by this uri-template implementation): "); + err.append(rawSpec); + throw new IllegalArgumentException(err.toString()); + } + else + { + // valid path segment + segmentSignature[i] = 'e'; // exact + // build regex + regex.append('/'); + // escape regex special characters + for (char c : segment.toCharArray()) + { + 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('/'); + } + + regex.append('$'); + + this.pattern = Pattern.compile(regex.toString()); + + int varcount = varNames.size(); + this.variables = varNames.toArray(new String[varcount]); + + // Convert signature to group + String sig = String.valueOf(segmentSignature); + + if (Pattern.matches("^e*$",sig)) + { + this.group = PathSpecGroup.EXACT; + } + else if (Pattern.matches("^e*v+",sig)) + { + this.group = PathSpecGroup.PREFIX_GLOB; + } + else if (Pattern.matches("^v+e+",sig)) + { + this.group = PathSpecGroup.SUFFIX_GLOB; + } + else + { + this.group = PathSpecGroup.MIDDLE_GLOB; + } + } + + /** + * Validate variable literal name, per RFC6570, Section 2.1 Literals + * @param variable + * @param pathParamSpec + */ + private void assertIsValidVariableLiteral(String variable) + { + int len = variable.length(); + + int i = 0; + int codepoint; + boolean valid = (len > 0); // must not be zero length + + while (valid && i < len) + { + codepoint = variable.codePointAt(i); + i += Character.charCount(codepoint); + + // basic letters, digits, or symbols + if (isValidBasicLiteralCodepoint(codepoint)) + { + 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)) + { + continue; + } + } + + valid = false; + } + + if (!valid) + { + // invalid variable name + StringBuilder err = new StringBuilder(); + err.append("Syntax Error: variable {"); + err.append(variable); + err.append("} an invalid variable name: "); + err.append(pathSpec); + throw new IllegalArgumentException(err.toString()); + } + } + + private boolean isValidBasicLiteralCodepoint(int codepoint) + { + // 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) + { + return true; // valid simple value + } + + // basic reserved symbols + if(VARIABLE_RESERVED.indexOf(codepoint) >= 0) + { + LOG.warn("Detected URI Template reserved symbol [{}] in path spec \"{}\"",(char)codepoint,pathSpec); + return false; // valid simple value + } + + return false; + } + + 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(this.variables[i - 1],matcher.group(i)); + } + return ret; + } + return null; + } + + public int getVariableCount() + { + return variables.length; + } + + public String[] getVariables() + { + return this.variables; + } +}