Skip to content

Commit 1c0fc45

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

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 javax.servlet.http.HttpServletRequest;
113113
import javax.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;
@@ -1827,14 +1829,22 @@ private void writeResourceAs(final DocumentImpl resource, final DBBroker broker,
18271829
outputProperties.setProperty("omit-xml-declaration", "no");
18281830
}
18291831

1830-
final OutputStreamWriter writer = new OutputStreamWriter(response.getOutputStream(), encoding);
1831-
sax.setOutput(writer, outputProperties);
1832-
serializer.setSAXHandlers(sax, sax);
1832+
// 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
1833+
@Nullable Writer writerToClose = null;
1834+
try {
1835+
final Writer writer = new OutputStreamWriter(response.getOutputStream(), encoding);
1836+
sax.setOutput(writer, outputProperties);
1837+
serializer.setSAXHandlers(sax, sax);
18331838

1834-
serializer.toSAX(resource);
1839+
serializer.toSAX(resource);
18351840

1836-
writer.flush();
1837-
writer.close(); // DO NOT use in try-write-resources, otherwise ther response stream is always closed, and we can't report the errors
1841+
writer.flush();
1842+
writerToClose = writer;
1843+
} finally {
1844+
if (writerToClose != null) {
1845+
writerToClose.close();
1846+
}
1847+
}
18381848
} catch (final SAXException saxe) {
18391849
LOG.warn(saxe);
18401850
throw new BadRequestException("Error while serializing XML: " + saxe.getMessage());
@@ -1870,28 +1880,36 @@ private void writeXPathExceptionHtml(final HttpServletResponse response,
18701880

18711881
response.setContentType(MimeType.HTML_TYPE.getName() + "; charset=" + encoding);
18721882

1873-
final OutputStreamWriter writer = new OutputStreamWriter(response.getOutputStream(), encoding);
1874-
writer.write(QUERY_ERROR_HEAD);
1875-
writer.write("<p class=\"path\"><span class=\"high\">Path</span>: ");
1876-
writer.write("<a href=\"");
1877-
writer.write(path);
1878-
writer.write("\">");
1879-
writer.write(path);
1880-
writer.write("</a>");
1881-
1882-
writer.write("<p class=\"errmsg\">");
1883-
final String message = e.getMessage() == null ? e.toString() : e.getMessage();
1884-
writer.write(XMLUtil.encodeAttrMarkup(message));
1885-
writer.write("");
1886-
if (query != null) {
1887-
writer.write("<span class=\"high\">Query</span>:<pre>");
1888-
writer.write(XMLUtil.encodeAttrMarkup(query));
1889-
writer.write("</pre>");
1890-
}
1891-
writer.write("</body></html>");
1883+
// 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
1884+
@Nullable Writer writerToClose = null;
1885+
try {
1886+
final Writer writer = new OutputStreamWriter(response.getOutputStream(), encoding);
1887+
writer.write(QUERY_ERROR_HEAD);
1888+
writer.write("<p class=\"path\"><span class=\"high\">Path</span>: ");
1889+
writer.write("<a href=\"");
1890+
writer.write(path);
1891+
writer.write("\">");
1892+
writer.write(path);
1893+
writer.write("</a>");
18921894

1893-
writer.flush();
1894-
writer.close();
1895+
writer.write("<p class=\"errmsg\">");
1896+
final String message = e.getMessage() == null ? e.toString() : e.getMessage();
1897+
writer.write(XMLUtil.encodeAttrMarkup(message));
1898+
writer.write("");
1899+
if (query != null) {
1900+
writer.write("<span class=\"high\">Query</span>:<pre>");
1901+
writer.write(XMLUtil.encodeAttrMarkup(query));
1902+
writer.write("</pre>");
1903+
}
1904+
writer.write("</body></html>");
1905+
1906+
writer.flush();
1907+
writerToClose = writer;
1908+
} finally {
1909+
if (writerToClose != null) {
1910+
writerToClose.close();
1911+
}
1912+
}
18951913
}
18961914

18971915
/**
@@ -1913,8 +1931,10 @@ private void writeXPathException(final HttpServletResponse response,
19131931

19141932
response.setContentType(MimeType.XML_TYPE.getName() + "; charset=" + encoding);
19151933

1916-
try(final OutputStreamWriter writer =
1917-
new OutputStreamWriter(response.getOutputStream(), encoding)) {
1934+
// 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
1935+
@Nullable Writer writerToClose = null;
1936+
try {
1937+
final Writer writer = new OutputStreamWriter(response.getOutputStream(), encoding);
19181938

19191939
writer.write("<?xml version=\"1.0\" ?>");
19201940
writer.write("<exception><path>");
@@ -1930,6 +1950,13 @@ private void writeXPathException(final HttpServletResponse response,
19301950
writer.write("</query>");
19311951
}
19321952
writer.write("</exception>");
1953+
1954+
writer.flush();
1955+
writerToClose = writer;
1956+
} finally {
1957+
if (writerToClose != null) {
1958+
writerToClose.close();
1959+
}
19331960
}
19341961
}
19351962

@@ -1947,17 +1974,23 @@ private void writeXUpdateResult(final HttpServletResponse response,
19471974

19481975
response.setContentType(MimeType.XML_TYPE.getName() + "; charset=" + encoding);
19491976

1950-
final OutputStreamWriter writer =
1951-
new OutputStreamWriter(response.getOutputStream(), encoding);
1952-
1953-
writer.write("<?xml version=\"1.0\" ?>");
1954-
writer.write("<exist:modifications xmlns:exist=\""
1977+
// 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
1978+
@Nullable Writer writerToClose = null;
1979+
try {
1980+
final Writer writer = new OutputStreamWriter(response.getOutputStream(), encoding);
1981+
writer.write("<?xml version=\"1.0\" ?>");
1982+
writer.write("<exist:modifications xmlns:exist=\""
19551983
+ Namespaces.EXIST_NS + "\" count=\"" + updateCount + "\">");
1956-
writer.write(updateCount + " modifications processed.");
1957-
writer.write("</exist:modifications>");
1984+
writer.write(updateCount + " modifications processed.");
1985+
writer.write("</exist:modifications>");
19581986

1959-
writer.flush();
1960-
writer.close();
1987+
writer.flush();
1988+
writerToClose = writer;
1989+
} finally {
1990+
if (writerToClose != null) {
1991+
writerToClose.close();
1992+
}
1993+
}
19611994
}
19621995

19631996
/**
@@ -1980,12 +2013,12 @@ protected void writeCollection(final HttpServletResponse response,
19802013

19812014
setCreatedAndLastModifiedHeaders(response, collection.getCreated(), collection.getCreated());
19822015

1983-
final OutputStreamWriter writer =
1984-
new OutputStreamWriter(response.getOutputStream(), encoding);
1985-
19862016
SAXSerializer serializer = null;
19872017

2018+
// 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
2019+
@Nullable Writer writerToClose = null;
19882020
try {
2021+
final Writer writer = new OutputStreamWriter(response.getOutputStream(), encoding);
19892022
serializer = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class);
19902023

19912024
serializer.setOutput(writer, defaultProperties);
@@ -2085,7 +2118,7 @@ protected void writeCollection(final HttpServletResponse response,
20852118
serializer.endDocument();
20862119

20872120
writer.flush();
2088-
writer.close();
2121+
writerToClose = writer;
20892122

20902123
} catch (final SAXException e) {
20912124
// should never happen
@@ -2094,6 +2127,9 @@ protected void writeCollection(final HttpServletResponse response,
20942127
if (serializer != null) {
20952128
SerializerPool.getInstance().returnObject(serializer);
20962129
}
2130+
if (writerToClose != null) {
2131+
writerToClose.close();
2132+
}
20972133
}
20982134
}
20992135

@@ -2165,14 +2201,23 @@ private void writeResultXML(final HttpServletResponse response,
21652201
if (wrap) {
21662202
outputProperties.setProperty("method", "xml");
21672203
}
2168-
final Writer writer = new OutputStreamWriter(response.getOutputStream(), encoding);
2169-
final XQuerySerializer serializer = new XQuerySerializer(broker, outputProperties, writer);
21702204

2171-
//Marshaller.marshall(broker, results, start, howmany, serializer.getContentHandler());
2172-
serializer.serialize(results, start, howmany, wrap, typed, compilationTime, executionTime);
2205+
// 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
2206+
@Nullable Writer writerToClose = null;
2207+
try {
2208+
final Writer writer = new OutputStreamWriter(response.getOutputStream(), encoding);
2209+
final XQuerySerializer serializer = new XQuerySerializer(broker, outputProperties, writer);
21732210

2174-
writer.flush();
2175-
writer.close();
2211+
//Marshaller.marshall(broker, results, start, howmany, serializer.getContentHandler());
2212+
serializer.serialize(results, start, howmany, wrap, typed, compilationTime, executionTime);
2213+
2214+
writer.flush();
2215+
writerToClose = writer;
2216+
} finally {
2217+
if (writerToClose != null) {
2218+
writerToClose.close();
2219+
}
2220+
}
21762221

21772222
} catch (final SAXException e) {
21782223
LOG.warn(e);
@@ -2208,7 +2253,9 @@ private void writeResultJSON(final HttpServletResponse response,
22082253
outputProperties.setProperty(Serializer.GENERATE_DOC_EVENTS, "false");
22092254
try {
22102255
serializer.setProperties(outputProperties);
2211-
try (final Writer writer = new OutputStreamWriter(response.getOutputStream(), outputProperties.getProperty(OutputKeys.ENCODING))) {
2256+
@Nullable Writer writerToClose = null;
2257+
try {
2258+
final Writer writer = new OutputStreamWriter(response.getOutputStream(), outputProperties.getProperty(OutputKeys.ENCODING));
22122259
final JSONObject root = new JSONObject();
22132260
root.addObject(new JSONSimpleProperty("start", Integer.toString(start), true));
22142261
root.addObject(new JSONSimpleProperty("count", Integer.toString(howmany), true));
@@ -2252,6 +2299,12 @@ private void writeResultJSON(final HttpServletResponse response,
22522299
root.serialize(writer, true);
22532300

22542301
writer.flush();
2302+
writerToClose = writer;
2303+
2304+
} finally {
2305+
if (writerToClose != null) {
2306+
writerToClose.close();
2307+
}
22552308
}
22562309
} catch (final IOException | XPathException | SAXException e) {
22572310
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)