package org.apache.poi.poifs.crypt;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
import org.apache.poi.poifs.filesystem.DirectoryEntry;
import org.apache.poi.poifs.filesystem.DocumentEntry;
import org.apache.poi.poifs.filesystem.POIFSWriterEvent;
import org.apache.poi.poifs.filesystem.POIFSWriterListener;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.LittleEndianOutput;
import org.apache.poi.util.StringUtil;
public class DataSpaceMapUtils {
private static final int MAX_RECORD_LENGTH = 100_000;
public static void addDefaultDataSpace(DirectoryEntry dir) throws IOException {
DataSpaceMapEntry dsme = new DataSpaceMapEntry(
new int[]{ 0 }
, new String[]{ Decryptor.DEFAULT_POIFS_ENTRY }
, "StrongEncryptionDataSpace"
);
DataSpaceMap dsm = new DataSpaceMap(new DataSpaceMapEntry[]{dsme});
createEncryptionEntry(dir, "\u0006DataSpaces/DataSpaceMap", dsm);
DataSpaceDefinition dsd = new DataSpaceDefinition(new String[]{ "StrongEncryptionTransform" });
createEncryptionEntry(dir, "\u0006DataSpaces/DataSpaceInfo/StrongEncryptionDataSpace", dsd);
TransformInfoHeader tih = new TransformInfoHeader(
1
, "{FF9A3F03-56EF-4613-BDD5-5A41C1D07246}"
, "Microsoft.Container.EncryptionTransform"
, 1, 0, 1, 0, 1, 0
);
IRMDSTransformInfo irm = new IRMDSTransformInfo(tih, 0, null);
createEncryptionEntry(dir, "\u0006DataSpaces/TransformInfo/StrongEncryptionTransform/\u0006Primary", irm);
DataSpaceVersionInfo dsvi = new DataSpaceVersionInfo("Microsoft.Container.DataSpaces", 1, 0, 1, 0, 1, 0);
createEncryptionEntry(dir, "\u0006DataSpaces/Version", dsvi);
}
public static DocumentEntry createEncryptionEntry(DirectoryEntry dir, String path, EncryptionRecord out) throws IOException {
String[] parts = path.split("/");
for (int i=0; i<parts.length-1; i++) {
dir = dir.hasEntry(parts[i])
? (DirectoryEntry)dir.getEntry(parts[i])
: dir.createDirectory(parts[i]);
}
final byte[] buf = new byte[5000];
LittleEndianByteArrayOutputStream bos = new LittleEndianByteArrayOutputStream(buf, 0);
out.write(bos);
String fileName = parts[parts.length-1];
if (dir.hasEntry(fileName)) {
dir.getEntry(fileName).delete();
}
return dir.createDocument(fileName, bos.getWriteIndex(), new POIFSWriterListener(){
public void processPOIFSWriterEvent(POIFSWriterEvent event) {
try {
event.getStream().write(buf, 0, event.getLimit());
} catch (IOException e) {
throw new EncryptedDocumentException(e);
}
}
});
}
public static class DataSpaceMap implements EncryptionRecord {
DataSpaceMapEntry[] entries;
public DataSpaceMap(DataSpaceMapEntry[] entries) {
this.entries = entries.clone();
}
public DataSpaceMap(LittleEndianInput is) {
is.readInt();
int entryCount = is.readInt();
entries = new DataSpaceMapEntry[entryCount];
for (int i=0; i<entryCount; i++) {
entries[i] = new DataSpaceMapEntry(is);
}
}
public void write(LittleEndianByteArrayOutputStream os) {
os.writeInt(8);
os.writeInt(entries.length);
for (DataSpaceMapEntry dsme : entries) {
dsme.write(os);
}
}
}
public static class DataSpaceMapEntry implements EncryptionRecord {
final int[] referenceComponentType;
final String[] referenceComponent;
final String dataSpaceName;
public DataSpaceMapEntry(int[] referenceComponentType, String[] referenceComponent, String dataSpaceName) {
this.referenceComponentType = referenceComponentType.clone();
this.referenceComponent = referenceComponent.clone();
this.dataSpaceName = dataSpaceName;
}
public DataSpaceMapEntry(LittleEndianInput is) {
is.readInt();
int referenceComponentCount = is.readInt();
referenceComponentType = new int[referenceComponentCount];
referenceComponent = new String[referenceComponentCount];
for (int i=0; i<referenceComponentCount; i++) {
referenceComponentType[i] = is.readInt();
referenceComponent[i] = readUnicodeLPP4(is);
}
dataSpaceName = readUnicodeLPP4(is);
}
public void write(LittleEndianByteArrayOutputStream os) {
int start = os.getWriteIndex();
LittleEndianOutput sizeOut = os.createDelayedOutput(LittleEndianConsts.INT_SIZE);
os.writeInt(referenceComponent.length);
for (int i=0; i<referenceComponent.length; i++) {
os.writeInt(referenceComponentType[i]);
writeUnicodeLPP4(os, referenceComponent[i]);
}
writeUnicodeLPP4(os, dataSpaceName);
sizeOut.writeInt(os.getWriteIndex()-start);
}
}
public static class DataSpaceDefinition implements EncryptionRecord {
String[] transformer;
public DataSpaceDefinition(String[] transformer) {
this.transformer = transformer.clone();
}
public DataSpaceDefinition(LittleEndianInput is) {
is.readInt();
int transformReferenceCount = is.readInt();
transformer = new String[transformReferenceCount];
for (int i=0; i<transformReferenceCount; i++) {
transformer[i] = readUnicodeLPP4(is);
}
}
public void write(LittleEndianByteArrayOutputStream bos) {
bos.writeInt(8);
bos.writeInt(transformer.length);
for (String str : transformer) {
writeUnicodeLPP4(bos, str);
}
}
}
public static class IRMDSTransformInfo implements EncryptionRecord {
TransformInfoHeader ;
int ;
String xrMLLicense;
public (TransformInfoHeader transformInfoHeader, int extensibilityHeader, String xrMLLicense) {
this.transformInfoHeader = transformInfoHeader;
this.extensibilityHeader = extensibilityHeader;
this.xrMLLicense = xrMLLicense;
}
public IRMDSTransformInfo(LittleEndianInput is) {
transformInfoHeader = new TransformInfoHeader(is);
extensibilityHeader = is.readInt();
xrMLLicense = readUtf8LPP4(is);
}
public void write(LittleEndianByteArrayOutputStream bos) {
transformInfoHeader.write(bos);
bos.writeInt(extensibilityHeader);
writeUtf8LPP4(bos, xrMLLicense);
bos.writeInt(4);
}
}
public static class implements EncryptionRecord {
int ;
String ;
String ;
int = 1, ;
int = 1, ;
int = 1, ;
public (
int transformType,
String transformerId,
String transformerName,
int readerVersionMajor, int readerVersionMinor,
int updaterVersionMajor, int updaterVersionMinor,
int writerVersionMajor, int writerVersionMinor
){
this.transformType = transformType;
this.transformerId = transformerId;
this.transformerName = transformerName;
this.readerVersionMajor = readerVersionMajor;
this.readerVersionMinor = readerVersionMinor;
this.updaterVersionMajor = updaterVersionMajor;
this.updaterVersionMinor = updaterVersionMinor;
this.writerVersionMajor = writerVersionMajor;
this.writerVersionMinor = writerVersionMinor;
}
public (LittleEndianInput is) {
is.readInt();
transformType = is.readInt();
transformerId = readUnicodeLPP4(is);
transformerName = readUnicodeLPP4(is);
readerVersionMajor = is.readShort();
readerVersionMinor = is.readShort();
updaterVersionMajor = is.readShort();
updaterVersionMinor = is.readShort();
writerVersionMajor = is.readShort();
writerVersionMinor = is.readShort();
}
public void (LittleEndianByteArrayOutputStream bos) {
int start = bos.getWriteIndex();
LittleEndianOutput sizeOut = bos.createDelayedOutput(LittleEndianConsts.INT_SIZE);
bos.writeInt(transformType);
writeUnicodeLPP4(bos, transformerId);
sizeOut.writeInt(bos.getWriteIndex()-start);
writeUnicodeLPP4(bos, transformerName);
bos.writeShort(readerVersionMajor);
bos.writeShort(readerVersionMinor);
bos.writeShort(updaterVersionMajor);
bos.writeShort(updaterVersionMinor);
bos.writeShort(writerVersionMajor);
bos.writeShort(writerVersionMinor);
}
}
public static class DataSpaceVersionInfo implements EncryptionRecord {
String featureIdentifier;
int readerVersionMajor = 1, readerVersionMinor;
int updaterVersionMajor = 1, updaterVersionMinor;
int writerVersionMajor = 1, writerVersionMinor;
public DataSpaceVersionInfo(LittleEndianInput is) {
featureIdentifier = readUnicodeLPP4(is);
readerVersionMajor = is.readShort();
readerVersionMinor = is.readShort();
updaterVersionMajor = is.readShort();
updaterVersionMinor = is.readShort();
writerVersionMajor = is.readShort();
writerVersionMinor = is.readShort();
}
public DataSpaceVersionInfo(
String featureIdentifier,
int readerVersionMajor, int readerVersionMinor,
int updaterVersionMajor, int updaterVersionMinor,
int writerVersionMajor, int writerVersionMinor
){
this.featureIdentifier = featureIdentifier;
this.readerVersionMajor = readerVersionMajor;
this.readerVersionMinor = readerVersionMinor;
this.updaterVersionMajor = updaterVersionMajor;
this.updaterVersionMinor = updaterVersionMinor;
this.writerVersionMajor = writerVersionMajor;
this.writerVersionMinor = writerVersionMinor;
}
public void write(LittleEndianByteArrayOutputStream bos) {
writeUnicodeLPP4(bos, featureIdentifier);
bos.writeShort(readerVersionMajor);
bos.writeShort(readerVersionMinor);
bos.writeShort(updaterVersionMajor);
bos.writeShort(updaterVersionMinor);
bos.writeShort(writerVersionMajor);
bos.writeShort(writerVersionMinor);
}
}
public static String readUnicodeLPP4(LittleEndianInput is) {
int length = is.readInt();
if (length%2 != 0) {
throw new EncryptedDocumentException(
"UNICODE-LP-P4 structure is a multiple of 4 bytes. "
+ "If Padding is present, it MUST be exactly 2 bytes long");
}
String result = StringUtil.readUnicodeLE(is, length/2);
if (length%4==2) {
is.readShort();
}
return result;
}
public static void writeUnicodeLPP4(LittleEndianOutput os, String string) {
byte[] buf = StringUtil.getToUnicodeLE(string);
os.writeInt(buf.length);
os.write(buf);
if (buf.length%4==2) {
os.writeShort(0);
}
}
public static String readUtf8LPP4(LittleEndianInput is) {
int length = is.readInt();
if (length == 0 || length == 4) {
is.readInt();
return length == 0 ? null : "";
}
byte[] data = IOUtils.safelyAllocate(length, MAX_RECORD_LENGTH);
is.readFully(data);
int scratchedBytes = length%4;
if (scratchedBytes > 0) {
for (int i=0; i<(4-scratchedBytes); i++) {
is.readByte();
}
}
return new String(data, 0, data.length, StandardCharsets.UTF_8);
}
public static void writeUtf8LPP4(LittleEndianOutput os, String str) {
if (str == null || str.isEmpty()) {
os.writeInt(str == null ? 0 : 4);
os.writeInt(0);
} else {
byte[] buf = str.getBytes(StandardCharsets.UTF_8);
os.writeInt(buf.length);
os.write(buf);
int scratchBytes = buf.length%4;
if (scratchBytes > 0) {
for (int i=0; i<(4-scratchBytes); i++) {
os.writeByte(0);
}
}
}
}
}