MIME4J-233 Fix ByteBufferInputStream read implementation (#33)

* Fix ByteBufferInputStream read implementation
* Check preconditions in read
* Add license to ByteBufferInputStreamTest
diff --git a/mbox/src/main/java/org/apache/james/mime4j/mboxiterator/CharBufferWrapper.java b/mbox/src/main/java/org/apache/james/mime4j/mboxiterator/CharBufferWrapper.java
index aed6c1d..4f85d1f 100644
--- a/mbox/src/main/java/org/apache/james/mime4j/mboxiterator/CharBufferWrapper.java
+++ b/mbox/src/main/java/org/apache/james/mime4j/mboxiterator/CharBufferWrapper.java
@@ -23,6 +23,7 @@
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 import java.nio.charset.Charset;
+import java.util.Objects;
 
 /**
  * Wraps a CharBuffer and exposes some convenience methods to easy parse with Mime4j.
@@ -89,11 +90,29 @@
 
         @Override
         public int read(byte[] bytes, int off, int len) throws IOException {
+            if (bytes == null) throw new NullPointerException("bytes is null");
+            if (off < 0) throw new IndexOutOfBoundsException("read index negative: " + off);
+            if (len < 0) throw new IndexOutOfBoundsException("read length negative: " + len);
+
+            if (len > (bytes.length - off)) {
+                throw new IndexOutOfBoundsException(
+                        "read would write invalid array index: array size: " +
+                                bytes.length +
+                                ", array index: " +
+                                off +
+                                ", requested size: " +
+                                len
+                );
+            }
+
             if (!buf.hasRemaining()) {
                 return -1;
             }
-            buf.get(bytes, off, Math.min(len, buf.remaining()));
-            return len;
+
+            int actualAmount = Math.min(len, buf.remaining());
+            buf.get(bytes, off, actualAmount);
+
+            return actualAmount;
         }
 
     }
diff --git a/mbox/src/test/java/org/apache/james/mime4j/mboxiterator/ByteBufferInputStreamTest.java b/mbox/src/test/java/org/apache/james/mime4j/mboxiterator/ByteBufferInputStreamTest.java
new file mode 100644
index 0000000..388743b
--- /dev/null
+++ b/mbox/src/test/java/org/apache/james/mime4j/mboxiterator/ByteBufferInputStreamTest.java
@@ -0,0 +1,89 @@
+/****************************************************************
+ * 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.james.mime4j.mboxiterator;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+public class ByteBufferInputStreamTest {
+    private InputStream createTestUtf8Stream() {
+        return new CharBufferWrapper(CharBuffer.wrap("ABCDE§")).asInputStream(StandardCharsets.UTF_8);
+    }
+
+    @Test
+    public void testSingleRead() throws IOException {
+        InputStream stream = createTestUtf8Stream();
+        Assert.assertEquals(0x41, stream.read());
+        Assert.assertEquals(0x42, stream.read());
+        Assert.assertEquals(0x43, stream.read());
+        Assert.assertEquals(0x44, stream.read());
+        Assert.assertEquals(0x45, stream.read());
+        Assert.assertEquals(0xC2, stream.read());
+        Assert.assertEquals(0xA7, stream.read());
+        Assert.assertEquals(-1, stream.read());
+    }
+
+    @Test
+    public void testBulkRead() throws IOException {
+        InputStream stream = createTestUtf8Stream();
+
+        {
+            byte[] byteArr = new byte[3];
+            int bytesRead = stream.read(byteArr);
+            Assert.assertEquals(3, bytesRead);
+            Assert.assertArrayEquals(new byte[]{ 0x41, 0x42, 0x43 }, byteArr);
+        }
+
+        {
+            byte[] byteArr = new byte[5];
+            Arrays.fill(byteArr, (byte) -1);
+
+            int bytesRead = stream.read(byteArr);
+            Assert.assertEquals(4, bytesRead);
+            Assert.assertArrayEquals(new byte[]{ 0x44, 0x45, (byte) 0xC2, (byte) 0xA7, (byte) - 1 }, byteArr);
+        }
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testNullArray() throws IOException {
+        createTestUtf8Stream().read(null, 0, 12);
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testNegativeOffset() throws IOException {
+        createTestUtf8Stream().read(new byte[12], -12, 0);
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testNegativeLength() throws IOException {
+        createTestUtf8Stream().read(new byte[12], 0, -12);
+    }
+
+    @Test(expected = IndexOutOfBoundsException.class)
+    public void testLongLength() throws IOException {
+        createTestUtf8Stream().read(new byte[12], 4, 13);
+    }
+}