JSR-356 initial pass at PathParam based PathSpec

This commit is contained in:
Joakim Erdfelt 2013-04-09 16:13:54 -07:00
parent 540a41bdc5
commit 36f0cf41ff
3 changed files with 355 additions and 0 deletions

View File

@ -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;
}
}

View File

@ -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());
}
}
}

View File

@ -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]);
}
}