|
|
@@ -17,11 +17,17 @@ |
|
|
|
|
|
|
|
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 |