File: mapidump.py

package info (click to toggle)
kopanocore 8.7.0-3
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, buster, sid
  • size: 15,400 kB
  • sloc: cpp: 175,422; python: 24,623; perl: 7,319; php: 6,056; sh: 2,172; makefile: 1,294; xml: 45; ansic: 1
file content (123 lines) | stat: -rwxr-xr-x 3,584 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#!/usr/bin/python3
# SPDX-License-Identifier: AGPL-3.0-only
from __future__ import print_function
import datetime
import logging
import sys
import traceback

from MAPI.Tags import *
import kopano

"""
Dump MAPI-level content for given user (store) or folder in a mostly
deterministic fashion, skipping over properties which are always
different for different items, such as entry-ids and creation times.

The goal is to be able to use 'diff' to now check for important
differences between two stores or folders, which have come about
in different ways. Example usages:

-Check that after a backup-restore cycle the resulting data is
identical to the original data.
-Check that equivalent programs result in equivalent output (such
as a Python version and a PHP version, or python2 versus python3).

Usage: ./mapidump.py [-u username] [-f folderpath]

By default, all users/folders are included.

"""

if sys.hexversion >= 0x03000000:
    def _encode(s):
        return s
else:
    def _encode(s):
        return s.encode(sys.stdout.encoding or 'utf8')

IGNORE = [
    PR_SOURCE_KEY,
    PR_PARENT_SOURCE_KEY,
    PR_CHANGE_KEY,
    PR_PREDECESSOR_CHANGE_LIST,
    PR_ENTRYID,
    PR_PARENT_ENTRYID,
    PR_CREATION_TIME,
    PR_LAST_MODIFICATION_TIME,
    PR_RECORD_KEY,
    PR_MESSAGE_DELIVERY_TIME,
    PR_STORE_RECORD_KEY,
    PR_STORE_ENTRYID,
    PR_MAPPING_SIGNATURE,
    PR_EC_SERVER_UID,
    PR_MESSAGE_SIZE, # XXX why would there be a valid difference?
]

DEFAULT_DATETIME = datetime.datetime(1978, 1, 1)

def dump_folder(folder):
    print('(FOLDER)', _encode(folder.name)) # XXX show folder.path

    def item_key(item):
        # extend as needed to make as much items unique as possible
        # XXX generic solution based on all common properties
        return (
            item.received or DEFAULT_DATETIME,
            item.subject,
            item.name,
            item.get('address:32896'),
            item.get(PR_CLIENT_SUBMIT_TIME) or DEFAULT_DATETIME,
            item.get(PR_COMPANY_NAME_W),
            item.get(PR_INTERNET_ARTICLE_NUMBER) or 0,
        )

    items = sorted(folder.items(), key=item_key)
    for item in items:
        dump_item(item)

def dump_item(item, depth=0):
    print('(ITEM)' if depth == 0 else '(EMBEDDED ITEM)')
    print(_encode(item.subject), item.received.isoformat(' ') if item.received else '')
    try:
        dump_props(item.props())

        recipients = sorted(item.recipients(), key=lambda x: x.name)
        for recipient in item.recipients():
            print('(RECIPIENT)')
            dump_props(recipient.props())

        attachments = item.attachments()
        attachments = sorted(item.attachments(), key=lambda x: x.filename)
        for attachment in attachments:
            print('(ATTACHMENT)')
            dump_props(attachment.props())

        for item in item.items():
            dump_item(item, depth+1)
    except:
        print('(ERROR)')
        traceback.print_exc()

def dump_props(props):
    for prop in props:
        if prop.proptag not in IGNORE:
            print(prop, _encode(prop.strval))

def main():
    parser = kopano.parser('spkuf')
    options, args = parser.parse_args()
    server = kopano.Server(options=options)

    for user in kopano.users():
        if server.options.folders:
            folders = [user.folder(path) for path in server.options.folders]
        else:
            folders = [user.subtree]

        for base in folders:
            for folder in [base] + list(base.folders()):
                dump_folder(folder)

if __name__ == '__main__':
    main()