]> source.dussan.org Git - poi.git/commitdiff
fix for bug 45866 - allowed for change of unicode compression across Continue records
authorJosh Micich <josh@apache.org>
Sat, 11 Oct 2008 01:11:05 +0000 (01:11 +0000)
committerJosh Micich <josh@apache.org>
Sat, 11 Oct 2008 01:11:05 +0000 (01:11 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@703620 13f79535-47bb-0310-9956-ffa450edef68

src/documentation/content/xdocs/changes.xml
src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/record/RecordInputStream.java
src/testcases/org/apache/poi/hssf/record/AllRecordTests.java
src/testcases/org/apache/poi/hssf/record/TestRecordInputStream.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/record/TestUnicodeString.java

index 2ba7f0c978bb4e23344467e1a9c41c46887e8a59..28443d4270854a22b58afe84fdf7288228e665cf 100644 (file)
@@ -37,6 +37,7 @@
 
                <!-- Don't forget to update status.xml too! -->
         <release version="3.2-alpha1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="fix">45866 - allowed for change of unicode compression across Continue records</action>
            <action dev="POI-DEVELOPERS" type="fix">45964 - support for link formulas in Text Objects</action>
            <action dev="POI-DEVELOPERS" type="fix">43354 - support for evalating formulas with missing args</action>
            <action dev="POI-DEVELOPERS" type="fix">45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord</action>
index 8f661cdb4ed6f023a213c5918d9dc58a0e2410ff..80d6641c835296a3b1794ae747daad34f2e3ff9d 100644 (file)
@@ -34,6 +34,7 @@
        <!-- Don't forget to update changes.xml too! -->
     <changes>
         <release version="3.2-alpha1" date="2008-??-??">
+           <action dev="POI-DEVELOPERS" type="fix">45866 - allowed for change of unicode compression across Continue records</action>
            <action dev="POI-DEVELOPERS" type="fix">45964 - support for link formulas in Text Objects</action>
            <action dev="POI-DEVELOPERS" type="fix">43354 - support for evalating formulas with missing args</action>
            <action dev="POI-DEVELOPERS" type="fix">45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord</action>
index 02c4f7a05c7cfe6130aca5ee93331a556ff40bce..696f4fef7b5b8c8cbcf6c44df068551c47a6d542 100755 (executable)
@@ -218,54 +218,81 @@ public final class RecordInputStream extends InputStream {
                return result;
        }
 
-  /**
-   *  given a byte array of 16-bit unicode characters, compress to 8-bit and
-   *  return a string
-   *
-   * { 0x16, 0x00 } -0x16
-   *
-   * @param length the length of the final string
-   * @return                                     the converted string
-   * @exception  IllegalArgumentException        if len is too large (i.e.,
-   *      there is not enough data in string to create a String of that
-   *      length)
-   */
-  public String readUnicodeLEString(int length) {
-    if ((length < 0) || (((remaining() / 2) < length) && !isContinueNext())) {
-            throw new IllegalArgumentException("Illegal length - asked for " + length + " but only " + (remaining()/2) + " left!");
-    }
-
-    StringBuffer buf = new StringBuffer(length);
-    for (int i=0;i<length;i++) {
-      if ((remaining() == 0) && (isContinueNext())){
-        nextRecord();
-        int compressByte = readByte();
-        if(compressByte != 1) throw new IllegalArgumentException("compressByte in continue records must be 1 while reading unicode LE string");
-      }
-      char ch = (char)readShort();
-      buf.append(ch);
-    }
-    return buf.toString();
-  }
+       public String readString() {
+               int requestedLength = readUShort();
+               byte compressFlag = readByte();
+               return readStringCommon(requestedLength, compressFlag == 0);
+       }
+       /**
+        *  given a byte array of 16-bit unicode characters, compress to 8-bit and
+        *  return a string
+        *
+        * { 0x16, 0x00 } -0x16
+        *
+        * @param requestedLength the length of the final string
+        * @return                                     the converted string
+        * @exception  IllegalArgumentException        if len is too large (i.e.,
+        *      there is not enough data in string to create a String of that
+        *      length)
+        */
+       public String readUnicodeLEString(int requestedLength) {
+               return readStringCommon(requestedLength, false);
+       }
 
-  public String readCompressedUnicode(int length) {
-    if ((length < 0) || ((remaining() < length) && !isContinueNext())) {
-            throw new IllegalArgumentException("Illegal length " + length);
-    }
+       public String readCompressedUnicode(int requestedLength) {
+               return readStringCommon(requestedLength, true);
+       }
 
-    StringBuffer buf = new StringBuffer(length);
-    for (int i=0;i<length;i++) {
-      if ((remaining() == 0) && (isContinueNext())) {
-          nextRecord();
-          int compressByte = readByte();
-          if(compressByte != 0) throw new IllegalArgumentException("compressByte in continue records must be 0 while reading compressed unicode");
-      }
-      byte b = readByte();
-      char ch = (char)(0x00FF & b); // avoid sex
-      buf.append(ch);
-    }
-    return buf.toString();
-  }
+       private String readStringCommon(int requestedLength, boolean pIsCompressedEncoding) {
+               // Sanity check to detect garbage string lengths
+               if (requestedLength < 0 || requestedLength > 0x100000) { // 16 million chars?
+                       throw new IllegalArgumentException("Bad requested string length (" + requestedLength + ")");
+               }
+               char[] buf = new char[requestedLength];
+               boolean isCompressedEncoding = pIsCompressedEncoding;
+               int curLen = 0;
+               while(true) {
+                       int availableChars =isCompressedEncoding ?  remaining() : remaining() / LittleEndian.SHORT_SIZE;
+                       if (requestedLength - curLen <= availableChars) {
+                               // enough space in current record, so just read it out
+                               while(curLen < requestedLength) {
+                                       char ch;
+                                       if (isCompressedEncoding) {
+                                               ch = (char)readUByte();
+                                       } else {
+                                               ch = (char)readShort();
+                                       }
+                                       buf[curLen] = ch;
+                                       curLen++;
+                               }
+                               return new String(buf);
+                       }
+                       // else string has been spilled into next continue record
+                       // so read what's left of the current record
+                       while(availableChars > 0) {
+                               char ch;
+                               if (isCompressedEncoding) {
+                                       ch = (char)readUByte();
+                               } else {
+                                       ch = (char)readShort();
+                               }
+                               buf[curLen] = ch;
+                               curLen++;
+                               availableChars--;
+                       }
+                       if (!isContinueNext()) {
+                               throw new RecordFormatException("Expected to find a ContinueRecord in order to read remaining " 
+                                               + (requestedLength-curLen) + " of " + requestedLength + " chars");
+                       }
+                       if(remaining() != 0) {
+                               throw new RecordFormatException("Odd number of bytes(" + remaining() + ") left behind");
+                       }
+                       nextRecord();
+                       // note - the compressed flag may change on the fly
+                       byte compressFlag = readByte();
+                       isCompressedEncoding = (compressFlag == 0); 
+               }
+       }
 
   /** Returns an excel style unicode string from the bytes reminaing in the record.
    * <i>Note:</i> Unicode strings differ from <b>normal</b> strings due to the addition of
index fb3270ae753105ed830cd85b6f9c4cbb24b23fb6..5fb6f4aa03a7ed7ff5e820a2c9b8d98e5e981667 100755 (executable)
@@ -84,6 +84,7 @@ public final class AllRecordTests {
                result.addTestSuite(TestPaneRecord.class);
                result.addTestSuite(TestPlotAreaRecord.class);
                result.addTestSuite(TestPlotGrowthRecord.class);
+               result.addTestSuite(TestRecordInputStream.class);
                result.addTestSuite(TestRecordFactory.class);
                result.addTestSuite(TestSCLRecord.class);
                result.addTestSuite(TestSSTDeserializer.class);
diff --git a/src/testcases/org/apache/poi/hssf/record/TestRecordInputStream.java b/src/testcases/org/apache/poi/hssf/record/TestRecordInputStream.java
new file mode 100644 (file)
index 0000000..2b236e1
--- /dev/null
@@ -0,0 +1,97 @@
+/* ====================================================================
+   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.hssf.record;
+
+import org.apache.poi.util.HexRead;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link RecordInputStream}
+ *
+ * @author Josh Micich
+ */
+public final class TestRecordInputStream extends TestCase {
+
+       /**
+        * Data inspired by attachment 22626 of bug 45866<br/>
+        * A unicode string of 18 chars, with a continue record where the compression flag changes
+        */
+       private static final String HED_DUMP1 = ""
+                       + "1A 59 00 8A 9E 8A " // 3 uncompressed unicode chars
+                       + "3C 00 " // Continue sid
+                       + "10 00 " // rec size 16 (1+15)
+                       + "00"  // next chunk is compressed
+                       + "20 2D 20 4D 75 6C 74 69 6C 69 6E 67 75 61 6C " // 15 chars
+       ;
+       /**
+        * same string re-arranged
+        */
+       private static final String HED_DUMP2 = ""
+                       // 15 chars at end of current record
+                       + "4D 75 6C 74 69 6C 69 6E 67 75 61 6C 20 2D 20"
+                       + "3C 00 " // Continue sid
+                       + "07 00 " // rec size 7 (1+6)
+                       + "01"  // this bit uncompressed
+                       + "1A 59 00 8A 9E 8A " // 3 uncompressed unicode chars
+       ;
+       public void testChangeOfCompressionFlag_bug25866() {
+               byte[] changingFlagSimpleData = HexRead.readFromString(""
+                               + "AA AA "  // fake SID
+                               + "06 00 "  // first rec len 6
+                               + HED_DUMP1
+                               );
+               RecordInputStream in = TestcaseRecordInputStream.create(changingFlagSimpleData);
+               String actual;
+               try {
+                       actual = in.readUnicodeLEString(18);
+               } catch (IllegalArgumentException e) {
+                       if ("compressByte in continue records must be 1 while reading unicode LE string".equals(e.getMessage())) {
+                               throw new AssertionFailedError("Identified bug 45866");
+                       }
+                               
+                       throw e;
+               }
+               assertEquals("\u591A\u8A00\u8A9E - Multilingual", actual);
+       }
+
+       public void testChangeFromUnCompressedToCompressed() {
+               byte[] changingFlagSimpleData = HexRead.readFromString(""
+                               + "AA AA "  // fake SID
+                               + "0F 00 "  // first rec len 15
+                               + HED_DUMP2
+                               );
+               RecordInputStream in = TestcaseRecordInputStream.create(changingFlagSimpleData);
+               String actual = in.readCompressedUnicode(18);
+               assertEquals("Multilingual - \u591A\u8A00\u8A9E", actual);
+       }
+       
+       public void testReadString() {
+               byte[] changingFlagFullData = HexRead.readFromString(""
+                               + "AA AA "  // fake SID
+                               + "12 00 "  // first rec len 18 (15 + next 3 bytes)
+                               + "12 00 "  // total chars 18
+                               + "00 "  // this bit compressed
+                               + HED_DUMP2
+                               );
+               RecordInputStream in = TestcaseRecordInputStream.create(changingFlagFullData);
+               String actual = in.readString();
+               assertEquals("Multilingual - \u591A\u8A00\u8A9E", actual);
+       }
+}
index e449f167d5c1a1eb0114d6d5c5e1a8217d5f15b1..8f3c65be49b605e99ad64d149951b19060132379 100755 (executable)
@@ -1,4 +1,3 @@
-
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
@@ -19,6 +18,8 @@
 
 package org.apache.poi.hssf.record;
 
+import org.apache.poi.util.HexRead;
+
 import junit.framework.TestCase;
 
 /**
@@ -26,18 +27,10 @@ import junit.framework.TestCase;
  *
  * @author Jason Height (jheight at apache.org)
  */
-public class TestUnicodeString
-        extends TestCase
-{
+public final class TestUnicodeString extends TestCase {
 
-    public TestUnicodeString( String s )
-    {
-        super( s );
-    }
 
-    public void testSmallStringSize()
-            throws Exception
-    {
+    public void testSmallStringSize() {
         //Test a basic string
         UnicodeString s = makeUnicodeString("Test");
         UnicodeString.UnicodeRecordStats stats = new UnicodeString.UnicodeRecordStats();
@@ -80,9 +73,7 @@ public class TestUnicodeString
         assertEquals(30, stats.recordSize);
     }
 
-    public void testPerfectStringSize()
-            throws Exception
-    {
+    public void testPerfectStringSize() {
       //Test a basic string
       UnicodeString s = makeUnicodeString(SSTRecord.MAX_RECORD_SIZE-2-1);
       UnicodeString.UnicodeRecordStats stats = new UnicodeString.UnicodeRecordStats();
@@ -99,9 +90,7 @@ public class TestUnicodeString
       assertEquals(SSTRecord.MAX_RECORD_SIZE-1, stats.recordSize);
     }
 
-    public void testPerfectRichStringSize()
-            throws Exception
-    {
+    public void testPerfectRichStringSize() {
       //Test a rich text string
       UnicodeString s = makeUnicodeString(SSTRecord.MAX_RECORD_SIZE-2-1-8-2);
       s.addFormatRun(new UnicodeString.FormatRun((short)1,(short)0));
@@ -123,7 +112,7 @@ public class TestUnicodeString
       assertEquals(SSTRecord.MAX_RECORD_SIZE-1, stats.recordSize);
     }
 
-    public void testContinuedStringSize() throws Exception {
+    public void testContinuedStringSize() {
       //Test a basic string
       UnicodeString s = makeUnicodeString(SSTRecord.MAX_RECORD_SIZE-2-1+20);
       UnicodeString.UnicodeRecordStats stats = new UnicodeString.UnicodeRecordStats();
@@ -132,7 +121,7 @@ public class TestUnicodeString
     }
 
     /** Tests that a string size calculation that fits neatly in two records, the second being a continue*/
-    public void testPerfectContinuedStringSize() throws Exception {
+    public void testPerfectContinuedStringSize() {
       //Test a basic string
       int strSize = SSTRecord.MAX_RECORD_SIZE*2;
       //String overhead
@@ -150,19 +139,18 @@ public class TestUnicodeString
 
 
 
-    private UnicodeString makeUnicodeString( String s )
+    private static UnicodeString makeUnicodeString( String s )
     {
       UnicodeString st = new UnicodeString(s);
       st.setOptionFlags((byte)0);
       return st;
     }
 
-    private UnicodeString makeUnicodeString( int numChars) {
+    private static UnicodeString makeUnicodeString( int numChars) {
       StringBuffer b = new StringBuffer(numChars);
       for (int i=0;i<numChars;i++) {
         b.append(i%10);
       }
       return makeUnicodeString(b.toString());
     }
-
 }