fix for bug 45866 - allowed for change of unicode compression across Continue records

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@703620 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Josh Micich 2008-10-11 01:11:05 +00:00
parent b2988b3666
commit 7acf9a90b5
6 changed files with 183 additions and 68 deletions

View File

@ -37,6 +37,7 @@
<!-- Don't forget to update status.xml too! --> <!-- Don't forget to update status.xml too! -->
<release version="3.2-alpha1" date="2008-??-??"> <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">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">43354 - support for evalating formulas with missing args</action>
<action dev="POI-DEVELOPERS" type="fix">45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord</action> <action dev="POI-DEVELOPERS" type="fix">45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord</action>

View File

@ -34,6 +34,7 @@
<!-- Don't forget to update changes.xml too! --> <!-- Don't forget to update changes.xml too! -->
<changes> <changes>
<release version="3.2-alpha1" date="2008-??-??"> <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">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">43354 - support for evalating formulas with missing args</action>
<action dev="POI-DEVELOPERS" type="fix">45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord</action> <action dev="POI-DEVELOPERS" type="fix">45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord</action>

View File

@ -218,53 +218,80 @@ public final class RecordInputStream extends InputStream {
return result; return result;
} }
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 * given a byte array of 16-bit unicode characters, compress to 8-bit and
* return a string * return a string
* *
* { 0x16, 0x00 } -0x16 * { 0x16, 0x00 } -0x16
* *
* @param length the length of the final string * @param requestedLength the length of the final string
* @return the converted string * @return the converted string
* @exception IllegalArgumentException if len is too large (i.e., * @exception IllegalArgumentException if len is too large (i.e.,
* there is not enough data in string to create a String of that * there is not enough data in string to create a String of that
* length) * length)
*/ */
public String readUnicodeLEString(int length) { public String readUnicodeLEString(int requestedLength) {
if ((length < 0) || (((remaining() / 2) < length) && !isContinueNext())) { return readStringCommon(requestedLength, false);
throw new IllegalArgumentException("Illegal length - asked for " + length + " but only " + (remaining()/2) + " left!");
} }
StringBuffer buf = new StringBuffer(length); public String readCompressedUnicode(int requestedLength) {
for (int i=0;i<length;i++) { return readStringCommon(requestedLength, true);
if ((remaining() == 0) && (isContinueNext())){ }
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(); nextRecord();
int compressByte = readByte(); // note - the compressed flag may change on the fly
if(compressByte != 1) throw new IllegalArgumentException("compressByte in continue records must be 1 while reading unicode LE string"); byte compressFlag = readByte();
isCompressedEncoding = (compressFlag == 0);
} }
char ch = (char)readShort();
buf.append(ch);
}
return buf.toString();
}
public String readCompressedUnicode(int length) {
if ((length < 0) || ((remaining() < length) && !isContinueNext())) {
throw new IllegalArgumentException("Illegal length " + length);
}
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();
} }
/** Returns an excel style unicode string from the bytes reminaing in the record. /** Returns an excel style unicode string from the bytes reminaing in the record.

View File

@ -84,6 +84,7 @@ public final class AllRecordTests {
result.addTestSuite(TestPaneRecord.class); result.addTestSuite(TestPaneRecord.class);
result.addTestSuite(TestPlotAreaRecord.class); result.addTestSuite(TestPlotAreaRecord.class);
result.addTestSuite(TestPlotGrowthRecord.class); result.addTestSuite(TestPlotGrowthRecord.class);
result.addTestSuite(TestRecordInputStream.class);
result.addTestSuite(TestRecordFactory.class); result.addTestSuite(TestRecordFactory.class);
result.addTestSuite(TestSCLRecord.class); result.addTestSuite(TestSCLRecord.class);
result.addTestSuite(TestSSTDeserializer.class); result.addTestSuite(TestSSTDeserializer.class);

View File

@ -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);
}
}

View File

@ -1,4 +1,3 @@
/* ==================================================================== /* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with contributor license agreements. See the NOTICE file distributed with
@ -19,6 +18,8 @@
package org.apache.poi.hssf.record; package org.apache.poi.hssf.record;
import org.apache.poi.util.HexRead;
import junit.framework.TestCase; import junit.framework.TestCase;
/** /**
@ -26,18 +27,10 @@ import junit.framework.TestCase;
* *
* @author Jason Height (jheight at apache.org) * @author Jason Height (jheight at apache.org)
*/ */
public class TestUnicodeString public final class TestUnicodeString extends TestCase {
extends TestCase
{
public TestUnicodeString( String s )
{
super( s );
}
public void testSmallStringSize() public void testSmallStringSize() {
throws Exception
{
//Test a basic string //Test a basic string
UnicodeString s = makeUnicodeString("Test"); UnicodeString s = makeUnicodeString("Test");
UnicodeString.UnicodeRecordStats stats = new UnicodeString.UnicodeRecordStats(); UnicodeString.UnicodeRecordStats stats = new UnicodeString.UnicodeRecordStats();
@ -80,9 +73,7 @@ public class TestUnicodeString
assertEquals(30, stats.recordSize); assertEquals(30, stats.recordSize);
} }
public void testPerfectStringSize() public void testPerfectStringSize() {
throws Exception
{
//Test a basic string //Test a basic string
UnicodeString s = makeUnicodeString(SSTRecord.MAX_RECORD_SIZE-2-1); UnicodeString s = makeUnicodeString(SSTRecord.MAX_RECORD_SIZE-2-1);
UnicodeString.UnicodeRecordStats stats = new UnicodeString.UnicodeRecordStats(); UnicodeString.UnicodeRecordStats stats = new UnicodeString.UnicodeRecordStats();
@ -99,9 +90,7 @@ public class TestUnicodeString
assertEquals(SSTRecord.MAX_RECORD_SIZE-1, stats.recordSize); assertEquals(SSTRecord.MAX_RECORD_SIZE-1, stats.recordSize);
} }
public void testPerfectRichStringSize() public void testPerfectRichStringSize() {
throws Exception
{
//Test a rich text string //Test a rich text string
UnicodeString s = makeUnicodeString(SSTRecord.MAX_RECORD_SIZE-2-1-8-2); UnicodeString s = makeUnicodeString(SSTRecord.MAX_RECORD_SIZE-2-1-8-2);
s.addFormatRun(new UnicodeString.FormatRun((short)1,(short)0)); s.addFormatRun(new UnicodeString.FormatRun((short)1,(short)0));
@ -123,7 +112,7 @@ public class TestUnicodeString
assertEquals(SSTRecord.MAX_RECORD_SIZE-1, stats.recordSize); assertEquals(SSTRecord.MAX_RECORD_SIZE-1, stats.recordSize);
} }
public void testContinuedStringSize() throws Exception { public void testContinuedStringSize() {
//Test a basic string //Test a basic string
UnicodeString s = makeUnicodeString(SSTRecord.MAX_RECORD_SIZE-2-1+20); UnicodeString s = makeUnicodeString(SSTRecord.MAX_RECORD_SIZE-2-1+20);
UnicodeString.UnicodeRecordStats stats = new UnicodeString.UnicodeRecordStats(); 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*/ /** 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 //Test a basic string
int strSize = SSTRecord.MAX_RECORD_SIZE*2; int strSize = SSTRecord.MAX_RECORD_SIZE*2;
//String overhead //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); UnicodeString st = new UnicodeString(s);
st.setOptionFlags((byte)0); st.setOptionFlags((byte)0);
return st; return st;
} }
private UnicodeString makeUnicodeString( int numChars) { private static UnicodeString makeUnicodeString( int numChars) {
StringBuffer b = new StringBuffer(numChars); StringBuffer b = new StringBuffer(numChars);
for (int i=0;i<numChars;i++) { for (int i=0;i<numChars;i++) {
b.append(i%10); b.append(i%10);
} }
return makeUnicodeString(b.toString()); return makeUnicodeString(b.toString());
} }
} }