JSR-356 initial pass at PathParam based PathSpec
This commit is contained in:
parent
540a41bdc5
commit
36f0cf41ff
|
@ -18,6 +18,14 @@
|
|||
|
||||
package org.eclipse.jetty.websocket.jsr356.server.pathmap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.websocket.server.ServerEndpoint;
|
||||
|
||||
/**
|
||||
|
@ -25,8 +33,190 @@ import javax.websocket.server.ServerEndpoint;
|
|||
*/
|
||||
public class PathParamSpec extends PathSpec
|
||||
{
|
||||
private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{(.*)\\}");
|
||||
private static final Pattern VALID_VARIABLE_NAME = Pattern.compile("[a-zA-Z0-9._-]+");
|
||||
private static final Set<String> FORBIDDEN_SEGMENTS;
|
||||
|
||||
static
|
||||
{
|
||||
FORBIDDEN_SEGMENTS = new HashSet<>();
|
||||
FORBIDDEN_SEGMENTS.add("/./");
|
||||
FORBIDDEN_SEGMENTS.add("/../");
|
||||
FORBIDDEN_SEGMENTS.add("//");
|
||||
}
|
||||
|
||||
private String variables[];
|
||||
|
||||
public PathParamSpec(String pathParamSpec)
|
||||
{
|
||||
super();
|
||||
Objects.requireNonNull(pathParamSpec,"Path Param Spec cannot be null");
|
||||
|
||||
if ("".equals(pathParamSpec) || "/".equals(pathParamSpec))
|
||||
{
|
||||
super.pathSpec = "/";
|
||||
super.pattern = Pattern.compile("^/$");
|
||||
super.pathDepth = 1;
|
||||
this.specLength = 1;
|
||||
this.variables = new String[0];
|
||||
this.group = PathSpecGroup.EXACT;
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathParamSpec.charAt(0) != '/')
|
||||
{
|
||||
// path specs must start with '/'
|
||||
StringBuilder err = new StringBuilder();
|
||||
err.append("Syntax Error: path spec \"");
|
||||
err.append(pathParamSpec);
|
||||
err.append("\" must start with '/'");
|
||||
throw new IllegalArgumentException(err.toString());
|
||||
}
|
||||
|
||||
for (String forbidden : FORBIDDEN_SEGMENTS)
|
||||
{
|
||||
if (pathParamSpec.contains(forbidden))
|
||||
{
|
||||
StringBuilder err = new StringBuilder();
|
||||
err.append("Syntax Error: segment ");
|
||||
err.append(forbidden);
|
||||
err.append(" is forbidden in path spec: ");
|
||||
err.append(pathParamSpec);
|
||||
throw new IllegalArgumentException(err.toString());
|
||||
}
|
||||
}
|
||||
|
||||
this.pathSpec = pathParamSpec;
|
||||
|
||||
StringBuilder regex = new StringBuilder();
|
||||
regex.append('^');
|
||||
boolean wantDelim = false;
|
||||
boolean isLastSegmentVariable = false;
|
||||
|
||||
List<String> varNames = new ArrayList<>();
|
||||
String segments[] = pathParamSpec.split("/");
|
||||
this.pathDepth = segments.length - 1;
|
||||
for (String segment : segments)
|
||||
{
|
||||
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(pathParamSpec);
|
||||
throw new IllegalArgumentException(err.toString());
|
||||
}
|
||||
else if (VALID_VARIABLE_NAME.matcher(variable).matches())
|
||||
{
|
||||
// valid variable name
|
||||
varNames.add(variable);
|
||||
// build regex
|
||||
if (wantDelim)
|
||||
{
|
||||
regex.append('/');
|
||||
}
|
||||
regex.append("([^/]+)");
|
||||
wantDelim = true;
|
||||
isLastSegmentVariable = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// invalid variable name
|
||||
StringBuilder err = new StringBuilder();
|
||||
err.append("Syntax Error: variable {");
|
||||
err.append(variable);
|
||||
err.append("} an invalid variable name: ");
|
||||
err.append(pathParamSpec);
|
||||
throw new IllegalArgumentException(err.toString());
|
||||
}
|
||||
}
|
||||
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(pathParamSpec);
|
||||
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(pathParamSpec);
|
||||
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: ");
|
||||
err.append(pathParamSpec);
|
||||
throw new IllegalArgumentException(err.toString());
|
||||
}
|
||||
else
|
||||
{
|
||||
// valid path segment
|
||||
// build regex
|
||||
if (wantDelim)
|
||||
{
|
||||
regex.append('/');
|
||||
}
|
||||
regex.append(segment);
|
||||
wantDelim = true;
|
||||
isLastSegmentVariable = false;
|
||||
}
|
||||
}
|
||||
|
||||
regex.append('$');
|
||||
|
||||
this.pattern = Pattern.compile(regex.toString());
|
||||
|
||||
int varcount = varNames.size();
|
||||
this.variables = varNames.toArray(new String[varcount]);
|
||||
|
||||
if (varcount > 1)
|
||||
{
|
||||
this.group = PathSpecGroup.MIDDLE_GLOB;
|
||||
}
|
||||
if (varcount == 1)
|
||||
{
|
||||
if (isLastSegmentVariable)
|
||||
{
|
||||
this.group = PathSpecGroup.PREFIX_GLOB;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.group = PathSpecGroup.MIDDLE_GLOB;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.group = PathSpecGroup.EXACT;
|
||||
}
|
||||
}
|
||||
|
||||
public int getVariableCount()
|
||||
{
|
||||
return variables.length;
|
||||
}
|
||||
|
||||
public String[] getVariables()
|
||||
{
|
||||
return this.variables;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2013 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.websocket.jsr356.server.pathmap;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
/**
|
||||
* Tests for bad path specs on ServerEndpoint Path Param / URI Template
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class PathParamSpecBadSpecsTest
|
||||
{
|
||||
private static void bad(List<String[]> data, String str)
|
||||
{
|
||||
data.add(new String[]
|
||||
{ str });
|
||||
}
|
||||
|
||||
@Parameters
|
||||
public static Collection<String[]> data()
|
||||
{
|
||||
List<String[]> data = new ArrayList<>();
|
||||
bad(data,"/a/b{var}"); // bad syntax - variable does not encompass whole path segment
|
||||
bad(data,"a/{var}"); // bad syntax - no start slash
|
||||
bad(data,"/a/{var/b}"); // path segment separator in variable name
|
||||
bad(data,"/{var}/*"); // bad syntax - no globs allowed
|
||||
bad(data,"/{var}.do"); // bad syntax - variable does not encompass whole path segment
|
||||
bad(data,"/a/{var*}"); // use of glob character not allowed in variable name
|
||||
bad(data,"/a/{}"); // bad syntax - no variable name
|
||||
// MIGHT BE ALLOWED bad(data,"/a/{---}"); // no alpha in variable name
|
||||
bad(data,"{var}"); // bad syntax - no start slash
|
||||
bad(data,"/a/{my special variable}"); // bad syntax - space in variable name
|
||||
bad(data,"/a/{var}/{var}"); // variable name duplicate
|
||||
// MIGHT BE ALLOWED bad(data,"/a/{var}/{Var}/{vAR}"); // variable name duplicated (diff case)
|
||||
bad(data,"/a/../../../{var}"); // path navigation not allowed
|
||||
bad(data,"/a/./{var}"); // path navigation not allowed
|
||||
bad(data,"/a//{var}"); // bad syntax - double path slash (no path segment)
|
||||
return data;
|
||||
}
|
||||
|
||||
private String pathSpec;
|
||||
|
||||
public PathParamSpecBadSpecsTest(String pathSpec)
|
||||
{
|
||||
this.pathSpec = pathSpec;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadPathSpec()
|
||||
{
|
||||
try
|
||||
{
|
||||
new PathParamSpec(this.pathSpec);
|
||||
fail("Expected IllegalArgumentException for a bad PathParam pathspec on: " + pathSpec);
|
||||
}
|
||||
catch (IllegalArgumentException e)
|
||||
{
|
||||
// expected path
|
||||
System.out.println(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2013 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.websocket.jsr356.server.pathmap;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests for ServerEndpoint Path Param / URI Template Path Specs
|
||||
*/
|
||||
public class PathParamSpecTest
|
||||
{
|
||||
@Test
|
||||
public void testDefaultPathSpec()
|
||||
{
|
||||
PathParamSpec spec = new PathParamSpec("/");
|
||||
assertEquals("Spec.pathSpec","/",spec.getPathSpec());
|
||||
assertEquals("Spec.pattern","^/$",spec.getPattern().pattern());
|
||||
assertEquals("Spec.pathDepth",1,spec.getPathDepth());
|
||||
|
||||
assertEquals("Spec.variableCount",0,spec.getVariableCount());
|
||||
assertEquals("Spec.variable.length",0,spec.getVariables().length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExactOnePathSpec()
|
||||
{
|
||||
PathParamSpec spec = new PathParamSpec("/a");
|
||||
assertEquals("Spec.pathSpec","/a",spec.getPathSpec());
|
||||
assertEquals("Spec.pattern","^/a$",spec.getPattern().pattern());
|
||||
assertEquals("Spec.pathDepth",1,spec.getPathDepth());
|
||||
|
||||
assertEquals("Spec.variableCount",0,spec.getVariableCount());
|
||||
assertEquals("Spec.variable.length",0,spec.getVariables().length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExactTwoPathSpec()
|
||||
{
|
||||
PathParamSpec spec = new PathParamSpec("/a/b");
|
||||
assertEquals("Spec.pathSpec","/a/b",spec.getPathSpec());
|
||||
assertEquals("Spec.pattern","^/a/b$",spec.getPattern().pattern());
|
||||
assertEquals("Spec.pathDepth",2,spec.getPathDepth());
|
||||
|
||||
assertEquals("Spec.variableCount",0,spec.getVariableCount());
|
||||
assertEquals("Spec.variable.length",0,spec.getVariables().length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOneVarPathSpec()
|
||||
{
|
||||
PathParamSpec spec = new PathParamSpec("/a/{foo}");
|
||||
assertEquals("Spec.pathSpec","/a/{foo}",spec.getPathSpec());
|
||||
assertEquals("Spec.pattern","^/a/([^/]+)$",spec.getPattern().pattern());
|
||||
assertEquals("Spec.pathDepth",2,spec.getPathDepth());
|
||||
|
||||
assertEquals("Spec.variableCount",1,spec.getVariableCount());
|
||||
assertEquals("Spec.variable.length",1,spec.getVariables().length);
|
||||
assertEquals("Spec.variable[0]","foo",spec.getVariables()[0]);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue