[bug-66301] Add a method to properly write the header necessary for a MSG attachment. Thanks to Lyn Evans.

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1904685 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
PJ Fanning 2022-10-18 19:10:32 +00:00
parent 4c3a0b4e93
commit 933948a846
3 changed files with 113 additions and 17 deletions

View File

@ -218,7 +218,8 @@ public abstract class PropertiesChunk extends Chunk {
prop = MAPIProperty.createCustom(id, type, "Unknown " + id);
}
if (type == null) {
LOG.atWarn().log("Invalid type found, expected {} but got {} for property {}", prop.usualType, box(typeID),prop);
LOG.atWarn().log("Invalid type found, expected {} but got {} for property {}",
prop.usualType, box(typeID), prop);
going = false;
break;
}
@ -391,6 +392,47 @@ public abstract class PropertiesChunk extends Chunk {
return variableLengthProperties;
}
/**
* Writes the manually pre-calculated(have header and data written manually) properties.
*
* @param out
* The {@code OutputStream}.
* @return The variable-length properties that need to be written in another
* node.
* @throws IOException
* If an I/O error occurs.
*/
protected List<PropertyValue> writePreCalculatedProperties(OutputStream out) throws IOException {
List<PropertyValue> variableLengthProperties = new ArrayList<>();
for (Entry<MAPIProperty, PropertyValue> entry : properties.entrySet()) {
MAPIProperty property = entry.getKey();
PropertyValue value = entry.getValue();
if (value == null) {
continue;
}
if (property.id < 0) {
continue;
}
// generic header
// page 23, point 2.4.2
// tag is the property id and its type
long tag = Long.parseLong(getActualTypeTag(property, value.getActualType()), 16);
LittleEndian.putUInt(tag, out);
LittleEndian.putUInt(value.getFlags(), out); // readable + writable
MAPIType type = value.getActualType();
if (type.isFixedLength()) {
// page 11, point 2.1.2
writeFixedLengthValueHeader(out, property, type, value);
} else {
// page 12, point 2.1.3
writeVariableLengthPreCalculatedValue(out, value);
variableLengthProperties.add(value);
}
}
return variableLengthProperties;
}
private void writeFixedLengthValueHeader(OutputStream out, MAPIProperty property, MAPIType type, PropertyValue value) throws IOException {
// fixed type header
// page 24, point 2.4.2.1.1
@ -402,6 +444,19 @@ public abstract class PropertiesChunk extends Chunk {
out.write(new byte[8 - length]);
}
/**
* Writes out pre-calculated raw values which assume any variable length property `data`
* field to already have size, reserved and manually written header
* @param out
* @throws IOException
*/
private void writeVariableLengthPreCalculatedValue(OutputStream out, PropertyValue value) throws IOException {
// variable length header
// page 24, point 2.4.2.2
byte[] bytes = value.getRawValue();
out.write(bytes);
}
private void writeVariableLengthValueHeader(OutputStream out, MAPIProperty propertyEx, MAPIType type,
PropertyValue value) throws IOException {
// variable length header
@ -419,6 +474,15 @@ public abstract class PropertiesChunk extends Chunk {
LittleEndian.putUInt(0, out);
}
private String getActualTypeTag(MAPIProperty property, MAPIType actualType) {
StringBuilder buffer = new StringBuilder(Integer.toHexString(property.id).toUpperCase(Locale.ROOT));
while (buffer.length() < 4) {
buffer.insert(0, "0");
}
buffer.append(actualType.asFileEnding());
return buffer.toString();
}
private String getFileName(MAPIProperty property, MAPIType actualType) {
StringBuilder str = new StringBuilder(Integer.toHexString(property.id).toUpperCase(Locale.ROOT));
int need0count = 4 - str.length();

View File

@ -25,7 +25,7 @@ import org.apache.poi.util.LittleEndian;
/**
* A {@link PropertiesChunk} for a Storage Properties, such as Attachments and
* Recipients. This only has a 8 byte header
* Recipients. This only has an 8 byte header.
*/
public class StoragePropertiesChunk extends PropertiesChunk {
public StoragePropertiesChunk(ChunkGroup parentGroup) {
@ -49,4 +49,18 @@ public class StoragePropertiesChunk extends PropertiesChunk {
// Now properties
writeProperties(out);
}
/**
* Writes out pre-calculated header values which assume any variable length property `data`
* field to already have Size and Reserved
* @param out output stream (calling code must close this stream)
* @throws IOException
*/
public void writePreCalculatedValue(OutputStream out) throws IOException {
// 8 bytes of reserved zeros
out.write(new byte[8]);
// Now properties
writePreCalculatedProperties(out);
}
}

View File

@ -17,10 +17,14 @@
package org.apache.poi.hsmf.datatypes;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Verifies that the Chunks class is actually setup properly and hasn't been changed in ways
* that will break the library.
@ -28,22 +32,22 @@ import org.junit.jupiter.api.Test;
public final class TestChunkData {
@Test
void testChunkCreate() {
Chunk chunk;
Chunk chunk;
chunk = new StringChunk(0x0200, Types.createCustom(0x001E));
assertEquals("__substg1.0_0200001E", chunk.getEntryName());
assertEquals(0x0200, chunk.getChunkId());
assertEquals(0x001E, chunk.getType().getId());
chunk = new StringChunk("__substg1.0_", 0x0200, Types.createCustom(0x001E));
assertEquals("__substg1.0_0200001E", chunk.getEntryName());
assertEquals(0x0200, chunk.getChunkId());
assertEquals(0x001E, chunk.getType().getId());
chunk = new StringChunk("__substg1.0_", 0x0200, Types.createCustom(0x001E));
assertEquals("__substg1.0_0200001E", chunk.getEntryName());
assertEquals(0x0200, chunk.getChunkId());
assertEquals(0x001E, chunk.getType().getId());
chunk = new StringChunk("__substg1.0_", 0x0200, Types.getById(0x001E));
assertEquals("__substg1.0_0200001E", chunk.getEntryName());
assertEquals(0x0200, chunk.getChunkId());
assertEquals(0x001E, chunk.getType().getId());
chunk = new StringChunk("__substg1.0_", 0x0200, Types.getById(0x001E));
assertEquals("__substg1.0_0200001E", chunk.getEntryName());
assertEquals(0x0200, chunk.getChunkId());
assertEquals(0x001E, chunk.getType().getId());
/* test the lower and upper limits of the chunk ids */
chunk = new StringChunk(0x0000, Types.createCustom(0x001E));
@ -65,26 +69,40 @@ public final class TestChunkData {
@Test
void testDisplayToChunk() {
StringChunk chunk = new StringChunk(0x0E04, Types.UNICODE_STRING);
assertEquals(chunk.getChunkId(), MAPIProperty.DISPLAY_TO.id);
assertEquals(chunk.getChunkId(), MAPIProperty.DISPLAY_TO.id);
}
@Test
void testDisplayCCChunk() {
StringChunk chunk = new StringChunk(0x0E03, Types.UNICODE_STRING);
assertEquals(chunk.getChunkId(), MAPIProperty.DISPLAY_CC.id);
assertEquals(chunk.getChunkId(), MAPIProperty.DISPLAY_CC.id);
}
@Test
void testDisplayBCCChunk() {
StringChunk chunk = new StringChunk(0x0E02, Types.UNICODE_STRING);
assertEquals(chunk.getChunkId(), MAPIProperty.DISPLAY_BCC.id);
assertEquals(chunk.getChunkId(), MAPIProperty.DISPLAY_BCC.id);
}
@Test
void testSubjectChunk() {
Chunk chunk = new StringChunk(0x0037, Types.UNICODE_STRING);
assertEquals(chunk.getChunkId(), MAPIProperty.SUBJECT.id);
assertEquals(chunk.getChunkId(), MAPIProperty.SUBJECT.id);
}
@Test
void testWritePreCalculatedProperties() throws IOException {
try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
StoragePropertiesChunk storagePropertiesChunk = new StoragePropertiesChunk(null);
PropertyValue.LongPropertyValue attachSize =
new PropertyValue.LongPropertyValue(MAPIProperty.ATTACH_SIZE, 6L, new byte[0]);
PropertyValue currentValue = new PropertyValue(MAPIProperty.DISPLAY_BCC, 6L, new byte[0]);
attachSize.setValue(3934266);
storagePropertiesChunk.setProperty(attachSize);
storagePropertiesChunk.setProperty(currentValue);
List<PropertyValue> propertyValue= storagePropertiesChunk.writePreCalculatedProperties(stream);
assertEquals(propertyValue.size(),1);
}
}
}