diff --git a/pom.xml b/pom.xml index 417e170..a36a151 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 direct-policy Direct Project policy enablement engine - 8.1.0 + 8.1.2 Direct Project policy enablement engine 2010 http://api.nhindirect.org/x/www/api.nhindirect.org/java/site/direct-policy/${project.version} @@ -204,7 +204,6 @@ - org.sonatype.central central-publishing-maven-plugin diff --git a/src/main/java/org/nhindirect/policy/impl/SimpleTextV1LexiconPolicyParser.java b/src/main/java/org/nhindirect/policy/impl/SimpleTextV1LexiconPolicyParser.java index bbee05b..fe6c750 100644 --- a/src/main/java/org/nhindirect/policy/impl/SimpleTextV1LexiconPolicyParser.java +++ b/src/main/java/org/nhindirect/policy/impl/SimpleTextV1LexiconPolicyParser.java @@ -169,6 +169,7 @@ public class SimpleTextV1LexiconPolicyParser extends XMLLexiconPolicyParser tokenMap = new HashMap(); // build the token list + // Note: do not add a raw quote token here. Quoted text is handled specially in parseToTokens. tokenMap.put("(", TokenType.START_LEVEL); tokenMap.put(")", TokenType.END_LEVEL); @@ -180,7 +181,7 @@ public class SimpleTextV1LexiconPolicyParser extends XMLLexiconPolicyParser // add the X509Fields tokenMap.put(X509FieldType.SIGNATURE.getFieldToken(), TokenType.CERTIFICATE_REFERENCE_EXPRESSION); tokenMap.put(X509FieldType.SIGNATURE_ALGORITHM.getFieldToken(), TokenType.CERTIFICATE_REFERENCE_EXPRESSION); - + // add the TBS fields final TBSFieldName[] tbsFieldNames = (TBSFieldName[].class.cast(TBSFieldName.class.getEnumConstants())); for (TBSFieldName tbsFieldName : tbsFieldNames) @@ -489,9 +490,67 @@ protected Vector parseToTokens(InputStream stream) throws boolean holdMode = false; TokenType holdType = null; - for (int i; (i = isr.read()) > 0; ) + for (int ci; (ci = isr.read()) != -1; ) { - writer.write(i); + final char ch = (char)ci; + + // If we encounter an opening quote, flush any buffered text and read until the closing quote. + if (ch == '"') + { + final String pre = writer.toString().trim(); + if (!pre.isEmpty()) + { + final TokenType exactPreToken = tokenMap.get(pre); + final TokenType addPreType = (exactPreToken != null) ? exactPreToken : TokenType.LITERAL_EXPRESSION; + tokens.add(new TokenTypeAssociation(pre, addPreType)); + } + writer = new StringWriter(); + + // read until closing quote or EOF; everything inside is a literal expression + StringBuilder quoted = new StringBuilder(); + int cj; + while ((cj = isr.read()) != -1) + { + final char qc = (char)cj; + // handle escape sequences + if (qc == '\\') + { + int nd = isr.read(); + if (nd == -1) + { + // dangling backslash at EOF; treat the backslash literally + quoted.append('\\'); + break; + } + final char esc = (char)nd; + switch (esc) + { + case '\\': + quoted.append('\\'); + break; + case '"': + quoted.append('"'); + break; + default: + // unknown escape - preserve the backslash and the character (e.g., file paths like \t) + quoted.append('\\'); + quoted.append(esc); + break; + } + continue; + } + if (qc == '"') + break; // found closing quote + quoted.append(qc); + } + + tokens.add(new TokenTypeAssociation(quoted.toString(), TokenType.LITERAL_EXPRESSION)); + // continue to next character + continue; + } + + // Normal processing: append char and continue token detection + writer.write(ch); final String checkForTokenString = writer.toString(); // check to see if we have an exact match to a token @@ -499,7 +558,7 @@ protected Vector parseToTokens(InputStream stream) throws if (exactMatchToken != null) { - // if the token is an operator, we need to keep looking forward to the next + // if the token is an operator or certificate ref, we need to keep looking forward to the next // character because some operators are made up of the exact same characters.. we // may have a partial string of an operator with more characters if (exactMatchToken == TokenType.OPERATOR_EXPRESSION || exactMatchToken == TokenType.CERTIFICATE_REFERENCE_EXPRESSION) @@ -535,8 +594,8 @@ protected Vector parseToTokens(InputStream stream) throws exactMatchToken = tokenMap.get(nextToken); if (exactMatchToken != null) - { - + { + tokens.add(new TokenTypeAssociation(nextToken, exactMatchToken)); } else @@ -549,6 +608,7 @@ protected Vector parseToTokens(InputStream stream) throws { // we didn't hit an exact match, but the new character we hit may be a reserved token // check to see if the checkForTokenString now contains a reserved token + boolean handled = false; for (String key : tokenMap.keySet()) { int idx = checkForTokenString.indexOf(key); @@ -581,13 +641,19 @@ protected Vector parseToTokens(InputStream stream) throws // the token is not an operator, so add it the token vector tokens.add(new TokenTypeAssociation(secondToken, exactMatchToken)); } + handled = true; break; } } + + if (!handled) + { + // nothing special found; continue accumulating + } } } - } - + } + // now that we have completed traversing the expression lexicon, if there is anything left over in the writer then // add it as a token final String remainingString = writer.toString().trim(); @@ -638,7 +704,13 @@ protected static enum TokenType /** * A certificate reference expression */ - CERTIFICATE_REFERENCE_EXPRESSION; + CERTIFICATE_REFERENCE_EXPRESSION, + + /** + * Marks the beginning or end of LITERAL_EXPRESSION where all contents regardless of character will + * be considered part of the LIBERAL_EXPRESSION. + */ + LITERAL_QUOTE; } protected Integer resetLevel() diff --git a/src/test/java/org/nhindirect/policy/impl/SimpleTextV1LexiconPolicyParser_parseToTokensTest.java b/src/test/java/org/nhindirect/policy/impl/SimpleTextV1LexiconPolicyParser_parseToTokensTest.java index 1bcda92..0293b4f 100644 --- a/src/test/java/org/nhindirect/policy/impl/SimpleTextV1LexiconPolicyParser_parseToTokensTest.java +++ b/src/test/java/org/nhindirect/policy/impl/SimpleTextV1LexiconPolicyParser_parseToTokensTest.java @@ -75,4 +75,18 @@ public void testParse_requiredCertField_validateTokens() throws Exception stream.close(); } + + @Test + public void testParse_regExWithParenthsis_validateTokens() throws Exception + { + final SimpleTextV1LexiconPolicyParser parser = new SimpleTextV1LexiconPolicyParser(); + final InputStream stream = FileUtils.openInputStream(new File("./src/test/resources/policies/simpleLexiconWithRegEx.txt")); + + Vector tokens = parser.parseToTokens(stream); + assertEquals(3, tokens.size()); + + stream.close(); + } + + } diff --git a/src/test/java/org/nhindirect/policy/impl/SimpleTextV1LexiconPolicyParser_quotedEscapesTest.java b/src/test/java/org/nhindirect/policy/impl/SimpleTextV1LexiconPolicyParser_quotedEscapesTest.java new file mode 100644 index 0000000..886be12 --- /dev/null +++ b/src/test/java/org/nhindirect/policy/impl/SimpleTextV1LexiconPolicyParser_quotedEscapesTest.java @@ -0,0 +1,53 @@ +package org.nhindirect.policy.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.InputStream; +import java.nio.charset.Charset; + +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.Test; + +public class SimpleTextV1LexiconPolicyParser_quotedEscapesTest +{ + @Test + public void testQuotedLiteral_containsParenthesesAndOperators_isSingleLiteral() throws Exception + { + final SimpleTextV1LexiconPolicyParser parser = new SimpleTextV1LexiconPolicyParser(); + final InputStream stream = IOUtils.toInputStream("\"(this && that)\"", Charset.defaultCharset()); + + final java.util.Vector tokens = parser.parseToTokens(stream); + assertEquals(1, tokens.size()); + assertEquals(SimpleTextV1LexiconPolicyParser.TokenType.LITERAL_EXPRESSION, tokens.get(0).getType()); + assertEquals("(this && that)", tokens.get(0).getToken()); + + stream.close(); + } + + @Test + public void testQuotedLiteral_escapedQuote_insideLiteral() throws Exception + { + final SimpleTextV1LexiconPolicyParser parser = new SimpleTextV1LexiconPolicyParser(); + final InputStream stream = IOUtils.toInputStream("\"He said \\\"Hello\\\" to me\"", Charset.defaultCharset()); + + final java.util.Vector tokens = parser.parseToTokens(stream); + assertEquals(1, tokens.size()); + assertEquals("He said \"Hello\" to me", tokens.get(0).getToken()); + + stream.close(); + } + + @Test + public void testQuotedLiteral_escapedBackslash_insideLiteral() throws Exception + { + final SimpleTextV1LexiconPolicyParser parser = new SimpleTextV1LexiconPolicyParser(); + final InputStream stream = IOUtils.toInputStream("\"C:\\\\path\\to\\file\"", Charset.defaultCharset()); + + final java.util.Vector tokens = parser.parseToTokens(stream); + assertEquals(1, tokens.size()); + assertEquals("C:\\path\\to\\file", tokens.get(0).getToken()); + + stream.close(); + } +} + diff --git a/src/test/resources/policies/simpleLexiconWithRegEx.txt b/src/test/resources/policies/simpleLexiconWithRegEx.txt new file mode 100644 index 0000000..bb599d5 --- /dev/null +++ b/src/test/resources/policies/simpleLexiconWithRegEx.txt @@ -0,0 +1 @@ +X509.TBS.EXTENSION.CertificatePolicies.PolicyOIDs {?} "^1\\.3\\.6\\.1\\.4\\.1\\.41179\\.2\\.1(\\.[0-9]+)*$" \ No newline at end of file