Skip to content

Commit c694c87

Browse files
committed
[bugfix] Ensure that the HTTP response OutputStream is only closed if an exception does not occur during serialization
1 parent bf86185 commit c694c87

File tree

2 files changed

+108
-54
lines changed

2 files changed

+108
-54
lines changed

exist-core/src/main/java/org/exist/http/RESTServer.java

Lines changed: 102 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@
111111

112112
import jakarta.servlet.http.HttpServletRequest;
113113
import jakarta.servlet.http.HttpServletResponse;
114+
115+
import javax.annotation.Nullable;
114116
import javax.xml.XMLConstants;
115117
import javax.xml.parsers.ParserConfigurationException;
116118
import javax.xml.stream.XMLStreamException;
@@ -1851,14 +1853,22 @@ private void writeResourceAs(final DocumentImpl resource, final DBBroker broker,
18511853
outputProperties.setProperty("omit-xml-declaration", "no");
18521854
}
18531855

1854-
final OutputStreamWriter writer = new OutputStreamWriter(response.getOutputStream(), encoding);
1855-
sax.setOutput(writer, outputProperties);
1856-
serializer.setSAXHandlers(sax, sax);
1856+
// NOTE(AR) we only close the OutputStreamWriter if serialization succeeds, otherwise we raise a BadRequestException below which needs the OutputStream to remain open so that it can report the issue via the HTTP response to the client
1857+
@Nullable Writer writerToClose = null;
1858+
try {
1859+
final Writer writer = new OutputStreamWriter(response.getOutputStream(), encoding);
1860+
sax.setOutput(writer, outputProperties);
1861+
serializer.setSAXHandlers(sax, sax);
18571862

1858-
serializer.toSAX(resource);
1863+
serializer.toSAX(resource);
18591864

1860-
writer.flush();
1861-
writer.close(); // DO NOT use in try-write-resources, otherwise ther response stream is always closed, and we can't report the errors
1865+
writer.flush();
1866+
writerToClose = writer;
1867+
} finally {
1868+
if (writerToClose != null) {
1869+
writerToClose.close();
1870+
}
1871+
}
18621872
} catch (final SAXException saxe) {
18631873
LOG.warn(saxe);
18641874
throw new BadRequestException("Error while serializing XML: " + saxe.getMessage());
@@ -1894,28 +1904,36 @@ private void writeXPathExceptionHtml(final HttpServletResponse response,
18941904

18951905
response.setContentType(MimeType.HTML_TYPE.getName() + "; charset=" + encoding);
18961906

1897-
final OutputStreamWriter writer = new OutputStreamWriter(response.getOutputStream(), encoding);
1898-
writer.write(QUERY_ERROR_HEAD);
1899-
writer.write("<p class=\"path\"><span class=\"high\">Path</span>: ");
1900-
writer.write("<a href=\"");
1901-
writer.write(path);
1902-
writer.write("\">");
1903-
writer.write(path);
1904-
writer.write("</a>");
1905-
1906-
writer.write("<p class=\"errmsg\">");
1907-
final String message = e.getMessage() == null ? e.toString() : e.getMessage();
1908-
writer.write(XMLUtil.encodeAttrMarkup(message));
1909-
writer.write("");
1910-
if (query != null) {
1911-
writer.write("<span class=\"high\">Query</span>:<pre>");
1912-
writer.write(XMLUtil.encodeAttrMarkup(query));
1913-
writer.write("</pre>");
1914-
}
1915-
writer.write("</body></html>");
1907+
// NOTE(AR) we only close the OutputStreamWriter if serialization succeeds, otherwise we raise a BadRequestException below which needs the OutputStream to remain open so that it can report the issue via the HTTP response to the client
1908+
@Nullable Writer writerToClose = null;
1909+
try {
1910+
final Writer writer = new OutputStreamWriter(response.getOutputStream(), encoding);
1911+
writer.write(QUERY_ERROR_HEAD);
1912+
writer.write("<p class=\"path\"><span class=\"high\">Path</span>: ");
1913+
writer.write("<a href=\"");
1914+
writer.write(path);
1915+
writer.write("\">");
1916+
writer.write(path);
1917+
writer.write("</a>");
19161918

1917-
writer.flush();
1918-
writer.close();
1919+
writer.write("<p class=\"errmsg\">");
1920+
final String message = e.getMessage() == null ? e.toString() : e.getMessage();
1921+
writer.write(XMLUtil.encodeAttrMarkup(message));
1922+
writer.write("");
1923+
if (query != null) {
1924+
writer.write("<span class=\"high\">Query</span>:<pre>");
1925+
writer.write(XMLUtil.encodeAttrMarkup(query));
1926+
writer.write("</pre>");
1927+
}
1928+
writer.write("</body></html>");
1929+
1930+
writer.flush();
1931+
writerToClose = writer;
1932+
} finally {
1933+
if (writerToClose != null) {
1934+
writerToClose.close();
1935+
}
1936+
}
19191937
}
19201938

19211939
/**
@@ -1937,8 +1955,10 @@ private void writeXPathException(final HttpServletResponse response,
19371955

19381956
response.setContentType(MimeType.XML_TYPE.getName() + "; charset=" + encoding);
19391957

1940-
try(final OutputStreamWriter writer =
1941-
new OutputStreamWriter(response.getOutputStream(), encoding)) {
1958+
// NOTE(AR) we only close the OutputStreamWriter if serialization succeeds, otherwise we raise a BadRequestException below which needs the OutputStream to remain open so that it can report the issue via the HTTP response to the client
1959+
@Nullable Writer writerToClose = null;
1960+
try {
1961+
final Writer writer = new OutputStreamWriter(response.getOutputStream(), encoding);
19421962

19431963
writer.write("<?xml version=\"1.0\" ?>");
19441964
writer.write("<exception><path>");
@@ -1954,6 +1974,13 @@ private void writeXPathException(final HttpServletResponse response,
19541974
writer.write("</query>");
19551975
}
19561976
writer.write("</exception>");
1977+
1978+
writer.flush();
1979+
writerToClose = writer;
1980+
} finally {
1981+
if (writerToClose != null) {
1982+
writerToClose.close();
1983+
}
19571984
}
19581985
}
19591986

@@ -1971,17 +1998,23 @@ private void writeXUpdateResult(final HttpServletResponse response,
19711998

19721999
response.setContentType(MimeType.XML_TYPE.getName() + "; charset=" + encoding);
19732000

1974-
final OutputStreamWriter writer =
1975-
new OutputStreamWriter(response.getOutputStream(), encoding);
1976-
1977-
writer.write("<?xml version=\"1.0\" ?>");
1978-
writer.write("<exist:modifications xmlns:exist=\""
2001+
// NOTE(AR) we only close the OutputStreamWriter if serialization succeeds, otherwise we raise a BadRequestException below which needs the OutputStream to remain open so that it can report the issue via the HTTP response to the client
2002+
@Nullable Writer writerToClose = null;
2003+
try {
2004+
final Writer writer = new OutputStreamWriter(response.getOutputStream(), encoding);
2005+
writer.write("<?xml version=\"1.0\" ?>");
2006+
writer.write("<exist:modifications xmlns:exist=\""
19792007
+ Namespaces.EXIST_NS + "\" count=\"" + updateCount + "\">");
1980-
writer.write(updateCount + " modifications processed.");
1981-
writer.write("</exist:modifications>");
2008+
writer.write(updateCount + " modifications processed.");
2009+
writer.write("</exist:modifications>");
19822010

1983-
writer.flush();
1984-
writer.close();
2011+
writer.flush();
2012+
writerToClose = writer;
2013+
} finally {
2014+
if (writerToClose != null) {
2015+
writerToClose.close();
2016+
}
2017+
}
19852018
}
19862019

19872020
/**
@@ -2004,12 +2037,12 @@ protected void writeCollection(final HttpServletResponse response,
20042037

20052038
setCreatedAndLastModifiedHeaders(response, collection.getCreated(), collection.getCreated());
20062039

2007-
final OutputStreamWriter writer =
2008-
new OutputStreamWriter(response.getOutputStream(), encoding);
2009-
20102040
SAXSerializer serializer = null;
20112041

2042+
// NOTE(AR) we only close the OutputStreamWriter if serialization succeeds, otherwise we raise a BadRequestException below which needs the OutputStream to remain open so that it can report the issue via the HTTP response to the client
2043+
@Nullable Writer writerToClose = null;
20122044
try {
2045+
final Writer writer = new OutputStreamWriter(response.getOutputStream(), encoding);
20132046
serializer = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class);
20142047

20152048
serializer.setOutput(writer, defaultProperties);
@@ -2109,7 +2142,7 @@ protected void writeCollection(final HttpServletResponse response,
21092142
serializer.endDocument();
21102143

21112144
writer.flush();
2112-
writer.close();
2145+
writerToClose = writer;
21132146

21142147
} catch (final SAXException e) {
21152148
// should never happen
@@ -2118,6 +2151,9 @@ protected void writeCollection(final HttpServletResponse response,
21182151
if (serializer != null) {
21192152
SerializerPool.getInstance().returnObject(serializer);
21202153
}
2154+
if (writerToClose != null) {
2155+
writerToClose.close();
2156+
}
21212157
}
21222158
}
21232159

@@ -2193,14 +2229,23 @@ private void writeResultXML(final HttpServletResponse response,
21932229
if (wrap) {
21942230
outputProperties.setProperty("method", "xml");
21952231
}
2196-
final Writer writer = new OutputStreamWriter(response.getOutputStream(), encoding);
2197-
final XQuerySerializer serializer = new XQuerySerializer(broker, outputProperties, writer);
21982232

2199-
//Marshaller.marshall(broker, results, start, howmany, serializer.getContentHandler());
2200-
serializer.serialize(results, start, howmany, wrap, typed, compilationTime, executionTime);
2233+
// NOTE(AR) we only close the OutputStreamWriter if serialization succeeds, otherwise we raise a BadRequestException below which needs the OutputStream to remain open so that it can report the issue via the HTTP response to the client
2234+
@Nullable Writer writerToClose = null;
2235+
try {
2236+
final Writer writer = new OutputStreamWriter(response.getOutputStream(), encoding);
2237+
final XQuerySerializer serializer = new XQuerySerializer(broker, outputProperties, writer);
22012238

2202-
writer.flush();
2203-
writer.close();
2239+
//Marshaller.marshall(broker, results, start, howmany, serializer.getContentHandler());
2240+
serializer.serialize(results, start, howmany, wrap, typed, compilationTime, executionTime);
2241+
2242+
writer.flush();
2243+
writerToClose = writer;
2244+
} finally {
2245+
if (writerToClose != null) {
2246+
writerToClose.close();
2247+
}
2248+
}
22042249

22052250
} catch (final SAXException e) {
22062251
LOG.warn(e);
@@ -2236,7 +2281,9 @@ private void writeResultJSON(final HttpServletResponse response,
22362281
outputProperties.setProperty(Serializer.GENERATE_DOC_EVENTS, "false");
22372282
try {
22382283
serializer.setProperties(outputProperties);
2239-
try (final Writer writer = new OutputStreamWriter(response.getOutputStream(), getEncoding(outputProperties))) {
2284+
@Nullable Writer writerToClose = null;
2285+
try {
2286+
final Writer writer = new OutputStreamWriter(response.getOutputStream(), getEncoding(outputProperties));
22402287
final JSONObject root = new JSONObject();
22412288
root.addObject(new JSONSimpleProperty("start", Integer.toString(start), true));
22422289
root.addObject(new JSONSimpleProperty("count", Integer.toString(howmany), true));
@@ -2280,6 +2327,12 @@ private void writeResultJSON(final HttpServletResponse response,
22802327
root.serialize(writer, true);
22812328

22822329
writer.flush();
2330+
writerToClose = writer;
2331+
2332+
} finally {
2333+
if (writerToClose != null) {
2334+
writerToClose.close();
2335+
}
22832336
}
22842337
} catch (final IOException | XPathException | SAXException e) {
22852338
throw new BadRequestException("Error while serializing xml: " + e.toString(), e);

exist-core/src/test/java/org/exist/storage/XIncludeSerializerTest.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -289,12 +289,13 @@ public void fallback2() throws IOException {
289289
connect.setRequestMethod("GET");
290290
connect.connect();
291291

292-
final BufferedReader reader = new BufferedReader(new InputStreamReader(connect.getInputStream(), "UTF-8"));
293-
String line;
294292
final StringBuilder out = new StringBuilder();
295-
while ((line = reader.readLine()) != null) {
296-
out.append(line);
297-
out.append("\r\n");
293+
try (final BufferedReader reader = new BufferedReader(new InputStreamReader(connect.getInputStream(), "UTF-8"))) {
294+
String line;
295+
while ((line = reader.readLine()) != null) {
296+
out.append(line);
297+
out.append("\r\n");
298+
}
298299
}
299300
final String responseXML = out.toString();
300301
}

0 commit comments

Comments
 (0)