From 884d3aa4ad5a23a0d177a31852f3ac1e5d397f04 Mon Sep 17 00:00:00 2001 From: Sanjay Malakar Date: Wed, 15 Apr 2026 08:01:52 -0700 Subject: [PATCH] Handle exceptions of individual writers in MultipleWriters (#9276) Ensure all writers are called. --- .../zap/extension/script/MultipleWriters.java | 35 ++++++-- .../script/MultipleWritersUnitTest.java | 83 +++++++++++++++++++ 2 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 zap/src/test/java/org/zaproxy/zap/extension/script/MultipleWritersUnitTest.java diff --git a/zap/src/main/java/org/zaproxy/zap/extension/script/MultipleWriters.java b/zap/src/main/java/org/zaproxy/zap/extension/script/MultipleWriters.java index 394bf8358fa..70f97a11a87 100644 --- a/zap/src/main/java/org/zaproxy/zap/extension/script/MultipleWriters.java +++ b/zap/src/main/java/org/zaproxy/zap/extension/script/MultipleWriters.java @@ -33,26 +33,43 @@ public class MultipleWriters extends Writer { private List writers = new ArrayList<>(); + @FunctionalInterface + private interface WriterAction { + void accept(Writer writer) throws IOException; + } + + private void forEachWriter(WriterAction action) throws IOException { + IOException first = null; + for (Writer writer : writers) { + try { + action.accept(writer); + } catch (IOException e) { + if (first == null) { + first = e; + } else { + first.addSuppressed(e); + } + } + } + if (first != null) { + throw first; + } + } + @Override public void write(char[] cbuf, int off, int len) throws IOException { String str = new String(cbuf, off, len); - for (Writer writer : writers) { - writer.append(str); - } + forEachWriter(writer -> writer.append(str)); } @Override public void flush() throws IOException { - for (Writer writer : writers) { - writer.flush(); - } + forEachWriter(Writer::flush); } @Override public void close() throws IOException { - for (Writer writer : writers) { - writer.close(); - } + forEachWriter(Writer::close); } public void addWriter(Writer writer) { diff --git a/zap/src/test/java/org/zaproxy/zap/extension/script/MultipleWritersUnitTest.java b/zap/src/test/java/org/zaproxy/zap/extension/script/MultipleWritersUnitTest.java new file mode 100644 index 00000000000..cf5a8de7d1f --- /dev/null +++ b/zap/src/test/java/org/zaproxy/zap/extension/script/MultipleWritersUnitTest.java @@ -0,0 +1,83 @@ +/* + * Zed Attack Proxy (ZAP) and its related class files. + * + * ZAP is an HTTP/HTTPS proxy for assessing web application security. + * + * Copyright 2026 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.extension.script; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.io.IOException; +import java.io.Writer; +import org.junit.jupiter.api.Test; + +class MultipleWritersUnitTest { + + @Test + void shouldWriteToLaterWritersEvenIfEarlierWriterFails() throws Exception { + MultipleWriters writers = new MultipleWriters(); + Writer failingWriter = mock(Writer.class); + Writer succeedingWriter = mock(Writer.class); + + doThrow(new IOException("write failed")).when(failingWriter).append("abc"); + + writers.addWriter(failingWriter); + writers.addWriter(succeedingWriter); + + assertThrows(IOException.class, () -> writers.write("abc".toCharArray(), 0, 3)); + + verify(failingWriter).append("abc"); + verify(succeedingWriter).append("abc"); + } + + @Test + void shouldFlushLaterWritersEvenIfEarlierWriterFails() throws Exception { + MultipleWriters writers = new MultipleWriters(); + Writer failingWriter = mock(Writer.class); + Writer succeedingWriter = mock(Writer.class); + + doThrow(new IOException("flush failed")).when(failingWriter).flush(); + + writers.addWriter(failingWriter); + writers.addWriter(succeedingWriter); + + assertThrows(IOException.class, writers::flush); + + verify(failingWriter).flush(); + verify(succeedingWriter).flush(); + } + + @Test + void shouldCloseLaterWritersEvenIfEarlierWriterFails() throws Exception { + MultipleWriters writers = new MultipleWriters(); + Writer failingWriter = mock(Writer.class); + Writer succeedingWriter = mock(Writer.class); + + doThrow(new IOException("close failed")).when(failingWriter).close(); + + writers.addWriter(failingWriter); + writers.addWriter(succeedingWriter); + + assertThrows(IOException.class, writers::close); + + verify(failingWriter).close(); + verify(succeedingWriter).close(); + } +}