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