Skip to content

Commit 0a932f9

Browse files
authored
Merge pull request #4979 from evolvedbinary/hotfix/avoid-input-stream-available
InputStream#available() should not be used to determine if there is data available
2 parents fcf1a96 + 39ee0f2 commit 0a932f9

File tree

4 files changed

+162
-10
lines changed

4 files changed

+162
-10
lines changed

exist-core/src/main/java/org/exist/xquery/XQueryContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ public XQueryContext(final Database db, final Profiler profiler) {
450450
this(db, null, profiler);
451451
}
452452

453-
private XQueryContext(@Nullable final Database db, @Nullable final Configuration configuration, @Nullable final Profiler profiler) {
453+
public XQueryContext(@Nullable final Database db, @Nullable final Configuration configuration, @Nullable final Profiler profiler) {
454454
this(db, configuration, profiler, true);
455455
}
456456

exist-core/src/main/java/org/exist/xquery/functions/request/GetData.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public Sequence eval(final Sequence[] args, @Nonnull final RequestWrapper reques
9797
isRequest = request.getInputStream();
9898

9999
//was there any POST content?
100-
if (isRequest != null && isRequest.available() > 0) {
100+
if (isRequest != null) {
101101
// 1) determine if exists mime database considers this binary data
102102
String contentType = request.getContentType();
103103
if (contentType != null) {
@@ -123,7 +123,7 @@ public Sequence eval(final Sequence[] args, @Nonnull final RequestWrapper reques
123123
try {
124124
//we have to cache the input stream, so we can reread it, as we may use it twice (once for xml attempt and once for string attempt)
125125
cache = FilterInputStreamCacheFactory.getCacheInstance(()
126-
-> (String) context.getBroker().getConfiguration().getProperty(Configuration.BINARY_CACHE_CLASS_PROPERTY), isRequest);
126+
-> (String) context.getConfiguration().getProperty(Configuration.BINARY_CACHE_CLASS_PROPERTY), isRequest);
127127
is = new CachingFilterInputStream(cache);
128128

129129
//mark the start of the stream
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
* eXist-db Open Source Native XML Database
3+
* Copyright (C) 2001 The eXist-db Authors
4+
*
5+
6+
* http://www.exist-db.org
7+
*
8+
* This library is free software; you can redistribute it and/or
9+
* modify it under the terms of the GNU Lesser General Public
10+
* License as published by the Free Software Foundation; either
11+
* version 2.1 of the License, or (at your option) any later version.
12+
*
13+
* This library is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16+
* Lesser General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU Lesser General Public
19+
* License along with this library; if not, write to the Free Software
20+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21+
*/
22+
package org.exist.xquery.functions.request;
23+
24+
import org.apache.commons.io.input.UnsynchronizedByteArrayInputStream;
25+
import org.exist.http.servlets.RequestWrapper;
26+
import org.exist.storage.BrokerPool;
27+
import org.exist.storage.DBBroker;
28+
import org.exist.util.Configuration;
29+
import org.exist.util.XMLReaderObjectFactory;
30+
import org.exist.util.XMLReaderPool;
31+
import org.exist.xquery.XPathException;
32+
import org.exist.xquery.XQueryContext;
33+
import org.exist.xquery.value.Sequence;
34+
import org.junit.Test;
35+
import org.w3c.dom.Document;
36+
import org.xml.sax.SAXException;
37+
38+
import java.io.IOException;
39+
import java.io.InputStream;
40+
import java.io.StringWriter;
41+
import java.nio.charset.StandardCharsets;
42+
import java.util.Properties;
43+
44+
import static org.easymock.EasyMock.*;
45+
import static org.junit.Assert.*;
46+
47+
/**
48+
* Unlike {@link GetDataTest} this test tries to test the code of
49+
* the {@link GetData} class directly.
50+
*
51+
* @author <a href="mailto:[email protected]">Adam Retter</a>
52+
*/
53+
public class GetData2Test {
54+
55+
@Test
56+
public void xmlChunkedTransferNonBlockingAvailable() throws XPathException, IOException, SAXException {
57+
final String content = "<hello>world</hello>";
58+
try (final InputStream is = new UnsynchronizedByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))) {
59+
60+
final RequestWrapper mockRequestWrapper = createNiceMock(RequestWrapper.class);
61+
final Configuration mockConfiguration = createNiceMock(Configuration.class);
62+
final BrokerPool mockBrokerPool = createNiceMock(BrokerPool.class);
63+
final DBBroker mockBroker = createNiceMock(DBBroker.class);
64+
final XMLReaderObjectFactory xmlReaderObjectFactory = new XMLReaderObjectFactory();
65+
final XMLReaderPool xmlReaderPool = new XMLReaderPool(xmlReaderObjectFactory, 20, 0);
66+
67+
expect(mockRequestWrapper.getSession(false)).andReturn(null);
68+
expect(mockRequestWrapper.getContentLength()).andReturn(-1l);
69+
expect(mockRequestWrapper.getHeader("Transfer-Encoding")).andReturn("chunked");
70+
expect(mockRequestWrapper.getInputStream()).andReturn(is);
71+
expect(mockRequestWrapper.getContentType()).andReturn("application/xml");
72+
73+
expect(mockConfiguration.getProperty(Configuration.BINARY_CACHE_CLASS_PROPERTY)).andReturn("org.exist.util.io.FileFilterInputStreamCache");
74+
expect(mockConfiguration.getProperty(XQueryContext.PROPERTY_XQUERY_RAISE_ERROR_ON_FAILED_RETRIEVAL, Boolean.FALSE)).andReturn(Boolean.FALSE);
75+
76+
expect(mockBrokerPool.getConfiguration()).andReturn(mockConfiguration);
77+
expect(mockBrokerPool.getActiveBroker()).andReturn(mockBroker).anyTimes();
78+
expect(mockBrokerPool.getParserPool()).andReturn(xmlReaderPool).anyTimes();
79+
expect(mockBroker.getBrokerPool()).andReturn(mockBrokerPool).anyTimes();
80+
81+
replay(mockRequestWrapper, mockConfiguration, mockBrokerPool, mockBroker);
82+
83+
xmlReaderObjectFactory.configure(mockConfiguration);
84+
85+
final XQueryContext context = new XQueryContext(mockBrokerPool, mockConfiguration, null);
86+
context.setHttpContext(new XQueryContext.HttpContext(mockRequestWrapper, null));
87+
88+
final GetData getData = new GetData(context);
89+
final Sequence result = getData.eval((Sequence[]) null, (Sequence) null);
90+
assertNotNull(result);
91+
assertFalse(result.isEmpty());
92+
assertEquals(1, result.getItemCount());
93+
assertTrue(result.itemAt(0) instanceof Document);
94+
assertEquals("hello", ((Document) result.itemAt(0)).getDocumentElement().getLocalName());
95+
assertEquals("world", ((Document) result.itemAt(0)).getDocumentElement().getTextContent());
96+
97+
verify(mockRequestWrapper, mockConfiguration, mockBrokerPool, mockBroker);
98+
}
99+
}
100+
101+
@Test
102+
public void xmlChunkedTransferNonBlockingNoneAvailable() throws XPathException, IOException {
103+
final String content = "<hello>world</hello>";
104+
try (final InputStream is = new ZeroAvailableInputStream(content.getBytes(StandardCharsets.UTF_8))) {
105+
106+
final RequestWrapper mockRequestWrapper = createNiceMock(RequestWrapper.class);
107+
final Configuration mockConfiguration = createNiceMock(Configuration.class);
108+
final BrokerPool mockBrokerPool = createNiceMock(BrokerPool.class);
109+
final DBBroker mockBroker = createNiceMock(DBBroker.class);
110+
final XMLReaderObjectFactory xmlReaderObjectFactory = new XMLReaderObjectFactory();
111+
final XMLReaderPool xmlReaderPool = new XMLReaderPool(xmlReaderObjectFactory, 20, 0);
112+
113+
expect(mockRequestWrapper.getSession(false)).andReturn(null);
114+
expect(mockRequestWrapper.getContentLength()).andReturn(-1l);
115+
expect(mockRequestWrapper.getHeader("Transfer-Encoding")).andReturn("chunked");
116+
expect(mockRequestWrapper.getInputStream()).andReturn(is);
117+
expect(mockRequestWrapper.getContentType()).andReturn("application/xml");
118+
119+
expect(mockConfiguration.getProperty(Configuration.BINARY_CACHE_CLASS_PROPERTY)).andReturn("org.exist.util.io.FileFilterInputStreamCache");
120+
expect(mockConfiguration.getProperty(XQueryContext.PROPERTY_XQUERY_RAISE_ERROR_ON_FAILED_RETRIEVAL, Boolean.FALSE)).andReturn(Boolean.FALSE);
121+
122+
expect(mockBrokerPool.getConfiguration()).andReturn(mockConfiguration);
123+
expect(mockBrokerPool.getActiveBroker()).andReturn(mockBroker).anyTimes();
124+
expect(mockBrokerPool.getParserPool()).andReturn(xmlReaderPool).anyTimes();
125+
expect(mockBroker.getBrokerPool()).andReturn(mockBrokerPool).anyTimes();
126+
127+
replay(mockRequestWrapper, mockConfiguration, mockBrokerPool, mockBroker);
128+
129+
xmlReaderObjectFactory.configure(mockConfiguration);
130+
131+
final XQueryContext context = new XQueryContext(mockBrokerPool, mockConfiguration, null);
132+
context.setHttpContext(new XQueryContext.HttpContext(mockRequestWrapper, null));
133+
134+
final GetData getData = new GetData(context);
135+
final Sequence result = getData.eval((Sequence[]) null, (Sequence) null);
136+
assertNotNull(result);
137+
assertFalse(result.isEmpty());
138+
assertEquals(1, result.getItemCount());
139+
assertTrue(result.itemAt(0) instanceof Document);
140+
assertEquals("hello", ((Document) result.itemAt(0)).getDocumentElement().getLocalName());
141+
assertEquals("world", ((Document) result.itemAt(0)).getDocumentElement().getTextContent());
142+
143+
verify(mockRequestWrapper, mockConfiguration, mockBrokerPool, mockBroker);
144+
}
145+
}
146+
147+
public static class ZeroAvailableInputStream extends UnsynchronizedByteArrayInputStream {
148+
149+
public ZeroAvailableInputStream(final byte[] data) {
150+
super(data);
151+
}
152+
153+
@Override
154+
public int available() {
155+
return 0;
156+
}
157+
}
158+
}

extensions/exquery/restxq/src/main/java/org/exist/extensions/exquery/restxq/impl/RestXqServiceImpl.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,6 @@ protected Sequence extractRequestBody(final HttpRequest request) throws RestXqSe
137137
//first, get the content of the request
138138
is = new CloseShieldInputStream(request.getInputStream());
139139

140-
if (is.available() <= 0) {
141-
return null;
142-
}
143-
144140
//if marking is not supported, we have to cache the input stream, so we can reread it, as we may use it twice (once for xml attempt and once for string attempt)
145141
if (!is.markSupported()) {
146142
cache = FilterInputStreamCacheFactory.getCacheInstance(() -> {
@@ -160,7 +156,7 @@ protected Sequence extractRequestBody(final HttpRequest request) throws RestXqSe
160156
try {
161157

162158
//was there any POST content?
163-
if (is != null && is.available() > 0) {
159+
if (is != null) {
164160
String contentType = request.getContentType();
165161
// 1) determine if exists mime database considers this binary data
166162
if (contentType != null) {
@@ -214,8 +210,6 @@ protected Sequence extractRequestBody(final HttpRequest request) throws RestXqSe
214210
}
215211
}
216212
}
217-
} catch (IOException e) {
218-
throw new RestXqServiceException(e.getMessage());
219213
} finally {
220214

221215
if (cache != null) {

0 commit comments

Comments
 (0)