blob: 19b64dd3c17fa546fdf0701abc191999cc676b63 [file] [log] [blame]
/* ====================================================================
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;
}
}
}