diff --git a/docker/CHANGELOG.md b/docker/CHANGELOG.md index b0ca78770fd..2c979b33b44 100644 --- a/docker/CHANGELOG.md +++ b/docker/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog All notable changes to the docker containers will be documented in this file. +### 2025-11-21 +- Updated `Alert_on_HTTP_Response_Code_Errors.js` to work with GraalVM JavaScript engine. + ### 2025-11-03 - Set statsId and readonly for scan policies. diff --git a/docker/scripts/scripts/httpsender/Alert_on_HTTP_Response_Code_Errors.js b/docker/scripts/scripts/httpsender/Alert_on_HTTP_Response_Code_Errors.js index d03363daf71..fa74411318b 100644 --- a/docker/scripts/scripts/httpsender/Alert_on_HTTP_Response_Code_Errors.js +++ b/docker/scripts/scripts/httpsender/Alert_on_HTTP_Response_Code_Errors.js @@ -76,9 +76,7 @@ function responseReceived(msg, initiator, helper) { alert.setDescription("A response code of " + code + " was returned by the server.\n" + "This may indicate that the application is failing to handle unexpected input correctly.\n" + "Raised by the 'Alert on HTTP Response Code Error' script"); - // Use a regex to extract the evidence from the response header - var regex = new RegExp("^HTTP.*" + code) - alert.setEvidence(msg.getResponseHeader().toString().match(regex)) + alert.setEvidence(code.toString()) alert.setCweId(388) // CWE CATEGORY: Error Handling alert.setWascId(20) // WASC Improper Input Handling extensionAlert.alertFound(alert , ref) diff --git a/zap/src/main/java/org/parosproxy/paros/control/Control.java b/zap/src/main/java/org/parosproxy/paros/control/Control.java index 3eb203970a1..36bf81c12b5 100644 --- a/zap/src/main/java/org/parosproxy/paros/control/Control.java +++ b/zap/src/main/java/org/parosproxy/paros/control/Control.java @@ -90,6 +90,8 @@ // ZAP: 2022/02/24 Remove code deprecated in 2.5.0 // ZAP: 2022/09/21 Use format specifiers instead of concatenation when logging. // ZAP: 2023/01/10 Tidy up logger. +// ZAP: 2025/11/21 From now on we will not be recording changes here as the files have changed so +// much. package org.parosproxy.paros.control; import java.awt.Desktop; @@ -113,6 +115,7 @@ import org.zaproxy.zap.control.AddOnLoader; import org.zaproxy.zap.control.ControlOverrides; import org.zaproxy.zap.control.ExtensionFactory; +import org.zaproxy.zap.utils.ErrorUtils; import org.zaproxy.zap.utils.ZapHtmlLabel; /** Overall control with interaction on model and view. */ @@ -522,7 +525,9 @@ public void run() { .getTableSession() .insert(session.getSessionId(), session.getSessionName()); } catch (DatabaseException e) { - LOGGER.error(e.getMessage(), e); + if (!ErrorUtils.handleDiskSpaceException(e)) { + LOGGER.error(e.getMessage(), e); + } } return session; diff --git a/zap/src/main/java/org/parosproxy/paros/core/scanner/Scanner.java b/zap/src/main/java/org/parosproxy/paros/core/scanner/Scanner.java index 0335e71cb7a..2f1fe2c6f96 100644 --- a/zap/src/main/java/org/parosproxy/paros/core/scanner/Scanner.java +++ b/zap/src/main/java/org/parosproxy/paros/core/scanner/Scanner.java @@ -60,6 +60,8 @@ // ZAP: 2023/05/30 Stop HostProcess to stop the Analyser. // ZAP: 2024/11/20 Include ID of the scan in relevant log messages. // ZAP: 2025/11/03 Record stats for built-in policies. +// ZAP: 2025/11/21 From now on we will not be recording changes here as the files have changed so +// much. package org.parosproxy.paros.core.scanner; import java.security.InvalidParameterException; @@ -215,9 +217,10 @@ public void start(Target target) { Stats.incCounter(ASCAN_SCAN_STARTED_USER_STATS); } Stats.incCounter( - "stats.ascan.started.policy." + this.scanPolicy.getStatsId() != null - ? this.scanPolicy.getStatsId() - : "user"); + "stats.ascan.started.policy." + + (this.scanPolicy.getStatsId() != null + ? this.scanPolicy.getStatsId() + : "user")); } public void stop() { diff --git a/zap/src/main/java/org/parosproxy/paros/extension/history/ProxyListenerLog.java b/zap/src/main/java/org/parosproxy/paros/extension/history/ProxyListenerLog.java index ed266bb280b..cabd4a0042c 100644 --- a/zap/src/main/java/org/parosproxy/paros/extension/history/ProxyListenerLog.java +++ b/zap/src/main/java/org/parosproxy/paros/extension/history/ProxyListenerLog.java @@ -39,6 +39,8 @@ // ZAP: 2020/11/26 Use Log4j 2 classes for logging. // ZAP: 2023/01/10 Tidy up logger. // ZAP: 2023/08/22 Do not modify the requests being proxied (Issue 7353). +// ZAP: 2025/11/21 From now on we will not be recording changes here as the files have changed so +// much. package org.parosproxy.paros.extension.history; import java.awt.EventQueue; @@ -56,6 +58,7 @@ import org.parosproxy.paros.network.HttpMessage; import org.parosproxy.paros.network.HttpStatusCode; import org.zaproxy.zap.model.SessionStructure; +import org.zaproxy.zap.utils.ErrorUtils; public class ProxyListenerLog implements ProxyListener, ConnectRequestProxyListener { @@ -148,8 +151,9 @@ private HistoryReference createHistoryReference(HttpMessage message, int type) { try { return new HistoryReference(model.getSession(), type, message); } catch (Exception e) { - // ZAP: Log exceptions - LOGGER.warn(e.getMessage(), e); + if (!ErrorUtils.handleDiskSpaceException(e)) { + LOGGER.warn(e.getMessage(), e); + } } return null; } diff --git a/zap/src/main/java/org/parosproxy/paros/model/HistoryReference.java b/zap/src/main/java/org/parosproxy/paros/model/HistoryReference.java index 42039b62486..299e3cc3fb8 100644 --- a/zap/src/main/java/org/parosproxy/paros/model/HistoryReference.java +++ b/zap/src/main/java/org/parosproxy/paros/model/HistoryReference.java @@ -68,6 +68,8 @@ // ZAP: 2022/09/21 Use format specifiers instead of concatenation when logging. // ZAP: 2023/01/10 Tidy up logger. // ZAP: 2025/10/01 Alert handling tweaks. +// ZAP: 2025/11/21 From now on we will not be recording changes here as the files have changed so +// much. package org.parosproxy.paros.model; import java.util.ArrayList; @@ -94,6 +96,7 @@ import org.zaproxy.zap.ZAP; import org.zaproxy.zap.eventBus.Event; import org.zaproxy.zap.model.Target; +import org.zaproxy.zap.utils.ErrorUtils; /** * This class abstracts a reference to a http message stored in database. It reads the whole http @@ -595,7 +598,9 @@ private boolean insertTagDb(String tag) { staticTableTag.insert(historyId, tag); return true; } catch (DatabaseException e) { - LOGGER.error("Failed to persist tag: {}", e.getMessage(), e); + if (!ErrorUtils.handleDiskSpaceException(e)) { + LOGGER.error("Failed to persist tag: {}", e.getMessage(), e); + } } return false; } @@ -643,7 +648,9 @@ public void setNote(String note) { httpMessageCachedData.setNote(note != null && note.length() > 0); notifyEvent(HistoryReferenceEventPublisher.EVENT_NOTE_SET); } catch (DatabaseException e) { - LOGGER.error(e.getMessage(), e); + if (!ErrorUtils.handleDiskSpaceException(e)) { + LOGGER.error("Failed to persist tag: {}", e.getMessage(), e); + } } } diff --git a/zap/src/main/java/org/zaproxy/zap/ZAP.java b/zap/src/main/java/org/zaproxy/zap/ZAP.java index d45acf796de..55437afd730 100644 --- a/zap/src/main/java/org/zaproxy/zap/ZAP.java +++ b/zap/src/main/java/org/zaproxy/zap/ZAP.java @@ -191,12 +191,21 @@ private static void updateStats(Throwable e) { try { String baseKey = "stats.error.core.uncaught"; Stats.incCounter(baseKey); - Stats.incCounter(baseKey + "." + e.getClass().getSimpleName()); + Stats.incCounter(baseKey + "." + e.getClass().getSimpleName() + getSource(e)); } catch (Throwable ignore) { // Already handling an earlier error... } } + private static String getSource(Throwable t) { + StackTraceElement[] trace = t.getStackTrace(); + if (trace == null || trace.length == 0) { + return ""; + } + StackTraceElement top = trace[0]; + return "(" + top.getFileName() + ":" + top.getLineNumber() + ")"; + } + private boolean isLoggerConfigured() { if (loggerConfigured) { return true; diff --git a/zap/src/main/java/org/zaproxy/zap/extension/alert/ExtensionAlert.java b/zap/src/main/java/org/zaproxy/zap/extension/alert/ExtensionAlert.java index 3dbdecf19a5..69a1aea2b24 100644 --- a/zap/src/main/java/org/zaproxy/zap/extension/alert/ExtensionAlert.java +++ b/zap/src/main/java/org/zaproxy/zap/extension/alert/ExtensionAlert.java @@ -71,6 +71,7 @@ import org.zaproxy.zap.extension.help.ExtensionHelp; import org.zaproxy.zap.model.SessionStructure; import org.zaproxy.zap.model.Target; +import org.zaproxy.zap.utils.ErrorUtils; import org.zaproxy.zap.utils.ThreadUtils; import org.zaproxy.zap.view.popup.MenuWeights; @@ -233,6 +234,10 @@ public void alertFound(Alert alert, HistoryReference ref) { writeAlertToDB(alert, ref); } catch (Exception e) { + if (ErrorUtils.handleDiskSpaceException(e)) { + // No point doing anything else + return; + } LOGGER.error(e.getMessage(), e); } diff --git a/zap/src/main/java/org/zaproxy/zap/extension/anticsrf/ExtensionAntiCSRF.java b/zap/src/main/java/org/zaproxy/zap/extension/anticsrf/ExtensionAntiCSRF.java index b23dacb1323..fb75c21124b 100644 --- a/zap/src/main/java/org/zaproxy/zap/extension/anticsrf/ExtensionAntiCSRF.java +++ b/zap/src/main/java/org/zaproxy/zap/extension/anticsrf/ExtensionAntiCSRF.java @@ -55,6 +55,7 @@ import org.parosproxy.paros.network.HttpMalformedHeaderException; import org.parosproxy.paros.network.HttpMessage; import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; +import org.zaproxy.zap.utils.ErrorUtils; /** * An {@code Extension} that handles anti-csrf tokens. @@ -253,7 +254,9 @@ public void registerAntiCsrfToken(AntiCsrfToken token) { token.setHistoryReferenceId(hRef.getHistoryId()); valueToToken.put(getURLEncode(token.getValue()), token); } catch (HttpMalformedHeaderException | DatabaseException e) { - LOGGER.error("Failed to persist the message: ", e); + if (!ErrorUtils.handleDiskSpaceException(e)) { + LOGGER.error("Failed to persist the message: ", e); + } } } } @@ -424,7 +427,9 @@ public void sessionChanged(Session session) { } } } catch (DatabaseException | HttpMalformedHeaderException e) { - LOGGER.error(e.getMessage(), e); + if (!ErrorUtils.handleDiskSpaceException(e)) { + LOGGER.error(e.getMessage(), e); + } } } diff --git a/zap/src/main/java/org/zaproxy/zap/extension/ascan/ActiveScan.java b/zap/src/main/java/org/zaproxy/zap/extension/ascan/ActiveScan.java index 48c5098de90..0da51cfa828 100644 --- a/zap/src/main/java/org/zaproxy/zap/extension/ascan/ActiveScan.java +++ b/zap/src/main/java/org/zaproxy/zap/extension/ascan/ActiveScan.java @@ -46,6 +46,7 @@ import org.zaproxy.zap.extension.ruleconfig.RuleConfigParam; import org.zaproxy.zap.model.GenericScanner2; import org.zaproxy.zap.model.Target; +import org.zaproxy.zap.utils.ErrorUtils; public class ActiveScan extends org.parosproxy.paros.core.scanner.Scanner implements GenericScanner2, ScannerListener { @@ -82,7 +83,6 @@ public static enum State { private static final Logger LOGGER = LogManager.getLogger(ActiveScan.class); private boolean persistTemporaryMessages; - private boolean warnDbFull = true; @Deprecated public ActiveScan( @@ -299,14 +299,12 @@ public void notifyNewMessage(final HttpMessage msg) { msg.setHistoryRef(null); hRefs.add(hRef.getHistoryId()); } catch (HttpMalformedHeaderException | DatabaseException e) { - if (hasCause(e, "Data File size limit is reached")) { - if (warnDbFull) { - warnDbFull = false; - LOGGER.warn("Unable to persist temporary message, database is full.", e); - } - } else { - LOGGER.error(e.getMessage(), e); + if (ErrorUtils.handleDiskSpaceException(e)) { + // Its serious, stop the scans + this.getHostProcesses().forEach(HostProcess::stop); + return; } + LOGGER.error(e.getMessage(), e); } } else { hRefs.add(hRef.getHistoryId()); @@ -322,18 +320,6 @@ public void notifyNewMessage(final HttpMessage msg) { } } - private static boolean hasCause(Exception e, String wantedMessage) { - Throwable cause = e.getCause(); - if (cause == null) { - return false; - } - String message = cause.getMessage(); - if (message == null) { - return false; - } - return message.contains(wantedMessage); - } - private void addHistoryReferenceInEdt(final HistoryReference hRef) { EventQueue.invokeLater( new Runnable() { diff --git a/zap/src/main/java/org/zaproxy/zap/extension/params/ExtensionParams.java b/zap/src/main/java/org/zaproxy/zap/extension/params/ExtensionParams.java index 7a42bb28c1f..e5269737ce8 100644 --- a/zap/src/main/java/org/zaproxy/zap/extension/params/ExtensionParams.java +++ b/zap/src/main/java/org/zaproxy/zap/extension/params/ExtensionParams.java @@ -57,6 +57,7 @@ import org.zaproxy.zap.extension.httpsessions.ExtensionHttpSessions; import org.zaproxy.zap.extension.pscan.ExtensionPassiveScan; import org.zaproxy.zap.extension.search.ExtensionSearch; +import org.zaproxy.zap.utils.ErrorUtils; import org.zaproxy.zap.utils.ThreadUtils; import org.zaproxy.zap.view.SiteMapListener; import org.zaproxy.zap.view.SiteMapTreeCellRenderer; @@ -79,8 +80,6 @@ public class ExtensionParams extends ExtensionAdaptor private ExtensionHttpSessions extensionHttpSessions; private ParamScanner paramScanner; - private boolean warnDbFull = true; - public ExtensionParams() { super(NAME); this.setOrder(58); @@ -198,8 +197,6 @@ protected ParamsPanel getParamsPanel() { @Override public void sessionChanged(final Session session) { - warnDbFull = true; - if (EventQueue.isDispatchThread()) { sessionChangedEventHandler(session); @@ -385,35 +382,18 @@ private void persist(HtmlParameterStats param) { setToString(param.getValues())); } } catch (DatabaseException e) { - if (hasCause(e, "truncation")) { + if (ErrorUtils.hasCause(e, "truncation")) { LOGGER.warn("Could not add or update param: {}", param.getName()); LOGGER.warn( "It is likely that the length of one of the data elements exceeded the column size."); LOGGER.warn(e.getMessage()); LOGGER.debug(e.getMessage(), e); - } else if (hasCause(e, "Data File size limit is reached")) { - if (warnDbFull) { - warnDbFull = false; - LOGGER.warn("Unable to persist parameter, database is full.", e); - } - } else { + } else if (!ErrorUtils.handleDiskSpaceException(e)) { LOGGER.error(e.getMessage(), e); } } } - private static boolean hasCause(Exception e, String wantedMessage) { - Throwable cause = e.getCause(); - if (cause == null) { - return false; - } - String message = cause.getMessage(); - if (message == null) { - return false; - } - return message.contains(wantedMessage); - } - public boolean onHttpResponseReceive(HttpMessage msg) { // Check we know the site diff --git a/zap/src/main/java/org/zaproxy/zap/utils/ErrorUtils.java b/zap/src/main/java/org/zaproxy/zap/utils/ErrorUtils.java new file mode 100644 index 00000000000..6d2567c2abe --- /dev/null +++ b/zap/src/main/java/org/zaproxy/zap/utils/ErrorUtils.java @@ -0,0 +1,145 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.zap.utils; + +import java.util.List; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.parosproxy.paros.Constant; +import org.parosproxy.paros.control.Control; +import org.parosproxy.paros.view.View; + +public final class ErrorUtils { + + private static final Logger LOGGER = LogManager.getLogger(ErrorUtils.class); + + private static OutOfDiskSpaceHandler outOfDiskSpaceHandler = new DefaultOutOfDiskSpaceHandler(); + + private ErrorUtils() { + // Utility class + } + + public static boolean handleDiskSpaceException(Exception e) { + return outOfDiskSpaceHandler.handleDiskSpaceException(e); + } + + public static void setOutOfDiskSpaceHandler(OutOfDiskSpaceHandler outOfDiskSpaceHandler) { + ErrorUtils.outOfDiskSpaceHandler = + outOfDiskSpaceHandler != null + ? outOfDiskSpaceHandler + : new DefaultOutOfDiskSpaceHandler(); + } + + public static OutOfDiskSpaceHandler getOutOfDiskSpaceHandler() { + return outOfDiskSpaceHandler; + } + + public static boolean hasCause(Exception e, List wantedMessages) { + String message = getCauseMessage(e); + if (message == null) { + return false; + } + return wantedMessages.stream().anyMatch(message::contains); + } + + public static boolean hasCause(Exception e, String wantedMessage) { + String message = getCauseMessage(e); + return message != null && message.contains(wantedMessage); + } + + private static String getCauseMessage(Exception e) { + Throwable cause = e.getCause(); + if (cause == null) { + return null; + } + String message = cause.getMessage(); + if (message == null) { + return null; + } + return message; + } + + private static class DefaultOutOfDiskSpaceHandler implements OutOfDiskSpaceHandler { + + private boolean outOfSpace; + private boolean exitOnOutOfSpace = true; + private static final List DB_FULL_MSGS = List.of("Data File size limit is reached"); + private static final List DISK_FULL_MSGS = + List.of( + "No space left on device", + "There is not enough space on the disk", + "file input/output error"); + + @Override + public boolean handleDiskSpaceException(Exception e) { + boolean dbFull = hasCause(e, DB_FULL_MSGS); + boolean diskFull = hasCause(e, DISK_FULL_MSGS); + + if ((dbFull || diskFull) && !outOfSpace) { + String errorMsg; + if (dbFull) { + errorMsg = Constant.messages.getString("db.space.full"); + Stats.incCounter("stats.error.database.full"); + } else { + errorMsg = Constant.messages.getString("disk.space.full"); + Stats.incCounter("stats.error.diskspace.full"); + } + + logAndStderr(errorMsg); + outOfSpace = true; + if (View.isInitialised()) { + View.getSingleton().showWarningDialog(errorMsg); + } else if (exitOnOutOfSpace) { + logAndStderr("Shutting down ZAP due to space issues..."); + Control control = Control.getSingleton(); + control.setExitStatus(2, errorMsg); + control.exit(false, null); + } + } + + return dbFull || diskFull; + } + + /** + * Write to the log and stderr, in case theres not enough space to write to the log + * + * @param msg + */ + private void logAndStderr(String msg) { + System.err.println(msg); + LOGGER.error(msg); + } + + @Override + public boolean isOutOfSpace() { + return outOfSpace; + } + + @Override + public boolean isExitOnOutOfSpace() { + return exitOnOutOfSpace; + } + + @Override + public void setExitOnOutOfSpace(boolean exitOnOutOfSpace) { + this.exitOnOutOfSpace = exitOnOutOfSpace; + } + } +} diff --git a/zap/src/main/java/org/zaproxy/zap/utils/OutOfDiskSpaceHandler.java b/zap/src/main/java/org/zaproxy/zap/utils/OutOfDiskSpaceHandler.java new file mode 100644 index 00000000000..6bbd41f682d --- /dev/null +++ b/zap/src/main/java/org/zaproxy/zap/utils/OutOfDiskSpaceHandler.java @@ -0,0 +1,31 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.zap.utils; + +public interface OutOfDiskSpaceHandler { + + boolean handleDiskSpaceException(Exception e); + + boolean isOutOfSpace(); + + boolean isExitOnOutOfSpace(); + + void setExitOnOutOfSpace(boolean exitOnOutOfSpace); +} diff --git a/zap/src/main/resources/org/zaproxy/zap/resources/Messages.properties b/zap/src/main/resources/org/zaproxy/zap/resources/Messages.properties index f707483fe90..31765b4e97b 100644 --- a/zap/src/main/resources/org/zaproxy/zap/resources/Messages.properties +++ b/zap/src/main/resources/org/zaproxy/zap/resources/Messages.properties @@ -1563,6 +1563,9 @@ database.optionspanel.option.recoveryLog.tooltip = Controls whether or not database.optionspanel.option.request.body.size.label = Maximum Request Body Size database.optionspanel.option.response.body.size.label = Maximum Response Body Size +db.space.full = Out of database space, ZAP will effectively be unusable. +disk.space.full = Out of disk space, ZAP will effectively be unusable. + edit.find.button.cancel = Cancel edit.find.button.find = Find edit.find.label.notfound = String not found. diff --git a/zap/src/main/weekly-add-ons.json b/zap/src/main/weekly-add-ons.json index 46ba18d8125..c3adc287891 100644 --- a/zap/src/main/weekly-add-ons.json +++ b/zap/src/main/weekly-add-ons.json @@ -25,6 +25,7 @@ ":addOns:graaljs", ":addOns:graphql", ":addOns:grpc", + ":addOns:insights", ":addOns:invoke", ":addOns:network", ":addOns:oast", diff --git a/zap/src/test/java/org/zaproxy/zap/utils/ErrorUtilsUnitTest.java b/zap/src/test/java/org/zaproxy/zap/utils/ErrorUtilsUnitTest.java new file mode 100644 index 00000000000..cba7b6337df --- /dev/null +++ b/zap/src/test/java/org/zaproxy/zap/utils/ErrorUtilsUnitTest.java @@ -0,0 +1,122 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2025 The ZAP Development Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.zaproxy.zap.utils; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.parosproxy.paros.Constant; + +public class ErrorUtilsUnitTest { + + @BeforeAll + static void beforeAll() { + Constant.messages = mock(I18N.class); + } + + @AfterAll + static void afterAll() { + Constant.messages = null; + } + + @BeforeEach + void setup() throws Exception { + ErrorUtils.setOutOfDiskSpaceHandler(null); + ErrorUtils.getOutOfDiskSpaceHandler().setExitOnOutOfSpace(false); + } + + @ParameterizedTest + @ValueSource( + strings = { + "Data File size limit is reached", + "No space left on device, bad luck", + "There is not enough space on the disk", + "There was a file input/output error" + }) + void shouldHandleDiskSpaceException(String message) { + // Given + Exception e = new Exception("Test", new Exception(message)); + // When / Then + assertThat(ErrorUtils.handleDiskSpaceException(e), is(equalTo(true))); + assertThat(ErrorUtils.getOutOfDiskSpaceHandler().isOutOfSpace(), is(equalTo(true))); + } + + @Test + void shouldIgnoreNonDiskSpaceException() { + // Given + Exception e = new Exception("Test", new Exception("Not a disk space exception")); + // When / Then + assertThat(ErrorUtils.handleDiskSpaceException(e), is(equalTo(false))); + assertThat(ErrorUtils.getOutOfDiskSpaceHandler().isOutOfSpace(), is(equalTo(false))); + } + + @Test + void shouldSetOutOfDiskSpaceHandler() { + // Given + OutOfDiskSpaceHandler handler = mock(OutOfDiskSpaceHandler.class); + ErrorUtils.setOutOfDiskSpaceHandler(handler); + Exception e = new Exception("Test", new Exception("Data File size limit is reached")); + // When / Then + assertThat(ErrorUtils.handleDiskSpaceException(e), is(equalTo(false))); + verify(handler, times(1)).handleDiskSpaceException(e); + } + + @Test + void shouldReturnTrueIfCause() { + // Given + Exception e = new Exception("Test", new Exception("This exception has a cause")); + // When / Then + assertThat(ErrorUtils.hasCause(e, "has a"), is(equalTo(true))); + } + + @Test + void shouldReturnFalseIfDifferentCause() { + // Given + Exception e = new Exception("Test", new Exception("This exception has a cause")); + // When / Then + assertThat(ErrorUtils.hasCause(e, "not in the cause"), is(equalTo(false))); + } + + @Test + void shouldReturnFalseIfCauseHasNoMessage() { + // Given + Exception e = new Exception("Test", new Exception()); + // When / Then + assertThat(ErrorUtils.hasCause(e, "no message in the cause"), is(equalTo(false))); + } + + @Test + void shouldReturnFalseIfNoCause() { + // Given + Exception e = new Exception("Test"); + // When / Then + assertThat(ErrorUtils.hasCause(e, "Test"), is(equalTo(false))); + } +}