from __future__ import generators
# Dump every property we can find for a MAPI item

import pythoncom
import os, sys
import tempfile

from win32com.mapi import mapi, mapiutil
from win32com.mapi.mapitags import *
import win32clipboard

import mapi_driver

try:
    TBL_ALL_COLUMNS = mapi.TBL_ALL_COLUMNS
except AttributeError: # missing in early versions
    TBL_ALL_COLUMNS = 1

PR_USERFIELDS = 0x36E30102 # PROP_TAG(PT_BINARY, 0x36e3)

def GetPropTagName(obj, prop_tag):
    hr, tags, array = obj.GetNamesFromIDs( (prop_tag,) )
    if type(array[0][1])==type(u''):
        name = array[0][1]
    else:
        name = mapiutil.GetPropTagName(prop_tag)
    return name

# Also in new versions of mapituil
def GetAllProperties(obj, make_pretty = True):
    tags = obj.GetPropList(0)
    hr, data = obj.GetProps(tags)
    ret = []
    for tag, val in data:
        if make_pretty:
            name = GetPropTagName(obj, tag)
        else:
            name = tag
        ret.append((name, tag, val))
    return ret

def GetLargeProperty(item, prop_tag):
    prop_tag = PROP_TAG(PT_BINARY, PROP_ID(prop_tag))
    stream = item.OpenProperty(prop_tag,
                                pythoncom.IID_IStream,
                                0, 0)
    chunks = []
    while 1:
        chunk = stream.Read(4096)
        if not chunk:
            break
        chunks.append(chunk)
    return "".join(chunks)

def FormatPropertyValue(prop_tag, prop_val, item, shorten, get_large_props):
    # Do some magic rtf conversion
    if PROP_ID(prop_tag) == PROP_ID(PR_RTF_COMPRESSED):
        rtf_stream = item.OpenProperty(PR_RTF_COMPRESSED,
                                       pythoncom.IID_IStream, 0, 0)
        html_stream = mapi.WrapCompressedRTFStream(rtf_stream, 0)
        prop_val = mapi.RTFStreamToHTML(html_stream)
        prop_tag = PROP_TAG(PT_STRING8, PR_RTF_COMPRESSED)
    prop_repr = None
    if PROP_TYPE(prop_tag)==PT_ERROR:
        if get_large_props and \
           prop_val in [mapi.MAPI_E_NOT_ENOUGH_MEMORY,
                        'MAPI_E_NOT_ENOUGH_MEMORY']:
            # Use magic to get a large property.
            prop_val = GetLargeProperty(item, prop_tag)
            prop_repr = repr(prop_val)
        else:
            prop_val = prop_repr = mapiutil.GetScodeString(prop_val)
    if prop_repr is None:
        prop_repr = repr(prop_val)
    if shorten:
        prop_repr = prop_repr[:50]
    return prop_repr

def DumpItemProps(item, shorten, get_large_props, stream=None):
    all_props = GetAllProperties(item)
    all_props.sort() # sort by first tuple item, which is name :)
    for prop_name, prop_tag, prop_val in all_props:
        # If we want 'short' variables, drop 'not found' props.
        if shorten and PROP_TYPE(prop_tag)==PT_ERROR \
           and prop_val == mapi.MAPI_E_NOT_FOUND:
            continue
        prop_repr = FormatPropertyValue(prop_tag, prop_val, item,
                                        shorten, get_large_props)
        print >> stream, "%-20s: %s" % (prop_name, prop_repr)
    print >> stream, "-- end of item properties --"

def DumpProps(driver, mapi_folder, subject, include_attach, shorten,
              get_large, stream=None):
    hr, data = mapi_folder.GetProps( (PR_DISPLAY_NAME_A,), 0)
    name = data[0][1]
    for item in driver.GetItemsWithValue(mapi_folder, PR_SUBJECT_A, subject):
        DumpItemProps(item, shorten, get_large, stream)
        if include_attach:
            print >> stream
            table = item.GetAttachmentTable(0)
            rows = mapi.HrQueryAllRows(table, (PR_ATTACH_NUM,), None, None, 0)
            for row in rows:
                attach_num = row[0][1]
                print >> stream, \
                      "Dumping attachment (PR_ATTACH_NUM=%d)" % (attach_num,)
                attach = item.OpenAttach(attach_num, None,
                                         mapi.MAPI_DEFERRED_ERRORS)
                DumpItemProps(attach, shorten, get_large, stream)
            print >> stream
        print >> stream

# Generic table dumper.
def DumpTable(driver, table, name_query_ob, shorten, large_props, stream=None):
    cols = table.QueryColumns(TBL_ALL_COLUMNS)
    table.SetColumns(cols, 0)
    rows = mapi.HrQueryAllRows(table, cols, None, None, 0)
    print >> stream, \
          "Table has %d rows, each with %d columns" % (len(rows), len(cols))
    for row in rows:
        print >> stream, "-- new row --"
        for col in row:
            prop_tag, prop_val = col
            # If we want 'short' variables, drop 'not found' props.
            if shorten and PROP_TYPE(prop_tag)==PT_ERROR \
               and prop_val == mapi.MAPI_E_NOT_FOUND:
                continue
            prop_name = GetPropTagName(name_query_ob, prop_tag)
            prop_repr = FormatPropertyValue(prop_tag, prop_val, name_query_ob,
                                            shorten, large_props)
            print >> stream, "%-20s: %s" % (prop_name, prop_repr)

# This dumps the raw binary data of the property Outlook uses to store
# user defined fields.
def FindAndDumpTableUserProps(driver, table, folder, shorten,
                              get_large_props, stream=None):
    restriction = (mapi.RES_PROPERTY,
                  (mapi.RELOP_EQ,
                   PR_MESSAGE_CLASS_A,
                   (PR_MESSAGE_CLASS_A, 'IPC.MS.REN.USERFIELDS')))
    cols = (PR_USERFIELDS,)
    table.SetColumns(cols, 0)
    rows = mapi.HrQueryAllRows(table, cols, restriction, None, 0)
    assert len(rows)<=1, "Only expecting 1 (or 0) rows"
    tag, val = rows[0][0]
    prop_name = GetPropTagName(folder, tag)
    prop_repr = FormatPropertyValue(tag, val, folder,
                                    shorten, get_large_props)
    print >> stream, "%-20s: %s" % (prop_name, prop_repr)

def usage(driver, extra = None):
    folder_doc = driver.GetFolderNameDoc()
    if extra:
        print extra
        print
    msg = """\
Usage: %s [options ...] subject of the message

Dumps all properties for all messages that match the subject.  Subject
matching is substring and ignore-case.

-c - Write output to the clipboard, ready for pasting into an email
-f - Search for the message in the specified folder (default = Inbox)
-s - Shorten long property values.
-a - Include attachments
-l - Get the data for very large properties via a stream
-n - Show top-level folder names and exit
--dump-folder
     Dump the properties of the specified folder.
--dump-folder-assoc-contents
     Dump the 'associated contents' table of the specified folder.
--dump-folder-user-props
     Find and dump the PR_USERFIELDS field for the specified table.

%s
Use the -n option to see all top-level folder names from all stores.""" \
    % (os.path.basename(sys.argv[0]),folder_doc)
    print msg
    sys.exit(1)

def main():
    driver = mapi_driver.MAPIDriver()

    import getopt
    try:
        opts, args = getopt.getopt(sys.argv[1:], "caf:snl",
                                   ["dump-folder",
                                    "dump-folder-assoc-contents",
                                    "dump-folder-user-props",
                                    ])
    except getopt.error, e:
        usage(driver, e)
    folder_name = ""

    shorten = False
    get_large_props = False
    include_attach = False
    write_clipboard = False
    dump_folder = dump_folder_assoc_contents = dump_folder_user_props = False
    for opt, opt_val in opts:
        if opt == "-f":
            folder_name = opt_val
        elif opt == "-c":
            write_clipboard = True
        elif opt == "--dump-folder":
            dump_folder = True
        elif opt == "--dump-folder-assoc-contents":
            dump_folder_assoc_contents = True
        elif opt == "--dump-folder-user-props":
            dump_folder_user_props = True
        elif opt == "-s":
            shorten = True
        elif opt == "-a":
            include_attach = True
        elif opt == "-l":
            get_large_props = True
        elif opt == "-n":
            driver.DumpTopLevelFolders()
            sys.exit(1)
        else:
            usage(driver, "Unknown arg '%s'" % opt)

    stream = None
    if write_clipboard:
        stream_name = tempfile.mktemp("spambayes")
        stream = open(stream_name, "w")

    if not folder_name:
        folder_name = "Inbox" # Assume this exists!

    subject = " ".join(args)
    is_table_dump = dump_folder_assoc_contents or \
                    dump_folder or dump_folder_user_props
    if is_table_dump and subject or not is_table_dump and not subject:
        if is_table_dump:
            extra = "You must not specify a subject with '-p'"
        else:
            extra = "You must specify a subject (unless you use '-p')"
        usage(driver, extra)
    try:
        folder = driver.FindFolder(folder_name)
    except ValueError, details:
        print details
        sys.exit(1)

    if is_table_dump:
        if dump_folder:
            DumpItemProps(folder, shorten, get_large_props, stream)
        if dump_folder_assoc_contents:
            table = folder.GetContentsTable(mapi.MAPI_ASSOCIATED)
            DumpTable(driver, table, folder, shorten, get_large_props, stream)
        if dump_folder_user_props:
            table = folder.GetContentsTable(mapi.MAPI_ASSOCIATED)
            FindAndDumpTableUserProps(driver, table, folder,
                                      shorten, get_large_props, stream)
    else:
        DumpProps(driver, folder, subject, include_attach,
                  shorten, get_large_props, stream)
    if write_clipboard:
        stream.close()
        stream = open(stream_name, "r")
        win32clipboard.OpenClipboard()
        win32clipboard.EmptyClipboard()
        win32clipboard.SetClipboardText(stream.read())
        stream.close()
        os.unlink(stream_name)
        print "Output successfuly written to the Windows clipboard"

if __name__=='__main__':
    main()


syntax highlighted by Code2HTML, v. 0.9.1