1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4"""Methods for reporting bugs."""
5
6import subprocess, sys, os
7
8__all__ = ["ReportFailure", "BugReport", "getReporters"]
9
10#
11
12
13class ReportFailure(Exception):
14    """Generic exception for failures in bug reporting."""
15
16    def __init__(self, value):
17        self.value = value
18
19
20# Collect information about a bug.
21
22
23class BugReport(object):
24    def __init__(self, title, description, files):
25        self.title = title
26        self.description = description
27        self.files = files
28
29
30# Reporter interfaces.
31
32import os
33
34import email, mimetypes, smtplib
35from email import encoders
36from email.message import Message
37from email.mime.base import MIMEBase
38from email.mime.multipart import MIMEMultipart
39from email.mime.text import MIMEText
40
41# ===------------------------------------------------------------------------===#
42# ReporterParameter
43# ===------------------------------------------------------------------------===#
44
45
46class ReporterParameter(object):
47    def __init__(self, n):
48        self.name = n
49
50    def getName(self):
51        return self.name
52
53    def getValue(self, r, bugtype, getConfigOption):
54        return getConfigOption(r.getName(), self.getName())
55
56    def saveConfigValue(self):
57        return True
58
59
60class TextParameter(ReporterParameter):
61    def getHTML(self, r, bugtype, getConfigOption):
62        return """\
63<tr>
64<td class="form_clabel">%s:</td>
65<td class="form_value"><input type="text" name="%s_%s" value="%s"></td>
66</tr>""" % (
67            self.getName(),
68            r.getName(),
69            self.getName(),
70            self.getValue(r, bugtype, getConfigOption),
71        )
72
73
74class SelectionParameter(ReporterParameter):
75    def __init__(self, n, values):
76        ReporterParameter.__init__(self, n)
77        self.values = values
78
79    def getHTML(self, r, bugtype, getConfigOption):
80        default = self.getValue(r, bugtype, getConfigOption)
81        return """\
82<tr>
83<td class="form_clabel">%s:</td><td class="form_value"><select name="%s_%s">
84%s
85</select></td>""" % (
86            self.getName(),
87            r.getName(),
88            self.getName(),
89            "\n".join(
90                [
91                    """\
92<option value="%s"%s>%s</option>"""
93                    % (o[0], o[0] == default and ' selected="selected"' or "", o[1])
94                    for o in self.values
95                ]
96            ),
97        )
98
99
100# ===------------------------------------------------------------------------===#
101# Reporters
102# ===------------------------------------------------------------------------===#
103
104
105class EmailReporter(object):
106    def getName(self):
107        return "Email"
108
109    def getParameters(self):
110        return [TextParameter(x) for x in ["To", "From", "SMTP Server", "SMTP Port"]]
111
112    # Lifted from python email module examples.
113    def attachFile(self, outer, path):
114        # Guess the content type based on the file's extension.  Encoding
115        # will be ignored, although we should check for simple things like
116        # gzip'd or compressed files.
117        ctype, encoding = mimetypes.guess_type(path)
118        if ctype is None or encoding is not None:
119            # No guess could be made, or the file is encoded (compressed), so
120            # use a generic bag-of-bits type.
121            ctype = "application/octet-stream"
122        maintype, subtype = ctype.split("/", 1)
123        if maintype == "text":
124            fp = open(path)
125            # Note: we should handle calculating the charset
126            msg = MIMEText(fp.read(), _subtype=subtype)
127            fp.close()
128        else:
129            fp = open(path, "rb")
130            msg = MIMEBase(maintype, subtype)
131            msg.set_payload(fp.read())
132            fp.close()
133            # Encode the payload using Base64
134            encoders.encode_base64(msg)
135        # Set the filename parameter
136        msg.add_header(
137            "Content-Disposition", "attachment", filename=os.path.basename(path)
138        )
139        outer.attach(msg)
140
141    def fileReport(self, report, parameters):
142        mainMsg = """\
143BUG REPORT
144---
145Title: %s
146Description: %s
147""" % (
148            report.title,
149            report.description,
150        )
151
152        if not parameters.get("To"):
153            raise ReportFailure('No "To" address specified.')
154        if not parameters.get("From"):
155            raise ReportFailure('No "From" address specified.')
156
157        msg = MIMEMultipart()
158        msg["Subject"] = "BUG REPORT: %s" % (report.title)
159        # FIXME: Get config parameters
160        msg["To"] = parameters.get("To")
161        msg["From"] = parameters.get("From")
162        msg.preamble = mainMsg
163
164        msg.attach(MIMEText(mainMsg, _subtype="text/plain"))
165        for file in report.files:
166            self.attachFile(msg, file)
167
168        try:
169            s = smtplib.SMTP(
170                host=parameters.get("SMTP Server"), port=parameters.get("SMTP Port")
171            )
172            s.sendmail(msg["From"], msg["To"], msg.as_string())
173            s.close()
174        except:
175            raise ReportFailure("Unable to send message via SMTP.")
176
177        return "Message sent!"
178
179
180class BugzillaReporter(object):
181    def getName(self):
182        return "Bugzilla"
183
184    def getParameters(self):
185        return [TextParameter(x) for x in ["URL", "Product"]]
186
187    def fileReport(self, report, parameters):
188        raise NotImplementedError
189
190
191class RadarClassificationParameter(SelectionParameter):
192    def __init__(self):
193        SelectionParameter.__init__(
194            self,
195            "Classification",
196            [
197                ["1", "Security"],
198                ["2", "Crash/Hang/Data Loss"],
199                ["3", "Performance"],
200                ["4", "UI/Usability"],
201                ["6", "Serious Bug"],
202                ["7", "Other"],
203            ],
204        )
205
206    def saveConfigValue(self):
207        return False
208
209    def getValue(self, r, bugtype, getConfigOption):
210        if bugtype.find("leak") != -1:
211            return "3"
212        elif bugtype.find("dereference") != -1:
213            return "2"
214        elif bugtype.find("missing ivar release") != -1:
215            return "3"
216        else:
217            return "7"
218
219
220###
221
222
223def getReporters():
224    reporters = []
225    reporters.append(EmailReporter())
226    return reporters
227