commons-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From dam...@apache.org
Subject svn commit: r1481509 - in /commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/sevenz: BoundedRandomAccessFileInputStream.java Coders.java SevenZFile.java
Date Sun, 12 May 2013 10:12:16 GMT
Author: damjan
Date: Sun May 12 10:12:16 2013
New Revision: 1481509

URL: http://svn.apache.org/r1481509
Log:
Add support for BZIP2 decompression and AES-256 + SHA-256 decryption
to the 7z archive format.


Added:
    commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/sevenz/BoundedRandomAccessFileInputStream.java
  (with props)
    commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/sevenz/Coders.java
  (with props)
Modified:
    commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFile.java

Added: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/sevenz/BoundedRandomAccessFileInputStream.java
URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/sevenz/BoundedRandomAccessFileInputStream.java?rev=1481509&view=auto
==============================================================================
--- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/sevenz/BoundedRandomAccessFileInputStream.java
(added)
+++ commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/sevenz/BoundedRandomAccessFileInputStream.java
Sun May 12 10:12:16 2013
@@ -0,0 +1,63 @@
+/*
+ *  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.compress.archivers.sevenz;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+
+class BoundedRandomAccessFileInputStream extends InputStream {
+    private final RandomAccessFile file;
+    private long bytesRemaining;
+
+    public BoundedRandomAccessFileInputStream(final RandomAccessFile file,
+            final long size) {
+        this.file = file;
+        this.bytesRemaining = size;
+    }
+    
+    @Override
+    public int read() throws IOException {
+        if (bytesRemaining > 0) {
+            --bytesRemaining;
+            return file.read();
+        } else {
+            return -1;
+        }
+    }
+
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException {
+        if (bytesRemaining == 0) {
+            return -1;
+        }
+        int bytesToRead = len;
+        if (bytesToRead > bytesRemaining) {
+            bytesToRead = (int) bytesRemaining;
+        }
+        final int bytesRead = file.read(b, off, bytesToRead);
+        if (bytesRead >= 0) {
+            bytesRemaining -= bytesRead;
+        }
+        return bytesRead;
+    }
+
+    @Override
+    public void close() {
+    }
+}
\ No newline at end of file

Propchange: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/sevenz/BoundedRandomAccessFileInputStream.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/sevenz/Coders.java
URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/sevenz/Coders.java?rev=1481509&view=auto
==============================================================================
--- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/sevenz/Coders.java
(added)
+++ commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/sevenz/Coders.java
Sun May 12 10:12:16 2013
@@ -0,0 +1,182 @@
+/*
+ *  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.compress.archivers.sevenz;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
+import org.tukaani.xz.LZMA2InputStream;
+
+class Coders {
+    static InputStream addDecoder(final InputStream is,
+            final Coder coder, final String password) throws IOException {
+        for (final CoderId coderId : coderTable) {
+            if (Arrays.equals(coderId.id, coder.decompressionMethodId)) {
+                return coderId.coder.decode(is, coder, password);
+            }
+        }
+        throw new IOException("Unsupported compression method " +
+                Arrays.toString(coder.decompressionMethodId));
+    }
+    
+    static CoderId[] coderTable = new CoderId[] {
+        new CoderId(new byte[] { (byte)0x00 }, new CopyDecoder()),
+        new CoderId(new byte[] { (byte)0x21 }, new LZMA2Decoder()),
+        // FIXME: gives corrupt output
+        //new CoderId(new byte[] { (byte)0x04, (byte)0x01, (byte)0x08 }, new DeflateDecoder()),
+        new CoderId(new byte[] { (byte)0x04, (byte)0x02, (byte)0x02 }, new BZIP2Decoder()),
+        new CoderId(new byte[] { (byte)0x06, (byte)0xf1, (byte)0x07, (byte)0x01 }, new AES256SHA256Decoder())
+    };
+    
+    static class CoderId {
+        CoderId(final byte[] id, final CoderBase coder) {
+            this.id = id;
+            this.coder = coder;
+        }
+
+        final byte[] id;
+        final CoderBase coder;
+    }
+    
+    static abstract class CoderBase {
+        abstract InputStream decode(final InputStream in, final Coder coder,
+                String password) throws IOException;
+    }
+    
+    static class CopyDecoder extends CoderBase {
+        @Override
+        InputStream decode(final InputStream in, final Coder coder,
+                String password) throws IOException {
+            return in; 
+        }
+    }
+    
+    static class LZMA2Decoder extends CoderBase {
+        @Override
+        InputStream decode(final InputStream in, final Coder coder,
+                String password) throws IOException {
+            final int dictionarySizeBits = 0xff & coder.properties[0];
+            if ((dictionarySizeBits & (~0x3f)) != 0) {
+                throw new IOException("Unsupported LZMA2 property bits");
+            }
+            if (dictionarySizeBits > 40) {
+                throw new IOException("Dictionary larger than 4GiB maximum size");
+            }
+            final int dictionarySize;
+            if (dictionarySizeBits == 40) {
+                dictionarySize = 0xFFFFffff;
+            } else {
+                dictionarySize = (2 | (dictionarySizeBits & 0x1)) << (dictionarySizeBits
/ 2 + 11);
+            }
+            return new LZMA2InputStream(in, dictionarySize);
+        }
+    }
+    
+//    static class DeflateDecoder extends CoderBase {
+//        @Override
+//        InputStream decode(final InputStream in, final Coder coder, final String password)
+//                throws IOException {
+//            System.out.println("deflate prop count = " + (coder.properties == null ? -1
: coder.properties.length));
+//            return new DeflaterInputStream(in, new Deflater(Deflater.DEFAULT_COMPRESSION,
true));
+//            //return new GZIPInputStream(in);
+//        }
+//    }
+
+    static class BZIP2Decoder extends CoderBase {
+        @Override
+        InputStream decode(final InputStream in, final Coder coder, final String password)
+                throws IOException {
+            return new BZip2CompressorInputStream(in);
+        }
+    }
+
+    static class AES256SHA256Decoder extends CoderBase {
+        @Override
+        InputStream decode(final InputStream in, final Coder coder,
+                String password) throws IOException {
+            final int byte0 = 0xff & coder.properties[0];
+            final int numCyclesPower = byte0 & 0x3f;
+            final int byte1 = 0xff & coder.properties[1];
+            final int ivSize = ((byte0 >> 6) & 1) + (byte1 & 0x0f);
+            final int saltSize = ((byte0 >> 7) & 1) + (byte1 >> 4);
+            //debug("numCyclesPower=" + numCyclesPower + ", saltSize=" + saltSize + ", ivSize="
+ ivSize);
+            if (2 + saltSize + ivSize > coder.properties.length) {
+                throw new IOException("Salt size + IV size too long");
+            }
+            final byte[] salt = new byte[saltSize];
+            System.arraycopy(coder.properties, 2, salt, 0, saltSize);
+            final byte[] iv = new byte[16];
+            System.arraycopy(coder.properties, 2 + saltSize, iv, 0, ivSize);
+            
+            if (password == null) {
+                throw new IOException("Cannot read encrypted files without a password");
+            }
+            final byte[] passwordBytes = password.getBytes("UTF-16LE");
+            final byte[] aesKeyBytes;
+            if (numCyclesPower == 0x3f) {
+                aesKeyBytes = new byte[32];
+                System.arraycopy(salt, 0, aesKeyBytes, 0, saltSize);
+                System.arraycopy(passwordBytes, 0, aesKeyBytes, saltSize,
+                        Math.min(passwordBytes.length, aesKeyBytes.length - saltSize));
+            } else {
+                final MessageDigest digest;
+                try {
+                    digest = MessageDigest.getInstance("SHA-256");
+                } catch (NoSuchAlgorithmException noSuchAlgorithmException) {
+                    throw new IOException("SHA-256 is unsupported by your Java implementation",
+                            noSuchAlgorithmException);
+                }
+                final byte[] extra = new byte[8];
+                for (long j = 0; j < (1L << numCyclesPower); j++) {
+                    digest.update(salt);
+                    digest.update(passwordBytes);
+                    digest.update(extra);
+                    for (int k = 0; k < extra.length; k++) {
+                        ++extra[k];
+                        if (extra[k] != 0) {
+                            break;
+                        }
+                    }
+                }
+                aesKeyBytes = digest.digest();
+            }
+            
+            final SecretKey aesKey = new SecretKeySpec(aesKeyBytes, "AES");
+            try {
+                Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
+                cipher.init(Cipher.DECRYPT_MODE, aesKey, new IvParameterSpec(iv));
+                return new CipherInputStream(in, cipher);
+            } catch (GeneralSecurityException generalSecurityException) {
+                throw new IOException("Decryption error " +
+                        "(do you have the JCE Unlimited Strength Jurisdiction Policy Files
installed?)",
+                        generalSecurityException);
+            }
+        }
+    }
+}

Propchange: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/sevenz/Coders.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFile.java
URL: http://svn.apache.org/viewvc/commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFile.java?rev=1481509&r1=1481508&r2=1481509&view=diff
==============================================================================
--- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFile.java
(original)
+++ commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/sevenz/SevenZFile.java
Sun May 12 10:12:16 2013
@@ -29,22 +29,23 @@ import java.util.BitSet;
 import java.util.zip.CRC32;
 
 import org.apache.commons.compress.utils.CRC32VerifyingInputStream;
-import org.tukaani.xz.LZMA2InputStream;
 
 /**
  * Reads a 7z file, using RandomAccessFile under
  * the covers.
  * <p>
  * The 7z file format is a flexible container
- * that can contain many compression types, but
- * at the moment only Copy and LZMA2 are
- * supported, and archive header compression
+ * that can contain many compression and
+ * encryption types, but at the moment only
+ * only Copy, LZMA2, BZIP2, and AES-256 + SHA-256
+ * are supported, and archive header compression
  * (which always uses the unsupported LZMA
  * compression) isn't. So the only archives
  * that can be read are the following:
  * <pre>
- * 7z -mhc=off -mx=0 archive.7z files
- * 7z -mhc=off -m0=LZMA2 archive.7z files
+ * 7z -mhc=off -mx=0 [-ppassword] archive.7z files
+ * 7z -mhc=off -m0=LZMA2 [-ppassword] archive.7z files
+ * 7z -mhc=off -m0=BZIP2 [-ppassword] archive.7z files
  * </pre>
  * <p>
  * The format is very Windows/Intel specific,
@@ -74,6 +75,7 @@ public class SevenZFile {
     private int currentFolderIndex = -1;
     private InputStream currentFolderInputStream = null;
     private InputStream currentEntryInputStream = null;
+    private String password;
         
     private static final byte[] sevenZSignature = {
         (byte)'7', (byte)'z', (byte)0xBC, (byte)0xAF, (byte)0x27, (byte)0x1C
@@ -92,6 +94,11 @@ public class SevenZFile {
         }
     }
     
+    public SevenZFile(final File filename, final String password) throws IOException {
+        this(filename);
+        this.password = password;
+    }
+
     public void close() {
         if (file != null) {
             try {
@@ -178,7 +185,7 @@ public class SevenZFile {
         DataInputStream dataInputStream = null;
         try {
              dataInputStream = new DataInputStream(new CRC32VerifyingInputStream(
-                    new BoundedRandomAccessFileInputStream(20), 20, startHeaderCrc));
+                    new BoundedRandomAccessFileInputStream(file, 20), 20, startHeaderCrc));
              startHeader.nextHeaderOffset = Long.reverseBytes(dataInputStream.readLong());
              startHeader.nextHeaderSize = Long.reverseBytes(dataInputStream.readLong());
              startHeader.nextHeaderCrc = Integer.reverseBytes(dataInputStream.readInt());
@@ -836,65 +843,14 @@ public class SevenZFile {
     
     private InputStream buildDecoderStack(final Folder folder, final long folderOffset,
             final int firstPackStreamIndex) throws IOException {
-        InputStream inputStreamStack = null;
-        for (int i = 0; i < folder.coders.length; i++) {
-            if (i > 0) {
-                throw new IOException("Unsupported multi-codec stream");
-            }
-            file.seek(folderOffset);
-            if (folder.coders[i].decompressionMethodId.length == 1 &&
-                    folder.coders[i].decompressionMethodId[0] == 0) {
-                // 00 - Copy
-                inputStreamStack = new BoundedRandomAccessFileInputStream(
-                        archive.packSizes[firstPackStreamIndex]);
-                // FIXME: LZMA is the default coder yet ironically we don't have it.
-//            } else if (folder.coders[i].decompressionMethodId.length == 3 &&
-//                    folder.coders[i].decompressionMethodId[0] == 3 &&
-//                    folder.coders[i].decompressionMethodId[1] == 1 &&
-//                    folder.coders[i].decompressionMethodId[2] == 1) {
-//                // 03.. - 7z
-//                //    01 - LZMA
-//                //       01 - Version
-            } else if (folder.coders[i].decompressionMethodId.length == 1 &&
-                    folder.coders[i].decompressionMethodId[0] == 0x21) {
-                // 21 - LZMA2
-                final int dictionarySizeBits = 0xff & folder.coders[i].properties[0];
-                if ((dictionarySizeBits & (~0x3f)) != 0) {
-                    throw new IOException("Unsupported LZMA2 property bits");
-                }
-                if (dictionarySizeBits > 40) {
-                    throw new IOException("Dictionary larger than 4GiB maximum size");
-                }
-                final int dictionarySize;
-                if (dictionarySizeBits == 40) {
-                    dictionarySize = 0xFFFFffff;
-                } else {
-                    dictionarySize = (2 | (dictionarySizeBits & 0x1)) << (dictionarySizeBits
/ 2 + 11);
-                }
-                inputStreamStack = new LZMA2InputStream(
-                      new BoundedRandomAccessFileInputStream(
-                              archive.packSizes[firstPackStreamIndex]),
-                              dictionarySize);
-                // FIXME: gives corrupt output:
-//            } else if (folder.coders[i].decompressionMethodId.length == 3 &&
-//                    folder.coders[i].decompressionMethodId[0] == 0x4 &&
-//                    folder.coders[i].decompressionMethodId[1] == 0x1 && 
-//                    folder.coders[i].decompressionMethodId[2] == 0x8) {
-//                // 04.. - Misc
-//                //    00 - Reserved
-//                //    01 - Zip
-//                //       00 - Copy (not used). Use {00} instead
-//                //       01 - Shrink
-//                //       06 - Implode
-//                //       08 - Deflate
-//                return new DeflaterInputStream(
-//                        new BoundedRandomAccessFileInputStream(
-//                                archive.packSizes[firstPackStreamIndex]),
-//                                new Deflater(Deflater.DEFAULT_COMPRESSION, true));
-            } else {
-                throw new IOException("Unsupported compression method " +
-                        Arrays.toString(folder.coders[i].decompressionMethodId));
+        file.seek(folderOffset);
+        InputStream inputStreamStack = new BoundedRandomAccessFileInputStream(file,
+                archive.packSizes[firstPackStreamIndex]);
+        for (final Coder coder : folder.coders) {
+            if (coder.numInStreams != 1 || coder.numOutStreams != 1) {
+                throw new IOException("Multi input/output stream coders are not yet supported");
             }
+            inputStreamStack = Coders.addDecoder(inputStreamStack, coder, password);
         }
         if (folder.hasCrc) {
             return new CRC32VerifyingInputStream(inputStreamStack,
@@ -931,44 +887,6 @@ public class SevenZFile {
         return value;
     }
     
-    private class BoundedRandomAccessFileInputStream extends InputStream {
-        private long bytesRemaining;
-        
-        public BoundedRandomAccessFileInputStream(final long size) {
-            bytesRemaining = size;
-        }
-        
-        @Override
-        public int read() throws IOException {
-            if (bytesRemaining > 0) {
-                --bytesRemaining;
-                return file.read();
-            } else {
-                return -1;
-            }
-        }
-
-        @Override
-        public int read(byte[] b, int off, int len) throws IOException {
-            if (bytesRemaining == 0) {
-                return -1;
-            }
-            int bytesToRead = len;
-            if (bytesToRead > bytesRemaining) {
-                bytesToRead = (int) bytesRemaining;
-            }
-            final int bytesRead = file.read(b, off, bytesToRead);
-            if (bytesRead >= 0) {
-                bytesRemaining -= bytesRead;
-            }
-            return bytesRead;
-        }
-
-        @Override
-        public void close() {
-        }
-    }
-
     private static class BoundedInputStream extends InputStream {
         private InputStream is;
         private long bytesRemaining;



Mime
View raw message