diff --git a/pom.xml b/pom.xml
index 6415dc1..4e0612d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -124,7 +124,7 @@
org.mockito
- mockito-core
+ mockito-inline
${mockito.version}
test
diff --git a/recog-verify/pom.xml b/recog-verify/pom.xml
index d62913b..3956c24 100644
--- a/recog-verify/pom.xml
+++ b/recog-verify/pom.xml
@@ -66,7 +66,7 @@
org.mockito
- mockito-core
+ mockito-inline
diff --git a/recog-verify/src/main/java/com/rapid7/recog/verify/RecogVerifier.java b/recog-verify/src/main/java/com/rapid7/recog/verify/RecogVerifier.java
index 479cc0f..c6d1eee 100644
--- a/recog-verify/src/main/java/com/rapid7/recog/verify/RecogVerifier.java
+++ b/recog-verify/src/main/java/com/rapid7/recog/verify/RecogVerifier.java
@@ -138,7 +138,8 @@ public static void main(String[] args) {
failures += verifier.getReporter().getFailureCount();
warnings += verifier.getReporter().getWarningCount();
} catch (ParseException exception) {
- System.err.printf("error: parsing fingerprints file '%s': %s%n", p.toFile(), exception.getMessage());
+ String message = exception.getCause() != null ? exception.getCause().getMessage() : exception.getMessage();
+ System.err.printf("error: parsing fingerprints file '%s': %s%n", p.toFile(), message);
System.exit(-1);
}
}
diff --git a/recog/pom.xml b/recog/pom.xml
index fa84377..ab26cc9 100644
--- a/recog/pom.xml
+++ b/recog/pom.xml
@@ -48,7 +48,7 @@
org.mockito
- mockito-core
+ mockito-inline
diff --git a/recog/src/main/java/com/rapid7/recog/RecogMatcher.java b/recog/src/main/java/com/rapid7/recog/RecogMatcher.java
index c41eda5..c760102 100644
--- a/recog/src/main/java/com/rapid7/recog/RecogMatcher.java
+++ b/recog/src/main/java/com/rapid7/recog/RecogMatcher.java
@@ -243,7 +243,7 @@ public void verifyExamples(BiConsumer consumer) {
for (Map.Entry entry : example.getAttributeMap().entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
- if (key.equals("_encoding")) {
+ if (key.equals("_encoding") || key.equals("_filename")) {
continue;
}
diff --git a/recog/src/main/java/com/rapid7/recog/parser/RecogParser.java b/recog/src/main/java/com/rapid7/recog/parser/RecogParser.java
index 128010f..d1dcc92 100644
--- a/recog/src/main/java/com/rapid7/recog/parser/RecogParser.java
+++ b/recog/src/main/java/com/rapid7/recog/parser/RecogParser.java
@@ -9,6 +9,10 @@
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.HashMap;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
@@ -32,6 +36,7 @@
* halt processing.
*/
public class RecogParser {
+ private static final String FILENAME_KEY = "_filename";
/**
* Factory used to create the underlying {@link RecogPatternMatcher} used
@@ -188,7 +193,15 @@ public RecogMatchers parse(Reader reader, String path, String name)
attributeMap.put(attrName, attrValue);
}
- fingerprintPattern.addExample(new FingerprintExample(example.getTextContent(), attributeMap));
+ String exampleText;
+ if (attributeMap.containsKey(FILENAME_KEY)) {
+ // process external example file
+ String filename = attributeMap.get(FILENAME_KEY);
+ exampleText = getExternalExampleText(path, name, filename);
+ } else {
+ exampleText = example.getTextContent();
+ }
+ fingerprintPattern.addExample(new FingerprintExample(exampleText, attributeMap));
}
// parse and add parameter specifications
@@ -260,4 +273,21 @@ private String getRequiredAttribute(Element element, String name) throws ParseEx
return value;
}
+
+ private String getExternalExampleText(String path, String name, String filename) throws ParseException {
+ Path examplePath;
+ if (path != null) {
+ examplePath = Paths.get(Paths.get(path).getParent().toString(), name, filename);
+ } else {
+ examplePath = Paths.get(name, filename);
+ }
+
+ byte[] exampleBytes;
+ try {
+ exampleBytes = Files.readAllBytes(examplePath);
+ } catch (IOException exception) {
+ throw new ParseException(String.format("Unable to process fingerprint example file '%s'", examplePath), exception);
+ }
+ return new String(exampleBytes, StandardCharsets.US_ASCII);
+ }
}
diff --git a/recog/src/test/java/com/rapid7/recog/parser/FingerprintMatcherParserTest.java b/recog/src/test/java/com/rapid7/recog/parser/FingerprintMatcherParserTest.java
index 0852167..6cebbf7 100644
--- a/recog/src/test/java/com/rapid7/recog/parser/FingerprintMatcherParserTest.java
+++ b/recog/src/test/java/com/rapid7/recog/parser/FingerprintMatcherParserTest.java
@@ -3,7 +3,13 @@
import com.rapid7.recog.RecogMatcher;
import com.rapid7.recog.RecogMatchers;
import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
import static com.rapid7.recog.RecogMatcher.pattern;
import static com.rapid7.recog.TestGenerators.anyString;
import static java.util.regex.Pattern.CASE_INSENSITIVE;
@@ -12,8 +18,11 @@
import static org.hamcrest.CoreMatchers.hasItems;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.beans.HasPropertyWithValue.hasProperty;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
public class FingerprintMatcherParserTest {
@@ -97,6 +106,40 @@ public void twoValidFingerprints() throws ParseException {
new RecogMatcher(pattern("^Apache$", DOTALL, MULTILINE)).addValue("service.vendor", "Apache").addValue("service.product", "HTTPD").addValue("service.family", "Apache")));
}
+ @Test
+ public void validFingerprintExternalExampleFile() throws ParseException {
+ // given
+ int exServiceVersion = 1;
+ String exText = String.format("Apache %d", exServiceVersion);
+ String exFilename = anyString();
+ String xml = String.format("\n"
+ + ""
+ + " \n"
+ + " Apache returning only its major version number\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + "", exFilename, exServiceVersion);
+
+ try (MockedStatic mockFiles = Mockito.mockStatic(Files.class)) {
+ mockFiles.when(() -> Files.readAllBytes(any(Path.class)))
+ .thenReturn(exText.getBytes(StandardCharsets.US_ASCII));
+
+ // when
+ RecogMatchers patterns = new RecogParser().parse(new StringReader(xml), anyString());
+
+ // then
+ assertThat(patterns.size(), is(1));
+ assertThat(patterns, hasItems(new RecogMatcher(pattern("^Apache (\\d)$")).addValue("service.vendor", "Apache").addValue("service.product", "HTTPD").addValue("service.family", "Apache").addParam(1, "service.version")));
+ assertThat(patterns.get(0).getExamples().size(), is(1));
+ mockFiles.verify(() -> Files.readAllBytes(any(Path.class)));
+ assertThat(patterns.get(0).getExamples(), contains(hasProperty("text", is(exText))));
+ }
+ }
+
@Test
public void invalidFingerprintsIgnored() throws ParseException {
// given
@@ -279,4 +322,31 @@ public void paramNonZeroPositionWithValueFailsWhenStrict() {
// then
assertEquals(expectedMessage, exception.getMessage());
}
-}
\ No newline at end of file
+
+ @Test
+ public void missingExternalExampleFileFailsWhenStrict() {
+ // given
+ String exFilename = anyString();
+ String xml = String.format("\n"
+ + ""
+ + " \n"
+ + " Apache returning only its major version number\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + "", exFilename);
+ String name = anyString();
+ String expectedMessage = String.format("Unable to process fingerprint example file '%s'", Paths.get(name, exFilename));
+
+ // when
+ Exception exception = assertThrows(ParseException.class, () -> {
+ new RecogParser(true).parse(new StringReader(xml), name);
+ }, expectedMessage);
+
+ // then
+ assertEquals(expectedMessage, exception.getMessage());
+ }
+}