Skip to content

Commit

Permalink
Avoid NullPointerException in ProxyInputStream.markSupported() when the
Browse files Browse the repository at this point in the history
underlying input stream is null

Javadoc
  • Loading branch information
garydgregory committed Aug 6, 2024
1 parent fb5e549 commit 1a2cfc8
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 27 deletions.
1 change: 1 addition & 0 deletions src/changes/changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ The <action> type attribute can be add,update,fix,remove.
<action dev="ggregory" type="fix" due-to="Gary Gregory">PathUtils.isPosix(Path, LinkOption...) should return false on null input.</action>
<action dev="ggregory" type="fix" due-to="Gary Gregory">AutoCloseInputStream(InputStream) uses ClosedInputStream.INSTANCE when its input is null.</action>
<action dev="ggregory" type="fix" due-to="Gary Gregory">Avoid NullPointerException in ProxyInputStream.available() when the underlying input stream is null.</action>
<action dev="ggregory" type="fix" due-to="Gary Gregory">Avoid NullPointerException in ProxyInputStream.markSupported() when the underlying input stream is null.</action>
<action dev="ggregory" type="fix" due-to="Gary Gregory">BufferedFileChannelInputStream.available() returns 0 before any reads.</action>
<action dev="ggregory" type="fix" due-to="Gary Gregory">BufferedFileChannelInputStream.available() should return 0 when the stream is closed instead of throwing an exception.</action>
<action dev="ggregory" type="fix" due-to="Gary Gregory">CharSequenceInputStream.available() should return 0 after the stream is closed.</action>
Expand Down
31 changes: 20 additions & 11 deletions src/main/java/org/apache/commons/io/input/ProxyInputStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,9 @@ protected void afterRead(final int n) throws IOException {
}

/**
* Invokes the delegate's {@code available()} method.
* Invokes the delegate's {@link InputStream#available()} method.
*
* @return the number of available bytes
* @return the number of available bytes, 0 if the stream is closed.
* @throws IOException if an I/O error occurs.
*/
@Override
Expand Down Expand Up @@ -145,7 +145,7 @@ protected void beforeRead(final int n) throws IOException {
}

/**
* Invokes the delegate's {@code close()} method.
* Invokes the delegate's {@link InputStream#close()} method.
*
* @throws IOException if an I/O error occurs.
*/
Expand Down Expand Up @@ -180,7 +180,7 @@ boolean isClosed() {
}

/**
* Invokes the delegate's {@code mark(int)} method.
* Invokes the delegate's {@link InputStream#mark(int)} method.
*
* @param readLimit read ahead limit
*/
Expand All @@ -190,17 +190,17 @@ public synchronized void mark(final int readLimit) {
}

/**
* Invokes the delegate's {@code markSupported()} method.
* Invokes the delegate's {@link InputStream#markSupported()} method.
*
* @return true if mark is supported, otherwise false
*/
@Override
public boolean markSupported() {
return in.markSupported();
return in != null && in.markSupported();
}

/**
* Invokes the delegate's {@code read()} method unless the stream is closed.
* Invokes the delegate's {@link InputStream#read()} method unless the stream is closed.
*
* @return the byte read or -1 if the end of stream
* @throws IOException if an I/O error occurs.
Expand All @@ -222,7 +222,7 @@ public int read() throws IOException {
}

/**
* Invokes the delegate's {@code read(byte[])} method.
* Invokes the delegate's {@link InputStream#read(byte[])} method.
*
* @param b the buffer to read the bytes into
* @return the number of bytes read or EOF if the end of stream
Expand All @@ -242,7 +242,7 @@ public int read(final byte[] b) throws IOException {
}

/**
* Invokes the delegate's {@code read(byte[], int, int)} method.
* Invokes the delegate's {@link InputStream#read(byte[], int, int)} method.
*
* @param b the buffer to read the bytes into
* @param off The start offset
Expand All @@ -264,7 +264,7 @@ public int read(final byte[] b, final int off, final int len) throws IOException
}

/**
* Invokes the delegate's {@code reset()} method.
* Invokes the delegate's {@link InputStream#reset()} method.
*
* @throws IOException if an I/O error occurs.
*/
Expand All @@ -278,7 +278,16 @@ public synchronized void reset() throws IOException {
}

/**
* Invokes the delegate's {@code skip(long)} method.
* Package-private for testing.
*
* @param in The input stream to set.
*/
void setIn(final InputStream in) {
this.in = in;
}

/**
* Invokes the delegate's {@link InputStream#skip(long)} method.
*
* @param n the number of bytes to skip
* @return the actual number of bytes skipped
Expand Down
62 changes: 48 additions & 14 deletions src/test/java/org/apache/commons/io/input/ProxyInputStreamTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*
* 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.
* WITHOUProxyInputStreamFixture WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Expand All @@ -20,6 +20,7 @@
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.spy;
Expand Down Expand Up @@ -47,10 +48,6 @@ private static final class ProxyInputStreamFixture extends ProxyInputStream {
ProxyInputStreamFixture(final InputStream proxy) {
super(proxy);
}

void setIn(final InputStream proxy) {
in = proxy;
}
}

@SuppressWarnings("resource")
Expand All @@ -69,12 +66,23 @@ static void testCloseHandleIOException(final ProxyInputStream inputStream) throw
assertFalse(spy.isClosed(), "closed");
}

@SuppressWarnings({ "resource", "unused" }) // For subclasses
/**
* Asserts that a ProxyInputStream's markSupported() equals the proxied value.
*
* @param inputStream The stream to test.
*/
@SuppressWarnings("resource") // unwrap() is a getter
protected void assertMarkSupportedEquals(final ProxyInputStream inputStream) {
assertNotNull(inputStream, "inputStream");
assertEquals(inputStream.unwrap().markSupported(), inputStream.markSupported());
}

@SuppressWarnings({ "resource", "unused", "unchecked" }) // For subclasses
protected T createFixture() throws IOException {
return (T) new ProxyInputStreamFixture(createProxySource());
return (T) new ProxyInputStreamFixture(createOriginInputStream());
}

protected InputStream createProxySource() {
protected InputStream createOriginInputStream() {
return CharSequenceInputStream.builder().setCharSequence("abc").get();
}

Expand Down Expand Up @@ -121,6 +129,32 @@ protected void testEos(final T inputStream) {
// empty
}

@Test
public void testMarkSupported() throws IOException {
try (T inputStream = createFixture()) {
assertMarkSupportedEquals(inputStream);
}
}

@SuppressWarnings("resource")
@Test
public void testMarkSupportedAfterClose() throws IOException {
final T shadow;
try (T inputStream = createFixture()) {
shadow = inputStream;
}
assertMarkSupportedEquals(shadow);
}

@Test
public void testMarkSupportedOnNull() throws IOException {
try (ProxyInputStream fixture = createFixture()) {
assertMarkSupportedEquals(fixture);
fixture.setIn(null);
assertFalse(fixture.markSupported());
}
}

@Test
public void testRead() throws IOException {
try (T inputStream = createFixture()) {
Expand All @@ -132,7 +166,7 @@ public void testRead() throws IOException {
assertEquals('c', found);
found = inputStream.read();
assertEquals(-1, found);
testEos(inputStream);
testEos((T) inputStream);
}
}

Expand All @@ -159,7 +193,7 @@ public void testReadArrayAtMiddleFully() throws IOException {
assertArrayEquals(new byte[] { 0, 0, 'a', 'b', 'c' }, dest);
found = inputStream.read(dest, 2, 3);
assertEquals(-1, found);
testEos(inputStream);
testEos((T) inputStream);
}
}

Expand All @@ -172,7 +206,7 @@ public void testReadArrayAtStartFully() throws IOException {
assertArrayEquals(new byte[] { 'a', 'b', 'c', 0, 0 }, dest);
found = inputStream.read(dest, 0, 5);
assertEquals(-1, found);
testEos(inputStream);
testEos((T) inputStream);
}
}

Expand All @@ -189,7 +223,7 @@ public void testReadArrayAtStartPartial() throws IOException {
assertArrayEquals(new byte[] { 'c', 0, 0, 0, 0 }, dest);
found = inputStream.read(dest, 0, 2);
assertEquals(-1, found);
testEos(inputStream);
testEos((T) inputStream);
}
}

Expand All @@ -202,7 +236,7 @@ public void testReadArrayFully() throws IOException {
assertArrayEquals(new byte[] { 'a', 'b', 'c', 0, 0 }, dest);
found = inputStream.read(dest);
assertEquals(-1, found);
testEos(inputStream);
testEos((T) inputStream);
}
}

Expand All @@ -219,7 +253,7 @@ public void testReadArrayPartial() throws IOException {
assertArrayEquals(new byte[] { 'c', 0 }, dest);
found = inputStream.read(dest);
assertEquals(-1, found);
testEos(inputStream);
testEos((T) inputStream);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@
public class ThrottledInputStreamTest extends ProxyInputStreamTest<ThrottledInputStream> {

@Override
@SuppressWarnings("resource")
@SuppressWarnings({ "resource" })
protected ThrottledInputStream createFixture() throws IOException {
return ThrottledInputStream.builder().setInputStream(createProxySource()).get();
return ThrottledInputStream.builder().setInputStream(createOriginInputStream()).get();
}

@Test
Expand Down

0 comments on commit 1a2cfc8

Please sign in to comment.