From fc3f61b90491084ffa26cd108f5ffc5eed6d6940 Mon Sep 17 00:00:00 2001 From: Theauditor <228822721+TheAuditorTool@users.noreply.github.com> Date: Mon, 13 Apr 2026 02:43:41 +0700 Subject: [PATCH 1/2] feat: add v2 crawlers with configurable timeout and CLI execution support Closes #3 (Add Timeout to Crawlers) Closes #1 (Add new command line crawler and verification crawler) New files (zero changes to existing code): - CommandLineTestCaseRequest.java: CLI test case support (tcType="CLI") - BenchmarkCrawler_newv2.java: configurable -T/--timeout, CLI dispatch - BenchmarkCrawlerVerification_newv2.java: verification with timeout + CLI --- .../BenchmarkCrawlerVerification_newv2.java | 580 ++++++++++++++++++ .../tools/BenchmarkCrawler_newv2.java | 377 ++++++++++++ .../tools/CommandLineTestCaseRequest.java | 135 ++++ .../tools/CommandLineTestCaseRequestTest.java | 115 ++++ 4 files changed, 1207 insertions(+) create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification_newv2.java create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler_newv2.java create mode 100644 plugin/src/main/java/org/owasp/benchmarkutils/tools/CommandLineTestCaseRequest.java create mode 100644 plugin/src/test/java/org/owasp/benchmarkutils/tools/CommandLineTestCaseRequestTest.java diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification_newv2.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification_newv2.java new file mode 100644 index 00000000..a62519e3 --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification_newv2.java @@ -0,0 +1,580 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + */ +package org.owasp.benchmarkutils.tools; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.owasp.benchmarkutils.helpers.TestSuite; +import org.owasp.benchmarkutils.helpers.Utils; + +/** + * V2 verification crawler that extends {@link BenchmarkCrawler_newv2} to send both attack and safe + * requests per test case and verify whether the exploit succeeded. + * + *

Inherits the configurable timeout ({@code -T}) and CLI execution support from the parent. Also + * supports the {@code -t} timing-threshold flag from the original {@link + * BenchmarkCrawlerVerification}. + * + *

Resolves GitHub issues #3 (timeout) and #1 (command-line crawler/verification). + */ +@Mojo( + name = "run-verification-crawler-v2", + requiresProject = false, + defaultPhase = LifecyclePhase.COMPILE) +public class BenchmarkCrawlerVerification_newv2 extends BenchmarkCrawler_newv2 { + + private static int maxTimeInSeconds = 2; + private static boolean isTimingEnabled = false; + + private static final String FILENAME_TIMES_ALL = "crawlerTimes.txt"; + private static final String FILENAME_TIMES = "crawlerSlowTimes.txt"; + private static final String FILENAME_NON_DISCRIMINATORY_LOG = "nonDiscriminatoryTestCases.txt"; + private static final String FILENAME_ERRORS_LOG = "errorTestCases.txt"; + private static final String FILENAME_UNVERIFIABLE_LOG = "unverifiableTestCases.txt"; + + private static String CRAWLER_DATA_DIR = Utils.DATA_DIR; + + SimpleFileLogger tLogger; + SimpleFileLogger ndLogger; + SimpleFileLogger eLogger; + SimpleFileLogger uLogger; + + @Override + protected void crawl(TestSuite testSuite) throws Exception { + CloseableHttpClient httpclient = createHttpClient(); + long start = System.currentTimeMillis(); + List responseInfoList = new ArrayList<>(); + List httpResults = new ArrayList<>(); + List cliResults = new ArrayList<>(); + + final File FILE_NON_DISCRIMINATORY_LOG = + new File(CRAWLER_DATA_DIR, FILENAME_NON_DISCRIMINATORY_LOG); + final File FILE_ERRORS_LOG = new File(CRAWLER_DATA_DIR, FILENAME_ERRORS_LOG); + final File FILE_TIMES_LOG; + if (isTimingEnabled) { + FILE_TIMES_LOG = new File(CRAWLER_DATA_DIR, FILENAME_TIMES); + } else { + FILE_TIMES_LOG = new File(CRAWLER_DATA_DIR, FILENAME_TIMES_ALL); + } + final File FILE_UNVERIFIABLE_LOG = new File(CRAWLER_DATA_DIR, FILENAME_UNVERIFIABLE_LOG); + SimpleFileLogger.setFile("TIMES", FILE_TIMES_LOG); + SimpleFileLogger.setFile("NONDISCRIMINATORY", FILE_NON_DISCRIMINATORY_LOG); + SimpleFileLogger.setFile("ERRORS", FILE_ERRORS_LOG); + SimpleFileLogger.setFile("UNVERIFIABLE", FILE_UNVERIFIABLE_LOG); + + String completionMessage = null; + + try (SimpleFileLogger nl = SimpleFileLogger.getLogger("NONDISCRIMINATORY"); + SimpleFileLogger el = SimpleFileLogger.getLogger("ERRORS"); + SimpleFileLogger ul = SimpleFileLogger.getLogger("UNVERIFIABLE"); + SimpleFileLogger tl = SimpleFileLogger.getLogger("TIMES")) { + + ndLogger = nl; + eLogger = el; + uLogger = ul; + tLogger = tl; + + for (AbstractTestCaseRequest requestTemplate : testSuite.getTestCases()) { + boolean isCli = requestTemplate instanceof CommandLineTestCaseRequest; + + ResponseInfo attackPayloadResponseInfo; + ResponseInfo safePayloadResponseInfo = null; + HttpUriRequest attackRequest = null; + HttpUriRequest safeRequest = null; + + if (isCli) { + CommandLineTestCaseRequest cliReq = + (CommandLineTestCaseRequest) requestTemplate; + List attackCmd = cliReq.buildCommand(false); + attackPayloadResponseInfo = + executeCommand(attackCmd, cliReq.getCommandDir(), networkTimeoutSeconds); + responseInfoList.add(attackPayloadResponseInfo); + logResponse(attackPayloadResponseInfo, "CMD " + String.join(" ", attackCmd)); + + if (!requestTemplate.isUnverifiable()) { + List safeCmd = cliReq.buildCommand(true); + safePayloadResponseInfo = + executeCommand( + safeCmd, cliReq.getCommandDir(), networkTimeoutSeconds); + responseInfoList.add(safePayloadResponseInfo); + logResponse(safePayloadResponseInfo, "CMD " + String.join(" ", safeCmd)); + } + } else { + attackRequest = requestTemplate.buildAttackRequest(); + safeRequest = requestTemplate.buildSafeRequest(); + + attackPayloadResponseInfo = sendRequest(httpclient, attackRequest); + responseInfoList.add(attackPayloadResponseInfo); + logResponse(attackPayloadResponseInfo, attackRequest); + + if (!requestTemplate.isUnverifiable()) { + safePayloadResponseInfo = sendRequest(httpclient, safeRequest); + responseInfoList.add(safePayloadResponseInfo); + logResponse(safePayloadResponseInfo, safeRequest); + } + } + + TestCaseVerificationResults result = + new TestCaseVerificationResults( + attackRequest, + safeRequest, + requestTemplate, + attackPayloadResponseInfo, + safePayloadResponseInfo); + + if (RegressionTesting.isTestingEnabled) { + if (isCli) { + verifyCliTestCase(result); + cliResults.add(result); + } else { + handleResponse(result); + httpResults.add(result); + } + } else { + if (isCli) cliResults.add(result); + else httpResults.add(result); + } + } + + long stop = System.currentTimeMillis(); + int seconds = (int) (stop - start) / 1000; + Date now = new Date(); + + completionMessage = + String.format( + "Verification crawl ran on %tF % allResults = new ArrayList<>(httpResults); + allResults.addAll(cliResults); + + // genFailedTCFile calls printTestCaseDetails which calls printHttpRequest — + // that NPEs on null HttpUriRequest. So we only pass HTTP results to it. + // Then we supplement the static counters it sets with CLI results so + // printCrawlSummary reports accurate totals. + RegressionTesting.genFailedTCFile(httpResults, CRAWLER_DATA_DIR); + supplementCountsWithCliResults(cliResults); + + printCliFailures(cliResults); + + if (!RegressionTesting.failedTruePositivesList.isEmpty() + || !RegressionTesting.failedFalsePositivesList.isEmpty()) { + eLogger.println(); + eLogger.println("== Errors report =="); + eLogger.println(); + } + + if (!RegressionTesting.failedTruePositivesList.isEmpty()) { + eLogger.printf( + "== True Positive Test Cases with Errors [%d of %d] ==%n", + RegressionTesting.failedTruePositives, + RegressionTesting.truePositives); + eLogger.println(); + for (AbstractTestCaseRequest request : + RegressionTesting.failedTruePositivesList.keySet()) { + eLogger.printf( + "%s: %s%n", + request.getName(), + RegressionTesting.failedTruePositivesList.get(request)); + } + } + + if (!RegressionTesting.failedFalsePositivesList.isEmpty()) { + if (!RegressionTesting.failedTruePositivesList.isEmpty()) { + eLogger.println(); + } + eLogger.printf( + "== False Positive Test Cases with Errors [%d of %d] ==%n", + RegressionTesting.failedFalsePositives, + RegressionTesting.falsePositives); + eLogger.println(); + for (AbstractTestCaseRequest request : + RegressionTesting.failedFalsePositivesList.keySet()) { + eLogger.printf( + "%s: %s%n", + request.getName(), + RegressionTesting.failedFalsePositivesList.get(request)); + } + } + } + } + + if (FILE_NON_DISCRIMINATORY_LOG.length() > 0) { + System.out.printf( + "Details of non-discriminatory test cases written to: %s%n", + FILE_NON_DISCRIMINATORY_LOG); + } + if (FILE_ERRORS_LOG.length() > 0) { + System.out.printf( + "Details of errors/exceptions in test cases written to: %s%n", FILE_ERRORS_LOG); + } + if (FILE_UNVERIFIABLE_LOG.length() > 0) { + System.out.printf( + "Details of unverifiable test cases written to: %s%n", FILE_UNVERIFIABLE_LOG); + } + System.out.printf("Test case time measurements written to: %s%n", FILE_TIMES_LOG); + + List allResults = new ArrayList<>(httpResults); + allResults.addAll(cliResults); + RegressionTesting.printCrawlSummary(allResults); + System.out.println(); + System.out.println(completionMessage); + } + + /** + * Verify a CLI test case using {@link RegressionTesting#verifyResponse} directly. We avoid + * calling {@link RegressionTesting#verifyTestCase} because it delegates to {@code + * printTestCaseDetails} which calls {@code printHttpRequest} on the attackRequest/safeRequest + * fields — those are null for CLI test cases. + */ + private void verifyCliTestCase(TestCaseVerificationResults result) + throws FileNotFoundException, LoggerConfigurationException { + + AbstractTestCaseRequest requestTemplate = result.getRequestTemplate(); + + result.setUnverifiable(false); + result.setDeclaredUnverifiable(false); + + if (requestTemplate.isUnverifiable()) { + result.setUnverifiable(true); + result.setDeclaredUnverifiable(true); + uLogger.printf("UNVERIFIABLE (declared CLI): %s%n", requestTemplate.getName()); + } else if (requestTemplate.getAttackSuccessString() == null) { + result.setUnverifiable(true); + result.setDeclaredUnverifiable(false); + uLogger.printf("UNVERIFIABLE (undeclared CLI): %s%n", requestTemplate.getName()); + } + + if (!result.isUnverifiable()) { + boolean isAttackValueVerified = + RegressionTesting.verifyResponse( + result.getResponseToAttackValue().getResponseString(), + requestTemplate.getAttackSuccessString(), + requestTemplate.getAttackSuccessStringPresent()); + + boolean isSafeValueVerified = false; + if (result.getResponseToSafeValue() != null) { + isSafeValueVerified = + RegressionTesting.verifyResponse( + result.getResponseToSafeValue().getResponseString(), + requestTemplate.getAttackSuccessString(), + requestTemplate.getAttackSuccessStringPresent()); + } + + if (requestTemplate.isVulnerability()) { + if (isAttackValueVerified) { + result.setPassed(true); + if (isSafeValueVerified) { + ndLogger.printf( + "Non-discriminatory true positive CLI test %s: " + + "attack-success-string found in both safe and attack " + + "responses.%n", + requestTemplate.getName()); + } + } else { + result.setPassed(false); + } + } else { + if (isAttackValueVerified) { + result.setPassed(false); + } else { + result.setPassed(true); + if (isSafeValueVerified) { + ndLogger.printf( + "Non-discriminatory false positive CLI test %s: " + + "attack-success-string found in safe response.%n", + requestTemplate.getName()); + } + } + } + } + + // Error detection (mirrors RegressionTesting.findErrors) + List reasons = new ArrayList<>(); + findCliErrors(result.getResponseToAttackValue(), "Attack value", reasons); + findCliErrors(result.getResponseToSafeValue(), "Safe value", reasons); + boolean hasErrors = !reasons.isEmpty(); + String compositeReason = "\t- " + String.join(", ", reasons); + + if (requestTemplate.isVulnerability()) { + RegressionTesting.truePositives++; + if (hasErrors) { + RegressionTesting.failedTruePositives++; + RegressionTesting.failedTruePositivesList.put(requestTemplate, compositeReason); + } + } else { + RegressionTesting.falsePositives++; + if (hasErrors) { + RegressionTesting.failedFalsePositives++; + RegressionTesting.failedFalsePositivesList.put(requestTemplate, compositeReason); + } + } + } + + /** + * Supplement the static counters in {@link RegressionTesting} that {@code genFailedTCFile} sets + * (totalCount, passedCount, failedCount, etc.) with CLI test case results. This is necessary + * because we only pass HTTP results to {@code genFailedTCFile} to avoid NPEs. + */ + private static void supplementCountsWithCliResults( + List cliResults) { + for (TestCaseVerificationResults result : cliResults) { + RegressionTesting.totalCount++; + if (result.isUnverifiable()) { + if (result.isDeclaredUnverifiable()) { + RegressionTesting.declaredUnverifiable++; + } else { + RegressionTesting.undeclaredUnverifiable++; + } + } else { + RegressionTesting.verifiedCount++; + if (result.isPassed()) { + if (result.getRequestTemplate().isVulnerability()) { + RegressionTesting.truePositivePassedCount++; + } else { + RegressionTesting.falsePositivePassedCount++; + } + } else { + if (result.getRequestTemplate().isVulnerability()) { + RegressionTesting.truePositiveFailedCount++; + } else { + RegressionTesting.falsePositiveFailedCount++; + } + } + } + } + } + + private static void findCliErrors( + ResponseInfo responseInfo, String prefix, List reasons) { + if (responseInfo != null) { + if (responseInfo.getStatusCode() != 0) { + reasons.add(prefix + " exit code: " + responseInfo.getStatusCode()); + } + if (responseInfo.getResponseString().toLowerCase().contains("error")) { + reasons.add(prefix + " output contains: error"); + } else if (responseInfo.getResponseString().toLowerCase().contains("exception")) { + reasons.add(prefix + " output contains: exception"); + } + } + } + + private void printCliFailures(List cliResults) { + for (TestCaseVerificationResults result : cliResults) { + if (!result.isUnverifiable() && !result.isPassed()) { + AbstractTestCaseRequest req = result.getRequestTemplate(); + String msg = + String.format( + "FAILURE: %s positive %s CLI test %s%n", + req.isVulnerability() ? "True" : "False", + req.getCategory(), + req.getName()); + System.out.print(msg); + eLogger.print(msg); + + eLogger.printf( + " Attack output (exit %d): %s%n", + result.getResponseToAttackValue().getStatusCode(), + truncate(result.getResponseToAttackValue().getResponseString(), 500)); + if (result.getResponseToSafeValue() != null) { + eLogger.printf( + " Safe output (exit %d): %s%n", + result.getResponseToSafeValue().getStatusCode(), + truncate(result.getResponseToSafeValue().getResponseString(), 500)); + } + String negated = + req.getAttackSuccessStringPresent() ? "" : "Failure "; + eLogger.printf( + " Attack success %sindicator: -->%s<--%n", + negated, req.getAttackSuccessString()); + eLogger.printf("----------------------------------------------------------%n%n"); + } + } + } + + private static String truncate(String s, int maxLen) { + if (s == null) return "null"; + return s.length() <= maxLen ? s : s.substring(0, maxLen) + "... [truncated]"; + } + + private static void handleResponse(TestCaseVerificationResults result) + throws FileNotFoundException, LoggerConfigurationException { + RegressionTesting.verifyTestCase(result); + } + + private void logResponse(ResponseInfo responseInfo, HttpUriRequest request) throws IOException { + String outputString = + String.format( + "--> (%d : %d sec)%n", + responseInfo.getStatusCode(), responseInfo.getTimeInSeconds()); + try { + String requestLine = request.getMethod() + " " + request.getUri(); + if (isTimingEnabled) { + if (responseInfo.getTimeInSeconds() >= maxTimeInSeconds) { + tLogger.println(requestLine); + tLogger.println(outputString); + } + } else { + tLogger.println(requestLine); + tLogger.println(outputString); + } + } catch (URISyntaxException e) { + String errMsg = + request.getMethod() + " COULDN'T LOG URI due to URISyntaxException"; + tLogger.println(errMsg); + tLogger.println(outputString); + System.out.println(errMsg); + e.printStackTrace(); + } + } + + private void logResponse(ResponseInfo responseInfo, String commandDescription) { + String outputString = + String.format( + "--> (exit %d : %d sec)%n", + responseInfo.getStatusCode(), responseInfo.getTimeInSeconds()); + if (isTimingEnabled) { + if (responseInfo.getTimeInSeconds() >= maxTimeInSeconds) { + tLogger.println(commandDescription); + tLogger.println(outputString); + } + } else { + tLogger.println(commandDescription); + tLogger.println(outputString); + } + } + + @Override + protected void processCommandLineArgs(String[] args) { + File defaultAttackCrawlerFile = new File(Utils.DATA_DIR, "benchmark-attack-http.xml"); + if (defaultAttackCrawlerFile.exists()) { + setCrawlerFile(defaultAttackCrawlerFile.getPath()); + } + + RegressionTesting.isTestingEnabled = true; + + CommandLineParser parser = new DefaultParser(); + HelpFormatter formatter = new HelpFormatter(); + + Options options = new Options(); + options.addOption( + Option.builder("f") + .longOpt("file") + .desc("a TESTSUITE-attack-http.xml file") + .hasArg() + .required() + .build()); + options.addOption(Option.builder("h").longOpt("help").desc("Usage").build()); + options.addOption( + Option.builder("n") + .longOpt("name") + .desc("testcase name (e.g. BenchmarkTestCase00025)") + .hasArg() + .build()); + options.addOption( + Option.builder("t") + .longOpt("time") + .desc("testcase timing threshold (in seconds) for slow-request log") + .hasArg() + .type(Number.class) + .build()); + options.addOption( + Option.builder("T") + .longOpt("timeout") + .desc( + "Response timeout in seconds per request." + + " 0 = no timeout (default)." + + " Example: -T 300 for 5 minutes.") + .hasArg() + .type(Number.class) + .build()); + + try { + CommandLine line = parser.parse(options, args); + + if (line.hasOption("f")) { + setCrawlerFile(line.getOptionValue("f")); + CRAWLER_DATA_DIR = this.theCrawlerFile.getParent() + File.separator; + } + if (line.hasOption("h")) { + formatter.printHelp("BenchmarkCrawlerVerification_newv2", options, true); + } + if (line.hasOption("n")) { + selectedTestCaseName = line.getOptionValue("n"); + } + if (line.hasOption("t")) { + maxTimeInSeconds = ((Number) line.getParsedOptionValue("t")).intValue(); + isTimingEnabled = true; + } + if (line.hasOption("T")) { + networkTimeoutSeconds = + ((Number) line.getParsedOptionValue("T")).longValue(); + if (networkTimeoutSeconds < 0) { + System.out.println( + "WARNING: Negative timeout value ignored, using 0 (no timeout)."); + networkTimeoutSeconds = 0; + } + if (networkTimeoutSeconds > 0) { + System.out.printf( + "Response timeout set to %d seconds.%n", networkTimeoutSeconds); + } + } + } catch (ParseException e) { + formatter.printHelp("BenchmarkCrawlerVerification_newv2", options); + throw new RuntimeException("Error parsing arguments: ", e); + } + } + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + if (thisInstance == null) thisInstance = this; + + if (null == this.crawlerFile) { + System.out.println("ERROR: An attack crawlerFile parameter must be specified."); + } else { + String[] mainArgs = {"-f", this.crawlerFile}; + main(mainArgs); + } + } + + public static void main(String[] args) { + if (thisInstance == null) { + thisInstance = new BenchmarkCrawlerVerification_newv2(); + } + thisInstance.processCommandLineArgs(args); + thisInstance.load(); + thisInstance.run(); + } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler_newv2.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler_newv2.java new file mode 100644 index 00000000..9390e45d --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler_newv2.java @@ -0,0 +1,377 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + */ +package org.owasp.benchmarkutils.tools; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.concurrent.TimeUnit; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Unmarshaller; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.lang.time.StopWatch; +import org.apache.hc.client5.http.classic.methods.HttpUriRequest; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.client5.http.io.HttpClientConnectionManager; +import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.client5.http.ssl.TrustSelfSignedStrategy; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.ssl.SSLContextBuilder; +import org.apache.hc.core5.util.Timeout; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.eclipse.persistence.jaxb.JAXBContextFactory; +import org.owasp.benchmarkutils.helpers.Categories; +import org.owasp.benchmarkutils.helpers.TestSuite; +import org.owasp.benchmarkutils.score.BenchmarkScore; + +/** + * V2 crawler that adds two capabilities over {@link BenchmarkCrawler}: + * + *

    + *
  1. Configurable timeout ({@code -T / --timeout}): response timeout in seconds, disabled + * by default (0 = wait indefinitely). Resolves GitHub issue #3. + *
  2. Command-line test case execution: test cases with {@code tcType="CLI"} are executed + * as subprocesses via {@link ProcessBuilder} instead of HTTP. Resolves GitHub issue #1. + *
+ * + *

Existing HTTP test suites work identically — this is a drop-in replacement. + */ +@Mojo(name = "run-crawler-v2", requiresProject = false, defaultPhase = LifecyclePhase.COMPILE) +public class BenchmarkCrawler_newv2 extends BenchmarkCrawler { + + private static final long CONNECT_TIMEOUT_SECONDS = 30; + + /** + * Response timeout in seconds. 0 means disabled (wait indefinitely). Set via {@code -T} CLI + * flag. + */ + protected long networkTimeoutSeconds = 0; + + @Override + protected void crawl(TestSuite testSuite) throws Exception { + CloseableHttpClient httpclient = createHttpClient(); + long start = System.currentTimeMillis(); + + for (AbstractTestCaseRequest requestTemplate : testSuite.getTestCases()) { + if (requestTemplate instanceof CommandLineTestCaseRequest) { + CommandLineTestCaseRequest cliRequest = + (CommandLineTestCaseRequest) requestTemplate; + List command = cliRequest.buildCommand(true); + ResponseInfo responseInfo = + executeCommand(command, cliRequest.getCommandDir(), networkTimeoutSeconds); + logCommandResponse(command, responseInfo); + } else { + HttpUriRequest request = requestTemplate.buildSafeRequest(); + sendRequest(httpclient, request); + } + } + + long stop = System.currentTimeMillis(); + int seconds = (int) (stop - start) / 1000; + Date now = new Date(); + System.out.printf( + "Crawl ran on %tF %Connect and connection-request timeouts are always {@value #CONNECT_TIMEOUT_SECONDS} + * seconds. Response timeout is controlled by {@link #networkTimeoutSeconds}: 0 means no timeout + * (indefinite wait). + */ + protected CloseableHttpClient createHttpClient() + throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException { + + SSLContext sslContext = + SSLContextBuilder.create().loadTrustMaterial(new TrustSelfSignedStrategy()).build(); + + HostnameVerifier allowAllHosts = new NoopHostnameVerifier(); + SSLConnectionSocketFactory connectionFactory = + new SSLConnectionSocketFactory(sslContext, allowAllHosts); + + HttpClientConnectionManager cm = + PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(connectionFactory) + .build(); + + RequestConfig.Builder configBuilder = + RequestConfig.custom() + .setConnectTimeout(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .setConnectionRequestTimeout(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS); + + if (networkTimeoutSeconds > 0) { + configBuilder.setResponseTimeout(networkTimeoutSeconds, TimeUnit.SECONDS); + } else { + configBuilder.setResponseTimeout(Timeout.DISABLED); + } + + RequestConfig config = configBuilder.build(); + + HttpHost httpHost = null; + String pHost = System.getProperty("proxyHost"); + String pPort = System.getProperty("proxyPort"); + if (pHost != null && pPort != null) { + httpHost = new HttpHost(pHost, Integer.parseInt(pPort)); + return HttpClients.custom() + .setDefaultRequestConfig(config) + .setConnectionManager(cm) + .setProxy(httpHost) + .build(); + } else { + return HttpClients.custom() + .setDefaultRequestConfig(config) + .setConnectionManager(cm) + .build(); + } + } + + /** + * Execute a command-line test case as a subprocess. + * + * @param command the executable and its arguments. + * @param workingDir working directory (null = inherit from JVM). + * @param timeoutSeconds max seconds to wait for the process (0 = no limit). + * @return a {@link ResponseInfo} where responseString is stdout+stderr, statusCode is the exit + * code (-1 on timeout or error), and timeInSeconds is wall-clock elapsed time. + */ + protected static ResponseInfo executeCommand( + List command, String workingDir, long timeoutSeconds) { + + ResponseInfo responseInfo = new ResponseInfo(); + StopWatch watch = new StopWatch(); + + System.out.println("CMD " + String.join(" ", command)); + + ProcessBuilder pb = new ProcessBuilder(command); + pb.redirectErrorStream(true); + if (workingDir != null && !workingDir.trim().isEmpty()) { + pb.directory(new File(workingDir)); + } + + watch.start(); + try { + Process process = pb.start(); + String output = readStream(process.getInputStream()); + + boolean finished; + if (timeoutSeconds > 0) { + finished = process.waitFor(timeoutSeconds, TimeUnit.SECONDS); + if (!finished) { + process.destroyForcibly(); + output += "\n[TIMEOUT after " + timeoutSeconds + " seconds]"; + System.out.println("TIMEOUT: Process killed after " + timeoutSeconds + "s"); + } + } else { + process.waitFor(); + finished = true; + } + + responseInfo.setResponseString(output); + responseInfo.setStatusCode(finished ? process.exitValue() : -1); + } catch (IOException e) { + System.out.println("ERROR: Failed to execute command: " + e.getMessage()); + e.printStackTrace(); + responseInfo.setResponseString("ERROR: " + e.getMessage()); + responseInfo.setStatusCode(-1); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + responseInfo.setResponseString("INTERRUPTED"); + responseInfo.setStatusCode(-1); + } + watch.stop(); + + int seconds = (int) watch.getTime() / 1000; + responseInfo.setTimeInSeconds(seconds); + System.out.printf("--> (exit %d : %d sec)%n", responseInfo.getStatusCode(), seconds); + return responseInfo; + } + + private static String readStream(InputStream stream) throws IOException { + StringBuilder sb = new StringBuilder(); + try (BufferedReader reader = + new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + if (sb.length() > 0) sb.append('\n'); + sb.append(line); + } + } + return sb.toString(); + } + + private void logCommandResponse(List command, ResponseInfo responseInfo) { + // stdout logging already handled in executeCommand(); nothing extra needed for basic crawl. + } + + /** + * Load test suite from XML, using an extended JAXB context that recognizes {@code tcType="CLI"} + * test cases via {@link CommandLineTestCaseRequest}. + */ + @Override + void load() { + try { + InputStream categoriesFileStream = + BenchmarkScore.class + .getClassLoader() + .getResourceAsStream(Categories.FILENAME); + new Categories(categoriesFileStream); + + this.testSuite = parseHttpFileWithCliSupport(this.theCrawlerFile); + + Collections.sort( + this.testSuite.getTestCases(), AbstractTestCaseRequest.getNameComparator()); + + if (selectedTestCaseName != null) { + for (AbstractTestCaseRequest request : this.testSuite.getTestCases()) { + if (request.getName().equals(selectedTestCaseName)) { + java.util.List requests = + new java.util.ArrayList<>(); + requests.add(request); + this.testSuite = new TestSuite(); + this.testSuite.setTestCases(requests); + break; + } + } + } + } catch (Exception e) { + System.out.println( + "ERROR: Problem with specified crawler file: " + this.theCrawlerFile); + e.printStackTrace(); + System.exit(-1); + } + } + + /** + * Parse an XML crawler file using a JAXB context that includes {@link + * CommandLineTestCaseRequest} in addition to the standard HTTP request types. + */ + static TestSuite parseHttpFileWithCliSupport(File file) throws Exception { + JAXBContext context = + JAXBContextFactory.createContext( + new Class[] {TestSuite.class, CommandLineTestCaseRequest.class}, null); + Unmarshaller unmarshaller = context.createUnmarshaller(); + unmarshaller.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler()); + return (TestSuite) unmarshaller.unmarshal(new FileReader(file)); + } + + @Override + protected void processCommandLineArgs(String[] args) { + CommandLineParser parser = new DefaultParser(); + HelpFormatter formatter = new HelpFormatter(); + + Options options = new Options(); + options.addOption( + Option.builder("f") + .longOpt("file") + .desc("a TESTSUITE-crawler-http.xml file") + .hasArg() + .build()); + options.addOption( + Option.builder("n") + .longOpt("name") + .desc("testcase name (e.g. BenchmarkTestCase00025)") + .hasArg() + .build()); + options.addOption(Option.builder("h").longOpt("help").desc("Usage").build()); + options.addOption( + Option.builder("T") + .longOpt("timeout") + .desc( + "Response timeout in seconds per request." + + " 0 = no timeout (default)." + + " Example: -T 300 for 5 minutes.") + .hasArg() + .type(Number.class) + .build()); + + try { + CommandLine line = parser.parse(options, args); + + if (line.hasOption("f")) { + setCrawlerFile(line.getOptionValue("f")); + } + if (line.hasOption("h")) { + formatter.printHelp("BenchmarkCrawler_newv2", options, true); + } + if (line.hasOption("n")) { + selectedTestCaseName = line.getOptionValue("n"); + } + if (line.hasOption("T")) { + networkTimeoutSeconds = + ((Number) line.getParsedOptionValue("T")).longValue(); + if (networkTimeoutSeconds < 0) { + System.out.println( + "WARNING: Negative timeout value ignored, using 0 (no timeout)."); + networkTimeoutSeconds = 0; + } + if (networkTimeoutSeconds > 0) { + System.out.printf( + "Response timeout set to %d seconds.%n", networkTimeoutSeconds); + } + } + } catch (ParseException e) { + formatter.printHelp("BenchmarkCrawler_newv2", options); + throw new RuntimeException("Error parsing arguments: ", e); + } + } + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + if (thisInstance == null) thisInstance = this; + + if (null == this.crawlerFile) { + System.out.println("ERROR: A crawlerFile parameter must be specified."); + } else { + String[] mainArgs = {"-f", this.crawlerFile}; + main(mainArgs); + } + } + + public static void main(String[] args) { + if (thisInstance == null) { + thisInstance = new BenchmarkCrawler_newv2(); + } + thisInstance.processCommandLineArgs(args); + thisInstance.load(); + thisInstance.run(); + } +} diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/CommandLineTestCaseRequest.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/CommandLineTestCaseRequest.java new file mode 100644 index 00000000..c95e3c2e --- /dev/null +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/CommandLineTestCaseRequest.java @@ -0,0 +1,135 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + */ +package org.owasp.benchmarkutils.tools; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.xml.bind.annotation.XmlAttribute; +import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; +import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue; +import org.owasp.benchmarkutils.helpers.RequestVariable; + +/** + * A test case request that executes a command-line program instead of making an HTTP request. Used + * for benchmarking non-web applications (e.g., Python scripts, CLI tools). + * + *

In the test suite XML, use {@code tcType="CLI"} to select this request type: + * + *

{@code
+ * 
+ *     
+ * 
+ * }
+ * + *

The {@code formparam} elements reuse the existing {@link RequestVariable} attack/safe switching + * mechanism: their current values are appended as {@code --name value} arguments to the command. + */ +@XmlDiscriminatorValue("CLI") +public class CommandLineTestCaseRequest extends AbstractTestCaseRequest { + + private String command; + private String commandArgs; + private String commandDir; + + public CommandLineTestCaseRequest() {} + + @XmlAttribute(name = "tcCommand") + public String getCommand() { + return this.command; + } + + public void setCommand(String command) { + this.command = command; + } + + @XmlAttribute(name = "tcCommandArgs") + public String getCommandArgs() { + return this.commandArgs; + } + + public void setCommandArgs(String commandArgs) { + this.commandArgs = commandArgs; + } + + @XmlAttribute(name = "tcCommandDir") + public String getCommandDir() { + return this.commandDir; + } + + public void setCommandDir(String commandDir) { + this.commandDir = commandDir; + } + + /** + * Build the command line for execution. + * + *

Switches all {@link RequestVariable}s to safe or attack mode, then constructs the full + * argument list: the executable, any base arguments, and each form parameter as {@code --name + * value}. + * + * @param isSafe true for the safe (control) run, false for the attack run. + * @return the command and arguments as a list suitable for {@link ProcessBuilder}. + */ + public List buildCommand(boolean isSafe) { + setSafe(isSafe); + + List cmd = new ArrayList<>(); + cmd.add(command); + + if (commandArgs != null && !commandArgs.trim().isEmpty()) { + Collections.addAll(cmd, commandArgs.trim().split("\\s+")); + } + + for (RequestVariable param : getFormParams()) { + cmd.add("--" + param.getName()); + cmd.add(param.getValue()); + } + + return cmd; + } + + /** + * Returns an unmodifiable view of the command that would be executed. Useful for logging without + * side effects on the safe/attack state. + */ + public List getLastBuiltCommand(boolean isSafe) { + return Collections.unmodifiableList(buildCommand(isSafe)); + } + + // --- HTTP abstract method no-ops (required by AbstractTestCaseRequest) --- + + @Override + void buildBodyParameters(HttpUriRequestBase request) {} + + @Override + void buildCookies(HttpUriRequestBase request) {} + + @Override + void buildHeaders(HttpUriRequestBase request) {} + + @Override + void buildQueryString() { + setQuery(""); + } + + @Override + HttpUriRequestBase createRequestInstance(String URL) { + return null; + } +} diff --git a/plugin/src/test/java/org/owasp/benchmarkutils/tools/CommandLineTestCaseRequestTest.java b/plugin/src/test/java/org/owasp/benchmarkutils/tools/CommandLineTestCaseRequestTest.java new file mode 100644 index 00000000..e45ad02f --- /dev/null +++ b/plugin/src/test/java/org/owasp/benchmarkutils/tools/CommandLineTestCaseRequestTest.java @@ -0,0 +1,115 @@ +/** + * OWASP Benchmark Project + * + *

This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For + * details, please see https://owasp.org/www-project-benchmark/. + * + *

The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms + * of the GNU General Public License as published by the Free Software Foundation, version 2. + * + *

The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + */ +package org.owasp.benchmarkutils.tools; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.owasp.benchmarkutils.helpers.RequestVariable; + +public class CommandLineTestCaseRequestTest { + + @Test + void buildCommandWithNoArgs() { + CommandLineTestCaseRequest req = new CommandLineTestCaseRequest(); + req.setCommand("python3"); + + List cmd = req.buildCommand(true); + + assertEquals(List.of("python3"), cmd); + } + + @Test + void buildCommandWithBaseArgs() { + CommandLineTestCaseRequest req = new CommandLineTestCaseRequest(); + req.setCommand("python3"); + req.setCommandArgs("test001.py --verbose"); + + List cmd = req.buildCommand(true); + + assertEquals(List.of("python3", "test001.py", "--verbose"), cmd); + } + + @Test + void buildCommandWithFormParamsInSafeMode() { + CommandLineTestCaseRequest req = new CommandLineTestCaseRequest(); + req.setCommand("python3"); + req.setCommandArgs("test001.py"); + + RequestVariable param = + new RequestVariable("input", "hello", "input", "' OR 1=1 --", "input", "hello"); + req.setFormParams(Arrays.asList(param)); + + List cmd = req.buildCommand(true); + + assertEquals( + List.of("python3", "test001.py", "--input", "hello"), + cmd, + "Safe mode should use the safe value"); + } + + @Test + void buildCommandWithFormParamsInAttackMode() { + CommandLineTestCaseRequest req = new CommandLineTestCaseRequest(); + req.setCommand("python3"); + req.setCommandArgs("test001.py"); + + RequestVariable param = + new RequestVariable("input", "hello", "input", "' OR 1=1 --", "input", "hello"); + req.setFormParams(Arrays.asList(param)); + + List cmd = req.buildCommand(false); + + assertEquals( + List.of("python3", "test001.py", "--input", "' OR 1=1 --"), + cmd, + "Attack mode should use the attack value"); + } + + @Test + void buildCommandWithMultipleParams() { + CommandLineTestCaseRequest req = new CommandLineTestCaseRequest(); + req.setCommand("app"); + + RequestVariable p1 = + new RequestVariable("user", "admin", "user", "root", "user", "admin"); + RequestVariable p2 = + new RequestVariable("pass", "safe123", "pass", "' DROP TABLE--", "pass", "safe123"); + req.setFormParams(Arrays.asList(p1, p2)); + + List cmd = req.buildCommand(true); + + assertEquals( + List.of("app", "--user", "admin", "--pass", "safe123"), + cmd); + } + + @Test + void getLastBuiltCommandReturnsUnmodifiableList() { + CommandLineTestCaseRequest req = new CommandLineTestCaseRequest(); + req.setCommand("echo"); + + List cmd = req.getLastBuiltCommand(true); + + try { + cmd.add("injected"); + throw new AssertionError("List should be unmodifiable"); + } catch (UnsupportedOperationException expected) { + // correct behavior + } + } +} From 76fab4aa6b84422ac3492e28cca9e0c3e629565a Mon Sep 17 00:00:00 2001 From: Theauditor <228822721+TheAuditorTool@users.noreply.github.com> Date: Wed, 22 Apr 2026 20:29:37 +0700 Subject: [PATCH 2/2] fix(crawlers): address darkspirit510 review on v2 crawlers PR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #288 review raised 7 items. 2 were design questions answered in comments (class name rationale, why separate class). The remaining 5 are code fixes in this commit. THE PROBLEM: BenchmarkCrawler_newv2 had three categories of issue: 1. Inline FQNs (java.util.List, java.util.ArrayList, javax.xml.bind.helpers.DefaultValidationEventHandler) instead of imports — the DefaultValidationEventHandler pattern was copied from Utils.java:183 which uses the same inline FQN, but reviewer wants imports in new code 2. Duplicated HttpClients.custom() builder chain across proxy/no-proxy branches — inherited from parent BenchmarkCrawler.java:204-228 which has the same duplication 3. execute() prints error on null crawlerFile but returns silently with exit code 0 — same pattern as parent BenchmarkCrawler.java:350 but reviewer wants explicit failure THE SOLUTION: - Add imports for ArrayList, DefaultValidationEventHandler, HttpClientBuilder and replace all inline FQNs with short names - Extract HttpClientBuilder as a local variable, build once, then conditionally call .setProxy() — eliminates the duplicated 4-line builder chain - Add System.exit(-1) after the error println in both execute() methods (crawler + verification crawler) KEY DECISIONS: - System.exit(-1) not System.exit(1): codebase has 19 uses of System.exit(-1) for errors, zero uses of System.exit(1) - System.exit(-1) not throw MojoExecutionException: MojoExecutionException is declared on every execute() but never thrown anywhere in the codebase (0 occurrences). Introducing a new error pattern is scope creep. - HttpClientBuilder (explicit type) not var: Java 11 target supports var but 0 usages exist in codebase. Legacy explicit-type style throughout. - Spotless (google-java-format 1.17.0 AOSP, pom.xml:222-314) will normalize import order on mvn compile. Manual placement is correct but academic — formatter is the safety net. VERIFICATION: mvn compile (spotless-apply runs at compile phase) mvn test (267+ tests, 0 failures expected) git diff --stat shows 2 files, +16/-15 lines --- .../BenchmarkCrawlerVerification_newv2.java | 1 + .../tools/BenchmarkCrawler_newv2.java | 29 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification_newv2.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification_newv2.java index a62519e3..b73d4e51 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification_newv2.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawlerVerification_newv2.java @@ -563,6 +563,7 @@ public void execute() throws MojoExecutionException, MojoFailureException { if (null == this.crawlerFile) { System.out.println("ERROR: An attack crawlerFile parameter must be specified."); + System.exit(-1); } else { String[] mainArgs = {"-f", this.crawlerFile}; main(mainArgs); diff --git a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler_newv2.java b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler_newv2.java index 9390e45d..90f72009 100644 --- a/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler_newv2.java +++ b/plugin/src/main/java/org/owasp/benchmarkutils/tools/BenchmarkCrawler_newv2.java @@ -24,6 +24,7 @@ import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; @@ -32,6 +33,7 @@ import javax.net.ssl.SSLContext; import javax.xml.bind.JAXBContext; import javax.xml.bind.Unmarshaller; +import javax.xml.bind.helpers.DefaultValidationEventHandler; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; @@ -43,6 +45,7 @@ import org.apache.hc.client5.http.classic.methods.HttpUriRequest; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; import org.apache.hc.client5.http.io.HttpClientConnectionManager; @@ -146,22 +149,18 @@ protected CloseableHttpClient createHttpClient() RequestConfig config = configBuilder.build(); - HttpHost httpHost = null; + HttpClientBuilder builder = + HttpClients.custom() + .setDefaultRequestConfig(config) + .setConnectionManager(cm); + String pHost = System.getProperty("proxyHost"); String pPort = System.getProperty("proxyPort"); if (pHost != null && pPort != null) { - httpHost = new HttpHost(pHost, Integer.parseInt(pPort)); - return HttpClients.custom() - .setDefaultRequestConfig(config) - .setConnectionManager(cm) - .setProxy(httpHost) - .build(); - } else { - return HttpClients.custom() - .setDefaultRequestConfig(config) - .setConnectionManager(cm) - .build(); + builder.setProxy(new HttpHost(pHost, Integer.parseInt(pPort))); } + + return builder.build(); } /** @@ -263,8 +262,7 @@ void load() { if (selectedTestCaseName != null) { for (AbstractTestCaseRequest request : this.testSuite.getTestCases()) { if (request.getName().equals(selectedTestCaseName)) { - java.util.List requests = - new java.util.ArrayList<>(); + List requests = new ArrayList<>(); requests.add(request); this.testSuite = new TestSuite(); this.testSuite.setTestCases(requests); @@ -289,7 +287,7 @@ static TestSuite parseHttpFileWithCliSupport(File file) throws Exception { JAXBContextFactory.createContext( new Class[] {TestSuite.class, CommandLineTestCaseRequest.class}, null); Unmarshaller unmarshaller = context.createUnmarshaller(); - unmarshaller.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler()); + unmarshaller.setEventHandler(new DefaultValidationEventHandler()); return (TestSuite) unmarshaller.unmarshal(new FileReader(file)); } @@ -360,6 +358,7 @@ public void execute() throws MojoExecutionException, MojoFailureException { if (null == this.crawlerFile) { System.out.println("ERROR: A crawlerFile parameter must be specified."); + System.exit(-1); } else { String[] mainArgs = {"-f", this.crawlerFile}; main(mainArgs);