diff options
author | PJ Fanning <fanningpj@apache.org> | 2022-10-18 19:10:32 +0000 |
---|---|---|
committer | PJ Fanning <fanningpj@apache.org> | 2022-10-18 19:10:32 +0000 |
commit | 933948a8462d74e14d6c9d84bbcf5e095bc8c552 (patch) | |
tree | 800cca0dec23cc814d9e5b52ddc74a548e28c80a | |
parent | 4c3a0b4e93391047e723ba6ac3a5fd5c0a241d67 (diff) | |
download | poi-933948a8462d74e14d6c9d84bbcf5e095bc8c552.tar.gz poi-933948a8462d74e14d6c9d84bbcf5e095bc8c552.zip |
[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
3 files changed, 113 insertions, 17 deletions
diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hsmf/datatypes/PropertiesChunk.java b/poi-scratchpad/src/main/java/org/apache/poi/hsmf/datatypes/PropertiesChunk.java index 5d5cde36ec..809411bfb2 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hsmf/datatypes/PropertiesChunk.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hsmf/datatypes/PropertiesChunk.java @@ -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(); diff --git a/poi-scratchpad/src/main/java/org/apache/poi/hsmf/datatypes/StoragePropertiesChunk.java b/poi-scratchpad/src/main/java/org/apache/poi/hsmf/datatypes/StoragePropertiesChunk.java index 50b393d21b..0ba57106fe 100644 --- a/poi-scratchpad/src/main/java/org/apache/poi/hsmf/datatypes/StoragePropertiesChunk.java +++ b/poi-scratchpad/src/main/java/org/apache/poi/hsmf/datatypes/StoragePropertiesChunk.java @@ -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); + } }
\ No newline at end of file diff --git a/poi-scratchpad/src/test/java/org/apache/poi/hsmf/datatypes/TestChunkData.java b/poi-scratchpad/src/test/java/org/apache/poi/hsmf/datatypes/TestChunkData.java index 9574ac6b73..70ba066d5e 100644 --- a/poi-scratchpad/src/test/java/org/apache/poi/hsmf/datatypes/TestChunkData.java +++ b/poi-scratchpad/src/test/java/org/apache/poi/hsmf/datatypes/TestChunkData.java @@ -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); + } + } } |