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;
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);
}
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());
}
/**
*/
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);
}
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