├── src ├── META-INF │ └── MANIFEST.MF └── main │ ├── resources │ ├── log4j2.xml │ └── help.txt │ └── java │ └── com │ └── denimgroup │ └── threadfix │ └── cli │ └── endpoints │ ├── EndpointJob.java │ ├── Credentials.java │ ├── EndpointTester.java │ ├── EndpointValidation.java │ └── EndpointMain.java ├── .gitignore ├── .project ├── sample-project-list.txt ├── README.md ├── pom.xml └── LICENSE.md /src/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: com.denimgroup.threadfix.cli.endpoints.EndpointMain 3 | 4 | -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | threadfix-cli.log* 3 | threadfix-cli.log 4 | threadfix-ide-plugin/intellij/intellij.zip 5 | database 6 | threadfix-main/src/main/resources/threadfix.license 7 | 8 | threadfix-scanagent/threadfix.log 9 | bin 10 | out 11 | *.DS_Store 12 | *dependency-reduced-pom.xml 13 | *.iml 14 | 15 | .classpath 16 | .settings 17 | .idea/ 18 | *.DS_Store 19 | threadfix-enterprise 20 | threadfix-scanagent 21 | Build 22 | netrc 23 | scanagent.properties 24 | sonar-project.properties 25 | 26 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | EndpointCLI 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.m2e.core.maven2Nature 21 | org.eclipse.jdt.core.javanature 22 | 23 | 24 | -------------------------------------------------------------------------------- /sample-project-list.txt: -------------------------------------------------------------------------------- 1 | # This is a sample project listing file for use with the threadfix-endpoints-cli tool. 2 | # This is used via the -path-list-file="" parameter. 3 | 4 | # 1 folder path per line - HAM will auto-detect the framework types for these 5 | /files/Project 1/src 6 | /files/Project 2/src 7 | 8 | # Define a specific framework to use when parsing endpoints 9 | # by adding "FRAMEWORK_TYPE:" before the folder path. 10 | # The list of currently accepted values are demonstrated below. 11 | # If a new framework is added, its "FrameworkType" name will 12 | # automatically be usable in this file. 13 | 14 | # .NET WEB FORMS 15 | DOT_NET_WEB_FORMS:/files/ASP Web Forms Project/src 16 | 17 | # .NET MVC 18 | DOT_NET_MVC:/files/ASP MVC Project/src 19 | 20 | # STRUTS 21 | STRUTS:/files/Struts Project/src 22 | 23 | # JSP 24 | JSP:/files/JSP Project/src 25 | 26 | # Spring MVC 27 | SPRING_MVC:/files/Spring MVC Project/src 28 | 29 | # Rails 30 | RAILS:/files/Rails Project/src 31 | 32 | # Django 33 | PYTHON:/files/Django Project/src 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # attack-surface-detector-cli 2 | 3 | ### _[Latest Release - 1.3.5](https://github.com/secdec/attack-surface-detector-cli/releases/tag/v1.3.5)_ 4 | 5 | The `attack-surface-detector-cli` program is a command-line tool that takes in a folder location and outputs the set of endpoints detected within that codebase. It uses the [ASTAM Correlator's](https://github.com/secdec/astam-correlator) `threadfix-ham` module to generate these endpoints. The endpoints are output to the console by default, and can save a JSON version of those endpoints through the `-output-file` and `-json` flags. See the [Wiki](https://github.com/secdec/attack-surface-detector-cli/wiki/Usage,-Parameters,-and-Output) for more details. 6 | 7 | This tool supports the following frameworks, as supported by the `threadfix-ham` module: 8 | 9 | - ASP.NET MVC / Web API / Core / Web Forms 10 | - Struts 11 | - Django 12 | - Ruby on Rails 13 | - Spring MVC 14 | - JSP 15 | 16 | --- 17 | 18 | Licensed under the [MPL](https://github.com/secdec/attack-surface-detector-cli/blob/master/LICENSE.md) License. 19 | 20 | --- 21 | 22 | _This material is based on research sponsored by the Department of Homeland Security (DHS) Science and Technology Directorate, Cyber Security Division (DHS S&T/CSD) via contract number HHSP233201600058C._ 23 | -------------------------------------------------------------------------------- /src/main/java/com/denimgroup/threadfix/cli/endpoints/EndpointJob.java: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////// 2 | // 3 | // Copyright (C) 2018 Applied Visions - http://securedecisions.com 4 | // 5 | // The contents of this file are subject to the Mozilla Public License 6 | // Version 2.0 (the "License"); you may not use this file except in 7 | // compliance with the License. You may obtain a copy of the License at 8 | // http://www.mozilla.org/MPL/ 9 | // 10 | // Software distributed under the License is distributed on an "AS IS" 11 | // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing rights and limitations 13 | // under the License. 14 | // 15 | // This material is based on research sponsored by the Department of Homeland 16 | // Security (DHS) Science and Technology Directorate, Cyber Security Division 17 | // (DHS S&T/CSD) via contract number HHSP233201600058C. 18 | // 19 | // Contributor(s): 20 | // Secure Decisions, a division of Applied Visions, Inc 21 | // 22 | //////////////////////////////////////////////////////////////////////// 23 | 24 | package com.denimgroup.threadfix.cli.endpoints; 25 | 26 | import com.denimgroup.threadfix.data.enums.FrameworkType; 27 | 28 | import java.io.File; 29 | import java.util.Collection; 30 | 31 | public class EndpointJob { 32 | 33 | public Collection frameworkTypes; 34 | public File sourceCodePath; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/denimgroup/threadfix/cli/endpoints/Credentials.java: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////// 2 | // 3 | // Copyright (C) 2018 Applied Visions - http://securedecisions.com 4 | // 5 | // The contents of this file are subject to the Mozilla Public License 6 | // Version 2.0 (the "License"); you may not use this file except in 7 | // compliance with the License. You may obtain a copy of the License at 8 | // http://www.mozilla.org/MPL/ 9 | // 10 | // Software distributed under the License is distributed on an "AS IS" 11 | // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing rights and limitations 13 | // under the License. 14 | // 15 | // This material is based on research sponsored by the Department of Homeland 16 | // Security (DHS) Science and Technology Directorate, Cyber Security Division 17 | // (DHS S&T/CSD) via contract number HHSP233201600058C. 18 | // 19 | // Contributor(s): 20 | // Secure Decisions, a division of Applied Visions, Inc 21 | // 22 | //////////////////////////////////////////////////////////////////////// 23 | 24 | package com.denimgroup.threadfix.cli.endpoints; 25 | 26 | import java.util.Map; 27 | 28 | import static com.denimgroup.threadfix.CollectionUtils.map; 29 | 30 | public class Credentials { 31 | public String authenticationEndpoint; 32 | public Map parameters = map(); 33 | 34 | public Map authenticatedParameters = null; 35 | } 36 | -------------------------------------------------------------------------------- /src/main/resources/help.txt: -------------------------------------------------------------------------------- 1 | Attack Surface Detector CLI 2 | https://github.com/secdec/attack-surface-detector-cli 3 | 4 | A tool to detect endpoints from source code. Can output these endpoints in multiple JSON formats, 5 | or output to the console (by default) to inspect the quality and coverage of the detected endpoints. 6 | 7 | Usage: 8 | java -jar attack-surface-detector-cli.jar [flags] 9 | 10 | Flags: 11 | -debug -- Print debug information during endpoint detection 12 | 13 | -simple -- Print only endpoint detection summary, without the full list 14 | of detected endpoints 15 | 16 | -path-list-file= -- Detect endpoints from all source code paths listed in the given file 17 | 18 | -defaultFramework= -- Parse the source code using the given framework type 19 | Available values: 20 | DETECT : Attempt to automatically detect the framework 21 | JSP : Java JSP/Servlets 22 | SPRING_MVC : Java Spring MVC 23 | STRUTS : Java Struts 24 | DOT_NET_MVC : ASP.NET MVC/WebAPI 25 | DOT_NET_WEB_FORMS : ASP.NET Web Forms 26 | PYTHON : Django 27 | Rails : Ruby on Rails 28 | 29 | -help -- Displays this message 30 | 31 | [JSON Output] 32 | -json -- Print only simple-format JSON to the console 33 | Simple-format JSON uses a common format for all generated endpoints 34 | regardless of framework. Source code information is not included. 35 | 36 | -keep-source -- Include source code information when combined with the -json flag 37 | 38 | -full-json -- Print full JSON information to the console 39 | Full-format JSON uses unique data formats depending on the framework 40 | that declared the endpoints. Should be used with the 41 | astam-correlator.threadfix-ham module available on Maven. 42 | 43 | -output-file= -- Writes generated JSON to the specified file path. 44 | Must be used with the -json or -full-json flags; otherwise, has no effect. 45 | 46 | 47 | 48 | [Quality Testing] 49 | -validation-server= -- Run HTTP requests against the detected endpoints relative to the 50 | given BASE_URL; ie http://localhost:8080/mywebapp 51 | 52 | -validation-server-auth= -- Use the given form data values to authenticate when validating endpoints against 53 | a test server. Takes the format: 54 | 55 | "AUTH-ENDPOINT;HEADER=VALUE;HEADER=VALUE;..." 56 | 57 | ie: 58 | 59 | "/login;username=user;password=pass" 60 | 61 | The "/login" endpoint is queried with "username=user&password=pass" as form 62 | data. Any "Set-Cookie" headers in the response are used in later requests 63 | during testing. Must be used with the -validation-server flag; otherwise, has 64 | no effect. 65 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | 6 | com.github.secdec.astam-correlator 7 | attack-surface-detector-cli 8 | attack-surface-detector-cli 9 | 1.3.8 10 | 11 | 12 | This is a standalone tool that uses the ASTAM Correlator HAM module to detect endpoints from source code for 13 | a variety of languages and frameworks. See the readme for more information. 14 | 15 | 16 | jar 17 | 18 | 19 | src/main/java 20 | 21 | 22 | 23 | org.apache.maven.plugins 24 | maven-compiler-plugin 25 | 2.3.2 26 | 27 | 1.8 28 | 1.8 29 | 30 | 31 | 32 | 33 | 34 | 35 | maven-compiler-plugin 36 | 37 | 38 | maven-assembly-plugin 39 | 40 | 41 | 42 | com.denimgroup.threadfix.cli.endpoints.EndpointMain 43 | 44 | 45 | 46 | jar-with-dependencies 47 | 48 | 49 | 50 | 51 | 52 | attached 53 | single 54 | 55 | package 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | dependencycheck 64 | 65 | 66 | 67 | org.owasp 68 | dependency-check-maven 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | commons-io 77 | commons-io 78 | 2.4 79 | 80 | 81 | com.github.secdec.astam-correlator 82 | threadfix-entities 83 | 1.3.8 84 | 85 | 86 | com.github.secdec.astam-correlator 87 | threadfix-ham 88 | 1.3.8 89 | 90 | 91 | org.owasp 92 | dependency-check-maven 93 | 1.3.6 94 | test 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /src/main/java/com/denimgroup/threadfix/cli/endpoints/EndpointTester.java: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////// 2 | // 3 | // Copyright (C) 2018 Applied Visions - http://securedecisions.com 4 | // 5 | // The contents of this file are subject to the Mozilla Public License 6 | // Version 2.0 (the "License"); you may not use this file except in 7 | // compliance with the License. You may obtain a copy of the License at 8 | // http://www.mozilla.org/MPL/ 9 | // 10 | // Software distributed under the License is distributed on an "AS IS" 11 | // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing rights and limitations 13 | // under the License. 14 | // 15 | // This material is based on research sponsored by the Department of Homeland 16 | // Security (DHS) Science and Technology Directorate, Cyber Security Division 17 | // (DHS S&T/CSD) via contract number HHSP233201600058C. 18 | // 19 | // Contributor(s): 20 | // Secure Decisions, a division of Applied Visions, Inc 21 | // 22 | //////////////////////////////////////////////////////////////////////// 23 | 24 | package com.denimgroup.threadfix.cli.endpoints; 25 | 26 | import com.denimgroup.threadfix.data.entities.RouteParameter; 27 | import com.denimgroup.threadfix.data.entities.RouteParameterType; 28 | import com.denimgroup.threadfix.data.interfaces.Endpoint; 29 | import com.denimgroup.threadfix.framework.util.PathUtil; 30 | 31 | import java.io.IOException; 32 | import java.io.OutputStream; 33 | import java.io.UnsupportedEncodingException; 34 | import java.net.*; 35 | import java.nio.charset.StandardCharsets; 36 | import java.util.List; 37 | import java.util.Map; 38 | import java.util.StringJoiner; 39 | 40 | import static com.denimgroup.threadfix.CollectionUtils.list; 41 | import static com.denimgroup.threadfix.CollectionUtils.map; 42 | 43 | public class EndpointTester { 44 | String basePath; 45 | 46 | public EndpointTester(String basePath) { 47 | this.basePath = basePath; 48 | } 49 | 50 | public int test(Endpoint endpoint, Credentials credentials) throws IOException { 51 | URL url = new URL(PathUtil.combine(this.basePath, endpoint.getUrlPath())); 52 | HttpURLConnection conn = (HttpURLConnection)url.openConnection(); 53 | conn.setRequestMethod(endpoint.getHttpMethod()); 54 | 55 | if (credentials != null && credentials.authenticatedParameters != null) { 56 | for (Map.Entry header : credentials.authenticatedParameters.entrySet()) { 57 | conn.setRequestProperty(header.getKey(), header.getValue()); 58 | } 59 | } 60 | 61 | conn.getInputStream().close(); 62 | return conn.getResponseCode(); 63 | } 64 | 65 | public int authorize(Credentials credentials, Endpoint endpoint) throws IOException { 66 | // Get query settings 67 | String httpMethod = endpoint != null ? endpoint.getHttpMethod() : "POST"; 68 | 69 | HttpURLConnection.setFollowRedirects(false); 70 | 71 | // Try auth by best-match 72 | 73 | if (endpoint != null) { 74 | try { 75 | String urlParams = configureRequestWithBestMatchParameters(endpoint.getParameters(), credentials, null); 76 | String finalizedPath = credentials.authenticationEndpoint; 77 | if (!urlParams.isEmpty()) { 78 | finalizedPath += "?" + urlParams; 79 | } 80 | 81 | URL url = new URL(PathUtil.combine(this.basePath, finalizedPath)); 82 | // Configure connection 83 | HttpURLConnection conn = (HttpURLConnection)url.openConnection(); 84 | conn.setRequestMethod(httpMethod); 85 | configureRequestWithBestMatchParameters(endpoint.getParameters(), credentials, conn); 86 | 87 | conn.getInputStream().close(); 88 | if (conn.getResponseCode() < 400 && saveCredentialsResponse(conn, credentials)) { 89 | return conn.getResponseCode(); 90 | } 91 | } catch (IOException e) { 92 | System.out.println("Unable to authorize using best-match parameters:"); 93 | e.printStackTrace(); 94 | } 95 | } 96 | 97 | try { 98 | URL url = new URL(PathUtil.combine(this.basePath, credentials.authenticationEndpoint)); 99 | HttpURLConnection conn = (HttpURLConnection)url.openConnection(); 100 | conn.setRequestMethod(httpMethod); 101 | configureRequestWithFormParameters(credentials, conn); 102 | 103 | conn.getInputStream().close(); 104 | if (conn.getResponseCode() < 400 && saveCredentialsResponse(conn, credentials)) { 105 | return conn.getResponseCode(); 106 | } 107 | } catch (IOException e) { 108 | System.out.println("Unable to authorize using all-forms parameters:"); 109 | e.printStackTrace(); 110 | } 111 | 112 | return 0xffff; 113 | } 114 | 115 | private boolean saveCredentialsResponse(HttpURLConnection conn, Credentials creds) { 116 | if (conn.getHeaderField("Set-Cookie") != null) { 117 | List cookies = conn.getHeaderFields().get("Set-Cookie"); 118 | List sanitizeCookies = list(); 119 | 120 | for (String cookie : cookies) { 121 | String mainPart = cookie; 122 | if (mainPart.contains(";")) { 123 | mainPart = mainPart.substring(0, mainPart.indexOf(';')); 124 | } 125 | sanitizeCookies.add(mainPart); 126 | } 127 | 128 | creds.authenticatedParameters = map(); 129 | creds.authenticatedParameters.put("Cookie", String.join("; ", sanitizeCookies)); 130 | 131 | return true; 132 | } 133 | return false; 134 | } 135 | 136 | private String configureRequestWithBestMatchParameters(Map parsedParameters, Credentials credentials, HttpURLConnection conn) throws IOException { 137 | StringJoiner queryString = new StringJoiner("&"); 138 | StringJoiner formParams = new StringJoiner("&"); 139 | 140 | for (Map.Entry credParam : credentials.parameters.entrySet()) { 141 | RouteParameter paramSpec = null; 142 | if (parsedParameters != null) { 143 | if (parsedParameters.containsKey(credParam.getKey())) { 144 | paramSpec = parsedParameters.get(credParam.getKey()); 145 | } 146 | } 147 | 148 | if (paramSpec == null) { 149 | paramSpec = RouteParameter.fromDataType(credParam.getKey(), "String"); 150 | paramSpec.setParamType(RouteParameterType.FORM_DATA); 151 | } 152 | 153 | String encodedKey, encodedValue; 154 | try { 155 | encodedKey = URLEncoder.encode(credParam.getKey(), "UTF-8"); 156 | encodedValue = URLEncoder.encode(credParam.getValue(), "UTF-8"); 157 | } catch (UnsupportedEncodingException e) { 158 | e.printStackTrace(); 159 | continue; 160 | } 161 | 162 | String encodedPair = encodedKey + "=" + encodedValue; 163 | 164 | switch (paramSpec.getParamType()) { 165 | case QUERY_STRING: 166 | queryString.add(encodedPair); 167 | break; 168 | 169 | default: 170 | // Assume form data for anything else 171 | formParams.add(encodedPair); 172 | } 173 | } 174 | 175 | if (formParams.length() > 0 && conn != null) { 176 | 177 | byte[] out = formParams.toString().getBytes(StandardCharsets.UTF_8); 178 | 179 | conn.setDoOutput(true); 180 | conn.setFixedLengthStreamingMode(out.length); 181 | conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); 182 | 183 | conn.connect(); 184 | try (OutputStream os = conn.getOutputStream()) { 185 | os.write(out); 186 | } 187 | } 188 | 189 | if (queryString.length() == 0) { 190 | return ""; 191 | } else { 192 | return "?" + queryString.toString(); 193 | } 194 | } 195 | 196 | private void configureRequestWithFormParameters(Credentials credentials, HttpURLConnection conn) throws IOException { 197 | StringJoiner joiner = new StringJoiner("&"); 198 | for (Map.Entry param : credentials.parameters.entrySet()) { 199 | try { 200 | joiner.add(URLEncoder.encode(param.getKey(), "UTF-8") + "=" + URLEncoder.encode(param.getValue(), "UTF-8")); 201 | } catch (UnsupportedEncodingException e) { 202 | e.printStackTrace(); 203 | } 204 | } 205 | 206 | byte[] out = joiner.toString().getBytes(StandardCharsets.UTF_8); 207 | 208 | conn.setDoOutput(true); 209 | conn.setFixedLengthStreamingMode(out.length); 210 | conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); 211 | 212 | conn.connect(); 213 | try (OutputStream os = conn.getOutputStream()) { 214 | os.write(out); 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/main/java/com/denimgroup/threadfix/cli/endpoints/EndpointValidation.java: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////// 2 | // 3 | // Copyright (C) 2018 Applied Visions - http://securedecisions.com 4 | // 5 | // The contents of this file are subject to the Mozilla Public License 6 | // Version 2.0 (the "License"); you may not use this file except in 7 | // compliance with the License. You may obtain a copy of the License at 8 | // http://www.mozilla.org/MPL/ 9 | // 10 | // Software distributed under the License is distributed on an "AS IS" 11 | // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing rights and limitations 13 | // under the License. 14 | // 15 | // This material is based on research sponsored by the Department of Homeland 16 | // Security (DHS) Science and Technology Directorate, Cyber Security Division 17 | // (DHS S&T/CSD) via contract number HHSP233201600058C. 18 | // 19 | // Contributor(s): 20 | // Secure Decisions, a division of Applied Visions, Inc 21 | // 22 | //////////////////////////////////////////////////////////////////////// 23 | 24 | package com.denimgroup.threadfix.cli.endpoints; 25 | 26 | import com.denimgroup.threadfix.data.entities.EndpointStructure; 27 | import com.denimgroup.threadfix.data.entities.RouteParameter; 28 | import com.denimgroup.threadfix.data.enums.EndpointRelevanceStrictness; 29 | import com.denimgroup.threadfix.data.enums.FrameworkType; 30 | import com.denimgroup.threadfix.data.interfaces.Endpoint; 31 | import com.denimgroup.threadfix.framework.engine.full.EndpointSerialization; 32 | import com.denimgroup.threadfix.framework.util.EndpointUtil; 33 | import org.apache.logging.log4j.LogManager; 34 | import org.apache.logging.log4j.Logger; 35 | 36 | import java.io.File; 37 | import java.io.IOException; 38 | import java.util.ArrayList; 39 | import java.util.Collection; 40 | import java.util.List; 41 | import java.util.Map; 42 | 43 | import static com.denimgroup.threadfix.CollectionUtils.list; 44 | 45 | public class EndpointValidation { 46 | 47 | private static Logger logger = LogManager.getLogger(EndpointValidation.class); 48 | 49 | public static boolean validateSerialization(File sourceCodeFolder, List endpoints) { 50 | List allEndpoints = EndpointUtil.flattenWithVariants(endpoints); 51 | List allUrls = list(); 52 | 53 | for (Endpoint endpoint : allEndpoints) { 54 | allUrls.add(endpoint.getUrlPath()); 55 | } 56 | 57 | try { 58 | String serializedCollection = EndpointSerialization.serializeAll(allEndpoints); 59 | Endpoint[] deserializedCollection = EndpointSerialization.deserializeAll(serializedCollection); 60 | if (deserializedCollection.length != allEndpoints.size()) { 61 | logger.warn("Collection serialization did not match the original input"); 62 | return false; 63 | } 64 | } catch (IOException e) { 65 | e.printStackTrace(); 66 | logger.warn("Exception thrown during collection serialization"); 67 | return false; 68 | } 69 | 70 | for (Endpoint endpoint : allEndpoints) { 71 | 72 | if (endpoint.getFilePath().startsWith(sourceCodeFolder.getAbsolutePath().replace('\\', '/'))) { 73 | logger.warn("Got an absolute file path when a relative path was expected instead, for: " + endpoint.toString()); 74 | return false; 75 | } 76 | 77 | if (endpoint.getFilePath().isEmpty()) { 78 | logger.warn("Got an empty file path for: " + endpoint.toString()); 79 | } 80 | else if (!endpoint.getFilePath().contains("(lib)")) { 81 | File fullPath = new File(sourceCodeFolder, endpoint.getFilePath()); 82 | if (!fullPath.exists()) { 83 | logger.warn("The source code path '" + fullPath.getAbsolutePath() + "' does not exist for: " + endpoint.toString()); 84 | return false; 85 | } 86 | } 87 | 88 | for (String url : allUrls) { 89 | try { 90 | endpoint.isRelevant(url, EndpointRelevanceStrictness.STRICT); 91 | endpoint.isRelevant(url, EndpointRelevanceStrictness.LOOSE); 92 | endpoint.compareRelevance(url); 93 | } catch (Exception e) { 94 | logger.warn("Exception occurred while testing relevancy comparisons between [" + url + "] and " + endpoint.toString()); 95 | e.printStackTrace(); 96 | return false; 97 | } 98 | } 99 | 100 | String serialized; 101 | Endpoint deserialized; 102 | try { 103 | serialized = EndpointSerialization.serialize(endpoint); 104 | } catch (IOException e) { 105 | logger.warn("Exception occurred while serializing: " + endpoint.toString()); 106 | e.printStackTrace(); 107 | return false; 108 | } 109 | 110 | try { 111 | deserialized = EndpointSerialization.deserialize(serialized); 112 | } catch (IOException e) { 113 | logger.warn("Exception occurred while deserializing: " + endpoint.toString()); 114 | e.printStackTrace(); 115 | return false; 116 | } 117 | 118 | if (deserialized == null) { 119 | logger.warn("Failed to validate serialization due to NULL DESERIALIZED ENDPOINT on " + endpoint.toString()); 120 | return false; 121 | } 122 | 123 | if (!endpoint.getClass().equals(deserialized.getClass())) { 124 | logger.warn("Failed to validate serialization due to MISMATCHED ENDPOINT DATATYPES on " + endpoint.toString()); 125 | return false; 126 | } 127 | 128 | if (!deserialized.getUrlPath().equals(endpoint.getUrlPath())) { 129 | logger.warn("Failed to validate serialization due to mismatched URL paths on " + endpoint.toString()); 130 | return false; 131 | } 132 | 133 | if (!deserialized.getFilePath().equals(endpoint.getFilePath())) { 134 | logger.warn("Failed to validate serialization due to mismatched FILE paths on " + endpoint.toString()); 135 | return false; 136 | } 137 | 138 | if (deserialized.getParameters().size() != endpoint.getParameters().size()) { 139 | logger.warn("Failed to validate serialization due to mismatched PARAMETER COUNTS on " + endpoint.toString()); 140 | return false; 141 | } 142 | 143 | if (!deserialized.getHttpMethod().equals(endpoint.getHttpMethod())) { 144 | logger.warn("Failed to validate serialization due to mismatched HTTP METHOD on " + endpoint.toString()); 145 | return false; 146 | } 147 | 148 | Map endpointParams = endpoint.getParameters(); 149 | Map deserializedParams = deserialized.getParameters(); 150 | 151 | if (!endpointParams.keySet().containsAll(deserializedParams.keySet()) || 152 | !deserializedParams.keySet().containsAll(endpointParams.keySet())) { 153 | 154 | logger.warn("Failed to validate serialization due to mismatched PARAMETER NAMES on " + endpoint.toString()); 155 | return false; 156 | } 157 | 158 | for (String param : endpointParams.keySet()) { 159 | RouteParameter endpointParam = endpointParams.get(param); 160 | RouteParameter deserializedParam = deserializedParams.get(param); 161 | 162 | if (endpointParam.getParamType() != deserializedParam.getParamType()) { 163 | logger.warn("Failed to validate serialization due to mismatched PARAM TYPE on " + endpoint.toString()); 164 | return false; 165 | } 166 | 167 | if ((endpointParam.getDataTypeSource() == null) != (deserializedParam.getDataTypeSource() == null)) { 168 | logger.warn("Failed to validate serialization due to mismatched PARAM DATA TYPE on " + endpoint.toString()); 169 | return false; 170 | } 171 | 172 | if (endpointParam.getDataTypeSource() != null && !endpointParam.getDataTypeSource().equals(deserializedParam.getDataTypeSource())) { 173 | logger.warn("Failed to validate serialization due to mismatched PARAM DATA TYPE on " + endpoint.toString()); 174 | return false; 175 | } 176 | 177 | if (!endpointParam.getName().equals(deserializedParam.getName())) { 178 | logger.warn("Failed to validate serialization due to mismatched PARAM NAME on " + endpoint.toString()); 179 | return false; 180 | } 181 | 182 | if ((endpointParam.getAcceptedValues() == null) != (deserializedParam.getAcceptedValues() == null)) { 183 | logger.warn("Failed to validate serialization due to mismatched ACCEPTED PARAM VALUES on " + endpoint.toString()); 184 | return false; 185 | } else if (endpointParam.getAcceptedValues() != null) { 186 | if (!endpointParam.getAcceptedValues().containsAll(deserializedParam.getAcceptedValues()) || 187 | !deserializedParam.getAcceptedValues().containsAll(endpointParam.getAcceptedValues())) { 188 | logger.warn("Failed to validate serialization due to mismatched ACCEPTED PARAM VALUES on " + endpoint.toString()); 189 | return false; 190 | } 191 | } 192 | } 193 | } 194 | 195 | try { 196 | EndpointStructure testStructure = new EndpointStructure(); 197 | testStructure.acceptAllEndpoints(endpoints); 198 | } catch (Exception e) { 199 | System.out.println("Failed to validate endpoint structure generation due to an exception: \n" + e); 200 | } 201 | 202 | return true; 203 | } 204 | 205 | public static boolean validateDuplicates(Collection endpoints) { 206 | boolean validated = true; 207 | List> duplicateEndpoints = detectDuplicates(endpoints); 208 | if (!duplicateEndpoints.isEmpty()) { 209 | logger.warn("Found " + duplicateEndpoints.size() + " duplicated endpoints:"); 210 | for (List duplicateSet : duplicateEndpoints) { 211 | logger.warn("- " + duplicateSet.size() + ": " + duplicateSet.get(0).toString()); 212 | validated = false; 213 | } 214 | } 215 | return validated; 216 | } 217 | 218 | private static List> detectDuplicates(Collection endpoints) { 219 | List> duplicates = new ArrayList<>(); 220 | 221 | for (Endpoint main : endpoints) { 222 | // Check if we already know that this endpoint has duplicates 223 | boolean wasChecked = false; 224 | for (List duplicatesSet : duplicates) { 225 | if (endpointsMatch(main, duplicatesSet.get(0))) { 226 | wasChecked = true; 227 | break; 228 | } 229 | } 230 | if (wasChecked) { 231 | continue; 232 | } 233 | 234 | List currentDuplicates = list(); 235 | 236 | for (Endpoint other : endpoints) { 237 | if (endpointsMatch(main, other)) { 238 | currentDuplicates.add(other); 239 | } 240 | } 241 | 242 | if (currentDuplicates.size() > 1) { 243 | duplicates.add(currentDuplicates); 244 | } 245 | } 246 | 247 | return duplicates; 248 | } 249 | 250 | private static boolean endpointsMatch(Endpoint a, Endpoint b) { 251 | return 252 | a.getUrlPath().equals(b.getUrlPath()) && 253 | a.getHttpMethod().equals(b.getHttpMethod()) && 254 | a.getFilePath().equals(b.getFilePath()) && 255 | a.getStartingLineNumber() == b.getStartingLineNumber() && 256 | a.getEndingLineNumber() == b.getEndingLineNumber() && 257 | endpointParametersMatch(a, b); 258 | } 259 | 260 | private static boolean endpointParametersMatch(Endpoint a, Endpoint b) { 261 | Map bParams = b.getParameters(); 262 | for (Map.Entry param : a.getParameters().entrySet()) { 263 | if (!bParams.containsKey(param.getKey())) 264 | return false; 265 | 266 | RouteParameter aParam = param.getValue(); 267 | RouteParameter bParam = bParams.get(param.getKey()); 268 | 269 | if ( 270 | aParam.getParamType() != bParam.getParamType() || 271 | aParam.getDataType() != bParam.getDataType() || 272 | (aParam.getAcceptedValues() == null) != (bParam.getAcceptedValues() == null) 273 | ) { 274 | return false; 275 | } 276 | 277 | if ( 278 | aParam.getAcceptedValues() != null && !( 279 | aParam.getAcceptedValues().containsAll(bParam.getAcceptedValues()) || 280 | bParam.getAcceptedValues().containsAll(aParam.getAcceptedValues()) 281 | ) 282 | ) { 283 | return false; 284 | } 285 | } 286 | 287 | return true; 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /src/main/java/com/denimgroup/threadfix/cli/endpoints/EndpointMain.java: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////// 2 | // 3 | // Copyright (c) 2009-2015 Denim Group, Ltd. 4 | // 5 | // The contents of this file are subject to the Mozilla Public License 6 | // Version 2.0 (the "License"); you may not use this file except in 7 | // compliance with the License. You may obtain a copy of the License at 8 | // http://www.mozilla.org/MPL/ 9 | // 10 | // Software distributed under the License is distributed on an "AS IS" 11 | // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing rights and limitations 13 | // under the License. 14 | // 15 | // The Original Code is ThreadFix. 16 | // 17 | // The Initial Developer of the Original Code is Denim Group, Ltd. 18 | // Portions created by Denim Group, Ltd. are Copyright (C) 19 | // Denim Group, Ltd. All Rights Reserved. 20 | // 21 | // Contributor(s): 22 | // Denim Group, Ltd. 23 | // Secure Decisions, a division of Applied Visions, Inc 24 | // 25 | //////////////////////////////////////////////////////////////////////// 26 | 27 | package com.denimgroup.threadfix.cli.endpoints; 28 | 29 | import com.denimgroup.threadfix.data.entities.RouteParameter; 30 | import com.denimgroup.threadfix.data.entities.RouteParameterType; 31 | import com.denimgroup.threadfix.data.entities.WildcardEndpointPathNode; 32 | import com.denimgroup.threadfix.data.enums.FrameworkType; 33 | import com.denimgroup.threadfix.data.interfaces.Endpoint; 34 | import com.denimgroup.threadfix.data.interfaces.EndpointPathNode; 35 | import com.denimgroup.threadfix.framework.engine.framework.FrameworkCalculator; 36 | import com.denimgroup.threadfix.framework.engine.full.EndpointDatabase; 37 | import com.denimgroup.threadfix.framework.engine.full.EndpointDatabaseFactory; 38 | import com.denimgroup.threadfix.framework.engine.full.EndpointSerialization; 39 | import com.denimgroup.threadfix.framework.engine.full.TemporaryExtractionLocation; 40 | import com.denimgroup.threadfix.framework.util.EndpointUtil; 41 | import com.fasterxml.jackson.databind.ObjectMapper; 42 | import org.apache.commons.io.FileUtils; 43 | import org.apache.commons.io.FilenameUtils; 44 | import org.apache.commons.io.IOUtils; 45 | import org.apache.commons.lang3.StringUtils; 46 | import org.apache.logging.log4j.Level; 47 | import org.apache.logging.log4j.LogManager; 48 | import org.apache.logging.log4j.core.LoggerContext; 49 | import org.apache.logging.log4j.core.config.Configuration; 50 | import org.apache.logging.log4j.core.config.LoggerConfig; 51 | 52 | import java.io.*; 53 | import java.net.UnknownHostException; 54 | import java.util.Collection; 55 | import java.util.List; 56 | import java.util.Map; 57 | 58 | import static com.denimgroup.threadfix.CollectionUtils.list; 59 | import static com.denimgroup.threadfix.CollectionUtils.map; 60 | import static com.denimgroup.threadfix.data.interfaces.Endpoint.PrintFormat.FULL_JSON; 61 | import static com.denimgroup.threadfix.data.interfaces.Endpoint.PrintFormat.SIMPLE_JSON; 62 | 63 | public class EndpointMain { 64 | private static final String FRAMEWORK_COMMAND = "-defaultFramework="; 65 | enum Logging { 66 | ON, OFF 67 | } 68 | 69 | static String PRINTLN_SEPARATOR = StringUtils.repeat('-', 10); 70 | 71 | static Logging logging = Logging.OFF; 72 | static Endpoint.PrintFormat printFormat = Endpoint.PrintFormat.DYNAMIC; 73 | static FrameworkType defaultFramework = FrameworkType.DETECT; 74 | static boolean simplePrint = false; 75 | static String pathListFile = null; 76 | static String outputFilePath = null; 77 | static boolean cleanSourceCode = true; 78 | 79 | static int totalDetectedEndpoints = 0; 80 | static int totalDistinctEndpoints = 0; 81 | static int totalDetectedParameters = 0; 82 | static int totalDistinctParameters = 0; 83 | 84 | static int numProjectsWithDuplicates = 0; 85 | 86 | static String testUrlPath = null; 87 | static Credentials testCredentials = null; 88 | 89 | private static void println(String line) { 90 | if (printFormat != SIMPLE_JSON && printFormat != FULL_JSON) { 91 | System.out.println(line); 92 | } 93 | } 94 | 95 | public static void main(String[] args) { 96 | if (checkArguments(args)) { 97 | resetLoggingConfiguration(); 98 | List projectsMissingEndpoints = list(); 99 | int numProjectsWithEndpoints = 0; 100 | int numProjects = 0; 101 | 102 | List allEndpoints = list(); 103 | 104 | if (outputFilePath != null && !(printFormat == SIMPLE_JSON || printFormat == FULL_JSON)) { 105 | System.out.println("An output file path was specified but neither -json nor -simple-json flags were set, output file path will be ignored"); 106 | } 107 | 108 | if (pathListFile != null) { 109 | println("Loading path list file at '" + pathListFile + "'"); 110 | List fileContents; 111 | boolean isLongComment = false; 112 | 113 | try { 114 | fileContents = FileUtils.readLines(new File(pathListFile)); 115 | List requestedTargets = list(); 116 | int lineNo = 1; 117 | for (String line : fileContents) { 118 | line = line.trim(); 119 | if (line.startsWith("#!")) { 120 | isLongComment = true; 121 | } else if (line.startsWith("!#")) { 122 | isLongComment = false; 123 | } else if (!line.startsWith("#") && !line.isEmpty() && !isLongComment) { 124 | 125 | List compositeFrameworkTypes = list(); 126 | FrameworkType frameworkType = FrameworkType.DETECT; 127 | File asFile; 128 | if (line.contains(":") && !(new File(line)).exists()) { 129 | String[] parts = StringUtils.split(line, ":", 2); 130 | frameworkType = FrameworkType.getFrameworkType(parts[0].trim()); 131 | asFile = new File(parts[1].trim()); 132 | } else { 133 | asFile = new File(line); 134 | } 135 | 136 | if (!asFile.exists()) { 137 | println("WARN - Unable to find input path '" + line + "' at line " + lineNo + " of " + pathListFile); 138 | } else if (!asFile.isDirectory() && !isZipFile(asFile.getAbsolutePath())) { 139 | println("WARN - Input path '" + line + "' is not a directory or ZIP, at line " + lineNo + " of " + pathListFile); 140 | } else { 141 | if (frameworkType == FrameworkType.NONE) { 142 | println("WARN: Couldn't parse framework type: '" + frameworkType + "', for '" + asFile.getName() + "' using DETECT"); 143 | } 144 | 145 | if (frameworkType == FrameworkType.DETECT) { 146 | compositeFrameworkTypes = FrameworkCalculator.getTypes(asFile); 147 | } 148 | 149 | EndpointJob newJob = new EndpointJob(); 150 | if (compositeFrameworkTypes.isEmpty()) { 151 | compositeFrameworkTypes.add(frameworkType); 152 | } 153 | newJob.frameworkTypes = compositeFrameworkTypes; 154 | newJob.sourceCodePath = asFile; 155 | requestedTargets.add(newJob); 156 | } 157 | } 158 | ++lineNo; 159 | } 160 | 161 | numProjects = requestedTargets.size(); 162 | 163 | boolean isFirst = true; 164 | for (EndpointJob job : requestedTargets) { 165 | if (isFirst) { 166 | println(PRINTLN_SEPARATOR); 167 | isFirst = false; 168 | } 169 | println("Beginning endpoint detection for '" + job.sourceCodePath.getAbsolutePath() + "' with " + job.frameworkTypes.size() + " framework types"); 170 | for (FrameworkType subType : job.frameworkTypes) { 171 | println("Using framework=" + subType); 172 | } 173 | List generatedEndpoints = listEndpoints(job.sourceCodePath, job.frameworkTypes); 174 | println("Finished endpoint detection for '" + job.sourceCodePath.getAbsolutePath() + "'"); 175 | println(PRINTLN_SEPARATOR); 176 | 177 | if (!generatedEndpoints.isEmpty()) { 178 | ++numProjectsWithEndpoints; 179 | 180 | if (printFormat == SIMPLE_JSON || printFormat == FULL_JSON) { 181 | allEndpoints.addAll(generatedEndpoints); 182 | } 183 | } else { 184 | projectsMissingEndpoints.add(job.sourceCodePath.getAbsolutePath()); 185 | } 186 | } 187 | 188 | } catch (IOException e) { 189 | e.printStackTrace(); 190 | println("Unable to read path-list at " + pathListFile); 191 | printError(); 192 | } 193 | } else { 194 | ++numProjects; 195 | 196 | File rootFolder = new File(args[0]); 197 | 198 | List compositeFrameworkTypes = list(); 199 | if (defaultFramework == FrameworkType.DETECT) { 200 | compositeFrameworkTypes.addAll(FrameworkCalculator.getTypes(rootFolder)); 201 | } else { 202 | compositeFrameworkTypes.add(defaultFramework); 203 | } 204 | 205 | println("Beginning endpoint detection for '" + rootFolder.getAbsolutePath() + "' with " + compositeFrameworkTypes.size() + " framework types"); 206 | for (FrameworkType subType : compositeFrameworkTypes) { 207 | println("Using framework=" + subType); 208 | } 209 | 210 | Collection newEndpoints = listEndpoints(rootFolder, compositeFrameworkTypes); 211 | 212 | println("Finished endpoint detection for '" + rootFolder.getAbsolutePath() + "'"); 213 | println(PRINTLN_SEPARATOR); 214 | 215 | if (!newEndpoints.isEmpty()) { 216 | ++numProjectsWithEndpoints; 217 | if (printFormat == SIMPLE_JSON || printFormat == FULL_JSON) { 218 | allEndpoints.addAll(newEndpoints); 219 | } 220 | } else { 221 | projectsMissingEndpoints.add(rootFolder.getAbsolutePath()); 222 | } 223 | } 224 | 225 | if (!simplePrint) { 226 | if (printFormat == SIMPLE_JSON) { 227 | Endpoint.Info[] infos = getEndpointInfo(allEndpoints); 228 | 229 | try { 230 | String s = new ObjectMapper().writeValueAsString(infos); 231 | System.out.println(s); 232 | 233 | if (outputFilePath != null) { 234 | FileUtils.writeStringToFile(new File(outputFilePath), s); 235 | } 236 | } catch (IOException e) { 237 | throw new RuntimeException(e); 238 | } 239 | } else if (printFormat == FULL_JSON) { 240 | try { 241 | String s = EndpointSerialization.serializeAll(allEndpoints); 242 | System.out.println(s); 243 | 244 | if (outputFilePath != null) { 245 | FileUtils.writeStringToFile(new File(outputFilePath), s); 246 | } 247 | } catch (IOException e) { 248 | throw new RuntimeException(e); 249 | } 250 | } 251 | } 252 | 253 | 254 | 255 | println("-- DONE --"); 256 | 257 | println(numProjectsWithDuplicates + " projects had duplicate endpoints"); 258 | 259 | println("Generated " + totalDistinctEndpoints + " distinct endpoints"); 260 | println("Generated " + totalDetectedEndpoints + " total endpoints"); 261 | println("Generated " + totalDistinctParameters + " distinct parameters"); 262 | println("Generated " + totalDetectedParameters + " total parameters"); 263 | println(numProjectsWithEndpoints + "/" + numProjects + " projects had endpoints generated"); 264 | if (!projectsMissingEndpoints.isEmpty()) { 265 | println("The following projects were missing endpoints:"); 266 | for (String path : projectsMissingEndpoints) { 267 | println("--- " + path); 268 | } 269 | } 270 | 271 | if (printFormat != SIMPLE_JSON && printFormat != FULL_JSON) { 272 | println("To enable logging include the -debug argument"); 273 | } 274 | } else { 275 | printError(); 276 | } 277 | } 278 | 279 | private static boolean isZipFile(String filePath) { 280 | String ext = FilenameUtils.getExtension(filePath).toLowerCase(); 281 | return 282 | ext.equals("zip") || 283 | ext.equals("war"); 284 | } 285 | 286 | private static boolean checkArguments(String[] args) { 287 | if (args.length == 0) { 288 | return false; 289 | } 290 | 291 | for (String arg : args) { 292 | if (arg.equals("-help")) { 293 | printHelp(); 294 | System.exit(0); 295 | } 296 | } 297 | 298 | File rootFile = new File(args[0]); 299 | 300 | if (rootFile.exists() && rootFile.isDirectory() || args[0].startsWith("-path-list-file")) { 301 | 302 | List arguments = list(args); 303 | 304 | if (rootFile.exists()) { 305 | arguments.remove(0); 306 | } 307 | 308 | for (String arg : arguments) { 309 | if (arg.equals("-debug")) { 310 | logging = Logging.ON; 311 | } else if (arg.equals("-lint")) { 312 | printFormat = Endpoint.PrintFormat.LINT; 313 | } else if (arg.equals("-json")) { 314 | printFormat = SIMPLE_JSON; 315 | } else if (arg.equals("-full-json")) { 316 | printFormat = FULL_JSON; 317 | } else if (arg.contains(FRAMEWORK_COMMAND)) { 318 | String frameworkName = arg.substring(arg.indexOf( 319 | FRAMEWORK_COMMAND) + FRAMEWORK_COMMAND.length(), arg.length()); 320 | defaultFramework = FrameworkType.getFrameworkType(frameworkName); 321 | } else if (arg.contains("-keep-source")) { 322 | cleanSourceCode = false; 323 | } else if (arg.equals("-simple")) { 324 | simplePrint = true; 325 | } else if (arg.startsWith("-output-file=")) { 326 | String[] parts = arg.split("="); 327 | String path = parts[1]; 328 | File outputFile = new File(path).getAbsoluteFile(); 329 | File parentDirectory = outputFile.getParentFile(); 330 | if (parentDirectory.isDirectory()) { 331 | parentDirectory.mkdirs(); 332 | } 333 | outputFilePath = outputFile.getAbsolutePath(); 334 | println("Writing output to file at: \"" + outputFilePath + "\""); 335 | } else if (arg.startsWith("-path-list-file=")) { 336 | String[] parts = arg.split("="); 337 | String path = parts[1]; 338 | if (path == null || path.isEmpty()) { 339 | println("Invalid -path-list-file argument, value is empty"); 340 | continue; 341 | } 342 | if (path.startsWith("\"") || path.startsWith("'")) { 343 | path = path.substring(1); 344 | } 345 | if (path.endsWith("\"") || path.endsWith("'")) { 346 | path = path.substring(0, path.length() - 1); 347 | } 348 | pathListFile = path; 349 | } else if (arg.startsWith("-validation-server=")) { 350 | String[] parts = arg.split("="); 351 | testUrlPath = parts[1]; 352 | } else if (arg.startsWith("-validation-server-auth=")) { 353 | arg = arg.substring("-validation-server-auth=".length()); 354 | String[] parts = arg.split(";"); 355 | testCredentials = new Credentials(); 356 | testCredentials.parameters = map(); 357 | 358 | for (String part : parts) { 359 | if (testCredentials.authenticationEndpoint == null) { 360 | testCredentials.authenticationEndpoint = part; 361 | } else { 362 | String[] paramParts = part.split("="); 363 | if (paramParts.length != 2) { 364 | println("Invalid authentication parameter format: " + part); 365 | } else { 366 | testCredentials.parameters.put(paramParts[0], paramParts[1]); 367 | } 368 | } 369 | } 370 | } else { 371 | println("Received unsupported option " + arg + ", run with -help to see available flags."); 372 | return false; 373 | } 374 | } 375 | 376 | return true; 377 | 378 | } else { 379 | println("Please enter a valid file path as the first parameter."); 380 | } 381 | 382 | return false; 383 | } 384 | 385 | static void printError() { 386 | println("The first argument should be a valid file path to scan. Run with -help to see available flags and usage."); 387 | } 388 | 389 | static void printHelp() { 390 | InputStream helpFileStream = EndpointMain.class.getResourceAsStream("/help.txt"); 391 | try { 392 | String helpInfo = IOUtils.toString(helpFileStream); 393 | println(helpInfo); 394 | } catch (IOException e) { 395 | e.printStackTrace(); 396 | } 397 | } 398 | 399 | private static int printEndpointWithVariants(int i, int currentDepth, Endpoint endpoint) { 400 | 401 | int numPrinted = 1; 402 | 403 | StringBuilder line = new StringBuilder(); 404 | 405 | line.append('['); 406 | line.append(i); 407 | line.append("] "); 408 | 409 | for (int s = 0; s < currentDepth * 2; s++) { 410 | line.append('-'); 411 | } 412 | if (currentDepth > 0) { 413 | line.append(' '); 414 | } 415 | 416 | line.append(endpoint.getHttpMethod()); 417 | line.append(": "); 418 | line.append(endpoint.getUrlPath()); 419 | 420 | line.append(" ("); 421 | line.append(endpoint.getVariants().size()); 422 | line.append(" variants): PARAMETERS="); 423 | line.append(endpoint.getParameters()); 424 | 425 | line.append("; FILE="); 426 | line.append(endpoint.getFilePath()); 427 | 428 | line.append(" (lines '"); 429 | line.append(endpoint.getStartingLineNumber()); 430 | line.append("'-'"); 431 | line.append(endpoint.getEndingLineNumber()); 432 | line.append("')"); 433 | 434 | println(line.toString()); 435 | 436 | for (Endpoint variant : endpoint.getVariants()) { 437 | numPrinted += printEndpointWithVariants(i + numPrinted, currentDepth + 1, variant); 438 | } 439 | 440 | return numPrinted; 441 | } 442 | 443 | private static List listEndpoints(File rootFile, Collection frameworkTypes) { 444 | List endpoints = list(); 445 | 446 | File sourceRootFile = rootFile; 447 | TemporaryExtractionLocation zipExtractor = null; 448 | if (TemporaryExtractionLocation.isArchive(rootFile.getAbsolutePath())) { 449 | zipExtractor = new TemporaryExtractionLocation(rootFile.getAbsolutePath()); 450 | zipExtractor.extract(); 451 | 452 | sourceRootFile = zipExtractor.getOutputPath(); 453 | } 454 | 455 | if (frameworkTypes.size() == 1 && frameworkTypes.iterator().next() == FrameworkType.DETECT) { 456 | frameworkTypes.addAll(FrameworkCalculator.getTypes(rootFile)); 457 | } 458 | 459 | List databases = list(); 460 | for (FrameworkType frameworkType : frameworkTypes) { 461 | EndpointDatabase database = EndpointDatabaseFactory.getDatabase(sourceRootFile, frameworkType); 462 | if (database != null) { 463 | databases.add(database); 464 | } else { 465 | println("EndpointDatabaseFactory.getDatabase returned null for framework type " + frameworkType); 466 | } 467 | } 468 | 469 | for (EndpointDatabase db : databases) { 470 | endpoints.addAll(db.generateEndpoints()); 471 | } 472 | 473 | // Don't do any validation if we're just writing JSON without any output 474 | if (printFormat == FULL_JSON || printFormat == SIMPLE_JSON) { 475 | return endpoints; 476 | } 477 | 478 | List allEndpoints = EndpointUtil.flattenWithVariants(endpoints); 479 | 480 | int numPrimaryEndpoints = endpoints.size(); 481 | int numEndpoints = allEndpoints.size(); 482 | 483 | totalDetectedEndpoints += numEndpoints; 484 | totalDistinctEndpoints += numPrimaryEndpoints; 485 | 486 | if (!simplePrint) { 487 | int i = 0; 488 | for (Endpoint endpoint : endpoints) { 489 | printEndpointWithVariants(i++, 0, endpoint); 490 | } 491 | } 492 | 493 | if (endpoints.isEmpty()) { 494 | println("No endpoints were found."); 495 | 496 | } else { 497 | println("Generated " + numPrimaryEndpoints + 498 | " distinct endpoints with " + 499 | (numEndpoints - numPrimaryEndpoints) + 500 | " variants for a total of " + numEndpoints + 501 | " endpoints"); 502 | } 503 | 504 | if (EndpointValidation.validateSerialization(sourceRootFile, endpoints)) { 505 | println("Successfully validated serialization for these endpoints"); 506 | } else { 507 | println("Failed to validate serialization for at least one of these endpoints"); 508 | } 509 | 510 | if (!EndpointValidation.validateDuplicates(endpoints)) { 511 | numProjectsWithDuplicates++; 512 | } 513 | 514 | // Run endpoint testing against a given server 515 | if (testUrlPath != null) { 516 | EndpointTester tester = new EndpointTester(testUrlPath); 517 | 518 | println("Testing endpoints against server at: " + testUrlPath); 519 | 520 | if (testCredentials != null) { 521 | try { 522 | if (tester.authorize(testCredentials, null) < 400) { 523 | println("Successfully authenticated"); 524 | } 525 | } catch (IOException e) { 526 | println("Warning - unable to authorize against server"); 527 | } 528 | } 529 | 530 | List successfulEndpoints = list(); 531 | List failedEndpoints = list(); 532 | for (Endpoint endpoint : allEndpoints) { 533 | boolean skip = false; 534 | for (EndpointPathNode node : endpoint.getUrlPathNodes()) { 535 | if (node.getClass().equals(WildcardEndpointPathNode.class)) { 536 | skip = true; 537 | break; 538 | } 539 | } 540 | 541 | if (skip) { 542 | continue; 543 | } 544 | 545 | try { 546 | int responseCode = tester.test(endpoint, testCredentials); 547 | if (responseCode != 404) { 548 | successfulEndpoints.add(endpoint); 549 | } else { 550 | failedEndpoints.add(endpoint); 551 | } 552 | } catch (IOException e) { 553 | // Any non-404 error is considered "successful", since any other 4xx or 5xx may indicate 554 | // that the endpoint exists but incorrect parameters were provided 555 | if (e.getMessage().contains("Server returned HTTP response code") && !e.getMessage().contains("code: 404")) { 556 | successfulEndpoints.add(endpoint); 557 | } else { 558 | failedEndpoints.add(endpoint); 559 | } 560 | } 561 | } 562 | 563 | for (Endpoint endpoint : failedEndpoints) { 564 | println("Failed: " + endpoint.getUrlPath() + "[" + endpoint.getHttpMethod() + "]"); 565 | } 566 | 567 | println(successfulEndpoints.size() + "/" + (successfulEndpoints.size() + failedEndpoints.size()) + " endpoints were queryable"); 568 | println("(" + (allEndpoints.size() - successfulEndpoints.size() - failedEndpoints.size()) + " endpoints skipped since they had a wildcard in the URL)"); 569 | } 570 | 571 | int numMissingStartLine = 0; 572 | int numMissingEndLine = 0; 573 | int numSameLineRange = 0; 574 | for (Endpoint endpoint : EndpointUtil.flattenWithVariants(endpoints)) { 575 | if (endpoint.getStartingLineNumber() < 0) { 576 | numMissingStartLine++; 577 | } 578 | if (endpoint.getEndingLineNumber() < 0) { 579 | numMissingEndLine++; 580 | } 581 | if (endpoint.getStartingLineNumber() >= 0 && endpoint.getStartingLineNumber() == endpoint.getEndingLineNumber()) { 582 | numSameLineRange++; 583 | } 584 | } 585 | 586 | println(numMissingStartLine + " endpoints were missing code start line"); 587 | println(numMissingEndLine + " endpoints were missing code end line"); 588 | println(numSameLineRange + " endpoints had the same code start and end line"); 589 | 590 | List distinctParameters = list(); 591 | for (Endpoint endpoint : endpoints) { 592 | distinctParameters.addAll(endpoint.getParameters().values()); 593 | } 594 | 595 | int numTotalParameters = 0; 596 | for (Endpoint endpoint : allEndpoints) { 597 | numTotalParameters += endpoint.getParameters().size(); 598 | } 599 | 600 | totalDistinctParameters += distinctParameters.size(); 601 | totalDetectedParameters += numTotalParameters; 602 | 603 | println("Generated " + distinctParameters.size() + " distinct parameters"); 604 | println("Generated " + numTotalParameters + " total parameters"); 605 | 606 | Map typeOccurrences = map(); 607 | int numHaveDataType = 0; 608 | int numHaveParamType = 0; 609 | int numHaveAcceptedValues = 0; 610 | for (RouteParameter param : distinctParameters) { 611 | if (param.getDataType() != null) { 612 | ++numHaveDataType; 613 | } 614 | if (param.getParamType() != RouteParameterType.UNKNOWN) { 615 | ++numHaveParamType; 616 | } 617 | if (param.getAcceptedValues() != null && param.getAcceptedValues().size() > 0) { 618 | ++numHaveAcceptedValues; 619 | } 620 | 621 | if (!typeOccurrences.containsKey(param.getParamType())) { 622 | typeOccurrences.put(param.getParamType(), 1); 623 | } else { 624 | int o = typeOccurrences.get(param.getParamType()); 625 | typeOccurrences.put(param.getParamType(), o + 1); 626 | } 627 | } 628 | 629 | int numParams = distinctParameters.size(); 630 | println("- " + numHaveDataType + "/" + numParams + " have their data type"); 631 | println("- " + numHaveAcceptedValues + "/" + numParams + " have a list of accepted values"); 632 | println("- " + numHaveParamType + "/" + numParams + " have their parameter type"); 633 | for (RouteParameterType paramType : typeOccurrences.keySet()) { 634 | println("--- " + paramType.name() + ": " + typeOccurrences.get(paramType)); 635 | } 636 | 637 | if (zipExtractor != null) { 638 | zipExtractor.release(); 639 | } 640 | 641 | return endpoints; 642 | } 643 | 644 | private static Endpoint.Info[] getEndpointInfo(List endpoints) { 645 | List allEndpoints = EndpointUtil.flattenWithVariants(endpoints); 646 | Endpoint.Info[] endpointsInfos = new Endpoint.Info[allEndpoints.size()]; 647 | 648 | for (int i = 0; i < allEndpoints.size(); i++) { 649 | endpointsInfos[i] = Endpoint.Info.fromEndpoint(allEndpoints.get(i), !cleanSourceCode); 650 | } 651 | 652 | return endpointsInfos; 653 | } 654 | 655 | private static void resetLoggingConfiguration() { 656 | LoggerContext ctx = (LoggerContext) LogManager.getContext(false); 657 | Configuration config = ctx.getConfiguration(); 658 | LoggerConfig loggerConfig = config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME); 659 | 660 | if (logging == Logging.ON) { 661 | loggerConfig.setLevel(Level.DEBUG); 662 | } else { 663 | loggerConfig.setLevel(Level.ERROR); 664 | } 665 | 666 | ctx.updateLoggers(); 667 | } 668 | } --------------------------------------------------------------------------------