aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPJ Fanning <fanningpj@apache.org>2022-10-18 19:10:32 +0000
committerPJ Fanning <fanningpj@apache.org>2022-10-18 19:10:32 +0000
commit933948a8462d74e14d6c9d84bbcf5e095bc8c552 (patch)
tree800cca0dec23cc814d9e5b52ddc74a548e28c80a
parent4c3a0b4e93391047e723ba6ac3a5fd5c0a241d67 (diff)
downloadpoi-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
-rw-r--r--poi-scratchpad/src/main/java/org/apache/poi/hsmf/datatypes/PropertiesChunk.java66
-rw-r--r--poi-scratchpad/src/main/java/org/apache/poi/hsmf/datatypes/StoragePropertiesChunk.java16
-rw-r--r--poi-scratchpad/src/test/java/org/apache/poi/hsmf/datatypes/TestChunkData.java48
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);
+ }
+ }
}