[VFS-744] org.apache.commons.vfs2.FileContent.getByteArray() can throw
NegativeArraySizeException for BZip2 files
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/FileContent.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/FileContent.java
index 0162f3c..7e571f0 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/FileContent.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/FileContent.java
@@ -22,8 +22,10 @@
 import java.io.OutputStream;
 import java.nio.charset.Charset;
 import java.security.cert.Certificate;
+import java.util.Arrays;
 import java.util.Map;
 
+import org.apache.commons.vfs2.provider.AbstractFileObject;
 import org.apache.commons.vfs2.util.RandomAccessMode;
 
 /**
@@ -92,15 +94,17 @@
         if (sizeL > Integer.MAX_VALUE) {
             throw new IllegalStateException(String.format("File content is too large for a byte array: %,d", sizeL));
         }
-        final int size = (int) sizeL;
+        final boolean sizeUndefined = sizeL < 0;
+        final int size = sizeUndefined ? AbstractFileObject.DEFAULT_BUFFER_SIZE : (int) sizeL;
         final byte[] buf = new byte[size];
+        int pos;
         try (final InputStream in = getInputStream(size)) {
             int read = 0;
-            for (int pos = 0; pos < size && read >= 0; pos += read) {
+            for (pos = 0; pos < size && read >= 0; pos += read) {
                 read = in.read(buf, pos, size - pos);
             }
         }
-        return buf;
+        return sizeUndefined && pos < buf.length ? Arrays.copyOf(buf, ++pos) : buf;
     }
 
     /**
@@ -135,7 +139,7 @@
      * @return An input stream to read the file's content from. The input stream is buffered, so there is no need to
      *         wrap it in a {@code BufferedInputStream}.
      * @throws FileSystemException If the file does not exist, or is being read, or is being written, or on error
-     *             opening the stream.
+     *         opening the stream.
      */
     InputStream getInputStream() throws FileSystemException;
 
@@ -149,7 +153,7 @@
      * @return An input stream to read the file's content from. The input stream is buffered, so there is no need to
      *         wrap it in a {@code BufferedInputStream}.
      * @throws FileSystemException If the file does not exist, or is being read, or is being written, or on error
-     *             opening the stream.
+     *         opening the stream.
      * @since 2.4
      */
     default InputStream getInputStream(final int bufferSize) throws FileSystemException {
@@ -161,7 +165,7 @@
      *
      * @return The last-modified timestamp.
      * @throws FileSystemException If the file does not exist, or is being written to, or on error determining the
-     *             last-modified timestamp.
+     *         last-modified timestamp.
      */
     long getLastModifiedTime() throws FileSystemException;
 
@@ -178,7 +182,7 @@
      * @return An output stream to write the file's content to. The stream is buffered, so there is no need to wrap it
      *         in a {@code BufferedOutputStream}.
      * @throws FileSystemException If the file is read-only, or is being read, or is being written, or on error opening
-     *             the stream.
+     *         the stream.
      */
     OutputStream getOutputStream() throws FileSystemException;
 
@@ -196,7 +200,7 @@
      * @return An output stream to write the file's content to. The stream is buffered, so there is no need to wrap it
      *         in a {@code BufferedOutputStream}.
      * @throws FileSystemException If the file is read-only, or is being read, or is being written, or bAppend is true
-     *             and the implementation does not support it, or on error opening the stream.
+     *         and the implementation does not support it, or on error opening the stream.
      */
     OutputStream getOutputStream(boolean bAppend) throws FileSystemException;
 
@@ -215,7 +219,7 @@
      * @return An output stream to write the file's content to. The stream is buffered, so there is no need to wrap it
      *         in a {@code BufferedOutputStream}.
      * @throws FileSystemException If the file is read-only, or is being read, or is being written, or bAppend is true
-     *             and the implementation does not support it, or on error opening the stream.
+     *         and the implementation does not support it, or on error opening the stream.
      * @since 2.4
      */
     default OutputStream getOutputStream(final boolean bAppend, final int bufferSize) throws FileSystemException {
@@ -236,7 +240,7 @@
      * @return An output stream to write the file's content to. The stream is buffered, so there is no need to wrap it
      *         in a {@code BufferedOutputStream}.
      * @throws FileSystemException If the file is read-only, or is being read, or is being written, or bAppend is true
-     *             and the implementation does not support it, or on error opening the stream.
+     *         and the implementation does not support it, or on error opening the stream.
      * @since 2.4
      */
     default OutputStream getOutputStream(final int bufferSize) throws FileSystemException {
@@ -257,7 +261,7 @@
      * @param mode The mode to use to access the file.
      * @return the stream for reading and writing the file's content.
      * @throws FileSystemException If the file is read-only, or is being read, or is being written, or on error opening
-     *             the stream.
+     *         the stream.
      */
     RandomAccessContent getRandomAccessContent(final RandomAccessMode mode) throws FileSystemException;
 
@@ -325,7 +329,7 @@
      *
      * @param attrName The name of the attribute.
      * @throws FileSystemException If the file does not exist, or is read-only, or does not support attributes, or on
-     *             error removing the attribute.
+     *         error removing the attribute.
      */
     void removeAttribute(String attrName) throws FileSystemException;
 
@@ -335,7 +339,7 @@
      * @param attrName The name of the attribute.
      * @param value The value of the attribute.
      * @throws FileSystemException If the file does not exist, or is read-only, or does not support attributes, or on
-     *             error setting the attribute.
+     *         error setting the attribute.
      */
     void setAttribute(String attrName, Object value) throws FileSystemException;
 
@@ -344,7 +348,7 @@
      *
      * @param modTime The time to set the last-modified timestamp to.
      * @throws FileSystemException If the file is read-only, or is being written to, or on error setting the
-     *             last-modified timestamp.
+     *         last-modified timestamp.
      */
     void setLastModifiedTime(long modTime) throws FileSystemException;
 
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/AbstractFileObject.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/AbstractFileObject.java
index f27c4de..04376b5 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/AbstractFileObject.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/AbstractFileObject.java
@@ -70,7 +70,7 @@
     /**

      * Same as {@link BufferedInputStream}.

      */

-    private static final int DEFAULT_BUFFER_SIZE = 8192;

+    public static final int DEFAULT_BUFFER_SIZE = 8192;

 

     private static final FileName[] EMPTY_FILE_ARRAY = {};

 

diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/compressed/CompressedFileFileObject.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/compressed/CompressedFileFileObject.java
index 3c03485..45de078 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/compressed/CompressedFileFileObject.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/compressed/CompressedFileFileObject.java
@@ -36,7 +36,7 @@
 
     /**
      * The value returned by {@link #doGetContentSize()} when not overriden by a subclass.
-     * 
+     *
      * @since 2.5.0
      */
     public static final int SIZE_UNDEFINED = -1;
diff --git a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/bzip2/Bzip2TestCase.java b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/bzip2/Bzip2TestCase.java
new file mode 100644
index 0000000..a6b10d8
--- /dev/null
+++ b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/bzip2/Bzip2TestCase.java
@@ -0,0 +1,59 @@
+/*
+ * 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.commons.vfs2.provider.bzip2;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.commons.AbstractVfsTestCase;
+import org.apache.commons.vfs2.FileContent;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.VFS;
+import org.apache.commons.vfs2.provider.compressed.CompressedFileFileObject;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class Bzip2TestCase extends AbstractVfsTestCase {
+
+    @Test
+    public void testBZip2() throws IOException {
+        final File testResource = getTestResource("bla.txt.bz2");
+        try (final FileObject bz2FileObject = VFS.getManager().resolveFile("bz2://" + testResource)) {
+            Assert.assertTrue(bz2FileObject.exists());
+            Assert.assertTrue(bz2FileObject.isFolder());
+            try (final FileObject fileObjectDir = bz2FileObject.resolveFile("bla.txt")) {
+                Assert.assertTrue(fileObjectDir.exists());
+                Assert.assertTrue(bz2FileObject.isFolder());
+                try (final FileObject fileObject = fileObjectDir.resolveFile("bla.txt")) {
+                    Assert.assertTrue(fileObject.exists());
+                    Assert.assertFalse(fileObject.isFolder());
+                    Assert.assertTrue(fileObject.isFile());
+                    try (final FileContent content = fileObject.getContent()) {
+                        Assert.assertEquals(CompressedFileFileObject.SIZE_UNDEFINED, content.getSize());
+                        // blows up, Commons Compress?
+                        final String string = content.getString(StandardCharsets.UTF_8);
+                        Assert.assertEquals(26, string.length());
+                        Assert.assertEquals("Hallo, dies ist ein Test.\n", string);
+                    }
+                }
+            }
+        }
+    }
+
+}
diff --git a/commons-vfs2/src/test/resources/test-data/bla.txt.bz2 b/commons-vfs2/src/test/resources/test-data/bla.txt.bz2
new file mode 100644
index 0000000..87309da
--- /dev/null
+++ b/commons-vfs2/src/test/resources/test-data/bla.txt.bz2
Binary files differ
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 9fc9f3f..00f1ff6 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -110,6 +110,9 @@
       <action issue="VFS-743" dev="ggregory" type="add" due-to="Gary Gregory">

         Add org.apache.commons.vfs2.provider.compressed.CompressedFileFileObject.SIZE_UNDEFINED.

       </action>

+      <action issue="VFS-744" dev="ggregory" type="fix" due-to="Gary Gregory">

+        org.apache.commons.vfs2.FileContent.getByteArray() can throw NegativeArraySizeException for BZip2 files.

+      </action>

     </release>

     <release version="2.4.1" date="2019-08-10" description="Bug fix release.">

       <action issue="VFS-725" dev="ggregory" type="fix" due-to="Gary Gregory">