ยปCore Development>Code coverage>Tools/scripts/mailerdaemon.py

Python code coverage for Tools/scripts/mailerdaemon.py

#countcontent
1n/a#!/usr/bin/env python3
2n/a"""Classes to parse mailer-daemon messages."""
3n/a
4n/aimport calendar
5n/aimport email.message
6n/aimport re
7n/aimport os
8n/aimport sys
9n/a
10n/a
11n/aclass Unparseable(Exception):
12n/a pass
13n/a
14n/a
15n/aclass ErrorMessage(email.message.Message):
16n/a def __init__(self):
17n/a email.message.Message.__init__(self)
18n/a self.sub = ''
19n/a
20n/a def is_warning(self):
21n/a sub = self.get('Subject')
22n/a if not sub:
23n/a return 0
24n/a sub = sub.lower()
25n/a if sub.startswith('waiting mail'):
26n/a return 1
27n/a if 'warning' in sub:
28n/a return 1
29n/a self.sub = sub
30n/a return 0
31n/a
32n/a def get_errors(self):
33n/a for p in EMPARSERS:
34n/a self.rewindbody()
35n/a try:
36n/a return p(self.fp, self.sub)
37n/a except Unparseable:
38n/a pass
39n/a raise Unparseable
40n/a
41n/a# List of re's or tuples of re's.
42n/a# If a re, it should contain at least a group (?P<email>...) which
43n/a# should refer to the email address. The re can also contain a group
44n/a# (?P<reason>...) which should refer to the reason (error message).
45n/a# If no reason is present, the emparse_list_reason list is used to
46n/a# find a reason.
47n/a# If a tuple, the tuple should contain 2 re's. The first re finds a
48n/a# location, the second re is repeated one or more times to find
49n/a# multiple email addresses. The second re is matched (not searched)
50n/a# where the previous match ended.
51n/a# The re's are compiled using the re module.
52n/aemparse_list_list = [
53n/a 'error: (?P<reason>unresolvable): (?P<email>.+)',
54n/a ('----- The following addresses had permanent fatal errors -----\n',
55n/a '(?P<email>[^ \n].*)\n( .*\n)?'),
56n/a 'remote execution.*\n.*rmail (?P<email>.+)',
57n/a ('The following recipients did not receive your message:\n\n',
58n/a ' +(?P<email>.*)\n(The following recipients did not receive your message:\n\n)?'),
59n/a '------- Failure Reasons --------\n\n(?P<reason>.*)\n(?P<email>.*)',
60n/a '^<(?P<email>.*)>:\n(?P<reason>.*)',
61n/a '^(?P<reason>User mailbox exceeds allowed size): (?P<email>.+)',
62n/a '^5\\d{2} <(?P<email>[^\n>]+)>\\.\\.\\. (?P<reason>.+)',
63n/a '^Original-Recipient: rfc822;(?P<email>.*)',
64n/a '^did not reach the following recipient\\(s\\):\n\n(?P<email>.*) on .*\n +(?P<reason>.*)',
65n/a '^ <(?P<email>[^\n>]+)> \\.\\.\\. (?P<reason>.*)',
66n/a '^Report on your message to: (?P<email>.*)\nReason: (?P<reason>.*)',
67n/a '^Your message was not delivered to +(?P<email>.*)\n +for the following reason:\n +(?P<reason>.*)',
68n/a '^ was not +(?P<email>[^ \n].*?) *\n.*\n.*\n.*\n because:.*\n +(?P<reason>[^ \n].*?) *\n',
69n/a ]
70n/a# compile the re's in the list and store them in-place.
71n/afor i in range(len(emparse_list_list)):
72n/a x = emparse_list_list[i]
73n/a if type(x) is type(''):
74n/a x = re.compile(x, re.MULTILINE)
75n/a else:
76n/a xl = []
77n/a for x in x:
78n/a xl.append(re.compile(x, re.MULTILINE))
79n/a x = tuple(xl)
80n/a del xl
81n/a emparse_list_list[i] = x
82n/a del x
83n/adel i
84n/a
85n/a# list of re's used to find reasons (error messages).
86n/a# if a string, "<>" is replaced by a copy of the email address.
87n/a# The expressions are searched for in order. After the first match,
88n/a# no more expressions are searched for. So, order is important.
89n/aemparse_list_reason = [
90n/a r'^5\d{2} <>\.\.\. (?P<reason>.*)',
91n/a r'<>\.\.\. (?P<reason>.*)',
92n/a re.compile(r'^<<< 5\d{2} (?P<reason>.*)', re.MULTILINE),
93n/a re.compile('===== stderr was =====\nrmail: (?P<reason>.*)'),
94n/a re.compile('^Diagnostic-Code: (?P<reason>.*)', re.MULTILINE),
95n/a ]
96n/aemparse_list_from = re.compile('^From:', re.IGNORECASE|re.MULTILINE)
97n/adef emparse_list(fp, sub):
98n/a data = fp.read()
99n/a res = emparse_list_from.search(data)
100n/a if res is None:
101n/a from_index = len(data)
102n/a else:
103n/a from_index = res.start(0)
104n/a errors = []
105n/a emails = []
106n/a reason = None
107n/a for regexp in emparse_list_list:
108n/a if type(regexp) is type(()):
109n/a res = regexp[0].search(data, 0, from_index)
110n/a if res is not None:
111n/a try:
112n/a reason = res.group('reason')
113n/a except IndexError:
114n/a pass
115n/a while 1:
116n/a res = regexp[1].match(data, res.end(0), from_index)
117n/a if res is None:
118n/a break
119n/a emails.append(res.group('email'))
120n/a break
121n/a else:
122n/a res = regexp.search(data, 0, from_index)
123n/a if res is not None:
124n/a emails.append(res.group('email'))
125n/a try:
126n/a reason = res.group('reason')
127n/a except IndexError:
128n/a pass
129n/a break
130n/a if not emails:
131n/a raise Unparseable
132n/a if not reason:
133n/a reason = sub
134n/a if reason[:15] == 'returned mail: ':
135n/a reason = reason[15:]
136n/a for regexp in emparse_list_reason:
137n/a if type(regexp) is type(''):
138n/a for i in range(len(emails)-1,-1,-1):
139n/a email = emails[i]
140n/a exp = re.compile(re.escape(email).join(regexp.split('<>')), re.MULTILINE)
141n/a res = exp.search(data)
142n/a if res is not None:
143n/a errors.append(' '.join((email.strip()+': '+res.group('reason')).split()))
144n/a del emails[i]
145n/a continue
146n/a res = regexp.search(data)
147n/a if res is not None:
148n/a reason = res.group('reason')
149n/a break
150n/a for email in emails:
151n/a errors.append(' '.join((email.strip()+': '+reason).split()))
152n/a return errors
153n/a
154n/aEMPARSERS = [emparse_list]
155n/a
156n/adef sort_numeric(a, b):
157n/a a = int(a)
158n/a b = int(b)
159n/a if a < b:
160n/a return -1
161n/a elif a > b:
162n/a return 1
163n/a else:
164n/a return 0
165n/a
166n/adef parsedir(dir, modify):
167n/a os.chdir(dir)
168n/a pat = re.compile('^[0-9]*$')
169n/a errordict = {}
170n/a errorfirst = {}
171n/a errorlast = {}
172n/a nok = nwarn = nbad = 0
173n/a
174n/a # find all numeric file names and sort them
175n/a files = list(filter(lambda fn, pat=pat: pat.match(fn) is not None, os.listdir('.')))
176n/a files.sort(sort_numeric)
177n/a
178n/a for fn in files:
179n/a # Lets try to parse the file.
180n/a fp = open(fn)
181n/a m = email.message_from_file(fp, _class=ErrorMessage)
182n/a sender = m.getaddr('From')
183n/a print('%s\t%-40s\t'%(fn, sender[1]), end=' ')
184n/a
185n/a if m.is_warning():
186n/a fp.close()
187n/a print('warning only')
188n/a nwarn = nwarn + 1
189n/a if modify:
190n/a os.rename(fn, ','+fn)
191n/a## os.unlink(fn)
192n/a continue
193n/a
194n/a try:
195n/a errors = m.get_errors()
196n/a except Unparseable:
197n/a print('** Not parseable')
198n/a nbad = nbad + 1
199n/a fp.close()
200n/a continue
201n/a print(len(errors), 'errors')
202n/a
203n/a # Remember them
204n/a for e in errors:
205n/a try:
206n/a mm, dd = m.getdate('date')[1:1+2]
207n/a date = '%s %02d' % (calendar.month_abbr[mm], dd)
208n/a except:
209n/a date = '??????'
210n/a if e not in errordict:
211n/a errordict[e] = 1
212n/a errorfirst[e] = '%s (%s)' % (fn, date)
213n/a else:
214n/a errordict[e] = errordict[e] + 1
215n/a errorlast[e] = '%s (%s)' % (fn, date)
216n/a
217n/a fp.close()
218n/a nok = nok + 1
219n/a if modify:
220n/a os.rename(fn, ','+fn)
221n/a## os.unlink(fn)
222n/a
223n/a print('--------------')
224n/a print(nok, 'files parsed,',nwarn,'files warning-only,', end=' ')
225n/a print(nbad,'files unparseable')
226n/a print('--------------')
227n/a list = []
228n/a for e in errordict.keys():
229n/a list.append((errordict[e], errorfirst[e], errorlast[e], e))
230n/a list.sort()
231n/a for num, first, last, e in list:
232n/a print('%d %s - %s\t%s' % (num, first, last, e))
233n/a
234n/adef main():
235n/a modify = 0
236n/a if len(sys.argv) > 1 and sys.argv[1] == '-d':
237n/a modify = 1
238n/a del sys.argv[1]
239n/a if len(sys.argv) > 1:
240n/a for folder in sys.argv[1:]:
241n/a parsedir(folder, modify)
242n/a else:
243n/a parsedir('/ufs/jack/Mail/errorsinbox', modify)
244n/a
245n/aif __name__ == '__main__' or sys.argv[0] == __name__:
246n/a main()