in poi-scratchpad/src/main/java/org/apache/poi/hmef/attribute/MAPIAttribute.java [105:220]
public static List<MAPIAttribute> create(TNEFAttribute parent) throws IOException {
if(parent.getProperty() == TNEFProperty.ID_MAPIPROPERTIES) {
// Regular MAPI Properties, normally on the message
}
else if(parent.getProperty() == TNEFProperty.ID_ATTACHMENT) {
// MAPI Properties for an attachment
}
else {
// Something else, oh dear...
throw new IllegalArgumentException(
"Can only create from a MAPIProperty attribute, " +
"instead received a " + parent.getProperty() + " one"
);
}
try(UnsynchronizedByteArrayInputStream inp = UnsynchronizedByteArrayInputStream.builder().setByteArray(parent.getData()).get()) {
// First up, get the number of attributes
int count = LittleEndian.readInt(inp);
List<MAPIAttribute> attrs = new ArrayList<>();
// Now, read each one in in turn
for(int i=0; i<count; i++) {
int typeAndMV = LittleEndian.readUShort(inp);
int id = LittleEndian.readUShort(inp);
// Is it either Multi-Valued or Variable-Length?
boolean isMV = false;
boolean isVL = false;
int typeId = typeAndMV;
if( (typeAndMV & Types.MULTIVALUED_FLAG) != 0 ) {
isMV = true;
typeId -= Types.MULTIVALUED_FLAG;
}
if(typeId == Types.ASCII_STRING.getId() || typeId == Types.UNICODE_STRING.getId() ||
typeId == Types.BINARY.getId() || typeId == Types.DIRECTORY.getId()) {
isVL = true;
}
// Turn the type ID into a strongly typed thing
MAPIType type = Types.getById(typeId);
if (type == null) {
type = Types.createCustom(typeId);
}
// If it's a named property, rather than a standard
// MAPI property, grab the details of it
MAPIProperty prop = MAPIProperty.get(id);
if(id >= 0x8000 && id <= 0xFFFF) {
byte[] guid = new byte[16];
if (IOUtils.readFully(inp, guid) < 0) {
throw new IOException("Not enough data to read guid");
}
int mptype = LittleEndian.readInt(inp);
// Get the name of it
String name;
if(mptype == 0) {
// It's based on a normal one
int mpid = LittleEndian.readInt(inp);
MAPIProperty base = MAPIProperty.get(mpid);
name = base.name;
} else {
// Custom name was stored
int mplen = LittleEndian.readInt(inp);
byte[] mpdata = IOUtils.safelyAllocate(mplen, MAX_RECORD_LENGTH);
if (IOUtils.readFully(inp, mpdata) < 0) {
throw new IOException("Not enough data to read " + mplen + " bytes for attribute name");
}
name = StringUtil.getFromUnicodeLE(mpdata, 0, (mplen/2)-1);
skipToBoundary(mplen, inp);
}
// Now create
prop = MAPIProperty.createCustom(id, type, name);
}
if(prop == MAPIProperty.UNKNOWN) {
prop = MAPIProperty.createCustom(id, type, "(unknown " + Integer.toHexString(id) + ")");
}
// Now read in the value(s)
int values = 1;
if(isMV || isVL) {
values = LittleEndian.readInt(inp);
IOUtils.safelyAllocateCheck(values, MAX_RECORD_COUNT);
}
if (type == Types.NULL && values > 1) {
throw new IOException("Placeholder/NULL arrays aren't supported.");
}
for(int j=0; j<values; j++) {
int len = getLength(type, inp);
byte[] data = IOUtils.safelyAllocate(len, MAX_RECORD_LENGTH);
if (IOUtils.readFully(inp, data) < 0) {
throw new IOException("Not enough data to read " + len + " bytes of attribute value");
}
skipToBoundary(len, inp);
// Create
MAPIAttribute attr;
if(type == Types.UNICODE_STRING || type == Types.ASCII_STRING) {
attr = new MAPIStringAttribute(prop, typeId, data);
} else if(type == Types.APP_TIME || type == Types.TIME) {
attr = new MAPIDateAttribute(prop, typeId, data);
} else if(id == MAPIProperty.RTF_COMPRESSED.id) {
attr = new MAPIRtfAttribute(prop, typeId, data);
} else {
attr = new MAPIAttribute(prop, typeId, data);
}
attrs.add(attr);
}
}
// All done
return attrs;
}
}