| /* ==================================================================== |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| 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. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| ==================================================================== */ |
| |
| package org.apache.poi.util; |
| |
| 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.assertThrows; |
| import static org.junit.jupiter.api.Assertions.assertTrue; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.EOFException; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.PushbackInputStream; |
| import java.nio.ByteBuffer; |
| import java.nio.channels.ReadableByteChannel; |
| import java.nio.charset.StandardCharsets; |
| import java.util.Random; |
| |
| import org.apache.poi.EmptyFileException; |
| import org.junit.jupiter.api.AfterAll; |
| import org.junit.jupiter.api.BeforeAll; |
| import org.junit.jupiter.api.Test; |
| |
| final class TestIOUtils { |
| private static File TMP; |
| private static final long LENGTH = 300 + new Random().nextInt(9000); |
| |
| @BeforeAll |
| public static void setUp() throws IOException { |
| TMP = File.createTempFile("poi-ioutils-", ""); |
| try (OutputStream os = new FileOutputStream(TMP)) { |
| for (int i = 0; i < LENGTH; i++) { |
| os.write(0x01); |
| } |
| } |
| } |
| |
| @AfterAll |
| public static void tearDown() { |
| if (TMP != null) { |
| assertTrue(TMP.delete()); |
| } |
| } |
| |
| private static InputStream data123() { |
| return new ByteArrayInputStream(new byte[]{1,2,3}); |
| } |
| |
| @Test |
| void testPeekFirst8Bytes() throws Exception { |
| assertArrayEquals("01234567".getBytes(StandardCharsets.UTF_8), |
| IOUtils.peekFirst8Bytes(new ByteArrayInputStream("0123456789".getBytes(StandardCharsets.UTF_8)))); |
| } |
| |
| @Test |
| void testPeekFirst8BytesWithPushbackInputStream() throws Exception { |
| assertArrayEquals("01234567".getBytes(StandardCharsets.UTF_8), |
| IOUtils.peekFirst8Bytes(new PushbackInputStream(new ByteArrayInputStream("0123456789".getBytes(StandardCharsets.UTF_8)), 8))); |
| } |
| |
| @Test |
| void testPeekFirst8BytesTooLessAvailable() throws Exception { |
| assertArrayEquals(new byte[] { 1, 2, 3, 0, 0, 0, 0, 0}, IOUtils.peekFirst8Bytes(data123())); |
| } |
| |
| @Test |
| void testPeekFirst8BytesEmpty() { |
| assertThrows(EmptyFileException.class, () -> |
| IOUtils.peekFirst8Bytes(new ByteArrayInputStream(new byte[0]))); |
| } |
| |
| @Test |
| void testToByteArray() throws Exception { |
| assertArrayEquals(new byte[] { 1, 2, 3}, IOUtils.toByteArray(data123())); |
| } |
| |
| @Test |
| void testToByteArrayToSmall() { |
| assertThrows(IOException.class, () -> IOUtils.toByteArray(data123(), 10)); |
| } |
| |
| @Test |
| void testToByteArrayMaxLengthToSmall() { |
| assertThrows(IOException.class, () -> IOUtils.toByteArray(data123(), 10, 10)); |
| } |
| |
| @Test |
| void testToByteArrayNegativeLength() { |
| assertThrows(RecordFormatException.class, () -> IOUtils.toByteArray(data123(), -1)); |
| } |
| |
| @Test |
| void testToByteArrayNegativeMaxLength() { |
| assertThrows(RecordFormatException.class, () -> IOUtils.toByteArray(data123(), 10, -1)); |
| } |
| |
| @Test |
| void testToByteArrayByteBuffer() { |
| assertArrayEquals(new byte[] { 1, 2, 3}, |
| IOUtils.toByteArray(ByteBuffer.wrap(new byte[]{1, 2, 3}), 10)); |
| } |
| |
| @Test |
| void testToByteArrayByteBufferNonArray() { |
| ByteBuffer buffer = ByteBuffer.allocate(3); |
| buffer.put(new byte[] { 1, 2, 3}); |
| buffer.position(0); |
| assertFalse(buffer.asReadOnlyBuffer().hasArray()); |
| assertEquals(3, buffer.asReadOnlyBuffer().remaining()); |
| |
| assertArrayEquals(new byte[] { 1, 2, 3}, |
| IOUtils.toByteArray(buffer.asReadOnlyBuffer(), 3)); |
| } |
| |
| @Test |
| void testToByteArrayByteBufferToSmall() { |
| assertArrayEquals(new byte[] { 1, 2, 3, 4, 5, 6, 7}, |
| IOUtils.toByteArray(ByteBuffer.wrap(new byte[]{1, 2, 3, 4, 5, 6, 7}), 3)); |
| } |
| |
| @Test |
| void testSkipFully() throws IOException { |
| try (InputStream is = new FileInputStream(TMP)) { |
| long skipped = IOUtils.skipFully(is, 20000L); |
| assertEquals(LENGTH, skipped); |
| } |
| } |
| |
| @Test |
| void testSkipFullyGtIntMax() throws IOException { |
| try (InputStream is = new FileInputStream(TMP)) { |
| long skipped = IOUtils.skipFully(is, Integer.MAX_VALUE + 20000L); |
| assertEquals(LENGTH, skipped); |
| } |
| } |
| |
| @Test |
| void testSkipFullyByteArray() throws IOException { |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
| try (InputStream is = new FileInputStream(TMP)) { |
| assertEquals(LENGTH, IOUtils.copy(is, bos)); |
| long skipped = IOUtils.skipFully(new ByteArrayInputStream(bos.toByteArray()), 20000L); |
| assertEquals(LENGTH, skipped); |
| } |
| } |
| |
| @Test |
| void testSkipFullyByteArrayGtIntMax() throws IOException { |
| ByteArrayOutputStream bos = new ByteArrayOutputStream(); |
| try (InputStream is = new FileInputStream(TMP)) { |
| assertEquals(LENGTH, IOUtils.copy(is, bos)); |
| long skipped = IOUtils.skipFully(new ByteArrayInputStream(bos.toByteArray()), Integer.MAX_VALUE + 20000L); |
| assertEquals(LENGTH, skipped); |
| } |
| } |
| |
| @Test |
| void testCopyToFile() throws IOException { |
| File dest = File.createTempFile("poi-ioutils-", ""); |
| try { |
| try (InputStream is = new FileInputStream(TMP)) { |
| assertEquals(LENGTH, IOUtils.copy(is, dest)); |
| } |
| |
| try (FileInputStream strOrig = new FileInputStream(TMP); |
| FileInputStream strDest = new FileInputStream(dest)) { |
| byte[] bytesOrig = new byte[(int)LENGTH]; |
| byte[] bytesDest = new byte[(int)LENGTH]; |
| IOUtils.readFully(strOrig, bytesOrig); |
| IOUtils.readFully(strDest, bytesDest); |
| assertArrayEquals(bytesOrig, bytesDest); |
| } |
| } finally { |
| assertTrue(dest.delete()); |
| } |
| } |
| |
| @Test |
| void testCopyToInvalidFile() throws IOException { |
| try (InputStream is = new FileInputStream(TMP)) { |
| assertThrows(RuntimeException.class, |
| () -> { |
| // try with two different paths so we fail on both Unix and Windows |
| IOUtils.copy(is, new File("/notexisting/directory/structure")); |
| IOUtils.copy(is, new File("c:\\note&/()\"§=§%&!§$81§0_:;,.-'#*+~`?ß´ß0´ß9243xisting\\directory\\structure")); |
| }); |
| } |
| } |
| |
| @Test |
| void testSkipFullyBug61294() throws IOException { |
| long skipped = IOUtils.skipFully(new ByteArrayInputStream(new byte[0]), 1); |
| assertEquals(-1L, skipped); |
| } |
| |
| @Test |
| void testZeroByte() throws IOException { |
| long skipped = IOUtils.skipFully((new ByteArrayInputStream(new byte[0])), 100); |
| assertEquals(-1L, skipped); |
| } |
| |
| @Test |
| void testSkipZero() throws IOException { |
| try (InputStream is = new FileInputStream(TMP)) { |
| long skipped = IOUtils.skipFully(is, 0); |
| assertEquals(0, skipped); |
| } |
| } |
| |
| @Test |
| void testSkipNegative() throws IOException { |
| try (InputStream is = new FileInputStream(TMP)) { |
| assertThrows(IllegalArgumentException.class, () -> IOUtils.skipFully(is, -1)); |
| } |
| } |
| |
| @Test |
| void testMaxLengthTooLong() throws IOException { |
| try (InputStream is = new FileInputStream(TMP)) { |
| assertThrows(RecordFormatException.class, () -> IOUtils.toByteArray(is, Integer.MAX_VALUE, 100)); |
| } |
| } |
| |
| @Test |
| void testMaxLengthIgnored() throws IOException { |
| try (InputStream is = new FileInputStream(TMP)) { |
| int len = IOUtils.toByteArray(is, 90, Integer.MAX_VALUE).length; |
| assertEquals(90, len); |
| len = IOUtils.toByteArray(is, 90, 100).length; |
| assertEquals(90, len); |
| len = IOUtils.toByteArray(is, Integer.MAX_VALUE, Integer.MAX_VALUE).length; |
| assertTrue(len > 300-2*90); |
| } |
| } |
| |
| @Test |
| void testMaxLengthInvalid() throws IOException { |
| try (InputStream is = new FileInputStream(TMP)) { |
| assertThrows(RecordFormatException.class, () -> IOUtils.toByteArray(is, 90, 80)); |
| } |
| } |
| |
| @Test |
| void testWonkyInputStream() throws IOException { |
| long skipped = IOUtils.skipFully(new WonkyInputStream(), 10000); |
| assertEquals(10000, skipped); |
| } |
| |
| @Test |
| void testSetMaxOverride() throws IOException { |
| ByteArrayInputStream stream = new ByteArrayInputStream("abc".getBytes(StandardCharsets.UTF_8)); |
| byte[] bytes = IOUtils.toByteArray(stream); |
| assertNotNull(bytes); |
| assertEquals("abc", new String(bytes, StandardCharsets.UTF_8)); |
| } |
| |
| @Test |
| void testSetMaxOverrideLimit() throws IOException { |
| IOUtils.setByteArrayMaxOverride(30 * 1024 * 1024); |
| try { |
| ByteArrayInputStream stream = new ByteArrayInputStream("abc".getBytes(StandardCharsets.UTF_8)); |
| byte[] bytes = IOUtils.toByteArray(stream); |
| assertNotNull(bytes); |
| assertEquals("abc", new String(bytes, StandardCharsets.UTF_8)); |
| } finally { |
| IOUtils.setByteArrayMaxOverride(-1); |
| } |
| } |
| |
| @Test |
| void testSetMaxOverrideOverLimit() { |
| IOUtils.setByteArrayMaxOverride(2); |
| try { |
| ByteArrayInputStream stream = new ByteArrayInputStream("abc".getBytes(StandardCharsets.UTF_8)); |
| assertThrows(RecordFormatException.class, () -> IOUtils.toByteArray(stream)); |
| } finally { |
| IOUtils.setByteArrayMaxOverride(-1); |
| } |
| } |
| |
| @Test |
| void testSetMaxOverrideWithLength() throws IOException { |
| ByteArrayInputStream stream = new ByteArrayInputStream("abc".getBytes(StandardCharsets.UTF_8)); |
| byte[] bytes = IOUtils.toByteArray(stream, 3, 100); |
| assertNotNull(bytes); |
| assertEquals("abc", new String(bytes, StandardCharsets.UTF_8)); |
| } |
| |
| @Test |
| void testSetMaxOverrideLimitWithLength() throws IOException { |
| IOUtils.setByteArrayMaxOverride(30 * 1024 * 1024); |
| try { |
| ByteArrayInputStream stream = new ByteArrayInputStream("abc".getBytes(StandardCharsets.UTF_8)); |
| byte[] bytes = IOUtils.toByteArray(stream, 3, 100); |
| assertNotNull(bytes); |
| assertEquals("abc", new String(bytes, StandardCharsets.UTF_8)); |
| } finally { |
| IOUtils.setByteArrayMaxOverride(-1); |
| } |
| } |
| |
| @Test |
| void testSetMaxOverrideOverLimitWithLength() { |
| IOUtils.setByteArrayMaxOverride(2); |
| try { |
| ByteArrayInputStream stream = new ByteArrayInputStream("abc".getBytes(StandardCharsets.UTF_8)); |
| assertThrows(RecordFormatException.class, () -> IOUtils.toByteArray(stream, 3, 100)); |
| } finally { |
| IOUtils.setByteArrayMaxOverride(-1); |
| } |
| } |
| |
| @Test |
| void testSafelyAllocate() { |
| byte[] bytes = IOUtils.safelyAllocate(30, 200); |
| assertNotNull(bytes); |
| assertEquals(30, bytes.length); |
| } |
| |
| @Test |
| void testSafelyAllocateLimit() { |
| IOUtils.setByteArrayMaxOverride(40); |
| try { |
| byte[] bytes = IOUtils.safelyAllocate(30, 200); |
| assertNotNull(bytes); |
| assertEquals(30, bytes.length); |
| } finally { |
| IOUtils.setByteArrayMaxOverride(-1); |
| } |
| } |
| |
| @Test |
| void testReadFully() throws IOException { |
| byte[] bytes = new byte[2]; |
| assertEquals(2, IOUtils.readFully(new ByteArrayInputStream(new byte[] {1, 2, 3}), bytes, 0, 2)); |
| assertArrayEquals(new byte[] {1,2}, bytes); |
| } |
| |
| @Test |
| void testReadFullyEOF() throws IOException { |
| byte[] bytes = new byte[2]; |
| assertEquals(2, IOUtils.readFully(new NullInputStream(2), bytes, 0, 4)); |
| assertArrayEquals(new byte[] {0,0}, bytes); |
| } |
| |
| @Test |
| void testReadFullyEOFZero() throws IOException { |
| byte[] bytes = new byte[2]; |
| assertEquals(-1, IOUtils.readFully(new NullInputStream(0), bytes, 0, 4)); |
| assertArrayEquals(new byte[] {0,0}, bytes); |
| } |
| |
| @Test |
| void testReadFullySimple() throws IOException { |
| byte[] bytes = new byte[2]; |
| assertEquals(2, IOUtils.readFully(new ByteArrayInputStream(new byte[] {1, 2, 3}), bytes)); |
| assertArrayEquals(new byte[] {1,2}, bytes); |
| } |
| |
| @Test |
| void testReadFullyOffset() throws IOException { |
| byte[] bytes = new byte[3]; |
| assertEquals(2, IOUtils.readFully(new ByteArrayInputStream(new byte[] {1, 2, 3}), bytes, 1, 2)); |
| assertArrayEquals(new byte[] {0, 1,2}, bytes); |
| } |
| |
| @Test |
| void testReadFullyAtLength() throws IOException { |
| byte[] bytes = new byte[3]; |
| assertEquals(3, IOUtils.readFully(new ByteArrayInputStream(new byte[] {1, 2, 3}), bytes, 0, 3)); |
| assertArrayEquals(new byte[] {1,2, 3}, bytes); |
| } |
| |
| |
| @Test |
| void testReadFullyChannel() throws IOException { |
| ByteBuffer bytes = ByteBuffer.allocate(2); |
| assertEquals(2, IOUtils.readFully(new SimpleByteChannel(new byte[]{1, 2, 3}), bytes)); |
| assertArrayEquals(new byte[] {1,2}, bytes.array()); |
| assertEquals(2, bytes.position()); |
| } |
| |
| @Test |
| void testReadFullyChannelEOF() throws IOException { |
| ByteBuffer bytes = ByteBuffer.allocate(2); |
| assertEquals(-1, IOUtils.readFully(new EOFByteChannel(false), bytes)); |
| assertArrayEquals(new byte[] {0,0}, bytes.array()); |
| assertEquals(0, bytes.position()); |
| } |
| |
| @Test |
| void testReadFullyChannelEOFException() { |
| ByteBuffer bytes = ByteBuffer.allocate(2); |
| assertThrows(IOException.class, |
| () -> IOUtils.readFully(new EOFByteChannel(true), bytes)); |
| } |
| |
| @Test |
| void testReadFullyChannelSimple() throws IOException { |
| ByteBuffer bytes = ByteBuffer.allocate(2); |
| assertEquals(2, IOUtils.readFully(new SimpleByteChannel(new byte[] {1, 2, 3}), bytes)); |
| assertArrayEquals(new byte[] {1,2}, bytes.array()); |
| assertEquals(2, bytes.position()); |
| } |
| |
| @Test |
| public void testChecksum() { |
| assertEquals(0L, IOUtils.calculateChecksum(new byte[0])); |
| assertEquals(3057449933L, IOUtils.calculateChecksum(new byte[] { 1, 2, 3, 4})); |
| } |
| |
| @Test |
| public void testChecksumStream() throws IOException { |
| assertEquals(0L, IOUtils.calculateChecksum(new NullInputStream(0))); |
| assertEquals(0L, IOUtils.calculateChecksum(new NullInputStream(1))); |
| assertEquals(3057449933L, IOUtils.calculateChecksum(new ByteArrayInputStream(new byte[] { 1, 2, 3, 4}))); |
| assertThrows(EOFException.class, |
| () -> IOUtils.calculateChecksum(new NullInputStream(1, true))); |
| } |
| |
| /** |
| * This returns 0 for the first call to skip and then reads |
| * as requested. This tests that the fallback to read() works. |
| */ |
| private static class WonkyInputStream extends InputStream { |
| int skipCalled; |
| int readCalled; |
| |
| @Override |
| public int read() { |
| readCalled++; |
| return 0; |
| } |
| |
| @Override |
| public int read(byte[] arr, int offset, int len) { |
| readCalled++; |
| return len; |
| } |
| |
| @Override |
| public long skip(long len) { |
| skipCalled++; |
| if (skipCalled == 1) { |
| return 0; |
| } else if (skipCalled > 100) { |
| return len; |
| } else { |
| return 100; |
| } |
| } |
| |
| @Override |
| public int available() { |
| return 100000; |
| } |
| } |
| |
| private static class EOFByteChannel implements ReadableByteChannel { |
| private final boolean throwException; |
| |
| public EOFByteChannel(boolean throwException) { |
| this.throwException = throwException; |
| } |
| |
| @Override |
| public int read(ByteBuffer dst) throws IOException { |
| if (throwException) { |
| throw new IOException("EOF"); |
| } |
| |
| return -1; |
| } |
| |
| @Override |
| public boolean isOpen() { |
| return false; |
| } |
| |
| @Override |
| public void close() throws IOException { |
| |
| } |
| } |
| |
| private static class SimpleByteChannel extends InputStream implements ReadableByteChannel { |
| private final byte[] bytes; |
| |
| public SimpleByteChannel(byte[] bytes) { |
| this.bytes = bytes; |
| } |
| |
| @Override |
| public int read() throws IOException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public int read(ByteBuffer dst) throws IOException { |
| int toRead = Math.min(bytes.length, dst.capacity()); |
| dst.put(bytes, 0, toRead); |
| return toRead; |
| } |
| |
| @Override |
| public boolean isOpen() { |
| return false; |
| } |
| } |
| |
| public class NullInputStream extends InputStream { |
| private final int bytes; |
| private final boolean exception; |
| |
| private int position; |
| |
| public NullInputStream(int bytes) { |
| this(bytes, false); |
| } |
| |
| public NullInputStream(int bytes, boolean exception) { |
| this.bytes = bytes; |
| this.exception = exception; |
| } |
| |
| @Override |
| public int read() throws IOException { |
| if (position >= bytes) { |
| return handleReturn(); |
| } |
| |
| position++; |
| return 0; |
| } |
| |
| private int handleReturn() throws EOFException { |
| if (exception) { |
| throw new EOFException(); |
| } else { |
| return -1; |
| } |
| } |
| |
| @Override |
| public int read(byte[] b, int off, int len) throws IOException { |
| int toRead = Math.min(b.length, len); |
| if (toRead > (bytes - position)) { |
| return handleReturn(); |
| } |
| toRead = Math.min(toRead, (bytes - position)); |
| |
| position += toRead; |
| return toRead; |
| } |
| } |
| } |