From 61186cf2c789e8f913b92a8ae9245378da351bb8 Mon Sep 17 00:00:00 2001 From: Hemanth Madala Date: Thu, 5 Nov 2026 10:30:11 +0530 Subject: [PATCH 1/2] Fixed Polaris reported CWE-404 --- .../example/handle/JacksonHandleExample.java | 17 +++++++------ .../client/datamovement/JSONSplitter.java | 24 +++++++++++++++++-- .../client/io/JacksonParserHandle.java | 12 +++++----- .../client/pojo/util/GenerateIndexConfig.java | 18 +++++++------- 4 files changed, 45 insertions(+), 26 deletions(-) diff --git a/examples/src/main/java/com/marklogic/client/example/handle/JacksonHandleExample.java b/examples/src/main/java/com/marklogic/client/example/handle/JacksonHandleExample.java index ecd063f1fd..a22d7ef5e2 100644 --- a/examples/src/main/java/com/marklogic/client/example/handle/JacksonHandleExample.java +++ b/examples/src/main/java/com/marklogic/client/example/handle/JacksonHandleExample.java @@ -84,21 +84,20 @@ public static void runStrongTyped(ExampleProperties props) throws IOException { // create a manager for JSON documents JSONDocumentManager docMgr = client.newJSONDocumentManager(); - // read the example file - InputStream docStream = Util.openStream("data"+File.separator+filename); - if (docStream == null) - throw new IOException("Could not read document example"); - // create an identifier for the document String docId = "/example/"+filename; // create a handle for the document JacksonHandle writeHandle = new JacksonHandle(); - // parse the example file with Jackson - JsonNode writeDocument = writeHandle.getMapper().readValue( - new InputStreamReader(docStream, "UTF-8"), JsonNode.class); - writeHandle.set(writeDocument); + // read the example file and parse it with Jackson + try (InputStream docStream = Util.openStream("data"+File.separator+filename)) { + if (docStream == null) + throw new IOException("Could not read document example"); + JsonNode writeDocument = writeHandle.getMapper().readValue( + new InputStreamReader(docStream, "UTF-8"), JsonNode.class); + writeHandle.set(writeDocument); + } // write the document docMgr.write(docId, writeHandle); diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/datamovement/JSONSplitter.java b/marklogic-client-api/src/main/java/com/marklogic/client/datamovement/JSONSplitter.java index 390fcba133..928d1e6e71 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/datamovement/JSONSplitter.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/datamovement/JSONSplitter.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; +import java.io.UncheckedIOException; import java.util.ArrayDeque; import java.util.Spliterator; import java.util.Spliterators; @@ -97,7 +98,21 @@ public Stream split(InputStream input) throws IOException { } JsonParser jsonParser = new JsonFactory().createParser(input); - return split(jsonParser); + try { + return split(jsonParser).onClose(() -> closeParser(jsonParser)); + } catch (IOException | RuntimeException e) { + jsonParser.close(); + throw e; + } + } + + // Closes a parser that this class created, so that closing the returned Stream releases it. + private static void closeParser(JsonParser jsonParser) { + try { + jsonParser.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } /** @@ -127,7 +142,12 @@ public Stream splitWriteOperations(InputStream input, St } JsonParser jsonParser = new JsonFactory().createParser(input); - return splitWriteOperations(jsonParser, splitFilename); + try { + return splitWriteOperations(jsonParser, splitFilename).onClose(() -> closeParser(jsonParser)); + } catch (RuntimeException e) { + jsonParser.close(); + throw e; + } } /** diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/io/JacksonParserHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/io/JacksonParserHandle.java index 293a3b9ba4..2e20560db1 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/io/JacksonParserHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/io/JacksonParserHandle.java @@ -197,9 +197,9 @@ protected boolean hasContent() { public void write(OutputStream out) throws IOException { try { if (parser != null && parser.nextToken() != null) { - JsonGenerator generator = getMapper().getFactory().createGenerator(out); - generator.copyCurrentStructure(parser); - generator.close(); + try (JsonGenerator generator = getMapper().getFactory().createGenerator(out)) { + generator.copyCurrentStructure(parser); + } } else if (content != null) { byte[] b = new byte[BUFFER_SIZE]; int len = 0; @@ -248,9 +248,9 @@ private OutputStreamSenderImpl(ObjectMapper mapper, JsonParser parser) { } @Override public void write(OutputStream out) throws IOException { - JsonGenerator generator = mapper.getFactory().createGenerator(out); - generator.copyCurrentStructure(parser); - generator.close(); + try (JsonGenerator generator = mapper.getFactory().createGenerator(out)) { + generator.copyCurrentStructure(parser); + } } } } diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/pojo/util/GenerateIndexConfig.java b/marklogic-client-api/src/main/java/com/marklogic/client/pojo/util/GenerateIndexConfig.java index c01baab1ec..490e852d9a 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/pojo/util/GenerateIndexConfig.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/pojo/util/GenerateIndexConfig.java @@ -176,15 +176,15 @@ private static void generateConfig(String[] classes, ObjectMapper mapper, Writer errorExactPair(className); } } - JsonGenerator config = mapper.getFactory().createGenerator(out); - config.useDefaultPrettyPrinter(); - config.writeStartObject(); - generatePathIndexes(paths, config); - generateGeoPathIndexes(geoPaths, config); - generateGeoPairIndexes(geoPairs, config); - config.writeEndObject(); - config.flush(); - config.close(); + try (JsonGenerator config = mapper.getFactory().createGenerator(out)) { + config.useDefaultPrettyPrinter(); + config.writeStartObject(); + generatePathIndexes(paths, config); + generateGeoPathIndexes(geoPaths, config); + generateGeoPairIndexes(geoPairs, config); + config.writeEndObject(); + config.flush(); + } } private static void errorExactPair(String className) { From c3d95a9fc0d4ae993b765a08eecc299565b2ecec Mon Sep 17 00:00:00 2001 From: Hemanth Madala Date: Tue, 10 Nov 2026 16:42:05 +0530 Subject: [PATCH 2/2] MLE-29977: Address review feedback on CWE-404 fix Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../example/handle/JacksonHandleExample.java | 2 +- .../client/datamovement/JSONSplitter.java | 9 ++- .../client/io/JacksonParserHandle.java | 2 +- .../client/pojo/util/GenerateIndexConfig.java | 2 +- .../test/datamovement/JSONSplitterTest.java | 67 ++++++++++++++++++- 5 files changed, 73 insertions(+), 9 deletions(-) diff --git a/examples/src/main/java/com/marklogic/client/example/handle/JacksonHandleExample.java b/examples/src/main/java/com/marklogic/client/example/handle/JacksonHandleExample.java index a22d7ef5e2..aa1c2d3c76 100644 --- a/examples/src/main/java/com/marklogic/client/example/handle/JacksonHandleExample.java +++ b/examples/src/main/java/com/marklogic/client/example/handle/JacksonHandleExample.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. + * Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. */ package com.marklogic.client.example.handle; diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/datamovement/JSONSplitter.java b/marklogic-client-api/src/main/java/com/marklogic/client/datamovement/JSONSplitter.java index 928d1e6e71..187b2c8bb3 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/datamovement/JSONSplitter.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/datamovement/JSONSplitter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. + * Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. */ package com.marklogic.client.datamovement; @@ -271,11 +271,10 @@ public String serialize(JsonParser containerParser) { throw new IllegalArgumentException("JsonParser cannot be null"); } - try { - StringWriter stringWriter = new StringWriter(); - JsonGenerator jsonGenerator = new JsonFactory().createGenerator(stringWriter); + try (StringWriter stringWriter = new StringWriter(); + JsonGenerator jsonGenerator = new JsonFactory().createGenerator(stringWriter)) { jsonGenerator.copyCurrentStructure(containerParser); - jsonGenerator.close(); + jsonGenerator.flush(); return stringWriter.toString(); } catch (IOException e) { throw new RuntimeException("Could not serialize the document", e); diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/io/JacksonParserHandle.java b/marklogic-client-api/src/main/java/com/marklogic/client/io/JacksonParserHandle.java index 2e20560db1..33c9f515f3 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/io/JacksonParserHandle.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/io/JacksonParserHandle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. + * Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. */ package com.marklogic.client.io; diff --git a/marklogic-client-api/src/main/java/com/marklogic/client/pojo/util/GenerateIndexConfig.java b/marklogic-client-api/src/main/java/com/marklogic/client/pojo/util/GenerateIndexConfig.java index 490e852d9a..15afe6269d 100644 --- a/marklogic-client-api/src/main/java/com/marklogic/client/pojo/util/GenerateIndexConfig.java +++ b/marklogic-client-api/src/main/java/com/marklogic/client/pojo/util/GenerateIndexConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. + * Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. */ package com.marklogic.client.pojo.util; diff --git a/marklogic-client-api/src/test/java/com/marklogic/client/test/datamovement/JSONSplitterTest.java b/marklogic-client-api/src/test/java/com/marklogic/client/test/datamovement/JSONSplitterTest.java index 8d1f4cdcd3..ab591cdda3 100644 --- a/marklogic-client-api/src/test/java/com/marklogic/client/test/datamovement/JSONSplitterTest.java +++ b/marklogic-client-api/src/test/java/com/marklogic/client/test/datamovement/JSONSplitterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2025 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. + * Copyright (c) 2010-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved. */ package com.marklogic.client.test.datamovement; @@ -11,9 +11,14 @@ import com.marklogic.client.io.marker.JSONWriteHandle; import org.junit.jupiter.api.Test; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.Iterator; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; @@ -321,4 +326,64 @@ public NodeOperation startArray(String containerKey) { return NodeOperation.DESCEND; } } + + @Test + public void splitClosesInputStreamWhenStreamIsClosed() throws Exception { + JSONSplitter splitter = JSONSplitter.makeArraySplitter(); + CloseTrackingInputStream input = new CloseTrackingInputStream(new ByteArrayInputStream(largeJsonArray())); + + try (Stream stream = splitter.split(input)) { + assertTrue(stream.findFirst().isPresent()); + assertFalse(input.closed.get(), "The input stream should remain open until the stream is closed"); + } + + assertTrue(input.closed.get(), "Closing the stream should close the underlying input stream"); + } + + @Test + public void splitWriteOperationsClosesInputStreamWhenStreamIsClosed() throws Exception { + JSONSplitter splitter = JSONSplitter.makeArraySplitter(); + CloseTrackingInputStream input = new CloseTrackingInputStream(new ByteArrayInputStream(largeJsonArray())); + + try (Stream stream = splitter.splitWriteOperations(input)) { + assertTrue(stream.findFirst().isPresent()); + assertFalse(input.closed.get(), "The input stream should remain open until the stream is closed"); + } + + assertTrue(input.closed.get(), "Closing the stream should close the underlying input stream"); + } + + // Large enough that the parser does not reach the end of the input (and auto-close it) after the first element. + private static byte[] largeJsonArray() { + StringBuilder json = new StringBuilder("["); + for (int i = 0; i < 5000; i++) { + json.append(i > 0 ? "," : "").append("{\"record\":").append(i).append("}"); + } + return json.append("]").toString().getBytes(StandardCharsets.UTF_8); + } + + private static class CloseTrackingInputStream extends InputStream { + final AtomicBoolean closed = new AtomicBoolean(false); + private final InputStream delegate; + + CloseTrackingInputStream(InputStream delegate) { + this.delegate = delegate; + } + + @Override + public int read() throws IOException { + return delegate.read(); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + return delegate.read(b, off, len); + } + + @Override + public void close() throws IOException { + closed.set(true); + delegate.close(); + } + } }