[OLINGO-568] Minor code and character validation improvements

This commit is contained in:
Michael Bolz 2015-11-17 15:29:01 +01:00
parent c0adc020b2
commit 6235f3a443
7 changed files with 147 additions and 30 deletions

View File

@ -217,7 +217,7 @@ public class Parser {
systemOption = (OrderByOptionImpl) uriParseTreeVisitor.visitOrderByEOF(ctxOrderByExpression); systemOption = (OrderByOptionImpl) uriParseTreeVisitor.visitOrderByEOF(ctxOrderByExpression);
} else if (option.name.equals(SystemQueryOptionKind.SEARCH.toString())) { } else if (option.name.equals(SystemQueryOptionKind.SEARCH.toString())) {
SearchParser searchParser = new SearchParser(); SearchParser searchParser = new SearchParser();
systemOption = searchParser.parse(path, option.value); systemOption = searchParser.parse(option.value);
} else if (option.name.equals(SystemQueryOptionKind.SELECT.toString())) { } else if (option.name.equals(SystemQueryOptionKind.SELECT.toString())) {
SelectEOFContext ctxSelectEOF = SelectEOFContext ctxSelectEOF =
(SelectEOFContext) parseRule(option.value, ParserEntryRules.Select); (SelectEOFContext) parseRule(option.value, ParserEntryRules.Select);

View File

@ -33,11 +33,11 @@ public class SearchParser {
private Iterator<SearchQueryToken> tokens; private Iterator<SearchQueryToken> tokens;
private SearchQueryToken token; private SearchQueryToken token;
public SearchOption parse(String path, String value) throws SearchParserException, SearchTokenizerException { public SearchOption parse(String searchQuery) throws SearchParserException, SearchTokenizerException {
SearchTokenizer tokenizer = new SearchTokenizer(); SearchTokenizer tokenizer = new SearchTokenizer();
SearchExpression searchExpression; SearchExpression searchExpression;
try { try {
searchExpression = parseInternal(tokenizer.tokenize(value)); searchExpression = parse(tokenizer.tokenize(searchQuery));
} catch (SearchTokenizerException e) { } catch (SearchTokenizerException e) {
return null; return null;
} }
@ -46,7 +46,7 @@ public class SearchParser {
return searchOption; return searchOption;
} }
protected SearchExpression parseInternal(List<SearchQueryToken> tokens) throws SearchParserException { protected SearchExpression parse(List<SearchQueryToken> tokens) throws SearchParserException {
this.tokens = tokens.iterator(); this.tokens = tokens.iterator();
nextToken(); nextToken();
if (token == null) { if (token == null) {
@ -101,10 +101,7 @@ public class SearchParser {
} }
private boolean isToken(SearchQueryToken.Token toCheckToken) { private boolean isToken(SearchQueryToken.Token toCheckToken) {
if (token == null) { return token != null && token.getToken() == toCheckToken;
return false;
}
return token.getToken() == toCheckToken;
} }
private void validateToken(SearchQueryToken.Token toValidateToken) throws SearchParserException { private void validateToken(SearchQueryToken.Token toValidateToken) throws SearchParserException {
@ -194,6 +191,6 @@ public class SearchParser {
private SearchTerm processPhrase() { private SearchTerm processPhrase() {
String literal = token.getLiteral(); String literal = token.getLiteral();
nextToken(); nextToken();
return new SearchTermImpl(literal); return new SearchTermImpl(literal.substring(1,literal.length()-1));
} }
} }

View File

@ -19,7 +19,7 @@
package org.apache.olingo.server.core.uri.parser.search; package org.apache.olingo.server.core.uri.parser.search;
public interface SearchQueryToken { public interface SearchQueryToken {
enum Token {OPEN, TERM, NOT, AND, OR, WORD, PHRASE, CLOSE} enum Token {OPEN, NOT, AND, OR, WORD, PHRASE, CLOSE}
Token getToken(); Token getToken();
String getLiteral(); String getLiteral();

View File

@ -98,16 +98,22 @@ public class SearchTokenizer {
} }
/** /**
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" * searchPhrase = quotation-mark 1*qchar-no-AMP-DQUOTE quotation-mark
* other-delims = "!" / "(" / ")" / "*" / "+" / "," / ";" *
* qchar-no-AMP-DQUOTE = qchar-unescaped / escape ( escape / quotation-mark )
*
* qchar-unescaped = unreserved / pct-encoded-unescaped / other-delims / ":" / "@" / "/" / "?" / "$" / "'" / "=" * qchar-unescaped = unreserved / pct-encoded-unescaped / other-delims / ":" / "@" / "/" / "?" / "$" / "'" / "="
*
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
*
* escape = "\" / "%5C" ; reverse solidus U+005C
*
* pct-encoded-unescaped = "%" ( "0" / "1" / "3" / "4" / "6" / "7" / "8" / "9" / A-to-F ) HEXDIG * pct-encoded-unescaped = "%" ( "0" / "1" / "3" / "4" / "6" / "7" / "8" / "9" / A-to-F ) HEXDIG
* / "%" "2" ( "0" / "1" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / A-to-F ) * / "%" "2" ( "0" / "1" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / A-to-F )
* / "%" "5" ( DIGIT / "A" / "B" / "D" / "E" / "F" ) * / "%" "5" ( DIGIT / "A" / "B" / "D" / "E" / "F" )
* *
* qchar-no-AMP-DQUOTE = qchar-unescaped / escape ( escape / quotation-mark ) * other-delims = "!" / "(" / ")" / "*" / "+" / "," / ";"
* *
* escape = "\" / "%5C" ; reverse solidus U+005C
* quotation-mark = DQUOTE / "%22" * quotation-mark = DQUOTE / "%22"
* *
* ALPHA = %x41-5A / %x61-7A * ALPHA = %x41-5A / %x61-7A
@ -119,19 +125,100 @@ public class SearchTokenizer {
*/ */
static boolean isAllowedPhrase(final char character) { static boolean isAllowedPhrase(final char character) {
// FIXME mibo: check missing // FIXME mibo: check missing
return isAlphaOrDigit(character) return isQCharUnescaped(character) || isEscaped(character);
|| character == '-'
|| character == '.'
|| character == '_'
|| character == '~'
|| character == ':'
|| character == '@'
|| character == '/'
|| character == '$'
|| character == '\''
|| character == '=';
} }
/**
* escape = "\" / "%5C" ; reverse solidus U+005C
* @param character which is checked
* @return true if character is allowed
*/
private static boolean isEscaped(char character) {
// TODO: mibo(151117): check how to implement
return false;
}
/**
* qchar-unescaped = unreserved / pct-encoded-unescaped / other-delims / ":" / "@" / "/" / "?" / "$" / "'" / "="
* @param character which is checked
* @return true if character is allowed
*/
private static boolean isQCharUnescaped(char character) {
return isUnreserved(character)
|| isPctEncodedUnescaped(character)
|| isOtherDelims(character)
|| character == ':'
|| character == '@'
|| character == '/'
|| character == '$'
|| character == '\''
|| character == '=';
}
/**
* other-delims = "!" / "(" / ")" / "*" / "+" / "," / ";"
* @param character which is checked
* @return true if character is allowed
*/
private static boolean isOtherDelims(char character) {
return character == '!'
|| character == '('
|| character == ')'
|| character == '*'
|| character == '+'
|| character == ','
|| character == ';';
}
/**
* pct-encoded-unescaped = "%" ( "0" / "1" / "3" / "4" / "6" / "7" / "8" / "9" / A-to-F ) HEXDIG
* / "%" "2" ( "0" / "1" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / A-to-F )
* / "%" "5" ( DIGIT / "A" / "B" / "D" / "E" / "F" )
*
* HEXDIG = DIGIT / A-to-F
*
* @param character which is checked
* @return true if character is allowed
*/
private static boolean isPctEncodedUnescaped(char character) {
String hex = Integer.toHexString((int) character);
char aschar[] = hex.toCharArray();
if(aschar[0] == '%') {
if(aschar[1] == '2') {
return aschar[2] != '2' && isHexDigit(aschar[2]);
} else if(aschar[1] == '5') {
return aschar[2] != 'C' && isHexDigit(aschar[2]);
} else if(isHexDigit(aschar[1])) {
return isHexDigit(aschar[2]);
}
}
return false;
}
private static boolean isHexDigit(char character) {
return 'A' <= character && character <= 'F' // case A..F
|| '0' <= character && character <= '9'; // case 0..9
}
/**
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* @param character which is checked
* @return true if character is allowed
*/
private static boolean isUnreserved(char character) {
return isAlphaOrDigit(character)
|| character == '-'
|| character == '.'
|| character == '_'
|| character == '~';
}
/**
* ALPHA = %x41-5A / %x61-7A
* DIGIT = %x30-39
* @param character which is checked
* @return true if character is allowed
*/
private static boolean isAlphaOrDigit(char character) { private static boolean isAlphaOrDigit(char character) {
return 'A' <= character && character <= 'Z' // case A..Z return 'A' <= character && character <= 'Z' // case A..Z
|| 'a' <= character && character <= 'z' // case a..z || 'a' <= character && character <= 'z' // case a..z
@ -220,7 +307,7 @@ public class SearchTokenizer {
private class SearchTermState extends LiteralState { private class SearchTermState extends LiteralState {
public SearchTermState() { public SearchTermState() {
super(Token.TERM); super(null);
} }
@Override @Override

View File

@ -185,7 +185,7 @@ public class SearchParserAndTokenizerTest {
private SearchExpression getSearchExpression() throws SearchParserException, SearchTokenizerException { private SearchExpression getSearchExpression() throws SearchParserException, SearchTokenizerException {
SearchParser tokenizer = new SearchParser(); SearchParser tokenizer = new SearchParser();
SearchOption result = tokenizer.parse(null, searchQuery); SearchOption result = tokenizer.parse(searchQuery);
Assert.assertNotNull(result); Assert.assertNotNull(result);
final SearchExpression searchExpression = result.getSearchExpression(); final SearchExpression searchExpression = result.getSearchExpression();
Assert.assertNotNull(searchExpression); Assert.assertNotNull(searchExpression);
@ -195,5 +195,4 @@ public class SearchParserAndTokenizerTest {
return searchExpression; return searchExpression;
} }
} }
} }

View File

@ -220,7 +220,7 @@ public class SearchParserTest extends SearchParser {
private SearchExpression run(SearchQueryToken.Token... tokenArray) throws SearchParserException { private SearchExpression run(SearchQueryToken.Token... tokenArray) throws SearchParserException {
List<SearchQueryToken> tokenList = prepareTokens(tokenArray); List<SearchQueryToken> tokenList = prepareTokens(tokenArray);
SearchExpression se = parseInternal(tokenList); SearchExpression se = parse(tokenList);
assertNotNull(se); assertNotNull(se);
return se; return se;
} }
@ -236,7 +236,7 @@ public class SearchParserTest extends SearchParser {
when(token.getLiteral()).thenReturn("word" + wordNumber); when(token.getLiteral()).thenReturn("word" + wordNumber);
wordNumber++; wordNumber++;
} else if (aTokenArray == Token.PHRASE) { } else if (aTokenArray == Token.PHRASE) {
when(token.getLiteral()).thenReturn("phrase" + phraseNumber); when(token.getLiteral()).thenReturn("\"phrase" + phraseNumber + "\"");
phraseNumber++; phraseNumber++;
} }
when(token.toString()).thenReturn("" + aTokenArray); when(token.toString()).thenReturn("" + aTokenArray);

View File

@ -19,6 +19,7 @@
package org.apache.olingo.server.core.uri.parser.search; package org.apache.olingo.server.core.uri.parser.search;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import java.util.ArrayList; import java.util.ArrayList;
@ -109,7 +110,40 @@ public class SearchTokenizerTest {
SearchValidator.init("abc or \"xyz\"").addExpected(WORD, WORD, PHRASE).validate(); SearchValidator.init("abc or \"xyz\"").addExpected(WORD, WORD, PHRASE).validate();
} }
/**
* https://tools.oasis-open.org/version-control/browse/wsvn/odata/trunk/spec/ABNF/odata-abnf-testcases.xml
* @throws Exception
*/
@Test @Test
@Ignore("Test must be moved to SearchParserTest and SearchParserAndTokenizerTest")
public void parsePhraseAbnfTestcases() throws Exception {
// <TestCase Name="5.1.7 Search - simple phrase" Rule="queryOptions">
SearchValidator.init("\"blue%20green\"").validate();
// <TestCase Name="5.1.7 Search - simple phrase" Rule="queryOptions">
SearchValidator.init("\"blue%20green%22").validate();
// <TestCase Name="5.1.7 Search - phrase with escaped double-quote" Rule="queryOptions">
// <Input>$search="blue\"green"</Input>
SearchValidator.init("\"blue\\\"green\"").validate();
// <TestCase Name="5.1.7 Search - phrase with escaped backslash" Rule="queryOptions">
// <Input>$search="blue\\green"</Input>
SearchValidator.init("\"blue\\\\green\"").validate();
// <TestCase Name="5.1.7 Search - phrase with unescaped double-quote" Rule="queryOptions" FailAt="14">
SearchValidator.init("\"blue\"green\"").validate();
// <TestCase Name="5.1.7 Search - phrase with unescaped double-quote" Rule="queryOptions" FailAt="16">
SearchValidator.init("\"blue%22green\"").validate();
// <TestCase Name="5.1.7 Search - implicit AND" Rule="queryOptions">
// <Input>$search=blue green</Input>
// SearchValidator.init("\"blue%20green\"").validate();
// <TestCase Name="5.1.7 Search - implicit AND, encoced" Rule="queryOptions">
// SearchValidator.init("blue%20green").validate();
}
@Test
public void parseNot() throws Exception { public void parseNot() throws Exception {
SearchTokenizer tokenizer = new SearchTokenizer(); SearchTokenizer tokenizer = new SearchTokenizer();
List<SearchQueryToken> result; List<SearchQueryToken> result;