1# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
2# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
3
4from email.mime.text import MIMEText
5from email.mime.multipart import MIMEMultipart
6import smtplib
7import time
8try:
9    from socket import sslerror
10except ImportError:
11    sslerror = None
12from paste.exceptions import formatter
13
14class Reporter(object):
15
16    def __init__(self, **conf):
17        for name, value in conf.items():
18            if not hasattr(self, name):
19                raise TypeError(
20                    "The keyword argument %s was not expected"
21                    % name)
22            setattr(self, name, value)
23        self.check_params()
24
25    def check_params(self):
26        pass
27
28    def format_date(self, exc_data):
29        return time.strftime('%c', exc_data.date)
30
31    def format_html(self, exc_data, **kw):
32        return formatter.format_html(exc_data, **kw)
33
34    def format_text(self, exc_data, **kw):
35        return formatter.format_text(exc_data, **kw)
36
37class EmailReporter(Reporter):
38
39    to_addresses = None
40    from_address = None
41    smtp_server = 'localhost'
42    smtp_username = None
43    smtp_password = None
44    smtp_use_tls = False
45    subject_prefix = ''
46
47    def report(self, exc_data):
48        msg = self.assemble_email(exc_data)
49        server = smtplib.SMTP(self.smtp_server)
50        if self.smtp_use_tls:
51            server.ehlo()
52            server.starttls()
53            server.ehlo()
54        if self.smtp_username and self.smtp_password:
55            server.login(self.smtp_username, self.smtp_password)
56        server.sendmail(self.from_address,
57                        self.to_addresses, msg.as_string())
58        try:
59            server.quit()
60        except sslerror:
61            # sslerror is raised in tls connections on closing sometimes
62            pass
63
64    def check_params(self):
65        if not self.to_addresses:
66            raise ValueError("You must set to_addresses")
67        if not self.from_address:
68            raise ValueError("You must set from_address")
69        if isinstance(self.to_addresses, (str, unicode)):
70            self.to_addresses = [self.to_addresses]
71
72    def assemble_email(self, exc_data):
73        short_html_version = self.format_html(
74            exc_data, show_hidden_frames=False)
75        long_html_version = self.format_html(
76            exc_data, show_hidden_frames=True)
77        text_version = self.format_text(
78            exc_data, show_hidden_frames=False)
79        msg = MIMEMultipart()
80        msg.set_type('multipart/alternative')
81        msg.preamble = msg.epilogue = ''
82        text_msg = MIMEText(text_version)
83        text_msg.set_type('text/plain')
84        text_msg.set_param('charset', 'ASCII')
85        msg.attach(text_msg)
86        html_msg = MIMEText(short_html_version)
87        html_msg.set_type('text/html')
88        # @@: Correct character set?
89        html_msg.set_param('charset', 'UTF-8')
90        html_long = MIMEText(long_html_version)
91        html_long.set_type('text/html')
92        html_long.set_param('charset', 'UTF-8')
93        msg.attach(html_msg)
94        msg.attach(html_long)
95        subject = '%s: %s' % (exc_data.exception_type,
96                              formatter.truncate(str(exc_data.exception_value)))
97        msg['Subject'] = self.subject_prefix + subject
98        msg['From'] = self.from_address
99        msg['To'] = ', '.join(self.to_addresses)
100        return msg
101
102class LogReporter(Reporter):
103
104    filename = None
105    show_hidden_frames = True
106
107    def check_params(self):
108        assert self.filename is not None, (
109            "You must give a filename")
110
111    def report(self, exc_data):
112        text = self.format_text(
113            exc_data, show_hidden_frames=self.show_hidden_frames)
114        f = open(self.filename, 'a')
115        try:
116            f.write(text + '\n' + '-'*60 + '\n')
117        finally:
118            f.close()
119
120class FileReporter(Reporter):
121
122    file = None
123    show_hidden_frames = True
124
125    def check_params(self):
126        assert self.file is not None, (
127            "You must give a file object")
128
129    def report(self, exc_data):
130        text = self.format_text(
131            exc_data, show_hidden_frames=self.show_hidden_frames)
132        self.file.write(text + '\n' + '-'*60 + '\n')
133
134class WSGIAppReporter(Reporter):
135
136    def __init__(self, exc_data):
137        self.exc_data = exc_data
138
139    def __call__(self, environ, start_response):
140        start_response('500 Server Error', [('Content-type', 'text/html')])
141        return [formatter.format_html(self.exc_data)]
142