getAttributeMap() {
+ return attributeMap;
+ }
+}
diff --git a/src/main/java/com/rapid7/recog/Recog.java b/recog/src/main/java/com/rapid7/recog/Recog.java
similarity index 100%
rename from src/main/java/com/rapid7/recog/Recog.java
rename to recog/src/main/java/com/rapid7/recog/Recog.java
diff --git a/src/main/java/com/rapid7/recog/RecogMatch.java b/recog/src/main/java/com/rapid7/recog/RecogMatch.java
similarity index 100%
rename from src/main/java/com/rapid7/recog/RecogMatch.java
rename to recog/src/main/java/com/rapid7/recog/RecogMatch.java
diff --git a/src/main/java/com/rapid7/recog/RecogMatchResult.java b/recog/src/main/java/com/rapid7/recog/RecogMatchResult.java
similarity index 100%
rename from src/main/java/com/rapid7/recog/RecogMatchResult.java
rename to recog/src/main/java/com/rapid7/recog/RecogMatchResult.java
diff --git a/src/main/java/com/rapid7/recog/RecogMatcher.java b/recog/src/main/java/com/rapid7/recog/RecogMatcher.java
similarity index 65%
rename from src/main/java/com/rapid7/recog/RecogMatcher.java
rename to recog/src/main/java/com/rapid7/recog/RecogMatcher.java
index c78e6e1..a7cf372 100644
--- a/src/main/java/com/rapid7/recog/RecogMatcher.java
+++ b/recog/src/main/java/com/rapid7/recog/RecogMatcher.java
@@ -11,6 +11,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;
+import java.util.function.BiConsumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.util.Collections.emptySet;
@@ -24,6 +25,54 @@
*/
public class RecogMatcher implements Serializable {
+ /**
+ * Interpolate the string using the "recog interpolation syntax"
+ * This syntax will take a string like "adsf {service.version} {service.family}"
+ * and attempt to resolve "{service.version}" and "{service.family}" in the string
+ * against the parameter map. So, given these parameters:
+ * - service.cpe23: "adsf {service.version} {service.family}"
+ * - service.version: "1.1"
+ * - service.family: "foo"
+ *
+ * The map will resolve to:
+ * - service.cpe23: "asdf 1.1 foo"
+ * - service.version: "1.1"
+ * - service.family: "foo"
+ *
+ * @param keyEndsWith A string used to filter which keys will have their
+ * values interpolated (any key ending with this value). If {@code null},
+ * all keys are considered.
+ * @param match The map containing values that can be interpolated. Must not
+ * be {@code null}.
+ * @return A map containing the interpolated key/values.
+ */
+ public static Map interpolate(String keyEndsWith, Map match) {
+ requireNonNull(match);
+
+ for (Entry entry : match.entrySet()) {
+ // For all keys that end with a certain extension (for optimization)...
+ if (keyEndsWith == null || entry.getKey().endsWith(keyEndsWith)) {
+ String value = entry.getValue();
+ if (value != null) {
+ // The operation below is a "fold left" -- basically iterate over
+ // all the items in the map, and attempt to replace the items in
+ // this string with those map items.
+ String result =
+ match.entrySet().stream()
+ .reduce(
+ entry.getValue(),
+ (part, item) -> {
+ return part.replace("{" + item.getKey() + "}", item.getValue() == null ? "-" : item.getValue());
+ },
+ (part, item) -> part);
+ match.put(entry.getKey(), result);
+ }
+ }
+ }
+
+ return match;
+ }
+
private final RecogPatternMatcher matcher;
/** "Constant" values always matched as parameters. Key is the name, value is the value. */
@@ -45,7 +94,7 @@ public class RecogMatcher implements Serializable {
private String description;
/** Optional examples that illustrate the matcher (or that can be used to test the matcher). */
- private Set examples;
+ private Set examples;
/**
* Creates a new RecogMatcher using a {@link JavaRegexRecogPatternMatcher} to
@@ -98,9 +147,10 @@ public String getDescription() {
* {@code null}.
* @return A reference to this matcher to allow for method chaining.
*/
- public RecogMatcher addExample(String example) {
- if (example != null)
+ public RecogMatcher addExample(FingerprintExample example) {
+ if (example != null) {
examples.add(example);
+ }
return this;
}
@@ -111,7 +161,7 @@ public RecogMatcher addExample(String example) {
*
* @return A non-null, immutable {@link Set} of examples. May be empty.
*/
- public Set getExamples() {
+ public Set getExamples() {
return examples == null ? emptySet() : unmodifiableSet(examples);
}
@@ -164,11 +214,81 @@ public Map match(String input) {
}
}
- return values;
+ return interpolate(null, values);
} else
return null;
}
+ public void verifyExamples(BiConsumer consumer) {
+ // look for the presence of test cases
+ if (examples.size() == 0) {
+ consumer.accept(VerifyStatus.Warn, String.format("'%s' has no test cases", description));
+ }
+
+ // make sure each test case passes
+ for (FingerprintExample example : examples) {
+ Map result = match(example.getText());
+ if (result == null) {
+ consumer.accept(VerifyStatus.Fail, String.format("'%s' failed to match \"%s\" with '%s'",
+ description, example.getText(), matcher.getPattern()));
+ continue;
+ }
+
+ VerifyStatus verifyStatus = VerifyStatus.Success;
+ String message = example.getText();
+ // Ensure that all the attributes as provided by the example were parsed
+ // out correctly and match the capture group values we expect.
+ for (Map.Entry entry : example.getAttributeMap().entrySet()) {
+ String key = entry.getKey();
+ String value = entry.getValue();
+ if (key.equals("_encoding")) {
+ continue;
+ }
+
+ if (!result.containsKey(key) || !result.get(key).equals(value)) {
+ verifyStatus = VerifyStatus.Fail;
+ message = String.format("'%s' failed to find expected capture group %s '%s'. Result was %s",
+ description, key, value, result.get(key));
+ break;
+ }
+ }
+ consumer.accept(verifyStatus, message);
+ }
+
+ verifyExamplesHaveCaptureGroups(consumer);
+ }
+
+ private void verifyExamplesHaveCaptureGroups(BiConsumer consumer) {
+ Map captureGroupUsed = new HashMap<>();
+ // get a list of parameters that are defined by capture groups
+ for (Entry parameter : positionalParameters.entrySet()) {
+ if (parameter.getValue() > 0 && !parameter.getKey().isEmpty()) {
+ captureGroupUsed.put(parameter.getKey(), false);
+ }
+ }
+
+ // match up the fingerprint parameters with test attributes
+ for (FingerprintExample example : examples) {
+ Map result = match(example.getText());
+ for (Entry entry : example.getAttributeMap().entrySet()) {
+ String key = entry.getKey();
+ if (captureGroupUsed.containsKey(key)) {
+ captureGroupUsed.replace(key, true);
+ }
+ }
+ }
+
+ // alert on untested parameters
+ for (Entry entry : captureGroupUsed.entrySet()) {
+ String paramName = entry.getKey();
+ Boolean paramUsed = entry.getValue();
+ if (!paramUsed) {
+ String message = String.format("'%s' is missing an example that checks for parameter '%s' which is derived from a capture group", description, paramName);
+ consumer.accept(VerifyStatus.Warn, message);
+ }
+ }
+ }
+
/**
* Adds a constant parameter value with the given name. If the matcher matches, this key-value
* pair is guaranteed to be returned in the result of {@link #match(String)}.
diff --git a/src/main/java/com/rapid7/recog/RecogMatchers.java b/recog/src/main/java/com/rapid7/recog/RecogMatchers.java
similarity index 50%
rename from src/main/java/com/rapid7/recog/RecogMatchers.java
rename to recog/src/main/java/com/rapid7/recog/RecogMatchers.java
index baf62b5..188633d 100644
--- a/src/main/java/com/rapid7/recog/RecogMatchers.java
+++ b/recog/src/main/java/com/rapid7/recog/RecogMatchers.java
@@ -15,7 +15,6 @@
*/
public class RecogMatchers extends ArrayList {
- private static final String CPE_SUFFIX = ".cpe23";
private String key;
private String protocol;
private String type;
@@ -48,54 +47,6 @@ public float getPreference() {
return preference;
}
- /**
- * Interpolate the string using the "recog interpolation syntax"
- * This syntax will take a string like "adsf {service.version} {service.family}"
- * and attempt to resolve "{service.version}" and "{service.family}" in the string
- * against the parameter map. So, given these parameters:
- * - service.cpe23: "adsf {service.version} {service.family}"
- * - service.version: "1.1"
- * - service.family: "foo"
- *
- * The map will resolve to:
- * - service.cpe23: "asdf 1.1 foo"
- * - service.version: "1.1"
- * - service.family: "foo"
- *
- * @param keyEndsWith A string used to filter which keys will have their
- * values interpolated (any key ending with this value). If {@code null},
- * all keys are considered.
- * @param match The map containing values that can be interpolated. Must not
- * be {@code null}.
- * @return A map containing the interpolated key/values.
- */
- public Map interpolate(String keyEndsWith, Map match) {
- requireNonNull(match);
-
- for (Map.Entry entry : match.entrySet()) {
- // For all keys that end with a certain extension (for optimization)...
- if (keyEndsWith == null || entry.getKey().endsWith(keyEndsWith)) {
- String value = entry.getValue();
- if (value != null) {
- // The operation below is a "fold left" -- basically iterate over
- // all the items in the map, and attempt to replace the items in
- // this string with those map items.
- String result =
- match.entrySet().stream()
- .reduce(
- entry.getValue(),
- (part, item) -> {
- return part.replace("{" + item.getKey() + "}", item.getValue() == null ? "-" : item.getValue());
- },
- (part, item) -> part);
- match.put(entry.getKey(), result.replaceAll(":$", ""));
- }
- }
- }
-
- return match;
- }
-
/**
* Finds matches for a string input against all matchers.
*
@@ -108,7 +59,7 @@ public List getMatches(String input) {
else
return stream().map(matcher -> {
Map match = matcher.match(input);
- return match != null ? new RecogMatch(matcher, interpolate(CPE_SUFFIX, match)) : null;
+ return match != null ? new RecogMatch(matcher, match) : null;
}).filter(Objects::nonNull).collect(toList());
}
@@ -125,7 +76,7 @@ public RecogMatch getFirstMatch(String input) {
for (RecogMatcher matcher : this) {
Map match = matcher.match(input);
if (match != null)
- return new RecogMatch(matcher, interpolate(CPE_SUFFIX, match));
+ return new RecogMatch(matcher, match);
}
return null;
diff --git a/src/main/java/com/rapid7/recog/RecogType.java b/recog/src/main/java/com/rapid7/recog/RecogType.java
similarity index 100%
rename from src/main/java/com/rapid7/recog/RecogType.java
rename to recog/src/main/java/com/rapid7/recog/RecogType.java
diff --git a/src/main/java/com/rapid7/recog/RecogVersion.java b/recog/src/main/java/com/rapid7/recog/RecogVersion.java
similarity index 100%
rename from src/main/java/com/rapid7/recog/RecogVersion.java
rename to recog/src/main/java/com/rapid7/recog/RecogVersion.java
diff --git a/recog/src/main/java/com/rapid7/recog/VerifyStatus.java b/recog/src/main/java/com/rapid7/recog/VerifyStatus.java
new file mode 100644
index 0000000..9640315
--- /dev/null
+++ b/recog/src/main/java/com/rapid7/recog/VerifyStatus.java
@@ -0,0 +1,8 @@
+package com.rapid7.recog;
+
+// Verifier status
+public enum VerifyStatus {
+ Warn,
+ Fail,
+ Success
+}
diff --git a/src/main/java/com/rapid7/recog/parser/ParseException.java b/recog/src/main/java/com/rapid7/recog/parser/ParseException.java
similarity index 100%
rename from src/main/java/com/rapid7/recog/parser/ParseException.java
rename to recog/src/main/java/com/rapid7/recog/parser/ParseException.java
diff --git a/src/main/java/com/rapid7/recog/parser/RecogParser.java b/recog/src/main/java/com/rapid7/recog/parser/RecogParser.java
similarity index 90%
rename from src/main/java/com/rapid7/recog/parser/RecogParser.java
rename to recog/src/main/java/com/rapid7/recog/parser/RecogParser.java
index c29c6c2..5d4f877 100644
--- a/src/main/java/com/rapid7/recog/parser/RecogParser.java
+++ b/recog/src/main/java/com/rapid7/recog/parser/RecogParser.java
@@ -1,5 +1,6 @@
package com.rapid7.recog.parser;
+import com.rapid7.recog.FingerprintExample;
import com.rapid7.recog.RecogMatcher;
import com.rapid7.recog.RecogMatchers;
import com.rapid7.recog.pattern.JavaRegexRecogPatternMatcher;
@@ -8,6 +9,7 @@
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
+import java.util.HashMap;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilderFactory;
@@ -16,6 +18,8 @@
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
@@ -162,8 +166,19 @@ public RecogMatchers parse(Reader reader, String name)
if ("base64".equals(example.getAttribute("_encoding"))) {
// TODO: these are currently ignored as the Base64 decoding isn't working properly
- } else
- fingerprintPattern.addExample(exampleContent);
+ } else {
+ HashMap attributeMap = new HashMap<>();
+ NamedNodeMap exAttributes = example.getAttributes();
+
+ for (int i = 0; i < exAttributes.getLength(); i++) {
+ Node attr = exAttributes.item(i);
+ String attrName = attr.getNodeName();
+ String attrValue = attr.getNodeValue();
+ attributeMap.put(attrName, attrValue);
+ }
+
+ fingerprintPattern.addExample(new FingerprintExample(exampleContent, attributeMap));
+ }
}
// parse and add parameter specifications
@@ -178,9 +193,12 @@ public RecogMatchers parse(Reader reader, String name)
if (position == 0) {
String paramValue = getRequiredAttribute(parameter, "value");
fingerprintPattern.addValue(paramName, paramValue);
- }
- // otherwise the position indicates a group match result
- else {
+ } else {
+ // otherwise the position indicates a group match result
+ String value = parameter.getAttribute("value");
+ if (!value.isEmpty()) {
+ throw new ParseException(String.format("Attribute \"%s\" has a non-zero position but specifies a value of \"%s\"", paramName, value));
+ }
fingerprintPattern.addParam(position, paramName);
}
}
diff --git a/src/main/java/com/rapid7/recog/pattern/JavaRegexRecogPatternMatcher.java b/recog/src/main/java/com/rapid7/recog/pattern/JavaRegexRecogPatternMatcher.java
similarity index 100%
rename from src/main/java/com/rapid7/recog/pattern/JavaRegexRecogPatternMatcher.java
rename to recog/src/main/java/com/rapid7/recog/pattern/JavaRegexRecogPatternMatcher.java
diff --git a/src/main/java/com/rapid7/recog/pattern/RecogPatternMatchResult.java b/recog/src/main/java/com/rapid7/recog/pattern/RecogPatternMatchResult.java
similarity index 100%
rename from src/main/java/com/rapid7/recog/pattern/RecogPatternMatchResult.java
rename to recog/src/main/java/com/rapid7/recog/pattern/RecogPatternMatchResult.java
diff --git a/src/main/java/com/rapid7/recog/pattern/RecogPatternMatcher.java b/recog/src/main/java/com/rapid7/recog/pattern/RecogPatternMatcher.java
similarity index 100%
rename from src/main/java/com/rapid7/recog/pattern/RecogPatternMatcher.java
rename to recog/src/main/java/com/rapid7/recog/pattern/RecogPatternMatcher.java
diff --git a/src/main/java/com/rapid7/recog/provider/CompositeRecogMatchersProvider.java b/recog/src/main/java/com/rapid7/recog/provider/CompositeRecogMatchersProvider.java
similarity index 100%
rename from src/main/java/com/rapid7/recog/provider/CompositeRecogMatchersProvider.java
rename to recog/src/main/java/com/rapid7/recog/provider/CompositeRecogMatchersProvider.java
diff --git a/src/main/java/com/rapid7/recog/provider/IRecogMatchersProvider.java b/recog/src/main/java/com/rapid7/recog/provider/IRecogMatchersProvider.java
similarity index 100%
rename from src/main/java/com/rapid7/recog/provider/IRecogMatchersProvider.java
rename to recog/src/main/java/com/rapid7/recog/provider/IRecogMatchersProvider.java
diff --git a/src/main/java/com/rapid7/recog/provider/RecogMatchersProvider.java b/recog/src/main/java/com/rapid7/recog/provider/RecogMatchersProvider.java
similarity index 100%
rename from src/main/java/com/rapid7/recog/provider/RecogMatchersProvider.java
rename to recog/src/main/java/com/rapid7/recog/provider/RecogMatchersProvider.java
diff --git a/src/test/java/com/rapid7/recog/CustomPatternMatcherTest.java b/recog/src/test/java/com/rapid7/recog/CustomPatternMatcherTest.java
similarity index 100%
rename from src/test/java/com/rapid7/recog/CustomPatternMatcherTest.java
rename to recog/src/test/java/com/rapid7/recog/CustomPatternMatcherTest.java
diff --git a/src/test/java/com/rapid7/recog/FingerprintMatcherTest.java b/recog/src/test/java/com/rapid7/recog/FingerprintMatcherTest.java
similarity index 100%
rename from src/test/java/com/rapid7/recog/FingerprintMatcherTest.java
rename to recog/src/test/java/com/rapid7/recog/FingerprintMatcherTest.java
diff --git a/src/test/java/com/rapid7/recog/FingerprintMatchersTest.java b/recog/src/test/java/com/rapid7/recog/FingerprintMatchersTest.java
similarity index 97%
rename from src/test/java/com/rapid7/recog/FingerprintMatchersTest.java
rename to recog/src/test/java/com/rapid7/recog/FingerprintMatchersTest.java
index d6f31d7..27b6ea1 100644
--- a/src/test/java/com/rapid7/recog/FingerprintMatchersTest.java
+++ b/recog/src/test/java/com/rapid7/recog/FingerprintMatchersTest.java
@@ -217,13 +217,12 @@ public void multipleOfTheSameInterpolationProperty() {
@Test
public void interpolateWithNullSuffix() {
// given
- RecogMatchers matchers = new RecogMatchers();
HashMap map = new HashMap<>();
map.put("foo", "test");
map.put("bar", "{foo}");
// when
- matchers.interpolate(null, map);
+ RecogMatcher.interpolate(null, map);
// then
assertThat(map.get("bar"), is("test"));
@@ -233,13 +232,12 @@ public void interpolateWithNullSuffix() {
@Test
public void interpolateWithNonNullSuffix() {
// given
- RecogMatchers matchers = new RecogMatchers();
HashMap map = new HashMap<>();
map.put("foo", "test");
map.put("bar", "{foo}");
// when
- matchers.interpolate("bar", map);
+ RecogMatcher.interpolate("bar", map);
// then
assertThat(map.get("bar"), is("test"));
diff --git a/src/test/java/com/rapid7/recog/RecogIntegration.java b/recog/src/test/java/com/rapid7/recog/RecogIntegration.java
similarity index 87%
rename from src/test/java/com/rapid7/recog/RecogIntegration.java
rename to recog/src/test/java/com/rapid7/recog/RecogIntegration.java
index 4f2637d..7190b5b 100644
--- a/src/test/java/com/rapid7/recog/RecogIntegration.java
+++ b/recog/src/test/java/com/rapid7/recog/RecogIntegration.java
@@ -30,9 +30,9 @@ public void allRecogContentParses() throws FileNotFoundException, ParseException
RecogMatchers matchers = parser.parse(file);
for (RecogMatcher matcher : matchers) {
// when - the matcher has examples
- for (String example : matcher.getExamples())
+ for (FingerprintExample example : matcher.getExamples())
// then - the example matches
- assertThat("Matcher in " + file + " with pattern '" + matcher.getPattern() + "' does not match example '" + example + "'.", matcher.matches(example), is(true));
+ assertThat("Matcher in " + file + " with pattern '" + matcher.getPattern() + "' does not match example '" + example.getText() + "'.", matcher.matches(example.getText()), is(true));
}
}
}
diff --git a/src/test/java/com/rapid7/recog/TestGenerators.java b/recog/src/test/java/com/rapid7/recog/TestGenerators.java
similarity index 100%
rename from src/test/java/com/rapid7/recog/TestGenerators.java
rename to recog/src/test/java/com/rapid7/recog/TestGenerators.java
diff --git a/recog/src/test/java/com/rapid7/recog/parser/FingerprintMatcherParserTest.java b/recog/src/test/java/com/rapid7/recog/parser/FingerprintMatcherParserTest.java
new file mode 100644
index 0000000..faee403
--- /dev/null
+++ b/recog/src/test/java/com/rapid7/recog/parser/FingerprintMatcherParserTest.java
@@ -0,0 +1,282 @@
+package com.rapid7.recog.parser;
+
+import com.rapid7.recog.RecogMatcher;
+import com.rapid7.recog.RecogMatchers;
+import java.io.StringReader;
+import org.junit.jupiter.api.Test;
+import static com.rapid7.recog.RecogMatcher.pattern;
+import static com.rapid7.recog.TestGenerators.anyString;
+import static java.util.regex.Pattern.CASE_INSENSITIVE;
+import static java.util.regex.Pattern.DOTALL;
+import static java.util.regex.Pattern.MULTILINE;
+import static org.hamcrest.CoreMatchers.hasItems;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class FingerprintMatcherParserTest {
+
+ @Test
+ public void noFingerprintsAreReadWhenInputIsEmpty() throws ParseException {
+ // given
+ String xml = "";
+
+ // when
+ RecogMatchers patterns = new RecogParser().parse(new StringReader(xml), anyString());
+
+ // then
+ assertThat(patterns.size(), is(0));
+ }
+
+ @Test
+ public void invalidXmlInputCausesExceptionToBeThrown() throws ParseException {
+ // given
+ String xml = "foo";
+
+ // when
+ assertThrows(ParseException.class, () -> new RecogParser().parse(new StringReader(xml), anyString()));
+
+ // then - throws exception
+ }
+
+ @Test
+ public void validFingerprint() throws ParseException {
+ // given
+ String xml = "\n"
+ + ""
+ + " \n"
+ + " Apache returning only its major version number\n"
+ + " Apache 1\n"
+ + " Apache 2\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + "";
+
+ // when
+ RecogMatchers patterns = new RecogParser().parse(new StringReader(xml), anyString());
+
+ // then
+ assertThat(patterns.size(), is(1));
+ assertThat(patterns, hasItems(new RecogMatcher(pattern("^Apache (\\d)$", DOTALL, MULTILINE)).addValue("service.vendor", "Apache").addValue("service.product", "HTTPD").addValue("service.family", "Apache").addParam(1, "service.version")));
+ }
+
+ @Test
+ public void twoValidFingerprints() throws ParseException {
+ // given
+ String xml = "\n"
+ + ""
+ + " \n"
+ + " Apache returning only its major version number\n"
+ + " Apache/1\n"
+ + " Apache/2\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " Apache returning no version information\n"
+ + " Apache\n"
+ + " apache\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " "
+ + "";
+
+ // when
+ RecogMatchers patterns = new RecogParser().parse(new StringReader(xml), anyString());
+
+ // then
+ assertThat(patterns.size(), is(2));
+ assertThat(patterns, hasItems(
+ new RecogMatcher(pattern("^Apache/\\d$", CASE_INSENSITIVE)).addValue("service.vendor", "Apache").addValue("service.product", "HTTPD").addValue("service.family", "Apache"),
+ new RecogMatcher(pattern("^Apache$", MULTILINE)).addValue("service.vendor", "Apache").addValue("service.product", "HTTPD").addValue("service.family", "Apache")));
+ }
+
+ @Test
+ public void invalidFingerprintsIgnored() throws ParseException {
+ // given
+ String xml = "\n"
+ + ""
+ + " \n"
+ + " Apache returning only its major version number\n"
+ + " Apache/1\n"
+ + " Apache/2\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " Apache returning only its major version number\n"
+ + " Apache/1\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " Apache returning no version information\n"
+ + " Apache\n"
+ + " apache\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " "
+ + "";
+
+ // when
+ RecogMatchers patterns = new RecogParser().parse(new StringReader(xml), anyString());
+
+ // then
+ assertThat(patterns.size(), is(2));
+ }
+
+ @Test
+ public void patternIsRequired() throws ParseException {
+ // given
+ String xml = "\n"
+ + ""
+ + " \n"
+ + " Apache returning only its major version number\n"
+ + " Apache 1\n"
+ + " Apache 2\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + "";
+
+ // when
+ RecogMatchers patterns = new RecogParser().parse(new StringReader(xml), anyString());
+
+ // then
+ assertThat(patterns.size(), is(0));
+ }
+
+ @Test
+ public void patternMustBeValid() throws ParseException {
+ // given
+ String xml = "\n"
+ + ""
+ + " \n"
+ + " Apache returning only its major version number\n"
+ + " Apache 1\n"
+ + " Apache 2\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + "";
+
+ // when
+ RecogMatchers patterns = new RecogParser().parse(new StringReader(xml), anyString());
+
+ // then
+ assertThat(patterns.size(), is(0));
+ }
+
+ @Test
+ public void emptyFlagsIgnored() throws ParseException {
+ // given
+ String xml = "\n"
+ + ""
+ + " \n"
+ + " Apache returning only its major version number\n"
+ + " Apache 1\n"
+ + " Apache 2\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + "";
+
+ // 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")));
+ }
+
+ @Test
+ public void unknownFlagIgnored() throws ParseException {
+ // given
+ String xml = "\n"
+ + ""
+ + " \n"
+ + " Apache returning only its major version number\n"
+ + " Apache 1\n"
+ + " Apache 2\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + "";
+
+ // 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")));
+ }
+
+ @Test
+ public void paramZeroPositionWithNoValueFailsWhenStrict() {
+ // given
+ String xml = "\n"
+ + ""
+ + " \n"
+ + " Apache returning only its major version number\n"
+ + " Apache 1\n"
+ + " Apache 2\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + "";
+ String expectedMessage = "Attribute \"value\" does not exist.";
+
+ // when
+ Exception exception = assertThrows(ParseException.class, () -> {
+ new RecogParser(true).parse(new StringReader(xml), anyString());
+ }, expectedMessage);
+
+ // then
+ assertEquals(expectedMessage, exception.getMessage());
+ }
+
+ @Test
+ public void paramNonZeroPositionWithValueFailsWhenStrict() {
+ // given
+ String paramName = "service.version";
+ String paramValue = "1";
+ String xml = String.format("\n"
+ + ""
+ + " \n"
+ + " Apache returning only its major version number\n"
+ + " Apache 1\n"
+ + " Apache 2\n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + " \n"
+ + "", paramName, paramValue);
+ String expectedMessage = String.format("Attribute \"%s\" has a non-zero position but specifies a value of \"%s\"", paramName, paramValue);
+
+ // when
+ Exception exception = assertThrows(ParseException.class, () -> {
+ new RecogParser(true).parse(new StringReader(xml), anyString());
+ }, expectedMessage);
+
+ // then
+ assertEquals(expectedMessage, exception.getMessage());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/rapid7/recog/provider/CompositeFingerprintMatchersProviderTest.java b/recog/src/test/java/com/rapid7/recog/provider/CompositeFingerprintMatchersProviderTest.java
similarity index 100%
rename from src/test/java/com/rapid7/recog/provider/CompositeFingerprintMatchersProviderTest.java
rename to recog/src/test/java/com/rapid7/recog/provider/CompositeFingerprintMatchersProviderTest.java
diff --git a/src/test/java/com/rapid7/recog/parser/FingerprintMatcherParserTest.java b/src/test/java/com/rapid7/recog/parser/FingerprintMatcherParserTest.java
deleted file mode 100644
index 9693d1c..0000000
--- a/src/test/java/com/rapid7/recog/parser/FingerprintMatcherParserTest.java
+++ /dev/null
@@ -1,227 +0,0 @@
-package com.rapid7.recog.parser;
-
-import com.rapid7.recog.RecogMatcher;
-import com.rapid7.recog.RecogMatchers;
-import java.io.StringReader;
-import org.junit.jupiter.api.Test;
-import static com.rapid7.recog.RecogMatcher.pattern;
-import static com.rapid7.recog.TestGenerators.anyString;
-import static java.util.regex.Pattern.CASE_INSENSITIVE;
-import static java.util.regex.Pattern.DOTALL;
-import static java.util.regex.Pattern.MULTILINE;
-import static org.hamcrest.CoreMatchers.hasItems;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
-public class FingerprintMatcherParserTest {
-
- @Test
- public void noFingerprintsAreReadWhenInputIsEmpty() throws ParseException {
- // given
- String xml = "";
-
- // when
- RecogMatchers patterns = new RecogParser().parse(new StringReader(xml), anyString());
-
- // then
- assertThat(patterns.size(), is(0));
- }
-
- @Test
- public void invalidXmlInputCausesExceptionToBeThrown() throws ParseException {
- // given
- String xml = "foo";
-
- // when
- assertThrows(ParseException.class, () -> new RecogParser().parse(new StringReader(xml), anyString()));
-
- // then - throws exception
- }
-
- @Test
- public void validFingerprint() throws ParseException {
- // given
- String xml = "\n"
- + ""
- + " \n"
- + " Apache returning only its major version number\n"
- + " Apache 1\n"
- + " Apache 2\n"
- + " \n"
- + " \n"
- + " \n"
- + " \n"
- + " \n"
- + "";
-
- // when
- RecogMatchers patterns = new RecogParser().parse(new StringReader(xml), anyString());
-
- // then
- assertThat(patterns.size(), is(1));
- assertThat(patterns, hasItems(new RecogMatcher(pattern("^Apache (\\d)$", DOTALL, MULTILINE)).addValue("service.vendor", "Apache").addValue("service.product", "HTTPD").addValue("service.family", "Apache").addParam(1, "service.version")));
- }
-
- @Test
- public void twoValidFingerprints() throws ParseException {
- // given
- String xml = "\n"
- + ""
- + " \n"
- + " Apache returning only its major version number\n"
- + " Apache/1\n"
- + " Apache/2\n"
- + " \n"
- + " \n"
- + " \n"
- + " \n"
- + " \n"
- + " Apache returning no version information\n"
- + " Apache\n"
- + " apache\n"
- + " \n"
- + " \n"
- + " \n"
- + " "
- + "";
-
- // when
- RecogMatchers patterns = new RecogParser().parse(new StringReader(xml), anyString());
-
- // then
- assertThat(patterns.size(), is(2));
- assertThat(patterns, hasItems(
- new RecogMatcher(pattern("^Apache/\\d$", CASE_INSENSITIVE)).addValue("service.vendor", "Apache").addValue("service.product", "HTTPD").addValue("service.family", "Apache"),
- new RecogMatcher(pattern("^Apache$", MULTILINE)).addValue("service.vendor", "Apache").addValue("service.product", "HTTPD").addValue("service.family", "Apache")));
- }
-
- @Test
- public void invalidFingerprintsIgnored() throws ParseException {
- // given
- String xml = "\n"
- + ""
- + " \n"
- + " Apache returning only its major version number\n"
- + " Apache/1\n"
- + " Apache/2\n"
- + " \n"
- + " \n"
- + " \n"
- + " \n"
- + " \n"
- + " Apache returning only its major version number\n"
- + " Apache/1\n"
- + " \n"
- + " \n"
- + " \n"
- + " Apache returning no version information\n"
- + " Apache\n"
- + " apache\n"
- + " \n"
- + " \n"
- + " \n"
- + " "
- + "";
-
- // when
- RecogMatchers patterns = new RecogParser().parse(new StringReader(xml), anyString());
-
- // then
- assertThat(patterns.size(), is(2));
- }
-
- @Test
- public void patternIsRequired() throws ParseException {
- // given
- String xml = "\n"
- + ""
- + " \n"
- + " Apache returning only its major version number\n"
- + " Apache 1\n"
- + " Apache 2\n"
- + " \n"
- + " \n"
- + " \n"
- + " \n"
- + " \n"
- + "";
-
- // when
- RecogMatchers patterns = new RecogParser().parse(new StringReader(xml), anyString());
-
- // then
- assertThat(patterns.size(), is(0));
- }
-
- @Test
- public void patternMustBeValid() throws ParseException {
- // given
- String xml = "\n"
- + ""
- + " \n"
- + " Apache returning only its major version number\n"
- + " Apache 1\n"
- + " Apache 2\n"
- + " \n"
- + " \n"
- + " \n"
- + " \n"
- + " \n"
- + "";
-
- // when
- RecogMatchers patterns = new RecogParser().parse(new StringReader(xml), anyString());
-
- // then
- assertThat(patterns.size(), is(0));
- }
-
- @Test
- public void emptyFlagsIgnored() throws ParseException {
- // given
- String xml = "\n"
- + ""
- + " \n"
- + " Apache returning only its major version number\n"
- + " Apache 1\n"
- + " Apache 2\n"
- + " \n"
- + " \n"
- + " \n"
- + " \n"
- + " \n"
- + "";
-
- // 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")));
- }
-
- @Test
- public void unknownFlagIgnored() throws ParseException {
- // given
- String xml = "\n"
- + ""
- + " \n"
- + " Apache returning only its major version number\n"
- + " Apache 1\n"
- + " Apache 2\n"
- + " \n"
- + " \n"
- + " \n"
- + " \n"
- + " \n"
- + "";
-
- // 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")));
- }
-}