mirror of
https://github.com/ctripcorp/zeus.git
synced 2024-11-10 09:02:49 +08:00
path validation v2
This commit is contained in:
parent
54ccdecacf
commit
d0088704c1
8 changed files with 308 additions and 7 deletions
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue