path validation v2

This commit is contained in:
Mengyi Zhou 2017-01-26 11:17:16 +08:00 committed by vfqq樊琪琦
parent 54ccdecacf
commit d0088704c1
8 changed files with 308 additions and 7 deletions

View file

@ -1,6 +1,6 @@
package com.ctrip.zeus.service.model;
import com.ctrip.zeus.service.model.handler.RewriteParseHandler;
import com.ctrip.zeus.service.model.grammar.RewriteParseHandler;
import com.ctrip.zeus.service.model.handler.impl.ParseException;
import java.util.ArrayList;

View file

@ -2,7 +2,7 @@ package com.ctrip.zeus.service.model;
import com.ctrip.zeus.exceptions.ValidationException;
import com.ctrip.zeus.service.model.common.MetaType;
import com.ctrip.zeus.util.PathUtils;
import com.ctrip.zeus.service.model.grammar.PathUtils;
import com.google.common.base.Joiner;
import org.springframework.stereotype.Service;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;

View file

@ -0,0 +1,14 @@
package com.ctrip.zeus.service.model.grammar;
/**
* Created by zhoumy on 2017/1/26.
*/
public class GrammarException extends Exception {
public GrammarException(String message) {
super(message);
}
public GrammarException(Throwable throwable) {
super(throwable);
}
}

View file

@ -0,0 +1,172 @@
package com.ctrip.zeus.service.model.grammar;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* Created by zhoumy on 2017/1/24.
*/
public class PathParseHandler {
private static final char[] StandardSuffixPattern = "($|/|\\?)".toCharArray();
private static final List<String> StandardSuffixIdentifier = Arrays.asList("$", "/");
private LoadingCache<char[], String[]> pathLookupCache = CacheBuilder.newBuilder()
.maximumSize(5000)
.expireAfterAccess(1, TimeUnit.DAYS)
.build(new CacheLoader<char[], String[]>() {
@Override
public String[] load(char[] key) throws GrammarException {
return parse(key);
}
});
public String[] parse(String path) throws GrammarException {
try {
return pathLookupCache.get(path.toCharArray());
} catch (ExecutionException e) {
if (e.getCause() instanceof GrammarException) {
throw (GrammarException) e.getCause();
} else {
throw new GrammarException(e.getCause());
}
}
}
public String[] parse(char[] path) throws GrammarException {
List<String> root = new ArrayList<>();
enumeratePathValues(path, root, 0, 0, '\0');
return root.toArray(new String[root.size()]);
}
private int enumeratePathValues(char[] pathArray, List<String> prefix, int start, int depth, char startSymbol) throws GrammarException {
StringBuilder pathBuilder = new StringBuilder();
int i = start;
if (depth < 0) {
throw new GrammarException("Invalid depth " + depth + " during path parsing.");
}
if (depth > 20) {
throw new RuntimeException("Unsupported depth " + depth + " for path parsing.");
}
boolean escaped = false;
for (; i < pathArray.length; i++) {
if (escaped) {
pathBuilder.append('\\').append(pathArray[i]);
escaped = false;
continue;
}
switch (pathArray[i]) {
case '(': {
List<String> subRoot;
if (Arrays.equals(StandardSuffixPattern, Arrays.copyOfRange(pathArray, i, i + 8))) {
subRoot = StandardSuffixIdentifier;
i = i + 7;
} else {
subRoot = new ArrayList<>();
i = enumeratePathValues(pathArray, subRoot, i + 1, depth + 1, '(');
}
String v = pathBuilder.toString();
pathBuilder.setLength(0);
if (prefix.size() == 0) {
for (String s : subRoot) {
prefix.add(v + s);
}
} else {
int psize = prefix.size();
for (int j = 1; j < subRoot.size(); j++) {
for (int k = 0; k < psize; k++) {
prefix.add(prefix.get(k) + v + subRoot.get(j));
}
}
for (int j = 0; j == 0 && subRoot.size() > 0; j++) {
for (int k = 0; k < psize; k++) {
prefix.set(k, prefix.get(k) + v + subRoot.get(j));
}
}
}
}
break;
case ')':
if (pathBuilder.length() > 0) {
String v = pathBuilder.toString();
pathBuilder.setLength(0);
if (prefix.size() == 0) {
prefix.add(v);
} else {
for (int j = 0; j < prefix.size(); j++) {
prefix.set(j, prefix.get(j) + v);
}
}
}
if (startSymbol == '\0') {
throw new GrammarException("Missing left parentheses '(' when parsing to " + new String(pathArray, 0, i) + ".");
}
return i;
case '|':
if (pathBuilder.length() > 0) {
String v = pathBuilder.toString();
pathBuilder.setLength(0);
if (prefix.size() == 0) {
prefix.add(v);
} else {
for (int j = 0; j < prefix.size(); j++) {
prefix.set(j, prefix.get(j) + v);
}
}
}
if (startSymbol == '|') {
return i;
} else {
List<String> subRoot = new ArrayList<>();
i = enumeratePathValues(pathArray, subRoot, i + 1, depth + 1, '|');
for (String s : subRoot) {
prefix.add(s);
}
i--;
}
break;
case '?':
case '*':
case '[':
case ']':
case '{':
case '}':
throw new GrammarException("Character \"" + pathArray[i] + "\" is not allowed in path.");
case '\\':
escaped = !escaped;
break;
default:
pathBuilder.append(pathArray[i]);
break;
}
}
if (depth > 0 && startSymbol != '|') {
throw new GrammarException("Unexpected end of path: invalid depth " + depth + " for end symbol " + startSymbol + ".");
}
if (pathBuilder.length() > 0) {
String v = pathBuilder.toString();
pathBuilder.setLength(0);
if (prefix.size() == 0) {
prefix.add(v);
} else {
for (int j = 0; j < prefix.size(); j++) {
prefix.set(j, prefix.get(j) + v);
}
}
}
return i;
}
}

View file

@ -1,4 +1,4 @@
package com.ctrip.zeus.util;
package com.ctrip.zeus.service.model.grammar;
import com.ctrip.zeus.exceptions.ValidationException;
import com.google.common.collect.Sets;
@ -12,6 +12,7 @@ public class PathUtils {
private static final Set<String> pathPrefixModifier = Sets.newHashSet("=", "~", "~*", "^~");
// 0 equivalent 1 higher priority 2 lower priority
@Deprecated
public static int prefixOverlapped(String path1, String path2, String stopFlag) {
int i = 0;
int idxPath1Suffix = path1.lastIndexOf(stopFlag);
@ -34,6 +35,33 @@ public class PathUtils {
return len1 < len2 ? 2 : 1;
}
/**
* @param path1
* @param path2
* @return the value -1 if path1 and path2 are logically non relevant;
* the value 0 if path1 and path2 is lexicographically equivalent;
* the value 1 if path1 overlaps path2;
* the value 2 if path1 is overlapped by path2.
*/
public static int prefixOverlaps(String path1, String path2) {
int i = 0;
int len1 = path1.length();
int len2 = path2.length();
while (i < len1 && i < len2) {
if (path1.charAt(i) == path2.charAt(i) || Character.toLowerCase(path1.charAt(i)) == Character.toLowerCase(path2.charAt(i))) {
i++;
} else {
return -1;
}
}
if (len1 == len2) {
return 0;
}
return len1 > len2 ? 1 : 2;
}
public static String pathReformat(String path) throws ValidationException {
int offset = 0;
String[] pathValues = new String[2];
@ -57,7 +85,6 @@ public class PathUtils {
return offset == 1 ? pathValues[0] : pathValues[0] + " " + pathValues[1];
}
// expose api for testing
public static String extractUriIgnoresFirstDelimiter(String path) throws ValidationException {
int idxPrefix = 0;
int idxModifier = 0;

View file

@ -1,4 +1,4 @@
package com.ctrip.zeus.service.model.handler;
package com.ctrip.zeus.service.model.grammar;
import com.ctrip.zeus.service.model.handler.impl.ParseException;

View file

@ -8,7 +8,7 @@ import com.ctrip.zeus.dal.core.RelGroupVsDo;
import com.ctrip.zeus.exceptions.ValidationException;
import com.ctrip.zeus.model.entity.*;
import com.ctrip.zeus.service.model.handler.GroupValidator;
import com.ctrip.zeus.util.PathUtils;
import com.ctrip.zeus.service.model.grammar.PathUtils;
import com.google.common.collect.Sets;
import org.junit.Assert;
import org.junit.Before;
@ -16,7 +16,6 @@ import org.junit.Test;
import org.unidal.dal.jdbc.DalException;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

View file

@ -0,0 +1,89 @@
package com.ctrip.zeus.service.model;
import com.ctrip.zeus.exceptions.ValidationException;
import com.ctrip.zeus.service.model.grammar.GrammarException;
import com.ctrip.zeus.service.model.grammar.PathParseHandler;
import com.ctrip.zeus.service.model.grammar.PathUtils;
import com.google.common.collect.Lists;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
/**
* Created by zhoumy on 2017/1/25.
*/
public class PathValidationV2Test {
@Test
public void testExtractUriFromRegexPath() throws ValidationException {
String normalValue1 = "abc($|/|\\?)";
String normalValue2 = "abc";
String normalValue3 = "/abc";
String normalValue4 = "^/abc";
String normalValue5 = "~* /\"abc\"";
String normalValue6 = "~* \"/abc\"";
String normalValue7 = "~* /abc($|/|\\?)";
String creepyValue1 = "\"/abc\"";
String creepyValue2 = "\"^/\\\"abc\\\"\"";
String creepyValue3 = "~* \"^/members($|/|\\?)|membersite($|/|\\?)\"";
String root1 = "~* /";
String root2 = "/";
String root3 = "~* ^/";
String root4 = "~* ^\"/\"";
String root5 = "~* \"^/\"";
Assert.assertEquals("abc($|/|\\?)", extractUri(normalValue1));
Assert.assertEquals("abc", extractUri(normalValue2));
Assert.assertEquals("abc", extractUri(normalValue3));
Assert.assertEquals("abc", extractUri(normalValue4));
Assert.assertEquals("abc", extractUri(normalValue5));
Assert.assertEquals("abc", extractUri(normalValue6));
Assert.assertEquals("abc($|/|\\?)", extractUri(normalValue7));
Assert.assertEquals("abc", extractUri(creepyValue1));
Assert.assertEquals("\\\"abc\\\"", extractUri(creepyValue2));
Assert.assertEquals("members($|/|\\?)|membersite($|/|\\?)", extractUri(creepyValue3));
Assert.assertEquals("/", extractUri(root1));
Assert.assertEquals("/", extractUri(root2));
Assert.assertEquals("/", extractUri(root3));
Assert.assertEquals("/", extractUri(root4));
Assert.assertEquals("/", extractUri(root5));
}
private static String extractUri(String path) throws ValidationException {
path = PathUtils.pathReformat(path);
return PathUtils.extractUriIgnoresFirstDelimiter(path);
}
@Test
public void testResolveRegexPattern() throws GrammarException {
PathParseHandler pph = new PathParseHandler();
Assert.assertArrayEquals(new String[]{"abc"}, pph.parse("abc"));
Assert.assertArrayEquals(new String[]{"/"}, pph.parse("/"));
Assert.assertArrayEquals(new String[]{"a"}, pph.parse("((((a))))"));
Assert.assertArrayEquals(new String[]{"abbefg", "abcefg", "abbeff", "abceff"}, pph.parse("ab(b|c)ef(g|f)"));
Assert.assertArrayEquals(new String[]{"rest", "html", "weba", "webc"}, pph.parse("rest|html|web(a|c)"));
Assert.assertArrayEquals(new String[]{"ababct/d", "abdeft/d", "abat/d", "abct/d"}, pph.parse("ab((abc|def)|(a|c))t/d"));
Assert.assertArrayEquals(new String[]{"ababcat/d", "abdefat/d", "ababcct/d", "abdefct/d"}, pph.parse("ab((abc|def)(a|c))t/d"));
Assert.assertArrayEquals(new String[]{"acd", "bcd", "html"}, pph.parse("(a|b)cd|html"));
Assert.assertArrayEquals(new String[]{"abc", "ef"}, pph.parse("abc|ef"));
Assert.assertArrayEquals(new String[]{"\\(\\(a"}, pph.parse("((\\(\\(a))"));
Assert.assertArrayEquals(new String[]{"abccc", "showjournal-testcc", "bacc", "btacc"}, pph.parse("(abc|show(journal)-test|b(a|ta))cc"));
Assert.assertArrayEquals(new String[]{"\\\"abc\\\""}, pph.parse("\\\"abc\\\""));
Assert.assertArrayEquals(new String[]{"members$", "members/", "membersite$", "membersite/"}, pph.parse("members($|/|\\?)|membersite($|/|\\?)"));
}
@Test
public void testErrorRegexPattern() {
PathParseHandler pph = new PathParseHandler();
List<String> err = Lists.newArrayList("((a", "(a))", "a?", "(a+)*aa");
System.out.printf("%-10s| %s\n", "input", "error");
for (String s : err) {
try {
pph.parse(s);
Assert.assertTrue(false);
} catch (GrammarException e) {
System.out.printf("%-10s| %s\n", s, e.getMessage());
}
}
}
}