]> source.dussan.org Git - poi.git/commitdiff
#66115 - Some Password protected XLS files are not read
authorAndreas Beeker <kiwiwings@apache.org>
Thu, 16 Jun 2022 22:56:47 +0000 (22:56 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Thu, 16 Jun 2022 22:56:47 +0000 (22:56 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1901996 13f79535-47bb-0310-9956-ffa450edef68

poi-integration/src/test/java/org/apache/poi/stress/ExcInfo.java
poi/src/main/java/org/apache/poi/hssf/record/RecordInputStream.java
poi/src/main/java/org/apache/poi/hssf/record/WriteAccessRecord.java
poi/src/main/java/org/apache/poi/hssf/record/crypto/Biff8DecryptingStream.java
poi/src/test/java/org/apache/poi/hssf/dev/TestBiffDrawingToXml.java
poi/src/test/java/org/apache/poi/hssf/dev/TestBiffViewer.java
poi/src/test/java/org/apache/poi/hssf/dev/TestEFBiffViewer.java
poi/src/test/java/org/apache/poi/hssf/dev/TestFormulaViewer.java
test-data/spreadsheet/stress.xls

index c9241cb0f701e537bc8fc2f1273f3024a09c0b10..8d62ec300c7a03c6f458088f3fa125f4cdf040c9 100644 (file)
@@ -90,7 +90,7 @@ public class ExcInfo {
     public boolean isValid(String testName, String handler) {
         return
             !IGNORED_TESTS.equals(tests) &&
-            (tests == null || tests.contains(testName)) &&
-            (this.handler == null || this.handler.contains(handler));
+            (tests == null || (tests.contains(testName) && !tests.contains("!"+testName))) &&
+            (this.handler == null || (this.handler.contains(handler) && !this.handler.contains("!"+handler)));
     }
 }
index 2084e6eb6b336016cf445282dfc8102ca38dd0b8..a64d86b9d0a44e205f6ca672145b4f3e411322b8 100644 (file)
@@ -533,4 +533,9 @@ public final class RecordInputStream implements LittleEndianInput {
         ((InputStream)_dataInput).reset();
         _currentDataOffset = _markedDataOffset;
     }
+
+    @Internal
+    public boolean isEncrypted() {
+        return _dataInput instanceof Biff8DecryptingStream && ((Biff8DecryptingStream)_dataInput).isCurrentRecordEncrypted();
+    }
 }
index 6c54028ffe8c62727b7bf93a0bc9916d0f9df50d..84061d0cdab2b7344d822cb8344a9946be59ce52 100644 (file)
 
 package org.apache.poi.hssf.record;
 
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Map;
 import java.util.function.Supplier;
 
+import org.apache.poi.util.BitField;
+import org.apache.poi.util.BitFieldFactory;
 import org.apache.poi.util.GenericRecordUtil;
+import org.apache.poi.util.IOUtils;
 import org.apache.poi.util.LittleEndian;
 import org.apache.poi.util.LittleEndianOutput;
 import org.apache.poi.util.RecordFormatException;
@@ -36,10 +42,14 @@ import org.apache.poi.util.StringUtil;
 public final class WriteAccessRecord extends StandardRecord {
     public static final short sid = 0x005C;
 
+    private static final BitField UTF16FLAG = BitFieldFactory.getInstance(1);
+
     private static final byte PAD_CHAR = (byte) ' ';
     private static final int DATA_SIZE = 112;
+    private static final int STRING_SIZE = DATA_SIZE - 3;
+
     /** this record is always padded to a constant length */
-    private static final byte[] PADDING = new byte[DATA_SIZE];
+    private static final byte[] PADDING = new byte[STRING_SIZE];
     static {
         Arrays.fill(PADDING, PAD_CHAR);
     }
@@ -58,42 +68,57 @@ public final class WriteAccessRecord extends StandardRecord {
 
     public WriteAccessRecord(RecordInputStream in) {
         if (in.remaining() > DATA_SIZE) {
-            throw new RecordFormatException("Expected data size (" + DATA_SIZE + ") but got ("
-                    + in.remaining() + ")");
+            throw new RecordFormatException("Expected data size (" + DATA_SIZE + ") but got (" + in.remaining() + ")");
         }
-        // The string is always 112 characters (padded with spaces), therefore
+
+        // The string is always 109 characters (padded with spaces), therefore
         // this record can not be continued.
 
         int nChars = in.readUShort();
         int is16BitFlag = in.readUByte();
-        if (nChars > DATA_SIZE || (is16BitFlag & 0xFE) != 0) {
-            // String header looks wrong (probably missing)
-            // OOO doc says this is optional anyway.
-            // reconstruct data
-            byte[] data = new byte[3 + in.remaining()];
-            LittleEndian.putUShort(data, 0, nChars);
-            LittleEndian.putByte(data, 2, is16BitFlag);
-            in.readFully(data, 3, data.length-3);
-            String rawValue = new String(data, StringUtil.UTF8);
-            setUsername(rawValue.trim());
-            return;
-        }
-
-        String rawText;
-        if ((is16BitFlag & 0x01) == 0x00) {
-            rawText = StringUtil.readCompressedUnicode(in, nChars);
+        final byte[] data;
+        final Charset charset;
+        final int byteCnt;
+        if (nChars > STRING_SIZE || (is16BitFlag & 0xFE) != 0) {
+            // something is wrong - reconstruct data
+            if (in.isEncrypted()) {
+                // WPS Office seems to generate files with this record unencrypted (#66115)
+                // Libre Office/Excel can read those, but Excel will convert those back to encrypted
+                data = IOUtils.safelyAllocate(in.remaining(), STRING_SIZE);
+                in.readPlain(data, 0, data.length);
+                int i = data.length;
+                // PAD_CHAR is filled for every byte even for UTF16 strings
+                while (i>0 && data[i-1] == PAD_CHAR) {
+                    i--;
+                }
+                byteCnt = i;
+                // poor mans utf16 detection ...
+                charset = (data.length > 1 && data[1] == 0) ? StandardCharsets.UTF_16LE : StandardCharsets.ISO_8859_1;
+            } else {
+                // String header looks wrong (probably missing)
+                // OOO doc says this is optional anyway.
+                byteCnt = 3 + in.remaining();
+                data = IOUtils.safelyAllocate(byteCnt, DATA_SIZE);
+                LittleEndian.putUShort(data, 0, nChars);
+                LittleEndian.putByte(data, 2, is16BitFlag);
+                in.readFully(data, 3, byteCnt-3);
+                charset = StandardCharsets.UTF_8;
+            }
         } else {
-            rawText = StringUtil.readUnicodeLE(in, nChars);
-        }
-        field_1_username = rawText.trim();
-
-        // consume padding
-        int padSize = in.remaining();
-        while (padSize > 0) {
-            // in some cases this seems to be garbage (non spaces)
-            in.readUByte();
-            padSize--;
+            // the normal case ...
+            data = IOUtils.safelyAllocate(in.remaining(), STRING_SIZE);
+            in.readFully(data);
+            if (UTF16FLAG.isSet(is16BitFlag)) {
+                byteCnt = Math.min(nChars * 2, data.length);
+                charset = StandardCharsets.UTF_16LE;
+            } else {
+                byteCnt = Math.min(nChars, data.length);
+                charset = StandardCharsets.ISO_8859_1;
+            }
         }
+
+        String rawValue = new String(data, 0, byteCnt, charset);
+        setUsername(rawValue.trim());
     }
 
     /**
@@ -104,9 +129,8 @@ public final class WriteAccessRecord extends StandardRecord {
      */
     public void setUsername(String username) {
         boolean is16bit = StringUtil.hasMultibyte(username);
-        int encodedByteCount = 3 + username.length() * (is16bit ? 2 : 1);
-        int paddingSize = DATA_SIZE - encodedByteCount;
-        if (paddingSize < 0) {
+        int encodedByteCount = username.length() * (is16bit ? 2 : 1);
+        if (encodedByteCount > STRING_SIZE) {
             throw new IllegalArgumentException("Name is too long: " + username);
         }
 
@@ -131,14 +155,14 @@ public final class WriteAccessRecord extends StandardRecord {
 
         out.writeShort(username.length());
         out.writeByte(is16bit ? 0x01 : 0x00);
+
+        byte[] buf = PADDING.clone();
         if (is16bit) {
-            StringUtil.putUnicodeLE(username, out);
+            StringUtil.putUnicodeLE(username, buf, 0);
         } else {
-            StringUtil.putCompressedUnicode(username, out);
+            StringUtil.putCompressedUnicode(username, buf, 0);
         }
-        int encodedByteCount = 3 + username.length() * (is16bit ? 2 : 1);
-        int paddingSize = DATA_SIZE - encodedByteCount;
-        out.write(PADDING, 0, paddingSize);
+        out.write(buf);
     }
 
     @Override
index d3dbf38ea24b941b0890da498ccc4d475fe9c9ac..74699d1bb11239cf8daa66009bfd391421d99080 100644 (file)
@@ -26,6 +26,7 @@ import org.apache.poi.poifs.crypt.ChunkedCipherInputStream;
 import org.apache.poi.poifs.crypt.Decryptor;
 import org.apache.poi.poifs.crypt.EncryptionInfo;
 import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.Internal;
 import org.apache.poi.util.LittleEndian;
 import org.apache.poi.util.LittleEndianConsts;
 import org.apache.poi.util.LittleEndianInput;
@@ -208,4 +209,8 @@ public final class Biff8DecryptingStream implements BiffHeaderInput, LittleEndia
         ccis.readPlain(b, off, len);
     }
 
+    @Internal
+    public boolean isCurrentRecordEncrypted() {
+        return !shouldSkipEncryptionOnCurrentRecord;
+    }
 }
index 3613d1b68cd85f3c2bce8dc01fe427f34503c74e..0b788309961845042678a116c746df98164c308e 100644 (file)
@@ -53,6 +53,7 @@ class TestBiffDrawingToXml extends BaseTestIteratingXLS {
         // HSSFWorkbook cannot open it as well
         excludes.put("43493.xls", RecordInputStream.LeftoverDataException.class);
         excludes.put("44958_1.xls", RecordInputStream.LeftoverDataException.class);
+        excludes.put("protected_66115.xls", EncryptedDocumentException.class);
         return excludes;
     }
 
index 5013e4525870f722cfc1c9444a17f3903c85ac19..82ae713ab1c3e29fb6079c8da9fada0bdf7c26a6 100644 (file)
@@ -20,6 +20,7 @@ import java.io.File;
 import java.io.IOException;
 import java.util.Map;
 
+import org.apache.poi.EncryptedDocumentException;
 import org.apache.poi.util.RecordFormatException;
 
 class TestBiffViewer extends BaseTestIteratingXLS {
@@ -41,6 +42,7 @@ class TestBiffViewer extends BaseTestIteratingXLS {
 
         excludes.put("61300.xls", IndexOutOfBoundsException.class);
         excludes.put("poi-fuzz.xls", RecordFormatException.class);
+        excludes.put("protected_66115.xls", RecordFormatException.class);
 
         return excludes;
     }
index bcd91e74d7a12d7adc023acef41153511d29d093..375bf8fa7699e9b128697e5d0597bd49f1ca097c 100644 (file)
@@ -26,6 +26,7 @@ import org.apache.poi.hssf.eventusermodel.HSSFEventFactory;
 import org.apache.poi.hssf.eventusermodel.HSSFRequest;
 import org.apache.poi.hssf.record.RecordInputStream;
 import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.apache.poi.util.RecordFormatException;
 import org.junit.jupiter.api.Assertions;
 
 class TestEFBiffViewer extends BaseTestIteratingXLS {
@@ -37,6 +38,7 @@ class TestEFBiffViewer extends BaseTestIteratingXLS {
         excludes.put("51832.xls", EncryptedDocumentException.class);
         excludes.put("xor-encryption-abc.xls", EncryptedDocumentException.class);
         excludes.put("password.xls", EncryptedDocumentException.class);
+        excludes.put("protected_66115.xls", EncryptedDocumentException.class);
         // HSSFWorkbook cannot open it as well
         excludes.put("43493.xls", RecordInputStream.LeftoverDataException.class);
         excludes.put("44958_1.xls", RecordInputStream.LeftoverDataException.class);
index 177871e25b29ce65d505343096a442ee413eac33..3b10498e6929aeff2f0a6f375008ae2680b62118 100644 (file)
@@ -44,6 +44,7 @@ class TestFormulaViewer extends BaseTestIteratingXLS {
         excludes.put("password.xls", EncryptedDocumentException.class);
         excludes.put("43493.xls", RecordInputStream.LeftoverDataException.class);  // HSSFWorkbook cannot open it as well
         excludes.put("44958_1.xls", RecordInputStream.LeftoverDataException.class);
+        excludes.put("protected_66115.xls", EncryptedDocumentException.class);
         return excludes;
     }
 
index ecfae46d48327d4a9ae75c48bfde0706c51a7cf9..37ff2aae1355f45668194e6fdedbbd7b4ea57c9b 100644 (file)
Binary files a/test-data/spreadsheet/stress.xls and b/test-data/spreadsheet/stress.xls differ