Skip to content

Commit 66ef450

Browse files
committed
Added check to PSTObject so that when getDescriptorNodeId is called on embedded attachments no NPE is raised.
Added class PSTConversationIndex that decoded the byte array into a usable form
1 parent 0c51e65 commit 66ef450

File tree

3 files changed

+122
-42
lines changed

3 files changed

+122
-42
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package com.pff;
2+
3+
import java.util.ArrayList;
4+
import java.util.Date;
5+
import java.util.List;
6+
import java.util.UUID;
7+
8+
/**
9+
* Class to hold decoded PidTagConversationIndex
10+
*
11+
* @author Nick Buller
12+
*/
13+
public class PSTConversationIndex {
14+
private static final int HUNDRED_NS_TO_MS = 1000;
15+
private static final int MINIMUM_HEADER_SIZE = 22;
16+
private static final int RESPONSE_LEVEL_SIZE = 5;
17+
18+
private Date deliveryTime;
19+
private UUID guid;
20+
private List<ResponseLevel> responseLevels = new ArrayList<ResponseLevel>();
21+
22+
public Date getDeliveryTime() {
23+
return deliveryTime;
24+
}
25+
26+
public UUID getGuid() {
27+
return guid;
28+
}
29+
30+
public List<ResponseLevel> getResponseLevels() {
31+
return responseLevels;
32+
}
33+
34+
public String toString() {
35+
return guid + "@" + deliveryTime + " " + responseLevels.size() + " ResponseLevels";
36+
}
37+
38+
public class ResponseLevel {
39+
short deltaCode;
40+
long timeDelta;
41+
short random;
42+
43+
public ResponseLevel(short deltaCode, long timeDelta, short random) {
44+
this.deltaCode = deltaCode;
45+
this.timeDelta = timeDelta;
46+
this.random = random;
47+
}
48+
49+
public short getDeltaCode() {
50+
return deltaCode;
51+
}
52+
53+
public long getTimeDelta() {
54+
return timeDelta;
55+
}
56+
57+
public short getRandom() {
58+
return random;
59+
}
60+
61+
public Date withOffset(Date anchorDate) {
62+
return new Date(anchorDate.getTime() + timeDelta);
63+
}
64+
}
65+
66+
protected PSTConversationIndex(byte[] rawConversationIndex) {
67+
if (rawConversationIndex != null && rawConversationIndex.length >= MINIMUM_HEADER_SIZE) {
68+
decodeHeader(rawConversationIndex);
69+
if (rawConversationIndex.length >= MINIMUM_HEADER_SIZE + RESPONSE_LEVEL_SIZE) {
70+
decodeResponseLevel(rawConversationIndex);
71+
}
72+
}
73+
}
74+
75+
private void decodeHeader(byte[] rawConversationIndex) {
76+
// According to the Spec the first byte is not included, but I believe the spec is incorrect!
77+
//int reservedheaderMarker = (int) PSTObject.convertBigEndianBytesToLong(rawConversationIndex, 0, 1);
78+
79+
long deliveryTimeHigh = PSTObject.convertBigEndianBytesToLong(rawConversationIndex, 0, 4);
80+
long deliveryTimeLow = PSTObject.convertBigEndianBytesToLong(rawConversationIndex, 4, 6) << 16;
81+
deliveryTime = PSTObject.filetimeToDate((int) deliveryTimeHigh, (int) deliveryTimeLow);
82+
83+
long guidHigh = PSTObject.convertBigEndianBytesToLong(rawConversationIndex, 6, 14);
84+
long guidLow = PSTObject.convertBigEndianBytesToLong(rawConversationIndex, 14, 22);
85+
86+
guid = new UUID(guidHigh, guidLow);
87+
}
88+
89+
private void decodeResponseLevel(byte[] rawConversationIndex) {
90+
int responseLevelCount = (rawConversationIndex.length - MINIMUM_HEADER_SIZE) / RESPONSE_LEVEL_SIZE;
91+
responseLevels = new ArrayList<ResponseLevel>(responseLevelCount);
92+
93+
for (int responseLevelIndex = 0, position = 22; responseLevelIndex < responseLevelCount; responseLevelIndex++, position += RESPONSE_LEVEL_SIZE) {
94+
95+
long responseLevelValue = PSTObject.convertBigEndianBytesToLong(rawConversationIndex, position, position + 5);
96+
short deltaCode = (short) (responseLevelValue >> 39);
97+
short random = (short) (responseLevelValue & 0xFF);
98+
99+
// shift by 1 byte (remove the random) and mask off the deltaCode
100+
long deltaTime = (responseLevelValue >> 8) & 0x7FFFFFFF;
101+
102+
if (deltaCode == 0) {
103+
deltaTime <<= 18;
104+
} else {
105+
deltaTime <<= 23;
106+
}
107+
108+
deltaTime /= HUNDRED_NS_TO_MS;
109+
110+
responseLevels.add(responseLevelIndex, new ResponseLevel(deltaCode, deltaTime, random));
111+
}
112+
}
113+
}

src/main/java/com/pff/PSTMessage.java

Lines changed: 5 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,9 @@ public Date getMessageDeliveryTime() {
588588
/**
589589
* Message content properties
590590
*/
591+
public int getNativeBodyType() {
592+
return this.getIntItem(0x1016);
593+
}
591594

592595
/**
593596
* Plain text e-mail body
@@ -991,7 +994,6 @@ public PSTRecipient getRecipient(int recipientNumber)
991994
return null;
992995
}
993996

994-
995997
public String getRecipientsString() {
996998
if ( recipientTable != null ) {
997999
return recipientTable.getItemsString();
@@ -1004,51 +1006,13 @@ public byte[] getConversationId() {
10041006
return getBinaryItem(0x3013);
10051007
}
10061008

1007-
public byte[] getConversationIndex() {
1008-
byte[] conversationIndex = getBinaryItem(0x0071);
1009-
return conversationIndex;
1010-
}
1011-
1012-
public String getConversationIndexAsString() {
1013-
byte[] conversationId = getConversationIndex();
1014-
1015-
if(conversationId != null && conversationId.length >= 22) {
1016-
// Header 22 Bytes
1017-
int reservedheaderMarker = (int) PSTObject.convertBigEndianBytesToLong(conversationId, 0, 1);
1018-
// According to the Spec the first byte is not included, but I believe the spec is incorrect!
1019-
long deliveryTimeHigh = PSTObject.convertBigEndianBytesToLong(conversationId, 0, 4);
1020-
long deliveryTimeLow = PSTObject.convertBigEndianBytesToLong(conversationId, 4, 6) << 16;
1021-
Date deliveryTime = PSTObject.filetimeToDate((int)deliveryTimeHigh, (int)deliveryTimeLow);
1022-
1023-
1024-
byte[] guidData = new byte[16];
1025-
System.arraycopy(conversationId, 6, guidData, 0, guidData.length);
1026-
String guid = guidToString(guidData);
1027-
1028-
return deliveryTime.toString() + " " + guid;
1029-
}
1030-
return null;
1031-
}
1032-
1033-
public String guidToString(byte[] guidData) {
1034-
1035-
1036-
if(guidData.length == 16) {
1037-
int data1 = (int)PSTObject.convertBigEndianBytesToLong(guidData, 0, 4);
1038-
int data2 = (int)PSTObject.convertBigEndianBytesToLong(guidData, 4, 6);
1039-
int data3 = (int)PSTObject.convertBigEndianBytesToLong(guidData, 6, 8);
1040-
int data41 = (int)PSTObject.convertBigEndianBytesToLong(guidData, 8, 10);
1041-
long data42 = PSTObject.convertBigEndianBytesToLong(guidData, 10, 16);
1042-
String guidString = String.format("%08X-%04X-%04X-%04X-%012X", data1, data2, data3, data41, data42);
1043-
return guidString;
1044-
}
1045-
return "";
1009+
public PSTConversationIndex getConversationIndex() {
1010+
return new PSTConversationIndex(getBinaryItem(0x0071));
10461011
}
10471012

10481013
public boolean isConversationIndexTracking() {
10491014
return getBooleanItem(0x3016, false);
10501015
}
1051-
10521016

10531017
/**
10541018
* string representation of this email

src/main/java/com/pff/PSTObject.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,10 @@ public DescriptorIndexNode getDescriptorNode() {
129129
* @return item's descriptor node identifier
130130
*/
131131
public long getDescriptorNodeId() {
132-
return this.descriptorIndexNode.descriptorIdentifier;
132+
if (this.descriptorIndexNode != null) { // Prevent null pointer exceptions for embedded messages
133+
return this.descriptorIndexNode.descriptorIdentifier;
134+
}
135+
return 0;
133136
}
134137

135138
public int getNodeType() {

0 commit comments

Comments
 (0)