diff --git a/pom.xml b/pom.xml
index db46a0cf7..b71a6e36f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
com.github.secdec.astam-correlator
master-pom
- 1.2.16
+ 1.2.17
pom
ThreadFix
@@ -37,7 +37,7 @@
Mozilla Public License v2.0
- https://www.mozilla.org/un-US/MPL/2.0/
+ https://www.mozilla.org/en-US/MPL/2.0/
@@ -426,7 +426,7 @@
2.17
2.17
0.7.0.201403182114
- 1.8.2
+ 1.8.3
2.2.4
diff --git a/ssvl-converter/pom.xml b/ssvl-converter/pom.xml
index a528712d7..3e7cac50c 100755
--- a/ssvl-converter/pom.xml
+++ b/ssvl-converter/pom.xml
@@ -7,11 +7,11 @@
com.github.secdec.astam-correlator
master-pom
- 1.2.16
+ 1.2.17
ssvl-converter
- 1.2.16
+ 1.2.17
src/main/java
diff --git a/threadfix-astam/pom.xml b/threadfix-astam/pom.xml
index a26ba4062..1750c530b 100644
--- a/threadfix-astam/pom.xml
+++ b/threadfix-astam/pom.xml
@@ -3,7 +3,7 @@
com.github.secdec.astam-correlator
master-pom
- 1.2.16
+ 1.2.17
4.0.0
diff --git a/threadfix-cli-importers/pom.xml b/threadfix-cli-importers/pom.xml
index 66364c7dd..ec1d98ab1 100644
--- a/threadfix-cli-importers/pom.xml
+++ b/threadfix-cli-importers/pom.xml
@@ -5,7 +5,7 @@
master-pom
com.github.secdec.astam-correlator
- 1.2.16
+ 1.2.17
4.0.0
diff --git a/threadfix-cli-importers/src/test/java/com/denimgroup/threadfix/framework/dotNetWebForm/EndToEndTests.java b/threadfix-cli-importers/src/test/java/com/denimgroup/threadfix/framework/dotNetWebForm/EndToEndTests.java
index 88855ef78..326a91696 100644
--- a/threadfix-cli-importers/src/test/java/com/denimgroup/threadfix/framework/dotNetWebForm/EndToEndTests.java
+++ b/threadfix-cli-importers/src/test/java/com/denimgroup/threadfix/framework/dotNetWebForm/EndToEndTests.java
@@ -104,32 +104,33 @@ public void assertDynamicXSSFindsEndpoint() {
assert foundBasicEndpoint : "Didn't find /WebForm1.aspx";
}
- @Test
- public void testXSSVulnsMerge() {
- Application application = getApplication(TestConstants.WEB_FORMS_DROP_DOWN,
- ScanLocationManager.getRoot(),
- "SBIR/webform.xml", "SBIR/webform.fpr");
-
- List scans = application.getScans();
- assert scans.size() == 2 :
- "Got " + scans.size() + " scans instead of 2.";
-
- boolean hasMergedXSSVuln = false;
-
- for (Vulnerability vulnerability : application.getVulnerabilities()) {
- if (vulnerability.getGenericVulnerability().getDisplayId().equals(79)) {
- if (vulnerability.getFindings().size() == 2) {
- hasMergedXSSVuln = true;
- System.out.println("Found it!");
- } else {
- System.out.println("Found a XSS vuln but it didn't have 2 findings. " +
- "It had " + vulnerability.getFindings().size());
- }
- }
- }
-
- assert hasMergedXSSVuln : "Didn't find a merged vulnerability.";
- }
+ // This test project was not provided with the public Threadfix release and has not been found online.
+// @Test
+// public void testXSSVulnsMerge() {
+// Application application = getApplication(TestConstants.WEB_FORMS_DROP_DOWN,
+// ScanLocationManager.getRoot(),
+// "SBIR/webform.xml", "SBIR/webform.fpr");
+//
+// List scans = application.getScans();
+// assert scans.size() == 2 :
+// "Got " + scans.size() + " scans instead of 2.";
+//
+// boolean hasMergedXSSVuln = false;
+//
+// for (Vulnerability vulnerability : application.getVulnerabilities()) {
+// if (vulnerability.getGenericVulnerability().getDisplayId().equals(79)) {
+// if (vulnerability.getFindings().size() == 2) {
+// hasMergedXSSVuln = true;
+// System.out.println("Found it!");
+// } else {
+// System.out.println("Found a XSS vuln but it didn't have 2 findings. " +
+// "It had " + vulnerability.getFindings().size());
+// }
+// }
+// }
+//
+// assert hasMergedXSSVuln : "Didn't find a merged vulnerability.";
+// }
private Application getApplication(String sourceLocation, String scanBase, String... scans) {
diff --git a/threadfix-cli-lib/pom.xml b/threadfix-cli-lib/pom.xml
index 20ecfbd23..4cca8e2ba 100644
--- a/threadfix-cli-lib/pom.xml
+++ b/threadfix-cli-lib/pom.xml
@@ -5,7 +5,7 @@
com.github.secdec.astam-correlator
master-pom
- 1.2.16
+ 1.2.17
threadfix-cli-lib
diff --git a/threadfix-cli/pom.xml b/threadfix-cli/pom.xml
index b9c22baff..da832ad02 100644
--- a/threadfix-cli/pom.xml
+++ b/threadfix-cli/pom.xml
@@ -5,7 +5,7 @@
com.github.secdec.astam-correlator
master-pom
- 1.2.16
+ 1.2.17
threadfix-cli
diff --git a/threadfix-data-access/pom.xml b/threadfix-data-access/pom.xml
index 1fcd5b3c3..09e3d12ca 100644
--- a/threadfix-data-access/pom.xml
+++ b/threadfix-data-access/pom.xml
@@ -5,7 +5,7 @@
master-pom
com.github.secdec.astam-correlator
- 1.2.16
+ 1.2.17
4.0.0
diff --git a/threadfix-data-migration/pom.xml b/threadfix-data-migration/pom.xml
index edbce6315..d3eb73752 100644
--- a/threadfix-data-migration/pom.xml
+++ b/threadfix-data-migration/pom.xml
@@ -5,7 +5,7 @@
master-pom
com.github.secdec.astam-correlator
- 1.2.16
+ 1.2.17
4.0.0
diff --git a/threadfix-entities/pom.xml b/threadfix-entities/pom.xml
index 463c20d75..e99a8403d 100644
--- a/threadfix-entities/pom.xml
+++ b/threadfix-entities/pom.xml
@@ -97,7 +97,7 @@
com.github.secdec.astam-correlator
master-pom
- 1.2.16
+ 1.2.17
diff --git a/threadfix-entities/src/main/java/com/denimgroup/threadfix/data/entities/WildcardEndpointPathNode.java b/threadfix-entities/src/main/java/com/denimgroup/threadfix/data/entities/WildcardEndpointPathNode.java
index 4266dc528..153bd6b6c 100644
--- a/threadfix-entities/src/main/java/com/denimgroup/threadfix/data/entities/WildcardEndpointPathNode.java
+++ b/threadfix-entities/src/main/java/com/denimgroup/threadfix/data/entities/WildcardEndpointPathNode.java
@@ -7,12 +7,15 @@
public class WildcardEndpointPathNode implements EndpointPathNode {
+ private static Pattern namedGroupPattern = Pattern.compile("\\(\\?P\\<\\w+\\>");
+
private Pattern wildcardPattern;
public WildcardEndpointPathNode(String pattern) {
if (pattern == null) {
this.wildcardPattern = Pattern.compile(".*");
} else {
+ pattern = namedGroupPattern.matcher(pattern).replaceAll("(");
this.wildcardPattern = Pattern.compile(pattern);
}
}
diff --git a/threadfix-ham/pom.xml b/threadfix-ham/pom.xml
index 944871aef..1c326d8b2 100644
--- a/threadfix-ham/pom.xml
+++ b/threadfix-ham/pom.xml
@@ -4,7 +4,7 @@
com.github.secdec.astam-correlator
master-pom
- 1.2.16
+ 1.2.17
4.0.0
@@ -188,7 +188,7 @@
org.jsoup
jsoup
- 1.8.3
+ ${jsoup.version}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/engine/ProjectDirectory.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/engine/ProjectDirectory.java
index d2dc57f87..496e1c3b8 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/engine/ProjectDirectory.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/engine/ProjectDirectory.java
@@ -300,7 +300,11 @@ public String findCanonicalFilePath(@Nonnull File file) {
filePath = file.getAbsolutePath().substring(directory.getAbsolutePath().length());
}
- return filePath;
+ if (filePath != null) {
+ return FilePathUtils.normalizePath(filePath);
+ } else {
+ return null;
+ }
}
@Nullable
@@ -334,7 +338,11 @@ private String calculateBestOption(@Nonnull String[] pathSegments, @Nonnull Set<
}
}
- return returnOption;
+ if (returnOption != null) {
+ return FilePathUtils.normalizePath(returnOption);
+ } else {
+ return null;
+ }
}
// split along / or \ or just return the whole path
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/engine/framework/FrameworkCalculator.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/engine/framework/FrameworkCalculator.java
index d90e280e0..991b9eed1 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/engine/framework/FrameworkCalculator.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/engine/framework/FrameworkCalculator.java
@@ -51,8 +51,8 @@ public class FrameworkCalculator {
static {
// TODO detect language first and use that to narrow down the frameworks
register(new JavaAndJspFrameworkChecker());
- register(new WebFormsFrameworkChecker());
register(new DotNetFrameworkChecker());
+ register(new WebFormsFrameworkChecker());
register(new RailsFrameworkChecker());
register(new DjangoFrameworkChecker());
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/engine/framework/JavaAndJspFrameworkChecker.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/engine/framework/JavaAndJspFrameworkChecker.java
index 5fae15fae..ecd9e0952 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/engine/framework/JavaAndJspFrameworkChecker.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/engine/framework/JavaAndJspFrameworkChecker.java
@@ -90,15 +90,6 @@ private boolean checkJsp(@Nonnull ProjectDirectory directory) {
@SuppressWarnings("unchecked")
public FrameworkType check(@Nonnull ProjectDirectory directory) {
- FrameworkType frameworkType = checkMappings(directory);
- if (frameworkType != FrameworkType.NONE) {
- return frameworkType;
- }
-
- if (checkSpringMvc(directory)) {
- return FrameworkType.SPRING_MVC;
- }
-
if (checkStruts(directory)) {
return FrameworkType.STRUTS;
}
@@ -114,6 +105,11 @@ public FrameworkType check(@Nonnull ProjectDirectory directory) {
return FrameworkType.JSP;
}
+ FrameworkType frameworkType = checkMappings(directory);
+ if (frameworkType != FrameworkType.NONE) {
+ return frameworkType;
+ }
+
return FrameworkType.NONE;
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/engine/full/GeneratorBasedEndpointDatabase.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/engine/full/GeneratorBasedEndpointDatabase.java
index 3c5131a01..2072356e8 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/engine/full/GeneratorBasedEndpointDatabase.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/engine/full/GeneratorBasedEndpointDatabase.java
@@ -25,11 +25,13 @@
////////////////////////////////////////////////////////////////////////
package com.denimgroup.threadfix.framework.engine.full;
+import com.denimgroup.threadfix.data.enums.EndpointRelevanceStrictness;
import com.denimgroup.threadfix.data.enums.FrameworkType;
import com.denimgroup.threadfix.data.enums.InformationSourceType;
import com.denimgroup.threadfix.data.interfaces.Endpoint;
import com.denimgroup.threadfix.framework.engine.CodePoint;
import com.denimgroup.threadfix.framework.engine.cleaner.PathCleaner;
+import com.denimgroup.threadfix.framework.util.EndpointUtil;
import com.denimgroup.threadfix.logging.SanitizedLogger;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -43,6 +45,9 @@ class GeneratorBasedEndpointDatabase implements EndpointDatabase {
@Nonnull
private final List endpoints;
+ @Nonnull
+ private final List flattenedEndpoints;
+
@Nonnull
private final PathCleaner pathCleaner;
@@ -64,6 +69,7 @@ public GeneratorBasedEndpointDatabase(@Nonnull EndpointGenerator endpointGenerat
log.info("Using generic EndpointGenerator-based translator.");
endpoints = endpointGenerator.generateEndpoints();
+ flattenedEndpoints = EndpointUtil.flattenWithVariants(endpoints);
log.info("Found ".concat(String.valueOf(endpoints.size())).concat(" endpoints:"));
for (int i = 0; i < endpoints.size(); i++) {
@@ -83,7 +89,7 @@ public GeneratorBasedEndpointDatabase(@Nonnull EndpointGenerator endpointGenerat
private void buildMappings() {
log.info("Building mappings.");
- for (Endpoint endpoint : endpoints) {
+ for (Endpoint endpoint : flattenedEndpoints) {
addToMap(dynamicMap, endpoint.getUrlPath(), endpoint);
addToMap(staticMap, endpoint.getFilePath(), endpoint);
@@ -121,19 +127,20 @@ public Endpoint findBestMatch(@Nonnull EndpointQuery query) {
Endpoint bestEndpoint = null;
int bestEndpointRelevance = -1;
- if (query.getDynamicPath() == null) {
- return null;
- }
- String dynamicPath = pathCleaner.cleanDynamicPath(query.getDynamicPath());
-
Set endpoints = findAllMatches(query);
- for (Endpoint currentEndpoint : endpoints) {
- int relevance = currentEndpoint.compareRelevance(dynamicPath);
- if (relevance > bestEndpointRelevance) {
- bestEndpoint = currentEndpoint;
- bestEndpointRelevance = relevance;
+ if (query.getDynamicPath() != null) {
+ String dynamicPath = pathCleaner.cleanDynamicPath(query.getDynamicPath());
+ for (Endpoint currentEndpoint : endpoints) {
+ int relevance = currentEndpoint.compareRelevance(dynamicPath);
+ if (relevance > bestEndpointRelevance && currentEndpoint.isRelevant(dynamicPath, EndpointRelevanceStrictness.STRICT)) {
+ bestEndpoint = currentEndpoint;
+ bestEndpointRelevance = relevance;
+ }
}
+ } else if (!endpoints.isEmpty()) {
+ bestEndpoint = endpoints.iterator().next();
+ bestEndpointRelevance = 1;
}
if (bestEndpointRelevance > 0) {
@@ -166,6 +173,7 @@ public Set findAllMatches(@Nonnull EndpointQuery query) {
if (!useStatic && query.getDynamicPath() != null) {
String cleaned = pathCleaner.cleanDynamicPath(query.getDynamicPath());
resultSets.add(getValueOrEmptySet(cleaned, dynamicMap));
+ resultSets.addAll(list(findEligibleEndpoints(cleaned)));
}
if (useStatic && query.getStaticPath() != null) {
@@ -184,7 +192,7 @@ public Set findAllMatches(@Nonnull EndpointQuery query) {
if (resultSets.size() > 0) {
for (Set endpoints : resultSets) {
- if (endpoints != null) {
+ if (endpoints != null && !endpoints.isEmpty()) {
if (!assignedInitial) {
resultingSet = endpoints;
@@ -221,8 +229,6 @@ public Set findAllMatches(@Nonnull EndpointQuery query) {
resultingSet.addAll(fromCodePoints);
}
- resultingSet.addAll(findEligibleEndpoints(pathCleaner.cleanDynamicPath(query.getDynamicPath())));
-
return resultingSet;
}
@@ -297,8 +303,8 @@ private Set getValueOrEmptySetWithSimpleKey(@Nullable String key,
private Set findEligibleEndpoints(String endpointPath) {
Set result = set();
- for (Endpoint endpoint : endpoints) {
- if (endpoint.compareRelevance(endpointPath) > 0) {
+ for (Endpoint endpoint : flattenedEndpoints) {
+ if (endpoint.isRelevant(endpointPath, EndpointRelevanceStrictness.STRICT)) {
result.add(endpoint);
}
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/django/DjangoEndpoint.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/django/DjangoEndpoint.java
index 253035ea2..7ebe540b0 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/django/DjangoEndpoint.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/django/DjangoEndpoint.java
@@ -287,4 +287,17 @@ public boolean matchesLineNumber(int lineNumber) {
protected List getLintLine() {
return null;
}
+
+ @Nonnull
+ @Override
+ public String toString() {
+ return
+ urlPath +
+ " - " +
+ (filePath == null ? "" : filePath) +
+ ":" +
+ startLineNumber +
+ "," +
+ endLineNumber;
+ }
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/django/DjangoEndpointGenerator.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/django/DjangoEndpointGenerator.java
index 4b4bdb54f..01304d28a 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/django/DjangoEndpointGenerator.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/django/DjangoEndpointGenerator.java
@@ -297,9 +297,12 @@ private List generateMappings(PythonCodeCollection codebase, boolean i
List mappings = list();
for (List routeSet : routeMap.values()) {
+ // Duplicate routes can occur if a test suite is included that references production routes
+ List distinctRoutes = getDistinctRoutes(routeSet);
+
inferHttpMethodsBySourceCode(codebase, routeSet);
- for (DjangoRoute route : routeSet) {
+ for (DjangoRoute route : distinctRoutes) {
String urlPath = route.getUrl();
String filePath = route.getViewPath();
@@ -323,6 +326,26 @@ private List generateMappings(PythonCodeCollection codebase, boolean i
return mappings;
}
+ private List getDistinctRoutes(Collection routes) {
+ List distinct = list();
+
+ for (DjangoRoute current : routes) {
+ boolean exists = false;
+ for (DjangoRoute existing : distinct) {
+ if (current != existing && current.equals(existing)) {
+ exists = true;
+ break;
+ }
+ }
+
+ if (!exists) {
+ distinct.add(current);
+ }
+ }
+
+ return distinct;
+ }
+
private List findUrlsByFileName() {
List urlFiles = list();
Collection projectFiles = FileUtils.listFiles(rootDirectory, new String[] { "py" }, true);
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/django/DjangoRoute.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/django/DjangoRoute.java
index 8fd2eeceb..2cbd3a2d1 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/django/DjangoRoute.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/django/DjangoRoute.java
@@ -84,4 +84,53 @@ public void setHttpMethod(String httpMethod) {
public void addParameter(String parameter, RouteParameter dataType) {
parameters.put(parameter, dataType);
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!obj.getClass().equals(DjangoRoute.class)) {
+ return false;
+ }
+
+ DjangoRoute other = (DjangoRoute)obj;
+
+ return
+ this.viewPath.equals(other.viewPath) &&
+ this.url.equals(other.url) &&
+ this.startLineNumber == other.startLineNumber &&
+ this.endLineNumber == other.endLineNumber &&
+ (this.httpMethod == null) == (other.httpMethod == null) &&
+ (this.httpMethod == null || this.httpMethod.equals(other.httpMethod)) &&
+ parametersMatch(this.parameters, other.parameters);
+ }
+
+ private static boolean parametersMatch(Map a, Map b) {
+ if (
+ !a.keySet().containsAll(b.keySet()) ||
+ !b.keySet().containsAll(a.keySet())
+ ) {
+ return false;
+ }
+
+ for (String paramName : a.keySet()) {
+ RouteParameter aParam = a.get(paramName);
+ RouteParameter bParam = b.get(paramName);
+
+ if (aParam.getParamType() != bParam.getParamType()) {
+ return false;
+ } else if (!aParam.getDataType().equals(bParam.getDataType())) {
+ return false;
+ } else if ((aParam.getAcceptedValues() == null) != (bParam.getAcceptedValues() == null)) {
+ return false;
+ } else if (aParam.getAcceptedValues() != null) {
+ if (
+ !aParam.getAcceptedValues().containsAll(bParam.getAcceptedValues()) ||
+ !bParam.getAcceptedValues().containsAll(aParam.getAcceptedValues())
+ ) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/django/python/PythonSyntaxParser.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/django/python/PythonSyntaxParser.java
index fb830d8ab..8bc5ccd17 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/django/python/PythonSyntaxParser.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/django/python/PythonSyntaxParser.java
@@ -117,6 +117,8 @@ private static void runParallel(File rootDirectory, PythonCodeCollection codebas
}
}
+ executor.shutdown();
+
log("Parsing tasks completed, reconstructing module hierarchy...");
// Reconstruct directory-based hierarchy first
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/django/python/runtime/PythonInterpreter.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/django/python/runtime/PythonInterpreter.java
index 3b20ce404..52ebefa34 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/django/python/runtime/PythonInterpreter.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/django/python/runtime/PythonInterpreter.java
@@ -191,7 +191,7 @@ public PythonValue run(@Nonnull PythonExpression expression, ExecutionContext ex
int currentStackDepth = executionContext.getStackDepth();
if (currentStackDepth >= getMaxStackDepth()) {
- LOG.warn("Execution context stack size '" + currentStackDepth +
+ LOG.debug("Execution context stack size '" + currentStackDepth +
"' exceeded the maximum support size '" + getMaxStackDepth() +
"', prematurely terminating current expression: " +
expression.toString());
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetControllerMappings.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetControllerMappings.java
index e11d08621..c763b55ac 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetControllerMappings.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetControllerMappings.java
@@ -41,6 +41,7 @@ public class DotNetControllerMappings {
private String areaName = null;
private String controllerName = null;
private List actions = list();
+ private String namespace = null;
public String getFilePath() {
return filePath;
@@ -58,6 +59,14 @@ public String getControllerName() {
return controllerName;
}
+ public void setNamespace(String namespace) {
+ this.namespace = namespace;
+ }
+
+ public String getNamespace() {
+ return namespace;
+ }
+
public void setAreaName(@Nonnull String areaName) {
assert this.areaName == null : "These mappings already have an area name.";
this.areaName = areaName;
@@ -93,11 +102,21 @@ public Action getActionForNameAndMethod(String actionName, String method) {
String allCapsMethod = method.toUpperCase();
for (Action action : actions) {
- if (action.name.equals(actionName) && action.getMethods().equals(allCapsMethod)) {
+ if (action.name.equalsIgnoreCase(actionName) && (action.getMethods().contains(allCapsMethod) || action.name.equalsIgnoreCase(method))) {
return action;
}
}
+ // No directly-matching action found; if method is GET, match against first non-decorated action with the given name
+ // since HTTP method is implicitly GET
+ if (method.equals("GET")) {
+ for (Action action : actions) {
+ if (action.name.equalsIgnoreCase(actionName) && action.getMethods().isEmpty()) {
+ return action;
+ }
+ }
+ }
+
return null;
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetControllerParser.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetControllerParser.java
index fae898793..f6440f4f6 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetControllerParser.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetControllerParser.java
@@ -29,9 +29,11 @@
import com.denimgroup.threadfix.data.entities.RouteParameter;
import com.denimgroup.threadfix.data.entities.RouteParameterType;
import com.denimgroup.threadfix.data.enums.ParameterDataType;
+import com.denimgroup.threadfix.framework.util.CodeParseUtil;
import com.denimgroup.threadfix.framework.util.EventBasedTokenizer;
import com.denimgroup.threadfix.framework.util.EventBasedTokenizerRunner;
import com.denimgroup.threadfix.logging.SanitizedLogger;
+import org.apache.commons.lang3.StringUtils;
import javax.annotation.Nonnull;
import java.io.File;
@@ -96,11 +98,12 @@ enum ParameterState {
String lastAttribute;
int currentCurlyBrace = 0, currentParen = 0, classBraceLevel = 0,
methodBraceLevel = 0, storedParen = 0, methodLineNumber = 0;
- boolean shouldContinue = true;
- String lastString = null, methodName = null, twoStringsAgo = null;
+ boolean shouldContinue = true, wasDefaultValue = false;
+ String lastString = null, methodName = null, twoStringsAgo = null, threeStringsAgo = null;
Set parametersWithTypes = set();
int lastLineNumber = -1;
String possibleParamType = null;
+ String currentNamespace = null;
@Override
public void processToken(int type, int lineNumber, String stringValue) {
@@ -114,6 +117,7 @@ public void processToken(int type, int lineNumber, String stringValue) {
processRequestDataReads(type, stringValue);
if (stringValue != null) {
+ threeStringsAgo = twoStringsAgo;
twoStringsAgo = lastString;
lastString = stringValue;
}
@@ -149,6 +153,12 @@ private void processMainThread(int type, int lineNumber, String stringValue) {
currentState = State.PUBLIC;
}else if( type == '['){
currentState = State.OPEN_BRACKET;
+ }else {
+ if (currentNamespace == null) {
+ currentNamespace = "";
+ }
+ if (currentCurlyBrace == 0)
+ currentNamespace += CodeParseUtil.buildTokenString(type, stringValue);
}
break;
case OPEN_BRACKET:
@@ -178,6 +188,7 @@ private void processMainThread(int type, int lineNumber, String stringValue) {
String controllerName = stringValue.substring(0, stringValue.indexOf("Controller"));
LOG.debug("Got Controller name " + controllerName);
mappings.setControllerName(controllerName);
+ mappings.setNamespace(currentNamespace);
}
currentState = State.TYPE_SIGNATURE;
@@ -222,16 +233,37 @@ private void processMainThread(int type, int lineNumber, String stringValue) {
if (stringValue == null) {
if (type == ',' || type == ')' && lastString != null) {
if (isValidParameterName(lastString)) {
- RouteParameter param = new RouteParameter(lastString);
- param.setDataType(twoStringsAgo);
+ String name, dataType;
+ if (wasDefaultValue) {
+ name = twoStringsAgo;
+ dataType = threeStringsAgo;
+ } else {
+ name = lastString;
+ dataType = twoStringsAgo;
+ }
+
+ RouteParameter param = new RouteParameter(name);
+ param.setDataType(dataType);
parametersWithTypes.add(param);
}
if (twoStringsAgo.equals("Include")) {
currentState = State.AFTER_BIND_INCLUDE;
}
+
+ wasDefaultValue = false;
} else if (type == '=' && !"Include".equals(lastString)) {
currentState = State.DEFAULT_VALUE;
}
+ } else if (lastString != null && lastString.equals("Include")) {
+ String paramNames = CodeParseUtil.trim(stringValue, "\"");
+ String[] paramNameParts = StringUtils.split(paramNames, ',');
+
+ for (String paramName : paramNameParts) {
+ paramName = paramName.trim();
+ RouteParameter param = new RouteParameter(paramName);
+ param.setParamType(RouteParameterType.FORM_DATA);
+ parametersWithTypes.add(param);
+ }
}
if (currentParen == storedParen) {
@@ -240,6 +272,7 @@ private void processMainThread(int type, int lineNumber, String stringValue) {
}
break;
case DEFAULT_VALUE:
+ wasDefaultValue = true;
if (stringValue != null) {
currentState = State.IN_ACTION_SIGNATURE;
}
@@ -284,11 +317,21 @@ private void processAttributes(int type, String stringValue) {
}
break;
case STRING:
+ boolean addAttribute = false;
if (type == ']') {
+ currentAttributeState = AttributeState.START;
+ addAttribute = true;
+ }
+
+ if (type == ',') {
+ addAttribute = true;
+ currentAttributeState = AttributeState.OPEN_BRACKET;
+ }
+
+ if (addAttribute) {
LOG.debug("Adding " + lastAttribute);
currentAttributes.add(lastAttribute);
}
- currentAttributeState = AttributeState.START;
break;
}
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetEndpoint.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetEndpoint.java
index bf9d0ea64..f1ac9535c 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetEndpoint.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetEndpoint.java
@@ -130,6 +130,16 @@ public String getHttpMethod() {
return forcedMethod;
} else if (action.getMethods().size() > 0) {
return action.getMethods().get(0);
+ } else if (action.name.equalsIgnoreCase("get")) {
+ return "GET";
+ } else if (action.name.equalsIgnoreCase("post")) {
+ return "POST";
+ } else if (action.name.equalsIgnoreCase("put")) {
+ return "PUT";
+ } else if (action.name.equalsIgnoreCase("patch")) {
+ return "PATCH";
+ } else if (action.name.equalsIgnoreCase("delete")) {
+ return "DELETE";
} else {
return "GET";
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetEndpointGenerator.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetEndpointGenerator.java
index cc40f91e2..ce33bac1d 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetEndpointGenerator.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetEndpointGenerator.java
@@ -37,6 +37,7 @@
import com.denimgroup.threadfix.framework.util.FilePathUtils;
import com.denimgroup.threadfix.framework.util.ParameterMerger;
import com.denimgroup.threadfix.logging.SanitizedLogger;
+import org.apache.commons.io.FileUtils;
import javax.annotation.Nonnull;
import java.io.File;
@@ -121,7 +122,7 @@ private void assembleEndpoints(File rootDirectory) {
continue;
}
- DotNetRouteMappings.MapRoute mapRoute = dotNetRouteMappings.getMatchingMapRoute(mappings.hasAreaName(), mappings.getControllerName());
+ DotNetRouteMappings.MapRoute mapRoute = dotNetRouteMappings.getMatchingMapRoute(mappings.hasAreaName(), mappings.getControllerName(), mappings.getNamespace());
if (mapRoute == null || mapRoute.url == null || mapRoute.url.equals(""))
continue;
@@ -138,6 +139,10 @@ private void assembleEndpoints(File rootDirectory) {
}
String pattern = mapRoute.url;
+ // If a specific action was set for this route, only create endpoints when we get to that action
+ if (!pattern.contains("{action}") && mapRoute.defaultRoute != null && !action.name.equals(mapRoute.defaultRoute.action)) {
+ continue;
+ }
LOG.debug("Substituting patterns from route " + action + " into template " + pattern);
@@ -185,9 +190,10 @@ private void assembleEndpoints(File rootDirectory) {
LOG.debug("Got result " + result);
String filePath = mappings.getFilePath();
- if (filePath.startsWith(rootDirectory.getAbsolutePath())) {
+ if (rootDirectory != null && filePath.startsWith(rootDirectory.getAbsolutePath())) {
filePath = FilePathUtils.getRelativePath(filePath, rootDirectory);
}
+
endpoints.add(new DotNetEndpoint(result, filePath, action));
}
}
@@ -229,6 +235,9 @@ private void assembleEndpoints(File rootDirectory) {
continue;
}
+ result = result.replaceAll("\\{controller\\}", controllerMappings.getControllerName());
+ result = result.replaceAll("\\{action\\}", action.name);
+
String filePath = controllerMappings.getFilePath();
if (filePath.startsWith(rootDirectory.getAbsolutePath())) {
filePath = FilePathUtils.getRelativePath(filePath, rootDirectory);
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetFrameworkChecker.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetFrameworkChecker.java
index 931769268..a2d35eae6 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetFrameworkChecker.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetFrameworkChecker.java
@@ -27,11 +27,14 @@
import com.denimgroup.threadfix.framework.engine.ProjectDirectory;
import com.denimgroup.threadfix.framework.engine.framework.FrameworkChecker;
import com.denimgroup.threadfix.framework.filefilter.FileExtensionFileFilter;
+import com.denimgroup.threadfix.framework.util.EventBasedTokenizer;
+import com.denimgroup.threadfix.framework.util.EventBasedTokenizerRunner;
import com.denimgroup.threadfix.logging.SanitizedLogger;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import javax.annotation.Nonnull;
+import java.io.File;
import java.util.Collection;
/**
@@ -44,21 +47,37 @@ public class DotNetFrameworkChecker extends FrameworkChecker {
@Nonnull
@Override
public FrameworkType check(@Nonnull ProjectDirectory directory) {
- Collection csFiles = FileUtils.listFiles(directory.getDirectory(),
- new FileExtensionFileFilter("cs"), TrueFileFilter.INSTANCE);
- LOG.info("Got " + csFiles.size() + " .cs files from the directory.");
-
- Collection routeConfig = FileUtils.listFiles(directory.getDirectory(),
- new FileExtensionFileFilter("Controller.cs"), TrueFileFilter.INSTANCE);
+ Collection configFiles = FileUtils.listFiles(directory.getDirectory(), new String[] { "config", "csproj" }, true);
- LOG.info("Got " + routeConfig.size() + " Controller files from the directory.");
+ MvcNamespaceParser parser = new MvcNamespaceParser();
+ for (File configFile : configFiles) {
+ EventBasedTokenizerRunner.run(configFile, parser);
+ if (parser.isMvc) {
+ break;
+ }
+ }
- FrameworkType type = csFiles.isEmpty() || routeConfig.isEmpty() ?
- FrameworkType.NONE :
- FrameworkType.DOT_NET_MVC;
+ return parser.isMvc ? FrameworkType.DOT_NET_MVC : FrameworkType.NONE;
+ }
+
+ static class MvcNamespaceParser implements EventBasedTokenizer {
+
+ private static String MVC_NAMESPACE = "System.Web.Mvc";
+
+ boolean isMvc = false;
+
+ @Override
+ public boolean shouldContinue() {
+ return !isMvc;
+ }
- return type;
+ @Override
+ public void processToken(int type, int lineNumber, String stringValue) {
+ if (stringValue != null && stringValue.contains(MVC_NAMESPACE)) {
+ isMvc = true;
+ }
+ }
}
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetMappings.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetMappings.java
index f07cbd5ed..5d34c8fe7 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetMappings.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetMappings.java
@@ -27,18 +27,14 @@
import com.denimgroup.threadfix.data.interfaces.Endpoint;
import com.denimgroup.threadfix.framework.engine.full.EndpointGenerator;
-import com.denimgroup.threadfix.framework.filefilter.FileExtensionFileFilter;
import com.denimgroup.threadfix.framework.util.EndpointValidationStatistics;
import com.denimgroup.threadfix.framework.util.EventBasedTokenizerRunner;
+import com.denimgroup.threadfix.framework.util.FilePathUtils;
import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.filefilter.TrueFileFilter;
import javax.annotation.Nonnull;
import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
+import java.util.*;
import static com.denimgroup.threadfix.CollectionUtils.list;
@@ -47,12 +43,9 @@
*/
public class DotNetMappings implements EndpointGenerator {
- final Collection cSharpFiles;
final File rootDirectory;
- DotNetRouteMappings routeMappings = null;
- List controllerMappingsList = list();
- DotNetEndpointGenerator generator = null;
+ List generators = list();
@SuppressWarnings("unchecked")
public DotNetMappings(@Nonnull File rootDirectory) {
@@ -61,22 +54,23 @@ public DotNetMappings(@Nonnull File rootDirectory) {
this.rootDirectory = rootDirectory;
- cSharpFiles = FileUtils.listFiles(rootDirectory,
- new FileExtensionFileFilter("cs"), TrueFileFilter.INSTANCE);
-
- generateMappings(rootDirectory);
+ for (File solutionFolder : findSolutionFolders(rootDirectory)) {
+ generateMappings(rootDirectory, solutionFolder);
+ }
EndpointValidationStatistics.printValidationStats(generateEndpoints());
}
- private void generateMappings(File rootDirectory) {
+ private void generateMappings(File rootDirectory, File solutionDirectory) {
List modelParsers = list();
+ List controllerMappingsList = list();
- routeMappings = new DotNetRouteMappings();
+ DotNetRouteMappings routeMappings = new DotNetRouteMappings();
+ Collection cSharpFiles = FileUtils.listFiles(solutionDirectory, new String[] { "cs" }, true);
for (File file : cSharpFiles) {
if (file != null && file.exists() && file.isFile() &&
- file.getAbsolutePath().contains(rootDirectory.getAbsolutePath())) {
+ file.getAbsolutePath().contains(solutionDirectory.getAbsolutePath())) {
DotNetControllerParser endpointParser = new DotNetControllerParser(file);
DotNetRoutesParser routesParser = new DotNetRoutesParser();
@@ -98,23 +92,77 @@ private void generateMappings(File rootDirectory) {
DotNetModelMappings modelMappings = new DotNetModelMappings(modelParsers);
- generator = new DotNetEndpointGenerator(rootDirectory, routeMappings, modelMappings, controllerMappingsList);
+ generators.add(new DotNetEndpointGenerator(rootDirectory, routeMappings, modelMappings, controllerMappingsList));
}
@Nonnull
@Override
public List generateEndpoints() {
- assert generator != null;
+ assert !generators.isEmpty();
+
+ List result = list();
+ for (EndpointGenerator generator : generators) {
+ result.addAll(generator.generateEndpoints());
+ }
- // We can't count on -ea being on
- return generator == null ? new ArrayList() : generator.generateEndpoints();
+ return result;
}
@Override
public Iterator iterator() {
- assert generator != null;
+ assert !generators.isEmpty();
+
+ return new MultiGeneratorIterator(generators);
+ }
+
+ private List findSolutionFolders(File rootDirectory) {
+ Collection slnFiles = FileUtils.listFiles(rootDirectory, new String[] { "sln" }, true);
+ List solutionFolders = list();
+ for (File slnFile : slnFiles) {
+ File parent = slnFile.getParentFile();
+ if (!solutionFolders.contains(parent))
+ solutionFolders.add(parent);
+ }
+
+ if (solutionFolders.isEmpty()) {
+ solutionFolders.add(rootDirectory);
+ }
+
+ return FilePathUtils.findRootFolders(solutionFolders);
+ }
+
+ private class MultiGeneratorIterator implements Iterator {
- // We can't count on -ea being on
- return generator == null ? new ArrayList().iterator() : generator.iterator();
+ private final Queue subIterators;
+ private Iterator currentIterator;
+
+ public MultiGeneratorIterator(List endpointIterators) {
+ this.subIterators = new LinkedList();
+ for (EndpointGenerator generator : endpointIterators) {
+ this.subIterators.add(generator.iterator());
+ }
+ this.currentIterator = this.subIterators.remove();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return this.currentIterator != null && this.currentIterator.hasNext();
+ }
+
+ @Override
+ public Endpoint next() {
+ Endpoint result = (Endpoint) this.currentIterator.next();
+
+ if (!this.currentIterator.hasNext()) {
+ this.currentIterator = this.subIterators.remove();
+ }
+
+ return result;
+ }
+
+ @Override
+ public void remove() {
+ this.next();
+ }
}
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetRouteMappings.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetRouteMappings.java
index 4b41ef8fc..e521eeee8 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetRouteMappings.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetRouteMappings.java
@@ -25,6 +25,7 @@
////////////////////////////////////////////////////////////////////////
package com.denimgroup.threadfix.framework.impl.dotNet;
+import java.util.Collection;
import java.util.List;
import java.util.Set;
@@ -64,13 +65,15 @@ static class MapRoute {
String name;
String url;
ConcreteRoute defaultRoute;
+ Collection namespaces;
- MapRoute(String name, String url, ConcreteRoute defaultRoute) {
+ MapRoute(String name, String url, ConcreteRoute defaultRoute, Collection namespaces) {
assert name != null;
assert url != null;
this.name = name;
this.url = url;
this.defaultRoute = defaultRoute;
+ this.namespaces = namespaces;
}
}
@@ -90,19 +93,37 @@ public void importFrom(DotNetRouteMappings otherMappings)
}
- public void addRoute(String name, String url,String area, String controller, String action, String parameter) {
+ public void addRoute(String name, String url,String area, String controller, String action, String parameter, Collection namespaces) {
ConcreteRoute defaultRoute = controller != null && action != null ?
new ConcreteRoute(area, controller, action, parameter) :
null;
- routes.add(new MapRoute(name, url, defaultRoute));
+ routes.add(new MapRoute(name, url, defaultRoute, namespaces));
}
- public MapRoute getMatchingMapRoute(boolean hasAreaInMappings, String controllerName){
+ public MapRoute getMatchingMapRoute(boolean hasAreaInMappings, String controllerName, String controllerNamespace){
if(routes.size() == 1) return routes.get(0);
if(routes.size() == 0) return null;
MapRoute mapRoute = null;
for(MapRoute route : routes){
+ if (route.namespaces.size() > 0) {
+ if (controllerNamespace == null) {
+ continue;
+ } else {
+ for (String routeNamespace : route.namespaces) {
+ if (controllerNamespace.startsWith(routeNamespace)) {
+ // Match if "controller" is variable, or if a controller was specified and it matches the current controller
+ if (route.url.contains("{controller}") || (route.defaultRoute != null && controllerName.equals(route.defaultRoute.controller))) {
+ mapRoute = route;
+ break;
+ }
+ }
+ }
+ // At this point the route doesn't match this namespace, search the next one
+ continue;
+ }
+ }
+
if(hasAreaInMappings && (route.url.contains("area") || "areaRoute".equalsIgnoreCase(route.name))){
mapRoute = route;
break;
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetRoutesParser.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetRoutesParser.java
index 9a7acb683..103b95ba5 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetRoutesParser.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNet/DotNetRoutesParser.java
@@ -31,7 +31,9 @@
import javax.annotation.Nonnull;
import java.io.File;
+import java.util.List;
+import static com.denimgroup.threadfix.CollectionUtils.list;
import static com.denimgroup.threadfix.framework.impl.dotNet.DotNetKeywords.*;
/**
@@ -250,11 +252,13 @@ else if (IROUTE_BUILDER.equals(stringValue))
currentDefaultAction = null,
parameterName = null,
parameterValue = null;
+ List currentNamespaces = list();
// TODO split this up
enum MapRouteState { // these states are to be used with the IN_MAP_ROUTE_METHOD Phase
START, URL, URL_COLON, NAME, NAME_COLON, DEFAULTS,
DEFAULTS_COLON, DEFAULTS_NEW, DEFAULTS_OBJECT, DEFAULTS_AREA, DEFAULTS_AREA_EQUALS, DEFAULTS_CONTROLLER, DEFAULTS_CONTROLLER_EQUALS,
- DEFAULTS_ACTION, DEFAULTS_ACTION_EQUALS, DEFAULTS_PARAM, DEFAULTS_PARAM_EQUALS, TEMPLATE, TEMPLATE_COLON
+ DEFAULTS_ACTION, DEFAULTS_ACTION_EQUALS, DEFAULTS_PARAM, DEFAULTS_PARAM_EQUALS, TEMPLATE, TEMPLATE_COLON,
+ NAMESPACES_NEW, NAMESPACES_COLON
}
int commaCount = 0;
@@ -289,6 +293,8 @@ private void processMapRouteCall(int type, String stringValue) {
}
} else if (NEW.equals(stringValue) && commaCount == 2) {
currentMapRouteState = MapRouteState.DEFAULTS_NEW;
+ } else if (NEW.equals(stringValue) && commaCount > 2) {
+ currentMapRouteState = MapRouteState.NAMESPACES_NEW;
} else if (TEMPLATE.equals(stringValue)) {
currentMapRouteState = MapRouteState.TEMPLATE;
} else if ((TEMPLATE + ":").equals(stringValue)){
@@ -440,12 +446,20 @@ private void processMapRouteCall(int type, String stringValue) {
currentMapRouteState = MapRouteState.START;
break;
+ case NAMESPACES_NEW:
+ if (type != '{' && type != ',' && type != '}' && type == '"' && stringValue != null) {
+ currentNamespaces.add(stringValue);
+ } else if (type == '}') {
+ currentMapRouteState = MapRouteState.START;
+ }
+ break;
+
}
if (parenCount == currentParenCount) {
log("Paren count: " + parenCount);
log("Paren current: " + currentParenCount);
- mappings.addRoute(currentName, currentUrl, currentDefaultArea, currentDefaultController, currentDefaultAction, parameterName);
+ mappings.addRoute(currentName, currentUrl, currentDefaultArea, currentDefaultController, currentDefaultAction, parameterName, currentNamespaces);
currentDefaultAction = null;
currentDefaultController = null;
currentDefaultArea = null;
@@ -453,6 +467,7 @@ private void processMapRouteCall(int type, String stringValue) {
commaCount = 0;
currentPhase = Phase.IN_CLASS;
currentMapRouteState = MapRouteState.START;
+ currentNamespaces = list();
}
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/AscxFileMappingsFileParser.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/AscxFileMappingsFileParser.java
index 0181dcda3..1e829af8a 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/AscxFileMappingsFileParser.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/AscxFileMappingsFileParser.java
@@ -24,6 +24,7 @@
package com.denimgroup.threadfix.framework.impl.dotNetWebForm;
import com.denimgroup.threadfix.framework.filefilter.FileExtensionFileFilter;
+import com.denimgroup.threadfix.framework.util.CaseInsensitiveStringMap;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
@@ -32,6 +33,7 @@
import java.util.Map;
import static com.denimgroup.threadfix.CollectionUtils.map;
+import static com.denimgroup.threadfix.framework.util.CollectionUtils.stringMap;
/**
* Created by mac on 10/24/14.
@@ -40,7 +42,7 @@ public class AscxFileMappingsFileParser {
private AscxFileMappingsFileParser(){}
- public static Map getMap(File rootDirectory) {
+ public static CaseInsensitiveStringMap getMap(File rootDirectory) {
if (!rootDirectory.exists() || !rootDirectory.isDirectory()) {
throw new IllegalArgumentException("Invalid directory passed to WebFormsEndpointGenerator: " + rootDirectory);
}
@@ -48,13 +50,13 @@ public static Map getMap(File rootDirectory) {
Collection ascxFiles = FileUtils.listFiles(rootDirectory,
new FileExtensionFileFilter("ascx"), TrueFileFilter.INSTANCE);
- Map map = map();
+ CaseInsensitiveStringMap map = stringMap();
for (Object aspxFile : ascxFiles) {
if (aspxFile instanceof File) {
File file = (File) aspxFile;
- String name = file.getName();
+ String name = file.getName().toLowerCase(); // Normalize all names to lower-case since element name is case-insensitive
String key = name.contains(".") ? name.substring(0, name.indexOf('.')) : name;
map.put(key, new AscxFile(file));
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/AspxControlStack.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/AspxControlStack.java
index 8542f39f1..26ef64ce7 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/AspxControlStack.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/AspxControlStack.java
@@ -23,6 +23,7 @@
////////////////////////////////////////////////////////////////////////
package com.denimgroup.threadfix.framework.impl.dotNetWebForm;
+import com.denimgroup.threadfix.framework.util.CaseInsensitiveStringMap;
import com.denimgroup.threadfix.logging.SanitizedLogger;
import java.util.List;
@@ -30,6 +31,7 @@
import static com.denimgroup.threadfix.CollectionUtils.list;
import static com.denimgroup.threadfix.CollectionUtils.map;
+import static com.denimgroup.threadfix.framework.util.CollectionUtils.stringMap;
/**
* Created by mac on 10/20/14.
@@ -38,7 +40,7 @@ class AspxControlStack {
private static final SanitizedLogger LOG = new SanitizedLogger(AspxControlStack.class);
- Map idMap = map(); // this helps us generate
+ Map idMap = stringMap(); // this helps us generate
List controls = list(new AspxControl("RootElement", generateIdFromCurrentStack(0)));
void add(AspxControl control) {
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/AspxUniqueIdParser.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/AspxUniqueIdParser.java
index f146decc5..725f6b290 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/AspxUniqueIdParser.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/AspxUniqueIdParser.java
@@ -23,11 +23,13 @@
////////////////////////////////////////////////////////////////////////
package com.denimgroup.threadfix.framework.impl.dotNetWebForm;
+import com.denimgroup.threadfix.framework.util.CaseInsensitiveStringMap;
import com.denimgroup.threadfix.framework.util.EventBasedTokenizer;
import com.denimgroup.threadfix.framework.util.EventBasedTokenizerRunner;
import com.denimgroup.threadfix.framework.util.FilePathUtils;
import com.denimgroup.threadfix.logging.SanitizedLogger;
import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
import javax.annotation.Nonnull;
import java.io.File;
@@ -37,6 +39,7 @@
import static com.denimgroup.threadfix.CollectionUtils.map;
import static com.denimgroup.threadfix.CollectionUtils.set;
+import static com.denimgroup.threadfix.framework.util.CollectionUtils.stringMap;
/**
* Created by mac on 10/20/14.
@@ -52,7 +55,7 @@ public class AspxUniqueIdParser implements EventBasedTokenizer {
String masterPage = null;
- private Map allControlMap;
+ private CaseInsensitiveStringMap allControlMap;
@Nonnull
public static AspxUniqueIdParser parse(@Nonnull File file) {
@@ -60,7 +63,7 @@ public static AspxUniqueIdParser parse(@Nonnull File file) {
}
@Nonnull
- public static AspxUniqueIdParser parse(@Nonnull File file, Map controlMap) {
+ public static AspxUniqueIdParser parse(@Nonnull File file, CaseInsensitiveStringMap controlMap) {
return runTokenizer(file, new AspxUniqueIdParser(file, controlMap));
}
@@ -86,7 +89,7 @@ private static AspxUniqueIdParser runTokenizer(File file, AspxUniqueIdParser par
}
- AspxUniqueIdParser(File file, Map controlMap) {
+ AspxUniqueIdParser(File file, CaseInsensitiveStringMap controlMap) {
assert file.exists() : "File didn't exist.";
assert file.isFile() : "File was not a valid file.";
LOG.debug("Parsing controller mappings for " + file.getAbsolutePath());
@@ -129,12 +132,12 @@ private void processMasterPage(int type, String stringValue) {
currentPageState = type == '@' ? PageState.ARROBA : PageState.START;
break;
case ARROBA:
- currentPageState = type == -3 && "Page".equals(stringValue) ? PageState.PAGE : PageState.START;
+ currentPageState = type == -3 && "Page".equalsIgnoreCase(stringValue) ? PageState.PAGE : PageState.START;
break;
case PAGE:
if (type == '>') {
currentPageState = PageState.DONE;
- } else if ("MasterPageFile".equals(stringValue)) {
+ } else if ("MasterPageFile".equalsIgnoreCase(stringValue)) {
currentPageState = PageState.MASTER_PAGE_FILE;
}
break;
@@ -159,7 +162,7 @@ private enum State {
}
State currentState = State.START;
String currentSrc, currentTagPrefix, currentTagName;
- Map includedControlMap = map();
+ CaseInsensitiveStringMap includedControlMap = stringMap();
private void processRequires(int type, String stringValue) {
switch (currentState) {
@@ -179,11 +182,11 @@ private void processRequires(int type, String stringValue) {
if (type == '>') {
saveControlData();
currentState = State.START;
- } else if ("Src".equals(stringValue)) {
+ } else if ("Src".equalsIgnoreCase(stringValue)) {
currentState = State.SRC;
- } else if ("TagPrefix".equals(stringValue)) {
+ } else if ("TagPrefix".equalsIgnoreCase(stringValue)) {
currentState = State.TAG_PREFIX;
- } else if ("TagName".equals(stringValue)) {
+ } else if ("TagName".equalsIgnoreCase(stringValue)) {
currentState = State.TAG_NAME;
}
break;
@@ -199,7 +202,7 @@ private void processRequires(int type, String stringValue) {
break;
case TAG_PREFIX:
if (type == '"') {
- currentTagPrefix = stringValue;
+ currentTagPrefix = stringValue.toLowerCase();
}
if (type != '=') {
currentState = State.REGISTER;
@@ -207,7 +210,7 @@ private void processRequires(int type, String stringValue) {
break;
case TAG_NAME:
if (type == '"') {
- currentTagName = stringValue;
+ currentTagName = stringValue.toLowerCase();
}
if (type != '=') {
currentState = State.REGISTER;
@@ -219,6 +222,9 @@ private void processRequires(int type, String stringValue) {
private void saveControlData() {
if (allControlMap != null) {
AscxFile ascxFile = allControlMap.get(currentTagName);
+ if (ascxFile == null && currentSrc != null) {
+ ascxFile = allControlMap.get(FilenameUtils.getBaseName(currentSrc).toLowerCase());
+ }
if (ascxFile == null && currentSrc != null) {
String srcName = FilePathUtils.normalizePath(currentSrc);
if (srcName.contains("/")) srcName = srcName.substring(srcName.lastIndexOf('/') + 1);
@@ -226,12 +232,13 @@ private void saveControlData() {
ascxFile = allControlMap.get(srcName);
}
if (ascxFile != null) {
+ // For directly registered controls
includedControlMap.put(currentTagPrefix + ":" + currentTagName, ascxFile);
} else {
- LOG.error("Unable to load control " + currentTagName + ".");
+ LOG.warn("Unable to load control " + currentTagName + ".");
}
} else {
- LOG.error("Got data for a control but wasn't passed any control definitions.");
+ LOG.warn("Got data for a control but wasn't passed any control definitions.");
}
currentTagName = null;
@@ -256,18 +263,20 @@ private void processCustomTags(int type, String stringValue) {
currentControlState = type == '<' ? ControlState.LEFT_ANGLE : ControlState.START;
break;
case LEFT_ANGLE:
+ String[] splitValue = stringValue == null ? null : stringValue.split(":");
if (includedControlMap.containsKey(stringValue)) {
currentFile = includedControlMap.get(stringValue);
currentControlTagName = stringValue;
- LOG.info("Got control from file " + currentFile.name);
+ LOG.debug("Got control from file " + currentFile.name);
currentControlState = ControlState.NAME;
+
} else {
currentControlState = ControlState.START;
}
break;
case NAME:
// -3 is the "token" code
- if (type == -3 && "ID".equals(stringValue)) {
+ if (type == -3 && "ID".equalsIgnoreCase(stringValue)) {
currentControlState = ControlState.ID;
}
break;
@@ -346,10 +355,10 @@ private void processBody(int type, String stringValue) {
gotIdAttribute = false;
}
- if ("asp:Content".equals(lastName)) {
- gotIdAttribute = gotIdAttribute || (needsId && stringValue != null && stringValue.equals("ContentPlaceHolderID"));
+ if ("asp:Content".equalsIgnoreCase(lastName)) {
+ gotIdAttribute = gotIdAttribute || (needsId && stringValue != null && stringValue.equalsIgnoreCase("ContentPlaceHolderID"));
} else {
- gotIdAttribute = gotIdAttribute || (needsId && stringValue != null && stringValue.equals("ID"));
+ gotIdAttribute = gotIdAttribute || (needsId && stringValue != null && stringValue.equalsIgnoreCase("ID"));
}
if (type == '>' && lastName != null) { // we only want to do this if we're in an asp:* tag
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/MasterPageParser.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/MasterPageParser.java
index c8611d1a6..f600b1a61 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/MasterPageParser.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/MasterPageParser.java
@@ -24,6 +24,7 @@
package com.denimgroup.threadfix.framework.impl.dotNetWebForm;
import com.denimgroup.threadfix.framework.filefilter.FileExtensionFileFilter;
+import com.denimgroup.threadfix.framework.util.CaseInsensitiveStringMap;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
@@ -32,6 +33,7 @@
import java.util.Map;
import static com.denimgroup.threadfix.CollectionUtils.map;
+import static com.denimgroup.threadfix.framework.util.CollectionUtils.stringMap;
/**
* Created by mac on 10/27/14.
@@ -40,19 +42,19 @@ public class MasterPageParser {
private MasterPageParser(){}
- public static Map getMasterFileMap(File rootDirectory) {
- Map map = AscxFileMappingsFileParser.getMap(rootDirectory);
+ public static CaseInsensitiveStringMap getMasterFileMap(File rootDirectory) {
+ CaseInsensitiveStringMap map = AscxFileMappingsFileParser.getMap(rootDirectory);
return getMasterFileMap(rootDirectory, map);
}
- public static Map getMasterFileMap(File rootDirectory, Map ascxFileMap) {
+ public static CaseInsensitiveStringMap getMasterFileMap(File rootDirectory, CaseInsensitiveStringMap ascxFileMap) {
if (rootDirectory == null) {
throw new IllegalArgumentException("Can't pass null argument to getMasterFileMap()");
} else if (!rootDirectory.isDirectory()) {
throw new IllegalArgumentException("Can't pass a non-directory file argument to getMasterFileMap()");
}
- Map parserMap = map();
+ CaseInsensitiveStringMap parserMap = stringMap();
Collection masterFiles = FileUtils.listFiles(rootDirectory,
new FileExtensionFileFilter("Master"), TrueFileFilter.INSTANCE);
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/WebFormsEndpointBase.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/WebFormsEndpointBase.java
index d8f188dfe..0c4aa6239 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/WebFormsEndpointBase.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/WebFormsEndpointBase.java
@@ -27,6 +27,7 @@
import com.denimgroup.threadfix.data.entities.ExplicitEndpointPathNode;
import com.denimgroup.threadfix.data.entities.RouteParameter;
+import com.denimgroup.threadfix.data.entities.RouteParameterType;
import com.denimgroup.threadfix.data.enums.EndpointRelevanceStrictness;
import com.denimgroup.threadfix.data.interfaces.EndpointPathNode;
import com.denimgroup.threadfix.framework.engine.AbstractEndpoint;
@@ -163,16 +164,18 @@ private void collectParameters(AspxParser aspxParser, AspxCsParser aspxCsParser)
}
if (!foundNormalParameter) {
- map.put(cleanViewParam(parameter), list(0));
+ map.put(parameter, list(0));
}
}
- for (List integers : map.values()) {
- Collections.sort(integers);
+ for (String paramName : map.keySet()) {
+ RouteParameter param = new RouteParameter(paramName);
+ param.setParamType(RouteParameterType.FORM_DATA);
+ params.put(paramName, param);
}
- for (String paramName : map.keySet()) {
- params.put(paramName, new RouteParameter(paramName));
+ for (List integers : map.values()) {
+ Collections.sort(integers);
}
}
@@ -271,12 +274,6 @@ protected void copyPropertiesTo(WebFormsEndpointBase target) {
target.map.putAll(this.map);
}
- private static String cleanViewParam(String param){
- if(StringUtils.isBlank(param)) return null;
- if (!param.contains("$")) return param;
- return param.substring(param.lastIndexOf('$') + 1, param.length());
- }
-
@Nonnull
@Override
final protected List getLintLine() {
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/WebFormsEndpointGenerator.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/WebFormsEndpointGenerator.java
index eca54f988..b0c1797a8 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/WebFormsEndpointGenerator.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/WebFormsEndpointGenerator.java
@@ -25,11 +25,12 @@
////////////////////////////////////////////////////////////////////////
package com.denimgroup.threadfix.framework.impl.dotNetWebForm;
-import com.denimgroup.threadfix.data.entities.Document;
import com.denimgroup.threadfix.data.interfaces.Endpoint;
import com.denimgroup.threadfix.framework.engine.full.EndpointGenerator;
import com.denimgroup.threadfix.framework.filefilter.FileExtensionFileFilter;
+import com.denimgroup.threadfix.framework.util.CaseInsensitiveStringMap;
import com.denimgroup.threadfix.framework.util.EndpointUtil;
+import com.denimgroup.threadfix.framework.util.FilePathUtils;
import com.denimgroup.threadfix.logging.SanitizedLogger;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
@@ -51,6 +52,7 @@
import static com.denimgroup.threadfix.CollectionUtils.list;
import static com.denimgroup.threadfix.CollectionUtils.map;
+import static com.denimgroup.threadfix.framework.util.CollectionUtils.stringMap;
/**
* Created by mac on 9/4/14.
@@ -70,7 +72,7 @@ public WebFormsEndpointGenerator(@Nonnull File rootDirectory) {
List projectDirectories = findProjectDirectories(rootDirectory);
LOG.debug("Detected " + projectDirectories.size() + " projects");
- Map ascxFiles = map();
+ CaseInsensitiveStringMap ascxFiles = stringMap();
// Collect ASCX controls across all projects
for (File projectDirectory : projectDirectories) {
@@ -79,13 +81,14 @@ public WebFormsEndpointGenerator(@Nonnull File rootDirectory) {
for (File projectDirectory : projectDirectories) {
File webConfig = getWebConfigFile(projectDirectory);
- Map masterFileMap = MasterPageParser.getMasterFileMap(projectDirectory, ascxFiles);
+ CaseInsensitiveStringMap masterFileMap = MasterPageParser.getMasterFileMap(projectDirectory, ascxFiles);
List aspxParsers = getAspxParsers(projectDirectory, ascxFiles, masterFileMap);
List aspxCsParsers = getAspxCsParsers(projectDirectory);
+ WebFormsSitemapParser sitemap = findSiteMap(projectDirectory);
List defaultPages = collectDefaultPages(webConfig);
- collapseToEndpoints(aspxCsParsers, aspxParsers, rootDirectory, projectDirectory, defaultPages);
+ collapseToEndpoints(aspxCsParsers, aspxParsers, rootDirectory, projectDirectory, defaultPages, sitemap);
}
// There's currently no way to distinguish HTTP methods for each route, nor for
@@ -113,15 +116,16 @@ public WebFormsEndpointGenerator(@Nonnull File rootDirectory) {
}
private List findProjectDirectories(File rootDirectory) {
- Collection csprojFiles = FileUtils.listFiles(rootDirectory, new String[] { "csproj" }, true);
- List result = list();
- for (File csproj : csprojFiles) {
- File folder = csproj.getParentFile();
- if (!FileUtils.listFiles(folder, new String[] { "aspx", "ascx", "asax" }, true).isEmpty()) {
- result.add(folder);
+ Collection projectFiles = FileUtils.listFiles(rootDirectory, new String[] { "csproj", "sitemap", "config" }, true);
+ List possibleResults = list();
+ for (File proj : projectFiles) {
+ File folder = proj.getParentFile();
+ if (!possibleResults.contains(folder) && !FileUtils.listFiles(folder, new String[] { "aspx", "ascx", "asax" }, true).isEmpty()) {
+ possibleResults.add(folder);
}
}
- return result;
+
+ return FilePathUtils.findRootFolders(possibleResults);
}
private File getWebConfigFile(File rootDirectory) {
@@ -144,7 +148,7 @@ private File getWebConfigFile(File rootDirectory) {
private List getAspxCsParsers(File rootDirectory) {
Collection aspxCsFiles = FileUtils.listFiles(rootDirectory,
- new FileExtensionFileFilter(".cs"), TrueFileFilter.INSTANCE);
+ new FileExtensionFileFilter(".aspx.cs"), TrueFileFilter.INSTANCE);
List aspxCsParsers = list();
@@ -158,8 +162,8 @@ private List getAspxCsParsers(File rootDirectory) {
}
private List getAspxParsers(File rootDirectory,
- Map map,
- Map masterFileMap) {
+ CaseInsensitiveStringMap map,
+ CaseInsensitiveStringMap masterFileMap) {
Collection aspxFiles = FileUtils.listFiles(rootDirectory,
new FileExtensionFileFilter("aspx"), TrueFileFilter.INSTANCE);
@@ -219,7 +223,6 @@ List collectDefaultPages(File webConfig) {
return result;
}
- Node documentNode = documentNodes.item(0);
NodeList fileNodes;
XPath xPath = XPathFactory.newInstance().newXPath();
@@ -259,6 +262,19 @@ List collectDefaultPages(File webConfig) {
return result;
}
+ WebFormsSitemapParser findSiteMap(File rootDirectory) {
+ for (File sitemapFile : FileUtils.listFiles(rootDirectory, new String[] { "sitemap" }, false)) {
+ if (sitemapFile.getName().equalsIgnoreCase("web")) {
+ try {
+ return new WebFormsSitemapParser(sitemapFile);
+ } catch (Exception ignored) {
+ return null;
+ }
+ }
+ }
+ return null;
+ }
+
File getAspxRoot(File rootDirectory) {
Collection aspxCsFiles = FileUtils.listFiles(rootDirectory,
new FileExtensionFileFilter(".config"), TrueFileFilter.INSTANCE);
@@ -287,7 +303,12 @@ void collapseToEndpoints(Collection csParsers,
Collection aspxParsers,
File solutionDirectory,
File projectDirectory,
- List defaultPages) {
+ List defaultPages,
+ WebFormsSitemapParser sitemap) {
+
+ // TODO - Make use of sitemap when available
+ // Need some way to restructure aspx/cs parsers if an endpoint path needs to be corrected
+
Map aspxParserMap = map();
Map aspxCsParserMap = map();
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/WebFormsSitemapParser.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/WebFormsSitemapParser.java
new file mode 100644
index 000000000..4dbfc25df
--- /dev/null
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/dotNetWebForm/WebFormsSitemapParser.java
@@ -0,0 +1,104 @@
+package com.denimgroup.threadfix.framework.impl.dotNetWebForm;
+
+import org.apache.commons.lang3.StringUtils;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import static com.denimgroup.threadfix.CollectionUtils.list;
+
+public class WebFormsSitemapParser {
+
+ List parsedEndpoints = list();
+
+ public WebFormsSitemapParser(File sitemapFile) throws ParserConfigurationException, SAXException, IOException {
+ Handler parsingHandler = new Handler(sitemapFile);
+
+ SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
+ parser.parse(sitemapFile, parsingHandler);
+
+ this.parsedEndpoints.addAll(parsingHandler.parsedEndpoints);
+
+ for (File referencedSitemap : parsingHandler.referencedSitemaps) {
+ WebFormsSitemapParser referencedParser = new WebFormsSitemapParser(referencedSitemap);
+ this.parsedEndpoints.addAll(referencedParser.parsedEndpoints);
+ }
+ }
+
+ public List getParsedEndpoints() {
+ return this.parsedEndpoints;
+ }
+
+ public int countEndpointsForFilename(String filename) {
+ int cnt = 0;
+ for (String endpoint : this.parsedEndpoints) {
+ if (endpoint.endsWith("/" + filename)) {
+ cnt++;
+ }
+ }
+ return cnt;
+ }
+
+ public List getEndpointsForFilename(String filename) {
+ List endpoints = list();
+ for (String endpoint : this.parsedEndpoints) {
+ if (endpoint.endsWith("/" + filename)) {
+ endpoints.add(endpoint);
+ }
+ }
+ return endpoints;
+ }
+
+ public String getEndpointForFilename(String filename) {
+ for (String endpoint : this.parsedEndpoints) {
+ if (endpoint.endsWith("/" + filename)) {
+ return endpoint;
+ }
+ }
+ return null;
+ }
+
+
+ private class Handler extends DefaultHandler {
+ public List parsedEndpoints = list();
+ public List referencedSitemaps = list();
+
+ File sitemapFile;
+
+ public Handler(File sitemapFile) {
+ this.sitemapFile = sitemapFile.getAbsoluteFile();
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+ super.startElement(uri, localName, qName, attributes);
+
+ if (qName.equals("siteMapNode")) {
+ String url = attributes.getValue("url");
+ if (url != null) {
+ if (url.startsWith("~")) {
+ url = url.substring(1);
+ }
+ url = StringUtils.replaceChars(url, '\\', '/');
+ if (!url.startsWith("/")) {
+ url = "/" + url;
+ }
+
+ this.parsedEndpoints.add(url);
+ }
+
+ String referencedSitemap = attributes.getValue("siteMapFile");
+ if (referencedSitemap != null) {
+ referencedSitemaps.add(new File(this.sitemapFile.getParent(), referencedSitemap));
+ }
+ }
+ }
+ }
+}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/jsp/JSPEndpoint.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/jsp/JSPEndpoint.java
index 45cf9e785..c4bff464b 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/jsp/JSPEndpoint.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/jsp/JSPEndpoint.java
@@ -37,11 +37,9 @@
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
-import java.util.ArrayList;
+import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import java.util.List;
-import java.util.Map;
import static com.denimgroup.threadfix.CollectionUtils.*;
@@ -52,38 +50,50 @@ public class JSPEndpoint extends AbstractEndpoint {
@Nonnull
private final Map parameters = map();
+ @Nonnull
+ private final Map> lineToParamMap;
+ @Nonnull
+ private final Map paramToLineMap;
+
@Nonnull
private String method;
private int startLine = -1, endLine = -1;
-// @Nonnull
-// private final Map paramToLineMap;
-
-// @Nonnull
-// private final Map> parameterMap;
-
private JSPEndpoint() {
-
+ this.lineToParamMap = map();
+ this.paramToLineMap = map();
}
public JSPEndpoint(@Nonnull String staticPath,
@Nonnull String dynamicPath,
@Nonnull String method,
- @Nonnull Map parameterMap) {
+ @Nonnull Map> lineToParamMap) {
this.method = method;
this.staticPath = staticPath;
this.dynamicPath = dynamicPath;
- this.parameters.putAll(parameterMap);
+ this.lineToParamMap = lineToParamMap;
this.dynamicPath = this.dynamicPath.replaceAll("\\\\", "/");
-// for (List lineParams : parameterMap.values()) {
-// for (String param : lineParams) {
-// parameters.put(param, RouteParameter.fromDataType(ParameterDataType.STRING));
-// }
-// }
+ List sortedLineNumbers = new ArrayList(lineToParamMap.keySet());
+ Collections.sort(sortedLineNumbers);
+
+ this.paramToLineMap = map();
+
+ for (Integer lineNo : sortedLineNumbers) {
+ List paramsAtLine = lineToParamMap.get(lineNo);
+ for (RouteParameter param : paramsAtLine) {
+ if (!paramToLineMap.containsKey(param.getName())) {
+ paramToLineMap.put(param.getName(), lineNo);
+ }
+
+ if (!parameters.containsKey(param.getName())) {
+ parameters.put(param.getName(), param);
+ }
+ }
+ }
}
@Override
@@ -159,54 +169,20 @@ public boolean isRelevant(String endpoint, EndpointRelevanceStrictness strictnes
return true;
}
- // @Nonnull
-// private Map getParamToLineMap(
-// Map> parameterMap) {
-// Map paramMap = map();
-//
-// for (String parameter : parameters.keySet()) {
-// paramMap.put(parameter, getFirstLineNumber(parameter, parameterMap));
-// }
-//
-// return paramMap;
-// }
-
-// private Integer getFirstLineNumber(@Nonnull String parameterName,
-// @Nonnull Map> parameterMap) {
-// Integer returnValue = Integer.MAX_VALUE;
-//
-// for (Map.Entry> entry : parameterMap.entrySet()) {
-// if (entry.getKey() < returnValue &&
-// entry.getValue() != null &&
-// entry.getValue().contains(parameterName)) {
-// returnValue = entry.getKey();
-// }
-// }
-//
-// if (returnValue == Integer.MAX_VALUE) {
-// returnValue = 1; // This way even if no parameter is found a marker can be created for the file
-// }
-//
-// return returnValue;
-// }
-
- // TODO improve
- // TODO - Re-enable
@Nullable
String getParameterName(@Nonnull Iterable codePoints) {
- return null;
-// String parameter = null;
-//
-// for (CodePoint codePoint : codePoints) {
-// List possibleParameters = parameterMap.get(codePoint.getLineNumber());
-//
-// if (possibleParameters != null && possibleParameters.size() == 1) {
-// parameter = possibleParameters.get(0);
-// break;
-// }
-// }
-//
-// return parameter;
+ String parameter = null;
+
+ for (CodePoint codePoint : codePoints) {
+ List possibleParameters = lineToParamMap.get(codePoint.getLineNumber());
+
+ if (possibleParameters != null && possibleParameters.size() == 1) {
+ parameter = possibleParameters.get(0).getName();
+ break;
+ }
+ }
+
+ return parameter;
}
@Nonnull
@@ -265,16 +241,14 @@ public int getEndingLineNumber() {
return endLine;
}
- // TODO - Re-enable
@Override
public int getLineNumberForParameter(String parameter) {
- return -1;
-// Integer value = paramToLineMap.get(parameter);
-// if (value == null) {
-// return 0;
-// } else {
-// return value;
-// }
+ Integer value = paramToLineMap.get(parameter);
+ if (value == null) {
+ return 0;
+ } else {
+ return value;
+ }
}
public void setLines(int startLine, int endLine) {
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/jsp/JSPEndpointGenerator.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/jsp/JSPEndpointGenerator.java
index 4e6adb0e4..db803a422 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/jsp/JSPEndpointGenerator.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/jsp/JSPEndpointGenerator.java
@@ -55,121 +55,134 @@ public class JSPEndpointGenerator implements EndpointGenerator {
private static final SanitizedLogger LOG = new SanitizedLogger(JSPEndpointGenerator.class);
- private JSPWebXmlConfiguration xmlConfiguration;
-
- private final Map> includeMap = map();
private final Map> jspEndpointMap = map();
private final List endpoints = list();
private final ProjectDirectory projectDirectory;
@Nullable
- private final File projectRoot, jspRoot;
+ private File rootFile;
@SuppressWarnings("unchecked")
public JSPEndpointGenerator(@Nonnull File rootFile) {
if (rootFile.exists()) {
- this.projectRoot = rootFile;
+ this.rootFile = rootFile;
projectDirectory = new ProjectDirectory(rootFile);
- File webXmlFile = findWebXmlFile(rootFile);
- if (webXmlFile != null) {
- JSPWebXmlParser webXmlParser = new JSPWebXmlParser(webXmlFile);
- xmlConfiguration = webXmlParser.getConfiguration();
- }
+ List projectFolders = findProjectFolders(rootFile);
- JSPServletParser servletParser = new JSPServletParser(rootFile);
+ for (File projectFolder : projectFolders) {
- String jspRootString = CommonPathFinder.findOrParseProjectRootFromDirectory(rootFile, "jsp");
+ File webXmlFile = findWebXmlFile(projectFolder);
+ JSPWebXmlConfiguration xmlConfiguration = null;
+ if (webXmlFile != null) {
+ JSPWebXmlParser webXmlParser = new JSPWebXmlParser(webXmlFile);
+ xmlConfiguration = webXmlParser.getConfiguration();
+ }
- LOG.info("Calculated JSP root to be: " + jspRootString);
+ JSPServletParser servletParser = new JSPServletParser(projectFolder);
- if (jspRootString == null) {
- jspRoot = projectRoot;
- } else {
- File possibleRoot = new File(jspRootString);
- if (!possibleRoot.isDirectory()) {
- jspRootString = jspRootString.substring(0, jspRootString.lastIndexOf('/'));
- jspRoot = new File(jspRootString);
- } else {
- jspRoot = possibleRoot;
+ String jspRootString = CommonPathFinder.findOrParseProjectRootFromDirectory(projectFolder, "jsp");
+ if (jspRootString != null) {
+ jspRootString = FilePathUtils.normalizePath(jspRootString);
}
- }
- Collection jspFiles = FileUtils.listFiles(jspRoot, JSPFileFilter.INSTANCE, NoDotDirectoryFileFilter.INSTANCE);
+ LOG.info("Calculated JSP root to be: " + jspRootString);
- LOG.info("Found " + jspFiles.size() + " JSP files.");
+ File jspRoot;
- for (File file : jspFiles) {
- if (!file.getAbsolutePath().toLowerCase().contains("web-inf")) {
- parseFile(file);
+ if (jspRootString == null) {
+ jspRoot = projectFolder;
+ } else {
+ File possibleRoot = new File(jspRootString);
+ if (!possibleRoot.isDirectory()) {
+ jspRootString = jspRootString.substring(0, jspRootString.lastIndexOf('/'));
+ jspRoot = new File(jspRootString);
+ } else {
+ jspRoot = possibleRoot;
+ }
}
- }
- Collection jspAndHtmlFiles = FileUtils.listFiles(rootFile, new String[] { "jsp", "html" }, true);
- List implicitParams = list();
- for (File file : jspAndHtmlFiles) {
- HyperlinkParameterDetector parameterDetector = new HyperlinkParameterDetector();
- String fileContents;
- try {
- fileContents = FileUtils.readFileToString(file);
- } catch (IOException e) {
- e.printStackTrace();
- continue;
+ Collection jspFiles = FileUtils.listFiles(jspRoot, JSPFileFilter.INSTANCE, NoDotDirectoryFileFilter.INSTANCE);
+ List projectEndpoints = list();
+ Map> includeMap = map();
+
+ LOG.info("Found " + jspFiles.size() + " JSP files.");
+
+ for (File file : jspFiles) {
+ if (!file.getAbsolutePath().toLowerCase().contains("web-inf")) {
+ parseFile(projectEndpoints, includeMap, jspRoot, file);
+ }
}
- fileContents = stripJspElements(fileContents);
- HyperlinkParameterDetectionResult parsedReferences = parameterDetector.parse(fileContents, file);
- if (parsedReferences != null) {
- implicitParams.add(parsedReferences);
+
+ Collection jspAndHtmlFiles = FileUtils.listFiles(projectFolder, new String[]{"jsp", "html"}, true);
+ List implicitParams = list();
+ for (File file : jspAndHtmlFiles) {
+ HyperlinkParameterDetector parameterDetector = new HyperlinkParameterDetector();
+ String fileContents;
+ try {
+ fileContents = FileUtils.readFileToString(file);
+ } catch (IOException e) {
+ e.printStackTrace();
+ continue;
+ }
+ fileContents = stripJspElements(fileContents);
+ HyperlinkParameterDetectionResult parsedReferences = parameterDetector.parse(fileContents, file);
+ if (parsedReferences != null) {
+ implicitParams.add(parsedReferences);
+ }
}
- }
- if (xmlConfiguration != null) {
- loadWebXmlWelcomeFiles();
- loadAnnotatedServlets(servletParser);
- loadWebXmlServletMappings(servletParser);
- }
+ if (xmlConfiguration != null) {
+ loadWebXmlWelcomeFiles(projectEndpoints, xmlConfiguration, jspRoot);
+ loadAnnotatedServlets(projectEndpoints, servletParser);
+ loadWebXmlServletMappings(projectEndpoints, xmlConfiguration, jspRoot, servletParser);
+ }
- updateFileParameters(endpoints, implicitParams);
+ addParametersFromIncludedFiles(includeMap, projectEndpoints);
+ updateFileParameters(projectEndpoints, implicitParams);
- int numAddedParams = 0, numRemovedParams = 0;
+ int numAddedParams = 0, numRemovedParams = 0;
- HyperlinkParameterMerger parameterMerger = new HyperlinkParameterMerger(true, true);
- for (HyperlinkParameterDetectionResult params : implicitParams) {
- HyperlinkParameterMergingGuide mergeGuide = parameterMerger.mergeParsedImplicitParameters(endpoints, params);
+ HyperlinkParameterMerger parameterMerger = new HyperlinkParameterMerger(true, true);
+ for (HyperlinkParameterDetectionResult params : implicitParams) {
+ HyperlinkParameterMergingGuide mergeGuide = parameterMerger.mergeParsedImplicitParameters(projectEndpoints, params);
- for (Endpoint endpoint : endpoints) {
- JSPEndpoint jspEndpoint = (JSPEndpoint)endpoint;
- List addedParams = mergeGuide.findAddedParameters(endpoint, endpoint.getHttpMethod());
- if (addedParams != null) {
- for (RouteParameter newParam : addedParams) {
- jspEndpoint.getParameters().put(newParam.getName(), newParam);
- ++numAddedParams;
+ for (Endpoint endpoint : projectEndpoints) {
+ JSPEndpoint jspEndpoint = (JSPEndpoint) endpoint;
+ List addedParams = mergeGuide.findAddedParameters(endpoint, endpoint.getHttpMethod());
+ if (addedParams != null) {
+ for (RouteParameter newParam : addedParams) {
+ jspEndpoint.getParameters().put(newParam.getName(), newParam);
+ ++numAddedParams;
+ }
}
- }
- List removedParams = mergeGuide.findRemovedParameters(endpoint, endpoint.getHttpMethod());
- if (removedParams != null) {
- for (RouteParameter oldParam : removedParams) {
- jspEndpoint.getParameters().remove(oldParam.getName());
- ++numRemovedParams;
+ List removedParams = mergeGuide.findRemovedParameters(endpoint, endpoint.getHttpMethod());
+ if (removedParams != null) {
+ for (RouteParameter oldParam : removedParams) {
+ jspEndpoint.getParameters().remove(oldParam.getName());
+ ++numRemovedParams;
+ }
}
}
}
- }
- LOG.info("Detected " + numAddedParams + " new parameters and removed " + numRemovedParams + " misassigned parameters after HTML reference parsing");
+ LOG.info("Detected " + numAddedParams + " new parameters and removed " + numRemovedParams + " misassigned parameters after HTML reference parsing");
- ParameterMerger genericMerger = new ParameterMerger();
- genericMerger.setCaseSensitive(true);
- Map> mergedParams = genericMerger.mergeParametersIn(endpoints);
+ ParameterMerger genericMerger = new ParameterMerger();
+ genericMerger.setCaseSensitive(true);
+ Map> mergedParams = genericMerger.mergeParametersIn(projectEndpoints);
- for (Endpoint endpoint : mergedParams.keySet()) {
- JSPEndpoint jspEndpoint = (JSPEndpoint)endpoint;
- Map replacedParameters = mergedParams.get(endpoint);
- for (String paramName : replacedParameters.keySet()) {
- jspEndpoint.getParameters().put(paramName, replacedParameters.get(paramName));
+ for (Endpoint endpoint : mergedParams.keySet()) {
+ JSPEndpoint jspEndpoint = (JSPEndpoint) endpoint;
+ Map replacedParameters = mergedParams.get(endpoint);
+ for (String paramName : replacedParameters.keySet()) {
+ jspEndpoint.getParameters().put(paramName, replacedParameters.get(paramName));
+ }
}
+
+ this.endpoints.addAll(projectEndpoints);
}
applyLineNumbers(endpoints);
@@ -182,8 +195,7 @@ public JSPEndpointGenerator(@Nonnull File rootFile) {
LOG.error("Root file didn't exist. Exiting.");
projectDirectory = null;
- projectRoot = null;
- jspRoot = null;
+ this.rootFile = null;
}
}
@@ -268,8 +280,8 @@ boolean areEndpointsAliased(Endpoint a, Endpoint b) {
String urlA = a.getUrlPath();
String urlB = b.getUrlPath();
- urlA = CodeParseUtil.trim(urlA, new String[] { "/" });
- urlB = CodeParseUtil.trim(urlB, new String[] { "/" });
+ urlA = CodeParseUtil.trim(urlA, "/");
+ urlB = CodeParseUtil.trim(urlB, "/");
return
StringUtils.countMatches(urlA, "/") == StringUtils.countMatches( urlB, "/") &&
@@ -297,7 +309,7 @@ void applyLineNumbers(Collection endpoints) {
String filePath = endpoint.getFilePath();
File file = new File(filePath);
if (!file.isAbsolute() || !file.exists()) {
- filePath = PathUtil.combine(projectRoot.getAbsolutePath(), filePath);
+ filePath = PathUtil.combine(rootFile.getAbsolutePath(), filePath);
file = new File(filePath);
}
@@ -318,7 +330,7 @@ void applyLineNumbers(Collection endpoints) {
}
}
- void loadWebXmlWelcomeFiles() {
+ void loadWebXmlWelcomeFiles(List endpoints, JSPWebXmlConfiguration xmlConfiguration, File jspRoot) {
List welcomeFileLocations = list();
for (File discoveredFile : FileUtils.listFiles(jspRoot, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE)) {
String fileName = discoveredFile.getName();
@@ -331,7 +343,7 @@ void loadWebXmlWelcomeFiles() {
}
for (File welcomeFile : welcomeFileLocations) {
- String relativePath = FilePathUtils.getRelativePath(welcomeFile.getAbsolutePath(), projectRoot);
+ String relativePath = FilePathUtils.getRelativePath(welcomeFile.getAbsolutePath(), rootFile);
String webRelativePath = FilePathUtils.getRelativePath(welcomeFile.getAbsolutePath(), jspRoot);
String endpointPath = webRelativePath.substring(0, webRelativePath.length() - welcomeFile.getName().length());
JSPEndpoint welcomeEndpoint = new JSPEndpoint(relativePath, endpointPath, "GET", JSPParameterParser.parse(welcomeFile));
@@ -340,7 +352,7 @@ void loadWebXmlWelcomeFiles() {
}
}
- void loadAnnotatedServlets(JSPServletParser servletParser) {
+ void loadAnnotatedServlets(List endpoints, JSPServletParser servletParser) {
// Add endpoints from servlets mapped via @WebServlet
for (JSPServlet servlet : servletParser.getServlets()) {
String relativeFilePath = getRelativePath(servlet.getFilePath());
@@ -348,14 +360,9 @@ void loadAnnotatedServlets(JSPServletParser servletParser) {
Set servletMethods = servlet.getHttpMethods();
for (String endpointPath : servlet.getAnnotatedEndpoints()) {
for (String method : servletMethods) {
- List params = servlet.getMethodParameters(method);
- Map paramMap = map();
- for (RouteParameter param : params) {
- paramMap.put(param.getName(), param);
- }
int startLine = servlet.getStartLine(method);
int endLine = servlet.getEndLine(method);
- JSPEndpoint newEndpoint = new JSPEndpoint(relativeFilePath, endpointPath, method, paramMap);
+ JSPEndpoint newEndpoint = new JSPEndpoint(relativeFilePath, endpointPath, method, servlet.getParameterLineMap());
newEndpoint.setLines(startLine, endLine);
endpoints.add(newEndpoint);
addToEndpointMap(relativeFilePath, newEndpoint);
@@ -364,12 +371,12 @@ void loadAnnotatedServlets(JSPServletParser servletParser) {
}
}
- void loadWebXmlServletMappings(JSPServletParser servletParser) {
+ void loadWebXmlServletMappings(List endpoints, JSPWebXmlConfiguration xmlConfiguration, File jspRoot, JSPServletParser servletParser) {
LOG.info("Found " + xmlConfiguration.getAllServletMappings().size() + " servlet mappings in web.xml.");
for (JSPWebXmlServletMapping mapping : xmlConfiguration.getAllServletMappings()) {
List urlPatterns = mapping.getUrlPatterns();
String filePath = null;
- Map> methodParameters = null;
+ Map>> methodParameters = null;
Collection supportedMethods = null;
Map methodStartLines = map();
Map methodEndLines = map();
@@ -389,13 +396,13 @@ void loadWebXmlServletMappings(JSPServletParser servletParser) {
supportedMethods = servlet.getHttpMethods();
for (String method : supportedMethods) {
- methodStartLines.put(method, servlet.getStartLine(method));
- methodEndLines.put(method, servlet.getEndLine(method));
- Map currentMap = map();
- for (RouteParameter param : servlet.getMethodParameters(method)) {
- currentMap.put(param.getName(), param);
- }
- methodParameters.put(method, currentMap);
+ int startLine = servlet.getStartLine(method);
+ int endLine = servlet.getEndLine(method);
+
+ methodStartLines.put(method, startLine);
+ methodEndLines.put(method, endLine);
+
+ methodParameters.put(method, servlet.getParameterLineMap(method));
}
break;
@@ -407,7 +414,7 @@ void loadWebXmlServletMappings(JSPServletParser servletParser) {
absolutePath = PathUtil.combine(jspRoot.getAbsolutePath(), jspServlet.getFilePath());
}
filePath = getRelativePath(absolutePath);
- Map jspParameters = JSPParameterParser.parse(new File(absolutePath));
+ Map> jspParameters = JSPParameterParser.parse(new File(absolutePath));
methodParameters = map();
supportedMethods = list("GET", "POST"); // Can't determine whether GET or POST is required, emit both
for (String method : supportedMethods) {
@@ -473,29 +480,29 @@ File findWebXmlFile(File startingDirectory) {
return result;
}
- void parseFile(File file) {
+ void parseFile(List outputCollection, Map> includeMap, File jspRoot, File file) {
- if (projectRoot != null) {
+ if (rootFile != null) {
// we will use both parsers on the same run through the file
- String staticPath = FilePathUtils.getRelativePath(file, projectRoot);
+ String staticPath = FilePathUtils.getRelativePath(file, rootFile);
JSPIncludeParser includeParser = new JSPIncludeParser(file);
JSPParameterParser parameterParser = new JSPParameterParser();
EventBasedTokenizerRunner.run(file, false, parameterParser, includeParser);
- addToIncludes(staticPath, includeParser.returnFiles);
+ addToIncludes(includeMap, staticPath, includeParser.returnFiles);
- createEndpoint(staticPath, file, parameterParser.buildParametersMap());
+ createEndpoint(outputCollection, jspRoot, staticPath, file, parameterParser.buildParametersMap());
}
}
- void createEndpoint(String staticPath, File file, Map parserResults) {
+ void createEndpoint(List targetEndpoints, File jspRoot, String staticPath, File file, Map> parserResults) {
staticPath = getInputOrEmptyString(staticPath);
String endpointPath = getInputOrEmptyString(FilePathUtils.getRelativePath(file, jspRoot));
JSPEndpoint primaryEndpoint = new JSPEndpoint(staticPath, endpointPath, "GET", parserResults);
addToEndpointMap(staticPath, primaryEndpoint);
- endpoints.add(primaryEndpoint);
+ targetEndpoints.add(primaryEndpoint);
JSPEndpoint subEndpoint = new JSPEndpoint(staticPath, endpointPath, "POST", parserResults);
primaryEndpoint.addVariant(subEndpoint);
@@ -503,8 +510,8 @@ void createEndpoint(String staticPath, File file, Map pa
//endpoints.add(endpoint);
}
- void addToIncludes(String staticPath, Set includedFiles) {
- if (projectRoot != null && projectDirectory != null) {
+ void addToIncludes(Map> includeMap, String staticPath, Set includedFiles) {
+ if (rootFile != null && projectDirectory != null) {
if (!includedFiles.isEmpty()) {
Set cleanedFilePaths = set();
@@ -528,20 +535,17 @@ void addToEndpointMap(String filePath, JSPEndpoint endpoint) {
endpoints.add(endpoint);
}
- void addParametersFromIncludedFiles() {
- for (Map.Entry> endpointEntry : jspEndpointMap.entrySet()) {
- if (endpointEntry != null && endpointEntry.getKey() != null) {
- for (JSPEndpoint endpoint : endpointEntry.getValue()) {
- endpoint.getParameters().putAll(
- getParametersFor(endpointEntry.getKey(),
- new HashSet(), new HashMap()));
- }
- }
- }
+ void addParametersFromIncludedFiles(Map> includeMap, List endpoints) {
+ for (Endpoint endpoint : endpoints) {
+ JSPEndpoint jspEndpoint = (JSPEndpoint)endpoint;
+ endpoint.getParameters().putAll(
+ getParametersFor(includeMap, endpoint.getFilePath(),
+ new HashSet(), new HashMap()));
+ }
}
// TODO memoize results
- Map getParametersFor(String key, Set alreadyVisited,
+ Map getParametersFor(Map> includeMap, String key, Set alreadyVisited,
Map soFar) {
if (alreadyVisited.contains(key)) {
@@ -558,7 +562,7 @@ Map getParametersFor(String key, Set alreadyVisi
if (endpoints != null) {
for (JSPEndpoint endpoint : endpoints) {
params.putAll(endpoint.getParameters());
- params.putAll(getParametersFor(fileKey, alreadyVisited, soFar));
+ params.putAll(getParametersFor(includeMap, fileKey, alreadyVisited, soFar));
}
}
}
@@ -603,24 +607,70 @@ public List getEndpoints(String staticPath) {
}
public String getRelativePath(String dataFlowLocation) {
- return FilePathUtils.getRelativePath(dataFlowLocation, projectRoot);
+ return FilePathUtils.getRelativePath(dataFlowLocation, rootFile);
}
- // Gets the path of the given web file path relative to the project path, where the web file path
- // is relative to the WebContent root instead of project root
- String getFullRelativeWebPath(String localRelativePath) {
- String fullPath = jspRoot.getAbsolutePath();
- if (fullPath.charAt(fullPath.length() - 1) == '/') {
- fullPath = fullPath.substring(0, fullPath.length() - 1);
+ private List findProjectFolders(File rootFolder) {
+ List webXmlFolders = list();
+ for (File xmlFile : FileUtils.listFiles(rootFolder, new String[] { "xml" }, true)) {
+ if (xmlFile.getName().equalsIgnoreCase("web.xml")) {
+ webXmlFolders.add(xmlFile.getParentFile());
+ }
}
- if (localRelativePath.length() > 0 && localRelativePath.charAt(0) == '/') {
- localRelativePath = localRelativePath.substring(1);
+ // If no web.xml files were found (or only one was found) treat the whole folder as the root directory
+ if (webXmlFolders.size() < 2) {
+ return list(rootFolder);
+ }
+
+ List distinctWebXmlFolders = FilePathUtils.findRootFolders(webXmlFolders);
+ // Traverse distinct folders to their parents
+
+ List projectFolders = list();
+
+ for (File main : distinctWebXmlFolders) {
+ String longestCommonSubstring = null;
+
+ for (File other : distinctWebXmlFolders) {
+ if (other.equals(main)) {
+ continue;
+ }
+
+ String commonSubstring = findCommonPath(main.getAbsolutePath(), other.getAbsolutePath());
+ if (longestCommonSubstring == null || commonSubstring.length() > longestCommonSubstring.length())
+ longestCommonSubstring = commonSubstring;
+ }
+
+ if (longestCommonSubstring != null) {
+ String uncommonPart = main.getAbsolutePath().substring(longestCommonSubstring.length() + 1);
+ String uncommonFolder = uncommonPart.split("[\\/\\\\]")[0];
+ projectFolders.add(new File(longestCommonSubstring, uncommonFolder));
+ } else {
+ projectFolders.add(main);
+ }
}
- fullPath += "/" + localRelativePath;
+ return projectFolders;
+ }
+
+ private String findCommonPath(String pathA, String pathB) {
+ String[] aParts = pathA.split("[\\/\\\\]");
+ String[] bParts = pathB.split("[\\/\\\\]");
+
+ StringBuilder sb = new StringBuilder();
+
+ for (int i = 0; i < aParts.length && i < bParts.length; i++) {
+ if (!aParts[i].equals(bParts[i]))
+ break;
+
+ if (sb.length() > 0) {
+ sb.append(File.separatorChar);
+ }
+
+ sb.append(aParts[i]);
+ }
- return getRelativePath(fullPath);
+ return sb.toString();
}
@Nonnull
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/jsp/JSPParameterParser.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/jsp/JSPParameterParser.java
index 158bf3cb2..71a8a499c 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/jsp/JSPParameterParser.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/jsp/JSPParameterParser.java
@@ -77,39 +77,31 @@ private enum PageState {
}
@Nonnull
- public static Map parse(File file) {
+ public static Map> parse(File file) {
JSPParameterParser parser = new JSPParameterParser();
EventBasedTokenizerRunner.run(file, false, parser);
return parser.buildParametersMap();
}
@Nonnull
- Map buildParametersMap() {
-// Map> lineNumToParamMap = map();
-//
-// for (String key : parameterToLineNumbersMap.keySet()) {
-// List lineNumbers = parameterToLineNumbersMap.get(key);
-//
-// for (Integer lineNumber : lineNumbers) {
-// if (!lineNumToParamMap.containsKey(lineNumber)) {
-// lineNumToParamMap.put(lineNumber, new ArrayList());
-// }
-// lineNumToParamMap.get(lineNumber).add(key);
-// }
-// }
-//
-// return lineNumToParamMap;
+ Map> buildParametersMap() {
+ Map> lineNumToParamMap = map();
- Map result = map();
- // All variables captured are from getParameter calls, which are populated via FORM data
for (String key : parameterToLineNumbersMap.keySet()) {
- RouteParameter newParam = new RouteParameter(key);
- newParam.setParamType(RouteParameterType.FORM_DATA);
- newParam.setDataType("String");
- result.put(key, newParam);
+ List lineNumbers = parameterToLineNumbersMap.get(key);
+
+ for (Integer lineNumber : lineNumbers) {
+ if (!lineNumToParamMap.containsKey(lineNumber)) {
+ lineNumToParamMap.put(lineNumber, new ArrayList());
+ }
+ RouteParameter param = new RouteParameter(key);
+ param.setParamType(RouteParameterType.FORM_DATA);
+ param.setDataType("String");
+ lineNumToParamMap.get(lineNumber).add(param);
+ }
}
- return result;
+ return lineNumToParamMap;
}
@Override
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/jsp/JSPServlet.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/jsp/JSPServlet.java
index a63848970..2d6a9f458 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/jsp/JSPServlet.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/jsp/JSPServlet.java
@@ -132,15 +132,20 @@ public List getMethodParameters(String httpMethod) {
return methodParameters.get(httpMethod.toUpperCase());
}
- public Map> getParameterLineMap() {
- Map> simpleParameterMap = map();
- for (int line : parameterLineMap.keySet()) {
- List lineParameterNames = list();
- for (RouteParameter param : parameterLineMap.get(line)) {
- lineParameterNames.add(param.getName());
+ public Map> getParameterLineMap() {
+ return this.parameterLineMap;
+ }
+
+ public Map> getParameterLineMap(String httpMethod) {
+ int startLine = getStartLine(httpMethod);
+ int endLine = getEndLine(httpMethod);
+
+ Map> result = map();
+ for (Map.Entry> lineEntry : this.parameterLineMap.entrySet()) {
+ if (lineEntry.getKey() >= startLine && lineEntry.getKey() <= endLine) {
+ result.put(lineEntry.getKey(), lineEntry.getValue());
}
- simpleParameterMap.put(line, lineParameterNames);
}
- return simpleParameterMap;
+ return result;
}
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/RailsControllerParser.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/RailsControllerParser.java
index 80b907e3e..c0c2c6636 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/RailsControllerParser.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/RailsControllerParser.java
@@ -25,6 +25,8 @@
////////////////////////////////////////////////////////////////////////
package com.denimgroup.threadfix.framework.impl.rails;
+import com.denimgroup.threadfix.data.entities.RouteParameter;
+import com.denimgroup.threadfix.data.entities.RouteParameterType;
import com.denimgroup.threadfix.data.enums.ParameterDataType;
import com.denimgroup.threadfix.framework.impl.rails.model.RailsController;
import com.denimgroup.threadfix.framework.impl.rails.model.RailsControllerMethod;
@@ -42,6 +44,7 @@
import java.util.*;
import static com.denimgroup.threadfix.CollectionUtils.list;
+import static com.denimgroup.threadfix.CollectionUtils.map;
/**
* Created by sgerick on 4/23/2015.
@@ -50,6 +53,16 @@ public class RailsControllerParser implements EventBasedTokenizer {
private static final SanitizedLogger LOG = new SanitizedLogger("RailsParser");
+ private static final Map RAILS_PARAM_TYPES = map(
+ "params", RouteParameterType.QUERY_STRING, // parameters will be in 'params' regardless of whether its a query string or form data
+ "path_parameters", RouteParameterType.PARAMETRIC_ENDPOINT,
+ "query_parameters", RouteParameterType.QUERY_STRING,
+ "GET", RouteParameterType.QUERY_STRING,
+ "request_parameters", RouteParameterType.FORM_DATA,
+ "POST", RouteParameterType.FORM_DATA,
+ "session", RouteParameterType.SESSION
+ );
+
private enum ControllerState {
INIT, MODULE, CLASS, METHOD, PARAMS
}
@@ -69,6 +82,7 @@ private enum ControllerState {
private String currentParamName;
private RubyScopeTracker scopeTracker = new RubyScopeTracker();
private int methodStartScopeDepth = -1;
+ private RouteParameterType currentParameterType = RouteParameterType.UNKNOWN;
private ControllerState currentCtrlState = ControllerState.INIT;
@@ -195,8 +209,9 @@ public void processToken(int type, int lineNumber, String stringValue) {
methodStartScopeDepth = scopeTracker.getScopeDepth();
currentCtrlMethod = new RailsControllerMethod();
currentCtrlMethod.setStartLine(lineNumber);
- } else if (s.equals("params")) {
+ } else if (RAILS_PARAM_TYPES.containsKey(s)) {
currentCtrlState = ControllerState.PARAMS;
+ currentParameterType = RAILS_PARAM_TYPES.get(s);
}
}
}
@@ -258,7 +273,12 @@ private void addMethodParam(String stringValue) {
String param = stringValue.concat(".").concat(p.getKey());
if (currentCtrlMethod.getMethodParams() == null
|| !currentCtrlMethod.getMethodParams().keySet().contains(param)) {
- currentCtrlMethod.addMethodParam(param, findTypeFromMatch(param));
+ RouteParameter newParameter = new RouteParameter(param);
+ newParameter.setDataType(findTypeFromMatch(param).getDisplayName());
+ newParameter.setParamType(currentParameterType);
+ currentCtrlMethod.addMethodParam(param, newParameter);
+
+ currentParameterType = RouteParameterType.UNKNOWN;
}
}
return;
@@ -266,7 +286,12 @@ private void addMethodParam(String stringValue) {
}
if (currentCtrlMethod != null && (currentCtrlMethod.getMethodParams() == null
|| !currentCtrlMethod.getMethodParams().keySet().contains(stringValue))) {
- currentCtrlMethod.addMethodParam(stringValue, findTypeFromMatch(stringValue));
+ RouteParameter newParameter = new RouteParameter(stringValue);
+ newParameter.setDataType(findTypeFromMatch(stringValue).getDisplayName());
+ newParameter.setParamType(currentParameterType);
+ currentCtrlMethod.addMethodParam(stringValue, newParameter);
+
+ currentParameterType = RouteParameterType.UNKNOWN;
}
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/RailsEndpointMappings.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/RailsEndpointMappings.java
index c14b49086..d766ea96b 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/RailsEndpointMappings.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/RailsEndpointMappings.java
@@ -104,7 +104,7 @@ public RailsEndpointMappings(@Nonnull File rootDirectory) {
int startLine = -1, endLine = -1;
- Map rawParams = map();
+ Map rawParams = map();
if (controller != null) {
RailsControllerMethod responseMethod = controller.getMethod(route.getControllerMethod());
if (responseMethod != null) {
@@ -119,18 +119,18 @@ public RailsEndpointMappings(@Nonnull File rootDirectory) {
Map params = map();
if (rawParams != null) {
- for (Map.Entry kvp : rawParams.entrySet()) {
- RouteParameter newParam = new RouteParameter(kvp.getKey());
- ParameterDataType dataType = kvp.getValue();
- newParam.setDataType(dataType.getDisplayName());
-
- if (route.getUrl().contains(kvp.getKey())) {
- newParam.setParamType(RouteParameterType.PARAMETRIC_ENDPOINT);
- } else if (route.getHttpMethod().equalsIgnoreCase("GET")) {
- newParam.setParamType(RouteParameterType.QUERY_STRING);
- } else {
- newParam.setParamType(RouteParameterType.FORM_DATA);
- }
+ for (Map.Entry kvp : rawParams.entrySet()) {
+ RouteParameter newParam = kvp.getValue();
+
+ if (newParam.getParamType() == RouteParameterType.UNKNOWN) {
+ if (route.getUrl().contains(kvp.getKey())) {
+ newParam.setParamType(RouteParameterType.PARAMETRIC_ENDPOINT);
+ } else if (route.getHttpMethod().equalsIgnoreCase("GET")) {
+ newParam.setParamType(RouteParameterType.QUERY_STRING);
+ } else {
+ newParam.setParamType(RouteParameterType.FORM_DATA);
+ }
+ }
params.put(kvp.getKey(), newParam);
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/RailsModelParser.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/RailsModelParser.java
index a9a3e8883..0d484a398 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/RailsModelParser.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/RailsModelParser.java
@@ -18,7 +18,9 @@
// Portions created by Denim Group, Ltd. are Copyright (C)
// Denim Group, Ltd. All Rights Reserved.
//
-// Contributor(s): Denim Group, Ltd.
+// Contributor(s):
+// Denim Group, Ltd.
+// Secure Decisions, a division of Applied Visions, Inc
//
////////////////////////////////////////////////////////////////////////
package com.denimgroup.threadfix.framework.impl.rails;
@@ -57,7 +59,7 @@ private enum ModelState {
private ModelState currentModelState = ModelState.INIT;
- public static Map parse(@Nonnull File rootFile) {
+ public static Map> parse(@Nonnull File rootFile) {
if (!rootFile.exists() || !rootFile.isDirectory()) {
LOG.error("Root file not found or is not directory. Exiting.");
return null;
@@ -68,7 +70,7 @@ public static Map parse(@Nonnull File rootFile) {
return null;
}
String[] rubyExtension = new String[] { "rb" };
- Collection rubyFiles = (Collection) FileUtils.listFiles(modelDir, rubyExtension, true);
+ Collection rubyFiles = FileUtils.listFiles(modelDir, rubyExtension, true);
RailsModelParser parser = new RailsModelParser();
for (File rubyFile : rubyFiles) {
@@ -124,7 +126,8 @@ public void processToken(int type, int lineNumber, String stringValue) {
} else if (s.equals("attr_accessor")) {
currentModelState = ModelState.ATTR_ACCESSOR;
- } else if (s.equals("validates")){
+ } else if (s.equals("validates") || s.equals("validates_presence_of")){
+ isBasicValidates = s.equals("validates");
currentModelState = ModelState.VALIDATES;
}
}
@@ -166,6 +169,7 @@ private enum ValidationState { START, FIELD, NUMERICALITY, OPEN_BRACKET, ONLY_IN
private String fieldName = null;
private String oneStringAgo = null;
private int oneTypeAgo;
+ private boolean isBasicValidates = false;
private void processValidation(int type, String stringValue, String charValue){
@@ -186,9 +190,15 @@ private void processValidation(int type, String stringValue, String charValue){
case FIELD:
if(type == StreamTokenizer.TT_WORD && NUMERICALITY.equals(stringValue)) {
currValidationState = ValidationState.NUMERICALITY;
- } else if ((type == StreamTokenizer.TT_WORD && VALIDATES.equals(stringValue))){
- currValidationState = ValidationState.END;
- }
+ } else if (type != StreamTokenizer.TT_WORD) {
+ if (!isBasicValidates && (type == ',' || type == '\n')) {
+ modelAttributes.put(fieldName, STRING);
+ currValidationState = ValidationState.START;
+ } else {
+ modelAttributes.put(fieldName, STRING);
+ currValidationState = ValidationState.END;
+ }
+ }
break;
case NUMERICALITY:
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/AbstractRailsRoutingEntry.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/AbstractRailsRoutingEntry.java
index 004da2b18..9f52e8f7d 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/AbstractRailsRoutingEntry.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/AbstractRailsRoutingEntry.java
@@ -23,6 +23,7 @@
package com.denimgroup.threadfix.framework.impl.rails.model;
+import com.denimgroup.threadfix.framework.util.CodeParseUtil;
import com.denimgroup.threadfix.framework.util.PathUtil;
import java.util.*;
@@ -42,7 +43,7 @@ public void onBegin(String identifier) {
}
@Override
- public void onParameter(String name, String value, RouteParameterValueType parameterType) {
+ public void onParameter(String name, RouteParameterValueType nameType, String value, RouteParameterValueType parameterType) {
}
@@ -61,6 +62,11 @@ public void onEnd() {
}
+ @Override
+ public boolean canGenerateEndpoints() {
+ return parentEntry == null || parentEntry.canGenerateEndpoints();
+ }
+
@Override
public void setLineNumber(int codeLine) {
lineNumber = codeLine;
@@ -74,7 +80,6 @@ public int getLineNumber() {
@Override
public void addChildEntry(RailsRoutingEntry child) {
if (!children.contains(child)) {
-
children.add(child);
}
child.setParent(this);
@@ -110,19 +115,9 @@ public RailsRoutingEntry getParent() {
return parentEntry;
}
- protected String stripColons(String symbol) {
- if (symbol.startsWith(":")) {
- symbol = symbol.substring(1);
- }
- if (symbol.endsWith(":")) {
- symbol = symbol.substring(0, symbol.length() - 1);
- }
- return symbol.trim();
- }
-
protected String cleanCodeString(String codeString) {
codeString = codeString.trim();
- codeString = stripColons(codeString);
+ codeString = CodeParseUtil.trim(codeString, ":");
while (codeString.startsWith("{") || codeString.startsWith("'") || codeString.startsWith("\"")) {
codeString = codeString.substring(1);
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/RailsController.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/RailsController.java
index e42569240..bf2a00e1c 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/RailsController.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/RailsController.java
@@ -25,6 +25,7 @@
////////////////////////////////////////////////////////////////////////
package com.denimgroup.threadfix.framework.impl.rails.model;
+import com.denimgroup.threadfix.data.entities.RouteParameter;
import com.denimgroup.threadfix.data.enums.ParameterDataType;
import java.io.File;
@@ -102,8 +103,8 @@ public RailsControllerMethod getMethod(String name) {
return null;
}
- public Map getParameters() {
- Map p = map();
+ public Map getParameters() {
+ Map p = map();
for (RailsControllerMethod rcm : controllerMethods) {
if (rcm.getMethodParams() != null)
p.putAll(rcm.getMethodParams());
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/RailsControllerMethod.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/RailsControllerMethod.java
index b1c378228..2ab671894 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/RailsControllerMethod.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/RailsControllerMethod.java
@@ -23,6 +23,7 @@
////////////////////////////////////////////////////////////////////////
package com.denimgroup.threadfix.framework.impl.rails.model;
+import com.denimgroup.threadfix.data.entities.RouteParameter;
import com.denimgroup.threadfix.data.enums.ParameterDataType;
import java.util.Map;
@@ -34,7 +35,7 @@
*/
public class RailsControllerMethod {
private String methodName;
- private Map methodParams;
+ private Map methodParams;
private int startLine, endLine;
public String getMethodName() {
@@ -45,16 +46,16 @@ public void setMethodName(String methodName) {
this.methodName = methodName;
}
- public Map getMethodParams() {
+ public Map getMethodParams() {
return methodParams;
}
- public void addMethodParam(String methodParam, ParameterDataType dataType) {
- if (methodParam == null) return;
+ public void addMethodParam(String paramName, RouteParameter parameter) {
+ if (paramName == null) return;
if (this.methodParams == null)
this.methodParams = map();
- this.methodParams.put(methodParam, dataType);
+ this.methodParams.put(paramName, parameter);
}
public void setStartLine(int startLine) {
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/RailsEndpointUtil.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/RailsEndpointUtil.java
new file mode 100644
index 000000000..7523ca6a4
--- /dev/null
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/RailsEndpointUtil.java
@@ -0,0 +1,10 @@
+package com.denimgroup.threadfix.framework.impl.rails.model;
+
+public class RailsEndpointUtil {
+ public static String cleanEndpointParameters(String endpoint, RouteParameterValueType parameterNameType) {
+ if (parameterNameType == RouteParameterValueType.SYMBOL_STRING_LITERAL) {
+ endpoint = ':' + endpoint;
+ }
+ return endpoint.replaceAll("[:#%]\\{?(\\w+)\\}?", "{$1}");
+ }
+}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/RailsRoute.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/RailsRoute.java
index 5581e0a7d..71c8d706b 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/RailsRoute.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/RailsRoute.java
@@ -43,6 +43,9 @@ public RailsRoute() {
}
public RailsRoute(String url, String method) {
+ if (!url.startsWith("/") && !url.isEmpty()) {
+ url = "/" + url;
+ }
this.setUrl(url);
this.httpMethod = method;
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/RailsRoutingEntry.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/RailsRoutingEntry.java
index eb9e3870e..29304da8b 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/RailsRoutingEntry.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/RailsRoutingEntry.java
@@ -38,12 +38,18 @@ public interface RailsRoutingEntry {
int getLineNumber();
void onToken(int type, int lineNumber, String stringValue);
- void onParameter(String name, String value, RouteParameterValueType parameterType);
+ void onParameter(String name, RouteParameterValueType nameType, String value, RouteParameterValueType parameterType);
void onInitializerParameter(String name, String value, RouteParameterValueType parameterType);
void onBegin(String identifier);
void onEnd();
+
+ /**
+ * @return Whether or not this entry, and all of its sub-entries should be used while generating endpoints.
+ */
+ boolean canGenerateEndpoints();
+
/**
* @return The main path for this entry that will host its endpoints and be the base endpoint for its children.
*/
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/CollectionEntry.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/CollectionEntry.java
index fdbea6f9a..1771acb4c 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/CollectionEntry.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/CollectionEntry.java
@@ -38,7 +38,7 @@ public class CollectionEntry extends AbstractRailsRoutingEntry {
@Override
public String getPrimaryPath() {
- return null;
+ return getParent() == null ? null : getParent().getPrimaryPath();
}
@Override
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/ConcernEntry.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/ConcernEntry.java
index 7f09a540c..968e5a75e 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/ConcernEntry.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/ConcernEntry.java
@@ -23,10 +23,7 @@
package com.denimgroup.threadfix.framework.impl.rails.model.defaultRoutingEntries;
-import com.denimgroup.threadfix.framework.impl.rails.model.AbstractRailsRoutingEntry;
-import com.denimgroup.threadfix.framework.impl.rails.model.PathHttpMethod;
-import com.denimgroup.threadfix.framework.impl.rails.model.RailsRoutingEntry;
-import com.denimgroup.threadfix.framework.impl.rails.model.RouteParameterValueType;
+import com.denimgroup.threadfix.framework.impl.rails.model.*;
import javax.annotation.Nonnull;
import java.util.Collection;
@@ -39,6 +36,8 @@ public class ConcernEntry extends AbstractRailsRoutingEntry {
String idSymbol = null;
+
+
@Override
public Collection getPaths() {
return null;
@@ -68,8 +67,8 @@ public String getConcernIdSymbol() {
}
@Override
- public void onParameter(String name, String value, RouteParameterValueType parameterType) {
- super.onParameter(name, value, parameterType);
+ public void onParameter(String name, RouteParameterValueType nameType, String value, RouteParameterValueType parameterType) {
+ super.onParameter(name, nameType, value, parameterType);
if (name == null) {
idSymbol = value;
}
@@ -80,6 +79,12 @@ public String getPrimaryPath() {
return null;
}
+ @Override
+ public boolean canGenerateEndpoints() {
+ // Concern declaration is just a template and doesn't make any endpoints on its own
+ return false;
+ }
+
@Override
public String toString() {
StringBuilder result = new StringBuilder();
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/ControllerEntry.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/ControllerEntry.java
index 93db2168b..fcedd6d81 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/ControllerEntry.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/ControllerEntry.java
@@ -23,10 +23,7 @@
package com.denimgroup.threadfix.framework.impl.rails.model.defaultRoutingEntries;
-import com.denimgroup.threadfix.framework.impl.rails.model.AbstractRailsRoutingEntry;
-import com.denimgroup.threadfix.framework.impl.rails.model.PathHttpMethod;
-import com.denimgroup.threadfix.framework.impl.rails.model.RailsRoutingEntry;
-import com.denimgroup.threadfix.framework.impl.rails.model.RouteParameterValueType;
+import com.denimgroup.threadfix.framework.impl.rails.model.*;
import javax.annotation.Nonnull;
import java.util.Collection;
@@ -55,8 +52,8 @@ public String getModule() {
}
@Override
- public void onParameter(String name, String value, RouteParameterValueType parameterType) {
- super.onParameter(name, value, parameterType);
+ public void onParameter(String name, RouteParameterValueType nameType, String value, RouteParameterValueType parameterType) {
+ super.onParameter(name, nameType, value, parameterType);
controllerName = value;
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/DirectHttpEntry.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/DirectHttpEntry.java
index 340526445..84794c41f 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/DirectHttpEntry.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/DirectHttpEntry.java
@@ -24,6 +24,7 @@
package com.denimgroup.threadfix.framework.impl.rails.model.defaultRoutingEntries;
import com.denimgroup.threadfix.framework.impl.rails.model.*;
+import com.denimgroup.threadfix.framework.util.CodeParseUtil;
import javax.annotation.Nonnull;
import java.util.Collection;
@@ -75,10 +76,10 @@ public Collection getPaths() {
}
@Override
- public void onParameter(String name, String value, RouteParameterValueType parameterType) {
- value = stripColons(value);
+ public void onParameter(String name, RouteParameterValueType nameType, String value, RouteParameterValueType parameterType) {
+ value = CodeParseUtil.trim(value, ":");
if (name == null) {
- mappedEndpoint = value;
+ mappedEndpoint = RailsEndpointUtil.cleanEndpointParameters(value, parameterType);
actionName = value;
} else if (name.equalsIgnoreCase("to")) {
String[] controllerParts = value.split("#");
@@ -105,13 +106,17 @@ public void onParameter(String name, String value, RouteParameterValueType param
} else {
if (mappedEndpoint == null) {
// Assume syntax '/endpoint' => 'controller#method' or '/endpoint' => 'method'
- mappedEndpoint = name;
+
+ mappedEndpoint = RailsEndpointUtil.cleanEndpointParameters(name, nameType);
String[] controllerParts = value.split("#");
if (controllerParts.length == 1) {
actionName = controllerParts[0];
} else if (controllerParts.length == 2) {
controller = controllerParts[0];
actionName = controllerParts[1];
+ } else if (controllerParts.length > 2) {
+ controller = controllerParts[0];
+ actionName = value.substring(value.indexOf('#') + 1);
}
}
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/MatchEntry.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/MatchEntry.java
index 62e37f7b0..c7277327b 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/MatchEntry.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/MatchEntry.java
@@ -24,6 +24,8 @@
package com.denimgroup.threadfix.framework.impl.rails.model.defaultRoutingEntries;
import com.denimgroup.threadfix.framework.impl.rails.model.*;
+import com.denimgroup.threadfix.framework.util.CodeParseUtil;
+import org.apache.commons.lang3.StringUtils;
import javax.annotation.Nonnull;
import java.util.Collection;
@@ -44,9 +46,9 @@ public class MatchEntry extends AbstractRailsRoutingEntry {
List httpMethods = list("GET");
@Override
- public void onParameter(String name, String value, RouteParameterValueType parameterType) {
+ public void onParameter(String name, RouteParameterValueType nameType, String value, RouteParameterValueType parameterType) {
if (name == null) {
- endpoint = value;
+ endpoint = RailsEndpointUtil.cleanEndpointParameters(value, parameterType);
} else if (name.equalsIgnoreCase("to")) {
String[] controllerParts = value.split("#");
if (controllerParts.length == 1) {
@@ -58,18 +60,15 @@ public void onParameter(String name, String value, RouteParameterValueType param
} else if (name.equalsIgnoreCase("via")) {
httpMethods.clear();
// Strip brackets
- if (parameterType == RouteParameterValueType.ARRAY) {
- value = value.substring(1, value.length() - 1);
- }
String[] methods = value.split(",");
if (methods.length == 1) {
- if (stripColons(methods[0]).equalsIgnoreCase("all")) {
+ if (CodeParseUtil.trim(methods[0], ":").equalsIgnoreCase("all")) {
methods = new String[] { "get", "post", "put", "patch", "delete" };
}
}
for (String method : methods) {
- httpMethods.add(stripColons(method).toUpperCase());
+ httpMethods.add(CodeParseUtil.trim(method, ":").toUpperCase());
}
} else if (name.equalsIgnoreCase("controller")) {
controller = value;
@@ -81,7 +80,7 @@ public void onParameter(String name, String value, RouteParameterValueType param
anchor = value.equalsIgnoreCase("true");
} else if (endpoint == null && controller == null && actionName == null) {
// Must be an initial parameter of ie '/path' => 'controller#action'
- endpoint = name;
+ endpoint = RailsEndpointUtil.cleanEndpointParameters(name, nameType);
controller = extractController(value);
actionName = extractAction(value);
}
@@ -136,12 +135,10 @@ public String toString() {
result.append('\'');
result.append(" to: ");
result.append(getControllerName());
- result.append("#<> ");
- result.append("via: [");
- for (String method : httpMethods) {
- result.append(method);
- result.append(",");
- }
+ result.append("#");
+ result.append(actionName);
+ result.append(" via: [");
+ result.append(StringUtils.join(httpMethods, ", "));
result.append("]");
return result.toString();
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/MemberEntry.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/MemberEntry.java
index 73e8ecb2b..9f39211d2 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/MemberEntry.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/MemberEntry.java
@@ -27,10 +27,7 @@
// https://stackoverflow.com/questions/3028653/difference-between-collection-route-and-member-route-in-ruby-on-rails
-import com.denimgroup.threadfix.framework.impl.rails.model.AbstractRailsRoutingEntry;
-import com.denimgroup.threadfix.framework.impl.rails.model.PathHttpMethod;
-import com.denimgroup.threadfix.framework.impl.rails.model.RailsRoutingEntry;
-import com.denimgroup.threadfix.framework.impl.rails.model.RouteParameterValueType;
+import com.denimgroup.threadfix.framework.impl.rails.model.*;
import com.denimgroup.threadfix.framework.util.PathUtil;
import javax.annotation.Nonnull;
@@ -45,7 +42,8 @@ public class MemberEntry extends AbstractRailsRoutingEntry {
@Override
public String getPrimaryPath() {
String basePath = getParent().getPrimaryPath();
- basePath = PathUtil.combine(basePath, ":id");
+ if (getParent() instanceof ResourcesEntry)
+ basePath = PathUtil.combine(basePath, "{id}");
return PathUtil.combine(basePath, endpoint);
}
@@ -65,8 +63,8 @@ public String getModule() {
}
@Override
- public void onParameter(String name, String value, RouteParameterValueType parameterType) {
- super.onParameter(name, value, parameterType);
+ public void onParameter(String name, RouteParameterValueType nameType, String value, RouteParameterValueType parameterType) {
+ super.onParameter(name, nameType, value, parameterType);
}
@Nonnull
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/NamespaceEntry.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/NamespaceEntry.java
index 1acdd4a7a..347a26593 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/NamespaceEntry.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/NamespaceEntry.java
@@ -23,10 +23,7 @@
package com.denimgroup.threadfix.framework.impl.rails.model.defaultRoutingEntries;
-import com.denimgroup.threadfix.framework.impl.rails.model.AbstractRailsRoutingEntry;
-import com.denimgroup.threadfix.framework.impl.rails.model.PathHttpMethod;
-import com.denimgroup.threadfix.framework.impl.rails.model.RailsRoutingEntry;
-import com.denimgroup.threadfix.framework.impl.rails.model.RouteParameterValueType;
+import com.denimgroup.threadfix.framework.impl.rails.model.*;
import javax.annotation.Nonnull;
import java.util.Collection;
@@ -43,7 +40,7 @@ public class NamespaceEntry extends AbstractRailsRoutingEntry {
String moduleName = null;
@Override
- public void onParameter(String name, String value, RouteParameterValueType parameterType) {
+ public void onParameter(String name, RouteParameterValueType nameType, String value, RouteParameterValueType parameterType) {
if (name == null) {
path = value;
moduleName = value;
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/ResourceEntry.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/ResourceEntry.java
index 30b917f92..bed45525a 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/ResourceEntry.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/ResourceEntry.java
@@ -25,6 +25,7 @@
import com.denimgroup.threadfix.framework.impl.rails.model.*;
import com.denimgroup.threadfix.framework.impl.rails.model.defaultRoutingShorthands.ConcernsParameterShorthand;
+import com.denimgroup.threadfix.framework.util.CodeParseUtil;
import com.denimgroup.threadfix.framework.util.PathUtil;
import javax.annotation.Nonnull;
@@ -46,15 +47,15 @@ public class ResourceEntry extends AbstractRailsRoutingEntry implements Concerna
List supportedPaths = list(
new PathHttpMethod("new", "GET", "new", null),
new PathHttpMethod("", "POST", "create", null),
- new PathHttpMethod(":id", "GET", "show", null),
- new PathHttpMethod(":id/edit", "GET", "edit", null),
- new PathHttpMethod(":id", "PATCH", "update", null),
- new PathHttpMethod(":id", "PUT", "update", null),
- new PathHttpMethod(":id", "DELETE", "destroy", null)
+ new PathHttpMethod("", "GET", "show", null),
+ new PathHttpMethod("edit", "GET", "edit", null),
+ new PathHttpMethod("", "PATCH", "update", null),
+ new PathHttpMethod("", "PUT", "update", null),
+ new PathHttpMethod("", "DELETE", "destroy", null)
);
@Override
- public void onParameter(String name, String value, RouteParameterValueType parameterType) {
+ public void onParameter(String name, RouteParameterValueType nameType, String value, RouteParameterValueType parameterType) {
if (!hasController) {
if (parameterType == RouteParameterValueType.SYMBOL) {
controller = value;
@@ -69,7 +70,7 @@ public void onParameter(String name, String value, RouteParameterValueType param
value = value.substring(1, value.length() - 1);
String[] concernNames = value.split(",");
for (String concernName : concernNames) {
- concerns.add(stripColons(concernName));
+ concerns.add(CodeParseUtil.trim(concernName, ":"));
}
} else if (name.equalsIgnoreCase("controller")) {
controller = value;
@@ -91,12 +92,12 @@ public void onParameter(String name, String value, RouteParameterValueType param
} else {
allowedPaths.add(value);
}
+ CodeParseUtil.trim(allowedPaths, ":");
for (int i = 0; i < supportedPaths.size(); i++) {
PathHttpMethod httpPath = supportedPaths.get(i);
if (!allowedPaths.contains(httpPath.getAction())) {
supportedPaths.remove(httpPath);
--i;
- break;
}
}
} else if (name.equalsIgnoreCase("except")) {
@@ -107,6 +108,7 @@ public void onParameter(String name, String value, RouteParameterValueType param
} else {
removedPaths.add(value);
}
+ CodeParseUtil.trim(removedPaths, ":");
for (int i = 0; i < supportedPaths.size(); i++) {
PathHttpMethod httpPath = supportedPaths.get(i);
if (removedPaths.contains(httpPath.getAction())) {
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/ResourcesEntry.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/ResourcesEntry.java
index 27b3e48fd..09d8d686a 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/ResourcesEntry.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/ResourcesEntry.java
@@ -26,9 +26,12 @@
import com.denimgroup.threadfix.framework.impl.rails.model.*;
import com.denimgroup.threadfix.framework.impl.rails.model.defaultRoutingShorthands.ConcernsParameterShorthand;
import com.denimgroup.threadfix.framework.impl.rails.model.defaultRoutingShorthands.ManyResourcesShorthand;
+import com.denimgroup.threadfix.framework.impl.rails.model.defaultRoutingShorthands.NestedResourcesMemberEntryShorthand;
+import com.denimgroup.threadfix.framework.util.CodeParseUtil;
import com.denimgroup.threadfix.framework.util.PathUtil;
import javax.annotation.Nonnull;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
@@ -51,15 +54,15 @@ public class ResourcesEntry extends AbstractRailsRoutingEntry implements Concern
new PathHttpMethod("", "GET", "index", null),
new PathHttpMethod("new", "GET", "new", null),
new PathHttpMethod("", "POST", "create", null),
- new PathHttpMethod(":id", "GET", "show", null),
- new PathHttpMethod(":id/edit", "GET", "edit", null),
- new PathHttpMethod(":id", "PATCH", "update", null),
- new PathHttpMethod(":id", "PUT", "update", null),
- new PathHttpMethod(":id", "DELETE", "destroy", null)
+ new PathHttpMethod("{id}", "GET", "show", null),
+ new PathHttpMethod("{id}/edit", "GET", "edit", null),
+ new PathHttpMethod("{id}", "PATCH", "update", null),
+ new PathHttpMethod("{id}", "PUT", "update", null),
+ new PathHttpMethod("{id}", "DELETE", "destroy", null)
);
@Override
- public void onParameter(String name, String value, RouteParameterValueType parameterType) {
+ public void onParameter(String name, RouteParameterValueType nameType, String value, RouteParameterValueType parameterType) {
if (name == null) {
// May be a shorthand declaring multiple resource routes at once, if so simply append the
// names and separate with a space and the MultiResourcesShorthand will expand into
@@ -89,7 +92,7 @@ public void onParameter(String name, String value, RouteParameterValueType param
value = value.substring(1, value.length() - 1);
String[] valueParts = value.split(",");
for (String concern : valueParts) {
- concerns.add(stripColons(concern));
+ concerns.add(CodeParseUtil.trim(concern, ":"));
}
} else if (name.equalsIgnoreCase("controller")) {
controllerName = value;
@@ -110,12 +113,12 @@ public void onParameter(String name, String value, RouteParameterValueType param
} else {
allowedPaths.add(value);
}
+ CodeParseUtil.trim(allowedPaths, ":");
for (int i = 0; i < supportedPaths.size(); i++) {
PathHttpMethod httpPath = supportedPaths.get(i);
if (!allowedPaths.contains(httpPath.getAction())) {
supportedPaths.remove(httpPath);
--i;
- break;
}
}
} else if (name.equalsIgnoreCase("except")) {
@@ -204,7 +207,7 @@ private String makeRelativeSubPath(String subPath) {
@Override
public Collection getConcerns() {
- return null;
+ return concerns;
}
@Override
@@ -214,7 +217,7 @@ public void resetConcerns() {
@Override
public Collection getSupportedShorthands() {
- return list(new ConcernsParameterShorthand(), new ManyResourcesShorthand());
+ return list(new ConcernsParameterShorthand(), new ManyResourcesShorthand(), new NestedResourcesMemberEntryShorthand());
}
@Nonnull
@@ -222,7 +225,7 @@ public Collection getSupportedShorthands() {
public RailsRoutingEntry cloneEntry() {
ResourcesEntry clone = new ResourcesEntry();
clone.concerns.addAll(concerns);
- clone.supportedPaths.addAll(supportedPaths);
+ clone.supportedPaths = new ArrayList(supportedPaths);
clone.basePath = basePath;
clone.dataSourceSymbol = dataSourceSymbol;
cloneChildrenInto(clone);
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/RootEntry.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/RootEntry.java
index d44872aaf..1414f7314 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/RootEntry.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/RootEntry.java
@@ -23,10 +23,7 @@
package com.denimgroup.threadfix.framework.impl.rails.model.defaultRoutingEntries;
-import com.denimgroup.threadfix.framework.impl.rails.model.AbstractRailsRoutingEntry;
-import com.denimgroup.threadfix.framework.impl.rails.model.PathHttpMethod;
-import com.denimgroup.threadfix.framework.impl.rails.model.RailsRoutingEntry;
-import com.denimgroup.threadfix.framework.impl.rails.model.RouteParameterValueType;
+import com.denimgroup.threadfix.framework.impl.rails.model.*;
import javax.annotation.Nonnull;
import java.util.Collection;
@@ -39,7 +36,7 @@
// Defines the response to use when the root of the current scope is queried.
public class RootEntry extends AbstractRailsRoutingEntry {
- String path = null;
+ String path = "";
String controllerName = null;
String methodName = null;
@@ -63,9 +60,8 @@ public RailsRoutingEntry cloneEntry() {
}
@Override
- public void onParameter(String name, String value, RouteParameterValueType parameterType) {
- if (name == null || name.equalsIgnoreCase("to")) {
- path = "/";
+ public void onParameter(String name, RouteParameterValueType nameType, String value, RouteParameterValueType parameterType) {
+ if (controllerName == null && (name == null || name.equalsIgnoreCase("to"))) {
String[] valueParts = value.split("#");
controllerName = valueParts[0];
methodName = valueParts[1];
@@ -88,7 +84,9 @@ public Collection getPaths() {
public String toString() {
StringBuilder result = new StringBuilder();
result.append("root to: '");
- result.append(path);
+ result.append(controllerName);
+ result.append("#");
+ result.append(methodName);
result.append("'");
return result.toString();
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/ScopeEntry.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/ScopeEntry.java
index c30ac2b31..64d36635e 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/ScopeEntry.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingEntries/ScopeEntry.java
@@ -23,10 +23,7 @@
package com.denimgroup.threadfix.framework.impl.rails.model.defaultRoutingEntries;
-import com.denimgroup.threadfix.framework.impl.rails.model.AbstractRailsRoutingEntry;
-import com.denimgroup.threadfix.framework.impl.rails.model.PathHttpMethod;
-import com.denimgroup.threadfix.framework.impl.rails.model.RailsRoutingEntry;
-import com.denimgroup.threadfix.framework.impl.rails.model.RouteParameterValueType;
+import com.denimgroup.threadfix.framework.impl.rails.model.*;
import javax.annotation.Nonnull;
import java.util.Collection;
@@ -69,7 +66,7 @@ public RailsRoutingEntry cloneEntry() {
}
@Override
- public void onParameter(String name, String value, RouteParameterValueType parameterType) {
+ public void onParameter(String name, RouteParameterValueType nameType, String value, RouteParameterValueType parameterType) {
if (name == null) {
endpoint = value;
} else if (name.equalsIgnoreCase("module")) {
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingShorthands/ConcernsEntryShorthand.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingShorthands/ConcernsEntryShorthand.java
index 2a018c64c..3819cb041 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingShorthands/ConcernsEntryShorthand.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingShorthands/ConcernsEntryShorthand.java
@@ -61,10 +61,10 @@ public RailsRoutingEntry expand(RailsConcreteRoutingTree sourceTree, RailsRoutin
for (ConcernEntry concern : necessaryConcerns) {
for (RailsRoutingEntry subEntry : concern.getChildren()) {
- RailsRoutingEntry entryCopy = subEntry.cloneEntry();
- entryCopy.setParent(entry);
+ RailsRoutingEntry subEntryCopy = subEntry.cloneEntry();
+ entry.addChildEntry(subEntryCopy);
if (newBase == null) {
- newBase = entryCopy;
+ newBase = subEntryCopy;
}
}
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingShorthands/ConcernsParameterShorthand.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingShorthands/ConcernsParameterShorthand.java
index 4d1e03e7b..f86bb106e 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingShorthands/ConcernsParameterShorthand.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingShorthands/ConcernsParameterShorthand.java
@@ -47,7 +47,7 @@ public RailsRoutingEntry expand(RailsConcreteRoutingTree sourceTree, RailsRoutin
Collection allConcerns = sourceTree.findEntriesOfType(ConcernEntry.class);
Collection concernNames = ((Concernable)entry).getConcerns();
- if (concernNames == null) {
+ if (concernNames == null || concernNames.isEmpty()) {
return entry;
}
@@ -62,7 +62,7 @@ public RailsRoutingEntry expand(RailsConcreteRoutingTree sourceTree, RailsRoutin
Collection concernChildren = concern.getChildren();
for (RailsRoutingEntry child : concernChildren) {
RailsRoutingEntry copy = child.cloneEntry();
- copy.setParent(entry);
+ entry.addChildEntry(copy);
}
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingShorthands/ManyResourcesShorthand.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingShorthands/ManyResourcesShorthand.java
index 4bbf52be4..b98e9f55d 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingShorthands/ManyResourcesShorthand.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingShorthands/ManyResourcesShorthand.java
@@ -26,6 +26,7 @@
import com.denimgroup.threadfix.framework.impl.rails.model.RailsRoutingEntry;
import com.denimgroup.threadfix.framework.impl.rails.model.RouteParameterValueType;
import com.denimgroup.threadfix.framework.impl.rails.model.RouteShorthand;
+import com.denimgroup.threadfix.framework.impl.rails.model.RoutingParameterType;
import com.denimgroup.threadfix.framework.impl.rails.model.defaultRoutingEntries.ResourcesEntry;
import com.denimgroup.threadfix.framework.impl.rails.model.defaultRoutingEntries.ScopeEntry;
import com.denimgroup.threadfix.framework.impl.rails.routeParsing.RailsConcreteRoutingTree;
@@ -44,7 +45,8 @@ public RailsRoutingEntry expand(RailsConcreteRoutingTree sourceTree, RailsRoutin
ResourcesEntry newEntry = new ResourcesEntry();
// Many-resource shorthands can only declare routes, doesn't allow
// customization of resource routes
- newEntry.onParameter(null, symbol, RouteParameterValueType.SYMBOL);
+ newEntry.onParameter(null, RouteParameterValueType.UNKNOWN, symbol, RouteParameterValueType.SYMBOL);
+ newEntry.setLineNumber(entry.getLineNumber());
resultContainer.addChildEntry(newEntry);
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingShorthands/NestedResourcesMemberEntryShorthand.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingShorthands/NestedResourcesMemberEntryShorthand.java
new file mode 100644
index 000000000..093c74f4d
--- /dev/null
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/model/defaultRoutingShorthands/NestedResourcesMemberEntryShorthand.java
@@ -0,0 +1,66 @@
+package com.denimgroup.threadfix.framework.impl.rails.model.defaultRoutingShorthands;
+
+
+import com.denimgroup.threadfix.framework.impl.rails.model.RailsRoutingEntry;
+import com.denimgroup.threadfix.framework.impl.rails.model.RouteShorthand;
+import com.denimgroup.threadfix.framework.impl.rails.model.defaultRoutingEntries.CollectionEntry;
+import com.denimgroup.threadfix.framework.impl.rails.model.defaultRoutingEntries.MemberEntry;
+import com.denimgroup.threadfix.framework.impl.rails.model.defaultRoutingEntries.ResourcesEntry;
+import com.denimgroup.threadfix.framework.impl.rails.model.defaultRoutingEntries.RootEntry;
+import com.denimgroup.threadfix.framework.impl.rails.routeParsing.RailsConcreteRoutingTree;
+
+import java.util.List;
+
+import static com.denimgroup.threadfix.CollectionUtils.list;
+
+/**
+ * A any entry directly within another 'resources' entry implicitly sets the inner entry base path to '{id}/...'
+ *
+ * ie:
+ *
+ * resources :users do
+ * resources :posts
+ * get :profile
+ * end
+ *
+ * The "posts" base path is set to "users/{id}/posts/{id}" rather than "users/posts/{id}", effectively
+ * placing the inner "posts" in a "member" entry:
+ *
+ * resources :users do
+ * member do
+ * resources :posts
+ * get :profile
+ * end
+ * end
+ */
+
+public class NestedResourcesMemberEntryShorthand implements RouteShorthand {
+ @Override
+ public RailsRoutingEntry expand(RailsConcreteRoutingTree sourceTree, RailsRoutingEntry entry) {
+ if (!(entry instanceof ResourcesEntry)) {
+ return entry;
+ }
+
+ List subEntries = list();
+
+ for (RailsRoutingEntry child : entry.getChildren()) {
+ if (child instanceof RootEntry || child instanceof CollectionEntry || child instanceof MemberEntry) {
+ continue;
+ }
+
+ subEntries.add(child);
+ }
+
+ if (!subEntries.isEmpty()) {
+ MemberEntry memberEntry = new MemberEntry();
+ memberEntry.setLineNumber(entry.getLineNumber());
+
+ for (RailsRoutingEntry subEntry : subEntries) {
+ memberEntry.addChildEntry(subEntry);
+ }
+ entry.addChildEntry(memberEntry);
+ }
+
+ return entry;
+ }
+}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/routeParsing/RailsAbstractParameter.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/routeParsing/RailsAbstractParameter.java
index 4b71def62..41bf81cc7 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/routeParsing/RailsAbstractParameter.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/routeParsing/RailsAbstractParameter.java
@@ -28,26 +28,34 @@
public class RailsAbstractParameter {
- RoutingParameterType labelType;
- RouteParameterValueType parameterType;
+ RoutingParameterType parameterType;
+ RouteParameterValueType labelType, valueType;
String name, value;
- public void setLabelType(RoutingParameterType identifierType) {
- this.labelType = identifierType;
+ public void setLabelType(RouteParameterValueType labelType) {
+ this.labelType = labelType;
}
- public RoutingParameterType getLabelType() {
+ public RouteParameterValueType getLabelType() {
return labelType;
}
- public void setParameterType(RouteParameterValueType parameterType) {
- this.parameterType = parameterType;
+ public void setParameterType(RoutingParameterType identifierType) {
+ this.parameterType = identifierType;
}
- public RouteParameterValueType getParameterType() {
+ public RoutingParameterType getParameterType() {
return parameterType;
}
+ public void setValueType(RouteParameterValueType parameterType) {
+ this.valueType = parameterType;
+ }
+
+ public RouteParameterValueType getValueType() {
+ return valueType;
+ }
+
public void setLabel(String name) {
this.name = name;
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/routeParsing/RailsAbstractRouteEntryDescriptor.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/routeParsing/RailsAbstractRouteEntryDescriptor.java
index 7aaf0add4..b1123e40f 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/routeParsing/RailsAbstractRouteEntryDescriptor.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/routeParsing/RailsAbstractRouteEntryDescriptor.java
@@ -115,13 +115,13 @@ public String toString() {
if (param.getName() != null) {
result.append(param.getName());
result.append(" (");
- result.append(param.getLabelType());
+ result.append(param.getParameterType());
result.append(") ");
}
result.append(param.getValue());
result.append(" (");
- result.append(param.getParameterType());
+ result.append(param.getValueType());
result.append(")");
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/routeParsing/RailsAbstractRoutesLexer.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/routeParsing/RailsAbstractRoutesLexer.java
index 95f774fc3..8d312be07 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/routeParsing/RailsAbstractRoutesLexer.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/routeParsing/RailsAbstractRoutesLexer.java
@@ -134,6 +134,10 @@ public void processToken(int type, int lineNumber, String stringValue) {
case INITIALIZER_PARAMETERS:
processInitializerParametersPhase(type, lineNumber, stringValue);
break;
+
+ case INITIALIZER_PARAMETERS_SEARCH_IDENTIFIER:
+ processInitializerParametersSearchIdentifierPhase(type, lineNumber, stringValue);
+ break;
}
}
@@ -157,7 +161,7 @@ boolean isEntryScope() {
return numOpenBrace == 0 && numOpenParen == 0 && numOpenBracket == 0;
}
- enum ParsePhase { START_RENDER, SEARCH_IDENTIFIER, PARAMETERS, INITIALIZER_PARAMETERS }
+ enum ParsePhase { START_RENDER, SEARCH_IDENTIFIER, PARAMETERS, INITIALIZER_PARAMETERS, INITIALIZER_PARAMETERS_SEARCH_IDENTIFIER }
ParsePhase parsePhase = ParsePhase.START_RENDER;
void processStartRenderPhase(int type, int lineNumber, String stringValue) {
@@ -190,9 +194,9 @@ void processSearchIdentifierPhase(int type, int lineNumber, String stringValue)
RouteParameterValueType detectParameterValueType(String paramValue) {
if (paramValue.startsWith(":")) {
return RouteParameterValueType.SYMBOL;
- } else if (paramValue.startsWith("':")) {
+ } else if (paramValue.startsWith("':") || paramValue.startsWith("\":")) {
return RouteParameterValueType.SYMBOL_STRING_LITERAL;
- } else if (paramValue.startsWith("'")) {
+ } else if (paramValue.startsWith("'") || paramValue.startsWith("\"")) {
return RouteParameterValueType.STRING_LITERAL;
} else if (paramValue.startsWith("[")) {
return RouteParameterValueType.ARRAY;
@@ -203,8 +207,8 @@ RouteParameterValueType detectParameterValueType(String paramValue) {
}
}
- RoutingParameterType detectParameterLabelType(String label) {
- if (label.startsWith(":") || label.endsWith(":") || wasHashParameter) {
+ RoutingParameterType detectParameterType(String label) {
+ if ((label.startsWith(":") || label.endsWith(":")) && wasHashParameter) {
return RoutingParameterType.HASH;
} else {
return RoutingParameterType.IMPLICIT_PARAMETER;
@@ -216,39 +220,18 @@ String cleanParameterString(String string) {
return null;
}
- if (string.startsWith("'")) {
- string = string.substring(1);
- }
- if (string.endsWith("'")) {
- string = string.substring(0, string.length() - 1);
- }
- if (string.startsWith("\"")) {
- string = string.substring(1);
- }
- if (string.endsWith("\"")) {
- string = string.substring(0, string.length() - 1);
- }
- if (string.startsWith(":")) {
- string = string.substring(1);
- }
- if (string.endsWith(":")) {
- string = string.substring(0, string.length() - 1);
- }
- if (string.startsWith("=>")) {
- string = string.substring(2, string.length());
- }
-
- return string;
+ return CodeParseUtil.trim(string, "'", "\"", ":", "[", "]", "=>");
}
RailsAbstractParameter makeParameter(String parameterLabel, String parameterValue) {
RailsAbstractParameter parameter = new RailsAbstractParameter();
RouteParameterValueType parameterType = detectParameterValueType(parameterValue);
- parameter.setParameterType(parameterType);
+ parameter.setValueType(parameterType);
if (parameterLabel != null) {
- parameter.setLabelType(detectParameterLabelType(parameterLabel));
+ parameter.setParameterType(detectParameterType(parameterLabel));
+ parameter.setLabelType(detectParameterValueType(parameterLabel));
}
parameter.setValue(cleanParameterString(parameterValue));
@@ -267,7 +250,11 @@ void processParametersPhase(int type, int lineNumber, String stringValue) {
return;
}
- spansNextLine = ((type == '\n' && lastType == ',') || !isEntryScope());
+ spansNextLine =
+ (type == '\n' && (lastType == ',' || (currentDescriptor.getIdentifier().equals(lastString) && lastType < 0)))
+ || !isEntryScope()
+ || (parameterLabel != null && parameterLabel.endsWith(":") && workingLine.isEmpty())
+ || lastType == '(';
boolean isDoStatement = (stringValue != null && stringValue.equalsIgnoreCase("do") && isEntryScope());
@@ -301,15 +288,15 @@ void processParametersPhase(int type, int lineNumber, String stringValue) {
return;
}
- if ((type == ',' || (stringValue != null && lastString.endsWith(":"))) && isEntryScope()) {
- if (workingLine.length() == 0) {
+ if ((type == ',' || (stringValue != null && !spansNextLine && lastString.endsWith(":")) || type == StreamTokenizer.TT_NUMBER) && isEntryScope()) {
+ if (workingLine.length() == 0 && type != StreamTokenizer.TT_NUMBER) {
workingLine = parameterLabel;
parameterLabel = null;
}
if (workingLine != null && workingLine.endsWith(":") && stringValue != null) {
parameterLabel = workingLine;
- workingLine = stringValue;
+ workingLine = CodeParseUtil.buildTokenString(type, stringValue);
}
if (workingLine != null) {
@@ -321,12 +308,12 @@ void processParametersPhase(int type, int lineNumber, String stringValue) {
workingLine = "";
} else {
if (stringValue != null && parameterLabel == null) {
- parameterLabel = stringValue;
+ parameterLabel = CodeParseUtil.buildTokenString(type, stringValue);
}
else {
if (stringValue != null && isEntryScope() && stringValue.equalsIgnoreCase("do")) {
wasRouteScope = true;
- } else {
+ } else if (type != '\n') {
workingLine += CodeParseUtil.buildTokenString(type, stringValue);
}
}
@@ -343,9 +330,35 @@ void processParametersPhase(int type, int lineNumber, String stringValue) {
void processInitializerParametersPhase(int type, int line, String stringValue) {
if (isEntryScope()) { // Will occur exactly on the last closing paren ')'
parsePhase = ParsePhase.PARAMETERS;
+ return;
}
- // TODO
-
+ // Treat this as a multi-line entry statement for now
+ // Simulate parameter processing in entry scope
+ numOpenParen--;
+ processParametersPhase(type, line, stringValue);
+ numOpenParen++;
+
+ // If SEARCH_IDENTIFIER is set while in INITIALIZER_PARAMETERS, the state will return to PARAMETERS
+ // before the INITIALIZER_PARAMETERS block has ended; simulate SEARCH_IDENTIFIER and go back to
+ // INITIALIZER_PARAMETERS afterwards
+ if (parsePhase == ParsePhase.SEARCH_IDENTIFIER) {
+ parsePhase = ParsePhase.INITIALIZER_PARAMETERS_SEARCH_IDENTIFIER;
+ }
}
+
+ void processInitializerParametersSearchIdentifierPhase(int type, int lineNumber, String stringValue) {
+ if (isEntryScope()) {
+ parsePhase = ParsePhase.SEARCH_IDENTIFIER;
+ return;
+ }
+
+ numOpenParen--;
+ processSearchIdentifierPhase(type, lineNumber, stringValue);
+ numOpenParen++;
+
+ if (parsePhase == ParsePhase.PARAMETERS) {
+ parsePhase = ParsePhase.INITIALIZER_PARAMETERS;
+ }
+ }
}
diff --git a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/routeParsing/RailsConcreteRouteTreeMapper.java b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/routeParsing/RailsConcreteRouteTreeMapper.java
index ee789cb24..8c082e8a2 100644
--- a/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/routeParsing/RailsConcreteRouteTreeMapper.java
+++ b/threadfix-ham/src/main/java/com/denimgroup/threadfix/framework/impl/rails/routeParsing/RailsConcreteRouteTreeMapper.java
@@ -31,6 +31,7 @@
import com.denimgroup.threadfix.framework.impl.rails.model.defaultRoutingEntries.UnknownEntry;
import com.denimgroup.threadfix.framework.util.PathUtil;
import com.denimgroup.threadfix.logging.SanitizedLogger;
+import org.apache.commons.lang3.StringUtils;
import java.util.*;
@@ -68,12 +69,11 @@ public void visitEntry(RailsRoutingEntry entry, ListIterator