Skip to content

Commit

Permalink
Compute MAPI PR_ENTRYID for messages and folders
Browse files Browse the repository at this point in the history
In e-discovery applications, messages will often be referred to by a
MAPI PR_ENTRYID.  This is a computed property, as described here:

  http://pstsdk.codeplex.com/Thread/View.aspx?ThreadId=215111

This patch adds support for computing PR_ENTRYID for top-level messages
and folders.  As suggested by Terry Mahaffey, all computations are done
in the PST layer.

All of the PR_ENTRYID values in the test suite were generated using
MFCMAPI, so we're comparing the computed values against known-good
values.

This patch does not include the corresponding functions for looking up
messages and folders using their PR_ENTRYID values.  While these would
be highly desirable, the current patch stands on its own.

According to Terry Mahaffey, OST files have a different and undocumented
algorithm for calculating PR_ENTRYID.  This patch adds an 'is_pst'
function to the database_context interface to detect this case, and
throws an error.  Note that it would be relatively easy to export the
database_type value directly (instead of a less-general 'is_pst')
function, but that this would require including disk.h into
database_iface.h and exposing the disk-level database_type enumeration
directly.
  • Loading branch information
emk committed Aug 9, 2010
1 parent ebbf7b7 commit 1065b00
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 1 deletion.
3 changes: 3 additions & 0 deletions pstsdk/ndb/database.h
Expand Up @@ -73,6 +73,9 @@ class database_impl : public db_context
{
public:

bool is_pst() const
{ return m_header.wVerClient == disk::database_pst; }

//! \name Lookup functions
//@{
node lookup_node(node_id nid)
Expand Down
4 changes: 4 additions & 0 deletions pstsdk/ndb/database_iface.h
Expand Up @@ -120,6 +120,10 @@ class db_context : public std::tr1::enable_shared_from_this<db_context>
public:
virtual ~db_context() { }

//! \brief Is this database a PST?
//! \returns Returns true if this is PST, false if an OST or something else.
virtual bool is_pst() const = 0;

//! \name Lookup functions
//@{
//! \brief Open a node
Expand Down
2 changes: 1 addition & 1 deletion pstsdk/ndb/node.h
Expand Up @@ -378,7 +378,7 @@ class node
//! \copydoc node_impl::get_parent_id()
node_id get_parent_id() const { return m_pimpl->get_parent_id(); }
//! \copydoc node_impl::is_subnode()
bool is_subnode() { return m_pimpl->is_subnode(); }
bool is_subnode() const { return m_pimpl->is_subnode(); }

//! \copydoc node_impl::get_data_block()
std::tr1::shared_ptr<data_block> get_data_block() const
Expand Down
5 changes: 5 additions & 0 deletions pstsdk/pst/folder.h
Expand Up @@ -298,6 +298,11 @@ class folder
node_id get_id() const
{ return m_bag.get_node().get_id(); }

//! \brief Get the MAPI entry ID of this folder
//! \returns The MAPI entry ID of this folder
std::vector<byte> get_entry_id() const
{ return detail::property_bag_entry_id(m_bag); }

private:
shared_db_ptr m_db;
property_bag m_bag;
Expand Down
47 changes: 47 additions & 0 deletions pstsdk/pst/message.h
Expand Up @@ -346,6 +346,10 @@ class message
node_id get_id() const
{ return m_bag.get_node().get_id(); }

//! \brief Get the MAPI entry ID of this message
//! \returns The MAPI entry ID of this message
std::vector<byte> get_entry_id() const;

private:
message& operator=(const message&); // = delete

Expand Down Expand Up @@ -378,6 +382,10 @@ class message_transform_info : public std::unary_function<node_info, message>
shared_db_ptr m_db;
};

namespace detail {
std::vector<byte> property_bag_entry_id(const property_bag& bag);
};

} // end namespace pstsdk

inline std::wstring pstsdk::attachment::get_filename() const
Expand Down Expand Up @@ -477,4 +485,43 @@ inline std::wstring pstsdk::message::get_subject() const
}
}

inline std::vector<pstsdk::byte> pstsdk::message::get_entry_id() const
{
if (m_bag.get_node().is_subnode())
{
// Submessages should not have PidTagEntryId.
throw key_not_found<prop_id>(0x0fff);
}
else
{
return detail::property_bag_entry_id(m_bag);
}
}

inline std::vector<pstsdk::byte> pstsdk::detail::property_bag_entry_id(const pstsdk::property_bag& bag)
{
using namespace std;

// This function doesn't know how to handle anything other than vanilla
// PSTs. OSTs, for example, require a more complex calculation.
if (!bag.get_node().get_db()->is_pst())
throw key_not_found<prop_id>(0x0fff);

// A MAPI entry id contains 4 leading 0 bytes, the data store ID, and
// the node ID (in little-endian byte order).
vector<byte> entry_id(4, 0);

node store(bag.get_node().get_db()->lookup_node(nid_message_store));
property_bag store_props(store);
vector<byte> store_id(store_props.read_prop<vector<byte> >(0x0ff9));
copy(store_id.begin(), store_id.end(),
insert_iterator<vector<byte> >(entry_id, entry_id.end()));

node_id nid(bag.get_node().get_id());
for (size_t i = 0; i < sizeof(node_id); ++i)
entry_id.push_back((nid >> 8*i) & 0xff);

return entry_id;
}

#endif
40 changes: 40 additions & 0 deletions test/pstlevel.cpp
Expand Up @@ -89,6 +89,43 @@ void process_pst(const pstsdk::pst& p)
process_folder(root);
}

void test_entry_id(pstsdk::pst& sample1, pstsdk::pst& submessage) {
using namespace std;
using namespace pstsdk;

// Folder "Sample1" (000000006a552b813c43f94384f18b7da2393e9582800000)
folder f(*(--sample1.folder_end()));
static const byte expected1_bytes[24] = {
0x00, 0x00, 0x00, 0x00, 0x6a, 0x55, 0x2b, 0x81, 0x3c, 0x43, 0xf9, 0x43,
0x84, 0xf1, 0x8b, 0x7d, 0xa2, 0x39, 0x3e, 0x95, 0x82, 0x80, 0x00, 0x00,
};
vector<byte> expected1(expected1_bytes, expected1_bytes + 24);
assert(expected1 == f.get_entry_id());

// Message (000000006a552b813c43f94384f18b7da2393e9524002000)
message m(*sample1.message_begin());
static const byte expected2_bytes[24] = {
0x00, 0x00, 0x00, 0x00, 0x6a, 0x55, 0x2b, 0x81, 0x3c, 0x43, 0xf9, 0x43,
0x84, 0xf1, 0x8b, 0x7d, 0xa2, 0x39, 0x3e, 0x95, 0x24, 0x00, 0x20, 0x00,
};
vector<byte> expected2(expected2_bytes, expected2_bytes + 24);
assert(expected2 == m.get_entry_id());

// Submessage should not have an entry ID.
attachment a(*submessage.message_begin()->attachment_begin());
message sm(a.open_as_message());
bool raised_exception = false;
try
{
sm.get_entry_id();
}
catch (key_not_found<prop_id> &)
{
raised_exception = true;
}
assert(raised_exception);
}

void test_pstlevel()
{
using namespace pstsdk;
Expand All @@ -107,4 +144,7 @@ void test_pstlevel()

// make sure searching by name works
process_folder(uni.open_folder(L"Folder"));

// make sure we can calculate entry_id values
test_entry_id(s1, submess);
}

0 comments on commit 1065b00

Please sign in to comment.