def pad_message(self, parts, to_domain): ''' Pad the Message with random characters. ''' # determine how large the bundled messages are original_size = 0 for part in parts: original_size += len(part.as_string()) # then pad the message with random characters current_size = original_size target_size = options.bundled_message_max_size() while current_size < target_size: # using urandom because it's less likely to lock up # a messaging program can't afford the potential locks up of /dev/random with open('/dev/urandom', 'rb') as rnd: rnd_bytes = rnd.read(target_size - current_size) if len(rnd_bytes) > target_size - current_size: rnd_bytes = rnd_bytes[:target_size - current_size] part = MIMEApplication(b64encode(rnd_bytes), mime_constants.ALTERNATIVE_SUB_TYPE, encode_base64) current_size += len(part.as_string()) parts.append(part) padded_bytes = target_size - original_size if padded_bytes < 0: self.log_message('original size: {}'.format(original_size)) self.log_message('target size: {}'.format(target_size)) self.log_message('padded with {} random bytes'.format(padded_bytes)) return parts
def main(): """Submit the program.""" usage = 'Usage: %prog [options] assignment@class FILES-AND-DIRECTORIES' parser = OptionParser(usage=usage) parser.add_option('-a', '--action', help='The action to trigger') parser.add_option('-e', '--email', help='The email to send the result to') options, args = parser.parse_args() if len(args) < 2: parser.error('Invalid number of arguments') # Get project, and destination user match = RE_TURNIN.match(args[0]) if not match: parser.error('Invalid assignment@class: {0}'.format(args[0])) project, user = match.groups() # Call turnin, afterall this is a wrapper ret = os.system('turnin {}'.format(' '.join(args))) if ret: # Exit it turnin failed return ret from_email = '{0}@cs.ucsb.edu'.format(os.getlogin()) to_email = '{0}@cs.ucsb.edu'.format(user) data = json.dumps({ 'action': options.action, 'base_path': os.getcwd(), 'project': project, 'user': os.getlogin(), 'user_email': options.email }) # Build message msg = MIMEApplication(data) msg['From'] = from_email msg['To'] = to_email msg['Subject'] = 'auto_grade email' # Notify user of turnin sys.stdout.write('Sending submission notification...') sys.stdout.flush() smtp = smtplib.SMTP() for hostname in SMTP_SERVERS: try: smtp.connect(hostname) sys.stdout.write(' connected to {0}...'.format(hostname)) sys.stdout.flush() break except socket.error: pass else: print 'could not connect, notify TA' return 1 smtp.sendmail(from_email, to_email, msg.as_string()) smtp.quit() print ' turnin complete!' return 0
def sendKeyMessage(gpg,server_key_fp,config,recipient): listaddress = "{0}@{1}".format(config.listname,config.listdomain) server_key_pub = str(gpg.export_keys(server_key_fp)) # create new message msg_parent = MIMEApplication(server_key_pub,'pgp-keys',email.encoders.encode_noop) msg_parent.add_header('Content-Disposition', 'attachment; filename="server.asc"') SendMessage(gpg,server_key_fp,config,recipient,"Public key of "+str(listaddress),msg_parent.as_string())
def email(self, data): msg = MIMEApplication(data) del msg['Content-Type'] msg['Content-Disposition'] = 'attachment; filename="smime.p7m"' msg['Content-Type'] = 'application/x-pkcs7-mime; smime-type=enveloped-data; name="smime.p7m"' data = msg.as_string() return data
def email(self, data, oaep): prefix = ['x-', ''][oaep] msg = MIMEApplication(data) del msg['Content-Type'] msg['Content-Disposition'] = 'attachment; filename="smime.p7m"' msg['Content-Type'] = 'application/%spkcs7-mime; smime-type=enveloped-data; name="smime.p7m"' % prefix data = msg.as_string() return data
class HTTPRequest(object): ''' Builds an HTTP GET or POST request that ORACC's server understands to send and retrieve ATF data. ''' def __init__(self, url, method, **kwargs): self.method = method self.url = url if method == 'POST': if 'command' in kwargs.keys(): self.create_request_message(kwargs['command'], kwargs['keys'], kwargs['atf_basename'], kwargs['atf_text']) else: self.create_response_message(kwargs['keys']) def create_request_message(self, command, keys, atf_basename, atf_text): """ Send attachment to server containing ATF file and necessary data to run given command (validate, lemmatise, etc). """ self.mtompkg = MIMEMultipart('related') self.set_multipart_params() self.set_soap_envelope(command=command, keys=keys, atf_basename=atf_basename, atf_text=atf_text) self.rootpkg = MIMEApplication(self.envelope, 'xop+xml', encode_7or8bit) self.set_multipart_payload() self.document = MIMEBase('*', '*') self.set_document_payload(atf_basename, atf_text) # The headers can't be created until the body is finished since they # need it to populate the Content-Length header self.set_multipart_headers() def create_response_message(self, keys): """ Asks the server for the response request.zip attachment containing validated/lemmatised/etc ATF file. """ self.set_soap_envelope(keys=keys) self.mtompkg = MIMEApplication(self.envelope, 'soap+xml', encode_7or8bit) self.mtompkg.add_header("Host", self.url) def set_response_headers(self): del (self.mtompkg['Content-Transfer-Encoding']) headers = ['Host', 'Content-Length', 'Connection'] values = [self.url, '623', 'close'] for header, value in zip(headers, values): self.mtompkg.add_header(header, value) def set_response_params(self): self.mtompkg.set_param('charset', 'utf-8') def set_multipart_payload(self): self.set_payload_headers() self.set_payload_params() self.mtompkg.attach(self.rootpkg) def set_document_payload(self, atf_basename, atf_text): self.set_document_headers() mem_data = StringIO() mem_zip = zipfile.ZipFile(mem_data, "w", zipfile.ZIP_DEFLATED, False) mem_zip.writestr("00atf/" + atf_basename, atf_text) mem_zip.close() mem_data.seek(0) self.document.set_payload(mem_data.getvalue()) self.mtompkg.attach(self.document) def set_document_headers(self): headers = ['Content-ID', 'Content-Transfer-Encoding'] values = ['<request_zip>', 'binary'] for header, value in zip(headers, values): self.document.add_header(header, value) def set_payload_params(self): params = ['charset', 'type'] values = ['utf-8', 'application/soap+xml'] for param, value in zip(params, values): self.rootpkg.set_param(param, value) def set_payload_headers(self): # Content-Transfer-Encoding is set to 7bit by default del (self.rootpkg['Content-Transfer-Encoding']) headers = ['Content-ID', 'Content-Transfer-Encoding'] values = ['<SOAP-ENV:Envelope>', 'binary'] for header, value in zip(headers, values): self.rootpkg.add_header(header, value) def set_multipart_headers(self): headers = ['Host', 'Content-Length', 'Connection'] values = [self.url, str(len(self.get_body())), 'close'] for header, value in zip(headers, values): self.mtompkg.add_header(header, value) def set_multipart_params(self): params = ['charset', 'type', 'start', 'start-info', 'boundary'] values = [ 'utf-8', 'application/xop+xml', '<SOAP-ENV:Envelope>', 'application/soap+xml', '==========boundary========' ] for param, value in zip(params, values): self.mtompkg.set_param(param, value) def set_soap_envelope(self, **kwargs): """ Format SOAP envelope to be attached in HTTP POST request. """ # The number of keys in the SOAP envelope depends on the command and # the message type (Request/Response) keys = '' # osc-data keys # Only Request messages have data, but the template has a reference to # it in both cases. data = '' if 'command' in kwargs.keys(): keys += '<osc-data:key>{}</osc-data:key>'.format(kwargs['command']) message_type = 'Request' data += """<osc-data:data> <osc-data:item xmime5:contentType="*/*"> <xop:Include href="cid:request_zip"/> </osc-data:item> </osc-data:data>""" else: message_type = 'Response' for key in kwargs['keys']: keys += '<osc-data:key>{}</osc-data:key>'.format(key) envelope = """<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xop="http://www.w3.org/2004/08/xop/include" xmlns:xmime5="http://www.w3.org/2005/05/xmlmime" xmlns:osc-data="http://oracc.org/wsdl/ows.xsd" xmlns:osc-meth="http://oracc.org/wsdl/ows.wsdl"> <SOAP-ENV:Body> <osc-meth:{type}> <osc-data:keys> {keys} </osc-data:keys> {data} </osc-meth:{type}> </SOAP-ENV:Body> </SOAP-ENV:Envelope>""".format(type=message_type, keys=keys, data=data) self.envelope = envelope def get_soap_envelope(self): return self.envelope def get_headers(self): """ Return dict with message headers - ready to use by requests module. """ return dict(self.mtompkg.items()) def get_body(self): """ Returns the body of the HTTP POST request containing the soap envelope including the encoded compressed ATF. """ # Header and body on a HTTP SOAP/MTOM message is separated by \n\n body = self.mtompkg.as_string().split('\n\n', 1)[1] # This returns the randomly created boundary string, which separates # the different elements in the request (document, envelope and # payload) bound = self.mtompkg.get_boundary() # There's two different kinds of SOAP HTTP POST requests handled, due # to the communication with the server being asynchronous: # * One that contains an ATF attachment submitted for validation/lemm # * One that contains only a request ID to ask for the validated/lemm'd # ATF. # The boundary will be empty in the second case. if bound is not None: # Encoded zip files start with "\n\nPK" and end with a new line # follwed by the ending boundary (in the form "--random_boundary--" attachment = body.split("\n\nPK")[1].split("\n--" + str(bound))[0] # Some line endings are \n and some are \r\n. The email module # automatically replaces some of them causing the server not to # understand line endings correctly. For this, we need to first # make sure all of the line endings are \n, then convert them all # to \r\n (to avoid having '\r\n' converted to '\r\r\n'. # Note the encoding of the zipped ATF might coincidentally contain # a '\n' substring. We have to avoid replacing those, otherwise # the zipped ATF will be corrupt and imposible to open by the # server. body = body.replace(attachment, '<attachment>') body = body.replace('\r\n', '\n').replace('\n', '\r\n') body = body.replace("<attachment>", attachment) else: body = body.replace('\r\n', '\n').replace('\n', '\r\n') return body
# coding=utf-8 import xlwt from io import BytesIO from email.mime.application import MIMEApplication excel = xlwt.Workbook('utf8') bio = BytesIO() excel.save(bio) excel_data = bio.getvalue() excel_msg = MIMEApplication(excel_data, _subtype='vnd.ms-excel') excel_msg.add_header('Content-Disposition', 'attachment; filename="%s"' % 'test_filename') print excel_msg.as_string()
class DisclaimerTestCase(TestCase): """ Build up different disclaimer scenarios and check if they react right. """ def setUp(self): """ A basic setup with a simple disclaimer, no directory servers, a basic rule and a basic action """ self.test_text = "Testmail" self.test_address = "*****@*****.**" self.disclaimer = models.Disclaimer() self.disclaimer.name = "Test" self.disclaimer.text = "Test-Disclaimer" self.disclaimer.save() self.rule = models.Rule() self.rule.save() self.action = models.Action() self.action.action = constants.ACTION_ACTION_ADD self.action.disclaimer = self.disclaimer self.action.rule = self.rule self.action.position = 0 self.action.save() requirement = models.Requirement() requirement.rule = self.rule requirement.action = constants.REQ_ACTION_ACCEPT requirement.save() def tool_get_helper(self): """ Return a configured milter helper :return: A Milter helper """ configuration = build_configuration() helper = MilterHelper(configuration) # The requirement should basically be enabled self.assertTrue( helper.enabled, "Helper wasn't enabled after initialization." ) return helper def tool_add_bodies(self, helper, mail): if mail.is_multipart(): for payload in mail.get_payload(): self.tool_add_bodies(helper, payload) else: helper.body(mail.as_string(), {}) def tool_run_real_test(self, header=None, make_mail=True): """ Runs the test using the milter helper and returns the action dictionary of eob :return: the action dictionary of eob() """ helper = self.tool_get_helper() helper.connect("", "", "1.1.1.1", "", {}) helper.mail_from(self.test_address, {}) helper.rcpt(self.test_address, {}) if header is not None: for key in header.iterkeys(): helper.header(key, header[key], {}) helper.eoh({}) else: # Add at least one header for proper mail processing helper.header("From", "nobody", {}) helper.eoh({}) if make_mail: self.test_mail = MIMEText(self.test_text, "plain", "UTF-8") helper.body(self.test_mail.as_string(), {}) return helper.eob({}) def tool_make_returned_mail(self, returned): headers = [] for key in self.test_mail.keys(): # Skip deleted headers if "delete_header" in returned and key in returned["delete_header"]: continue value = self.test_mail[key] # Change headers if "change_header" in returned and key in returned["change_header"]: value = returned["change_header"][key] headers.append("%s: %s" % (key, value)) if "add_header" in returned: for key in returned["add_header"]: headers.append("%s: %s" % (key, returned["add_header"][key])) return email.message_from_string( "%s\n\n%s" % ( "\n".join(headers), returned["repl_body"] ) ) def test_basic_add(self): """ The disclaimer will simply be added to the testmail. Check the returned body """ returned = self.tool_run_real_test() self.assertEqual( milter_helper.MilterHelper.decode_mail( self.tool_make_returned_mail(returned) )[1], "%s\n%s" % (self.test_text, self.disclaimer.text), "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( self.tool_make_returned_mail(returned) )[1], ) ) def test_disabled_action(self): """ If we disable the only action, we should get an empty action dictionary back """ self.action.enabled = False self.action.save() returned = self.tool_run_real_test() self.assertIsNone( returned, "We got an action dictionary back! %s" % returned ) def test_disabled_secondaction(self): """ If we add an disabled action to a working set, it should just work fine. """ action2 = models.Action() action2.rule = self.rule action2.position = 0 action2.enabled = False action2.disclaimer = self.disclaimer action2.save() self.action.position = 1 self.action.save() returned = self.tool_run_real_test() self.assertEqual( milter_helper.MilterHelper.decode_mail( self.tool_make_returned_mail(returned) )[1], "%s\n%s" % (self.test_text, self.disclaimer.text), "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( self.tool_make_returned_mail(returned) )[1], ) ) def test_basic_replace(self): """ Set the action to replace the disclaimer and not add it. Check the returned body """ self.test_text = "Testmail #DISCLAIMER#" self.action.action = constants.ACTION_ACTION_REPLACETAG self.action.action_parameters = "#DISCLAIMER#" self.action.save() returned = self.tool_run_real_test() self.assertEqual( milter_helper.MilterHelper.decode_mail( self.tool_make_returned_mail(returned) )[1], "Testmail %s" % self.disclaimer.text, "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( self.tool_make_returned_mail(returned) )[1] ) ) def test_replacements_add(self): """ Test the replacement feature of sender, recipient and header replacement [add mode] """ self.disclaimer.text = "{sender}|{recipient}|{header[\"Test\"]}" self.disclaimer.text_use_template = True self.disclaimer.save() returned = self.tool_run_real_test({"Test": "Test"}) self.assertEqual( milter_helper.MilterHelper.decode_mail( self.tool_make_returned_mail(returned) )[1], "%s\n%s|%s|Test" % ( self.test_text, self.test_address, self.test_address ), "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( self.tool_make_returned_mail(returned) )[1], ) ) def test_replacements_replace(self): """ Test the replacement feature of sender, recipient and header replacement [add mode] """ self.test_text = "Testmail #DISCLAIMER#" self.action.action = constants.ACTION_ACTION_REPLACETAG self.action.action_parameters = "#DISCLAIMER#" self.action.save() self.disclaimer.text = "{sender}|{recipient}|{header[\"Test\"]}" self.disclaimer.text_use_template = True self.disclaimer.save() returned = self.tool_run_real_test({"Test": "Test"}) self.assertEqual( milter_helper.MilterHelper.decode_mail( self.tool_make_returned_mail(returned) )[1], "Testmail %s|%s|Test" % ( self.test_address, self.test_address ), "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( self.tool_make_returned_mail(returned) )[1], ) ) def test_multiple_headers(self): """ Test a mail with multiple headers """ returned = self.tool_run_real_test(header={ "X-TEST1": "TEST", "X-TEST2": "TEST" }) self.assertEqual( milter_helper.MilterHelper.decode_mail( self.tool_make_returned_mail(returned) )[1], "%s\n%s" % (self.test_text, self.disclaimer.text), "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( self.tool_make_returned_mail(returned) )[1], ) ) def test_multipart(self): """ Test a multipart mail """ test_text = "TestPlain" test_html = "<p>TestHTML</p>" text_part = MIMEText(test_text, "plain", "UTF-8") html_part = MIMEText(test_html, "html", "UTF-8") self.test_mail = MIMEMultipart("alternative") self.test_mail.attach(text_part) self.test_mail.attach(html_part) returned = self.tool_run_real_test(make_mail=False) returned_mail = self.tool_make_returned_mail(returned) self.assertEqual( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[0] )[1], "%s\n%s" % (test_text, self.disclaimer.text), "Text-Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[0] )[1], ) ) self.assertEqual( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[1] )[1], "<html><body>\n%s\n<p>%s</p>\n</body></html>\n" % ( test_html, self.disclaimer.text ), "HTML-Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[1] )[1], ) ) def test_html(self): """ Test a HTML mail with an HTML disclaimer """ test_html = "<p>TestHTML</p>" self.test_mail = MIMEText(test_html, "html", "UTF-8") returned = self.tool_run_real_test(make_mail=False) returned_mail = self.tool_make_returned_mail(returned) self.assertEqual( milter_helper.MilterHelper.decode_mail( returned_mail )[1], "<html><body>\n%s\n<p>%s</p>\n</body></html>\n" % ( test_html, self.disclaimer.text ), "HTML-Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( returned_mail )[1] ) ) def test_unresolvable_tag(self): """ Test an unresolvable tag with template_fail=True inside a disclaimer, assuming that the mail isn't modified. """ self.disclaimer.text = "{FAIL}" self.disclaimer.template_fail = True self.disclaimer.save() returned = self.tool_run_real_test() returned_mail = self.tool_make_returned_mail(returned) self.assertEqual( milter_helper.MilterHelper.decode_mail( self.test_mail )[1], milter_helper.MilterHelper.decode_mail( returned_mail )[1], "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( returned_mail )[1], ) ) def test_unresolvable_tag_nofail(self): """ Test an unresolvable tag with template_fail=False inside a disclaimer, assuming that the mail is modified and the unresolvable tag is removed. """ self.disclaimer.text = "{FAIL}" self.disclaimer.template_fail = False self.disclaimer.save() returned = self.tool_run_real_test() returned_mail = self.tool_make_returned_mail(returned) self.assertEqual( milter_helper.MilterHelper.decode_mail( returned_mail )[1], "%s\n" % self.test_text, "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( returned_mail )[1], ) ) def test_unresolvable_subtag(self): """ Test an unresolvable subtag with template_fail=True inside a disclaimer assuming it will return an unmodified mail """ self.disclaimer.text = "{FAIL[\"FAIL\"]}" self.disclaimer.template_fail = True self.disclaimer.save() returned = self.tool_run_real_test() returned_mail = self.tool_make_returned_mail(returned) self.assertEqual( milter_helper.MilterHelper.decode_mail( self.test_mail )[1], milter_helper.MilterHelper.decode_mail( returned_mail )[1], "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( returned_mail )[1], ) ) def test_quoted_printable(self): """ Use a "quoted printable"-encoded mail as a test mail """ self.test_mail = MIMEText(self.test_text, "plain", "iso-8859-1") returned = self.tool_run_real_test(make_mail=False) self.assertEqual( milter_helper.MilterHelper.decode_mail( self.tool_make_returned_mail(returned) )[1], "%s\n%s" % (self.test_text, self.disclaimer.text), "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( self.tool_make_returned_mail(returned) )[1], ) ) def test_html_disclaimer(self): """ Test a HTML (not "use text") disclaimer """ self.disclaimer.html_use_text = False self.disclaimer.html = "<b>TEST-DISCLAIMER</b>" self.disclaimer.save() self.test_mail = MIMEText("<p>%s</p>" % self.test_text, "html") returned = self.tool_run_real_test(make_mail=False) self.assertEqual( milter_helper.MilterHelper.decode_mail( self.tool_make_returned_mail(returned) )[1], "<html><body>\n<p>%s</p>\n%s\n</body></html>\n" % ( self.test_text, self.disclaimer.html ), "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( self.tool_make_returned_mail(returned) )[1], ) ) def test_multiple_rules(self): """ Add multiple rules assuming that only the first rule will be carried out. """ rule2 = models.Rule() rule2.position = 1 rule2.save() action2 = models.Action() action2.action = constants.ACTION_ACTION_ADD action2.disclaimer = self.disclaimer action2.rule = rule2 action2.position = 0 action2.save() requirement2 = models.Requirement() requirement2.rule = rule2 requirement2.action = constants.REQ_ACTION_ACCEPT requirement2.save() returned = self.tool_run_real_test() self.assertEqual( milter_helper.MilterHelper.decode_mail( self.tool_make_returned_mail(returned) )[1], "%s\n%s" % (self.test_text, self.disclaimer.text), "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( self.tool_make_returned_mail(returned) )[1], ) ) def test_multiple_rules_continue(self): """ Use the continue flag for multiple rules assuming that all rules will be carried out. """ rule2 = models.Rule() rule2.position = 1 rule2.save() self.rule.continue_rules = True self.rule.save() disclaimer2 = models.Disclaimer() disclaimer2.name = "Test2" disclaimer2.text = "Test-Disclaimer2" disclaimer2.save() action2 = models.Action() action2.action = constants.ACTION_ACTION_ADD action2.disclaimer = disclaimer2 action2.rule = rule2 action2.position = 0 action2.save() requirement2 = models.Requirement() requirement2.rule = rule2 requirement2.action = constants.REQ_ACTION_ACCEPT requirement2.save() returned = self.tool_run_real_test() self.assertEqual( milter_helper.MilterHelper.decode_mail( self.tool_make_returned_mail(returned) )[1], "%s\n%s\nTest-Disclaimer2" % (self.test_text, self.disclaimer.text), "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( self.tool_make_returned_mail(returned) )[1], ) ) def test_disclaimer_add_part(self): """ Test an action, that adds a mime part with the disclaimer """ self.action.action = constants.ACTION_ACTION_ADDPART self.action.save() returned = self.tool_run_real_test() returned_mail = self.tool_make_returned_mail(returned) # The first part shouldn't have been modified, but added as an # rfc-822 attachment self.assertEqual( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[0].get_payload()[0] )[1], self.test_text, "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[0] )[1], ) ) # The second part should be our disclaimer self.assertEqual( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[1] )[1], self.disclaimer.text, "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[1] )[1], ) ) def test_disclaimer_add_part_html(self): """ Test an action, that adds a mime part with a HTML disclaimer based on the text disclaimer """ self.action.action = constants.ACTION_ACTION_ADDPART self.action.save() test_html = "<p>TestHTML</p>" self.test_mail = MIMEText(test_html, "html", "UTF-8") returned = self.tool_run_real_test(make_mail=False) returned_mail = self.tool_make_returned_mail(returned) # The first part shouldn't have been modified, but added as an # rfc-822 attachment self.assertEqual( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[0].get_payload()[0] )[1], test_html, "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[0] )[1], ) ) # The second part should be our disclaimer self.assertEqual( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[1] )[1], self.disclaimer.text, "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[1] )[1], ) ) def test_disclaimer_add_part_html_disclaimer(self): """ Test an action, that adds a mime part with a pure HTML disclaimer """ self.action.action = constants.ACTION_ACTION_ADDPART self.action.save() test_html = "<p>TestHTML</p>" self.test_mail = MIMEText(test_html, "html", "UTF-8") self.disclaimer.html_use_text = False self.disclaimer.html = "<b>TEST-DISCLAIMER</b>" self.disclaimer.save() returned = self.tool_run_real_test(make_mail=False) returned_mail = self.tool_make_returned_mail(returned) # The first part shouldn't have been modified, but added as an # rfc-822 attachment self.assertEqual( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[0].get_payload()[0] )[1], test_html, "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[0] )[1], ) ) # The second part should be our disclaimer self.assertEqual( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[1] )[1], self.disclaimer.html, "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[1] )[1], ) ) def test_disclaimer_add_part_fallback(self): """ Test an action, that adds a mime part with the disclaimer, but uses a not-supported original mime-part (not text/plain or /html). This should give a text-disclaimer back. """ self.action.action = constants.ACTION_ACTION_ADDPART self.action.save() self.disclaimer.html_use_text = False self.disclaimer.html = "<b>TEST-DISCLAIMER</b>" self.disclaimer.save() self.test_mail = MIMEApplication("TEST") returned = self.tool_run_real_test(make_mail=False) returned_mail = self.tool_make_returned_mail(returned) # The first part shouldn't have been modified, but added as an # rfc-822 attachment self.assertEqual( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[0].get_payload()[0] )[1], "TEST", "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[0] )[1], ) ) # The second part should be the text-part of the disclaimer self.assertEqual( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[1] )[1], self.disclaimer.text, "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[1] )[1], ) ) def test_disclaimer_add_part_fallback_html(self): """ Test an action, that adds a mime part with the disclaimer, but uses a not-supported original mime-part (not text/plain or /html). Use the html fallback to generate a html-disclaimer. """ self.action.action = constants.ACTION_ACTION_ADDPART self.action.save() self.disclaimer.html_use_text = False self.disclaimer.html = "<b>TEST-DISCLAIMER</b>" self.disclaimer.use_html_fallback = True self.disclaimer.save() self.test_mail = MIMEApplication("TEST") returned = self.tool_run_real_test(make_mail=False) returned_mail = self.tool_make_returned_mail(returned) # The first part shouldn't have been modified, but added as an # rfc-822 attachment self.assertEqual( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[0].get_payload()[0] )[1], "TEST", "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[0] )[1], ) ) # The second part should be the text-part of the disclaimer self.assertEqual( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[1] )[1], self.disclaimer.html, "Body was unexpectedly modified to %s" % ( milter_helper.MilterHelper.decode_mail( returned_mail.get_payload()[1] )[1], ) )
def main(): """Submit the program.""" usage = "Usage: %prog [options] assignment@class FILES-AND-DIRECTORIES" parser = OptionParser(usage=usage) parser.add_option("-a", "--action", help="The action to trigger") parser.add_option("-e", "--email", help="The email to send the result to") options, args = parser.parse_args() if len(args) < 2: parser.error("Invalid number of arguments") # Get project, and destination user match = RE_TURNIN.match(args[0]) if not match: parser.error("Invalid assignment@class: {0}".format(args[0])) project, user = match.groups() # Call turnin, afterall this is a wrapper ret = os.system("turnin {}".format(" ".join(args))) if ret: # Exit it turnin failed return ret from_email = "{0}@cs.ucsb.edu".format(os.getlogin()) to_email = "{0}@cs.ucsb.edu".format(user) data = json.dumps( { "action": options.action, "base_path": os.getcwd(), "project": project, "user": os.getlogin(), "user_email": options.email, } ) # Build message msg = MIMEApplication(data) msg["From"] = from_email msg["To"] = to_email msg["Subject"] = "auto_grade email" # Notify user of turnin sys.stdout.write("Sending submission notification...") sys.stdout.flush() smtp = smtplib.SMTP() for hostname in SMTP_SERVERS: try: smtp.connect(hostname) sys.stdout.write(" connected to {0}...".format(hostname)) sys.stdout.flush() break except socket.error: pass else: print "could not connect, notify TA" return 1 smtp.sendmail(from_email, to_email, msg.as_string()) smtp.quit() print " turnin complete!" return 0
class HTTPRequest(object): ''' Builds an HTTP GET or POST request that ORACC's server understands to send and retrieve ATF data. ''' def __init__(self, url, method, **kwargs): self.method = method self.url = url if method == 'POST': if 'command' in kwargs.keys(): self.create_request_message(kwargs['command'], kwargs['keys'], kwargs['atf_basename'], kwargs['atf_text']) else: self.create_response_message(kwargs['keys']) def create_request_message(self, command, keys, atf_basename, atf_text): """ Send attachment to server containing ATF file and necessary data to run given command (validate, lemmatise, etc). """ self.mtompkg = MIMEMultipart('related') self.set_multipart_params() self.set_soap_envelope(command=command, keys=keys, atf_basename=atf_basename, atf_text=atf_text) self.rootpkg = MIMEApplication(self.envelope, 'xop+xml', encode_7or8bit) self.set_multipart_payload() self.document = MIMEBase('*', '*') self.set_document_payload(atf_basename, atf_text) # The headers can't be created until the body is finished since they # need it to populate the Content-Length header self.set_multipart_headers() def create_response_message(self, keys): """ Asks the server for the response request.zip attachment containing validated/lemmatised/etc ATF file. """ self.set_soap_envelope(keys=keys) self.mtompkg = MIMEApplication(self.envelope, 'soap+xml', encode_7or8bit) self.mtompkg.add_header("Host", self.url) def set_response_headers(self): del(self.mtompkg['Content-Transfer-Encoding']) headers = ['Host', 'Content-Length', 'Connection'] values = [self.url, '623', 'close'] for header, value in zip(headers, values): self.mtompkg.add_header(header, value) def set_response_params(self): self.mtompkg.set_param('charset', 'utf-8') def set_multipart_payload(self): self.set_payload_headers() self.set_payload_params() self.mtompkg.attach(self.rootpkg) def set_document_payload(self, atf_basename, atf_text): self.set_document_headers() mem_data = StringIO() mem_zip = zipfile.ZipFile(mem_data, "w", zipfile.ZIP_DEFLATED, False) mem_zip.writestr("00atf/"+atf_basename, atf_text) mem_zip.close() mem_data.seek(0) self.document.set_payload(mem_data.getvalue()) self.mtompkg.attach(self.document) def set_document_headers(self): headers = ['Content-ID', 'Content-Transfer-Encoding'] values = ['<request_zip>', 'binary'] for header, value in zip(headers, values): self.document.add_header(header, value) def set_payload_params(self): params = ['charset', 'type'] values = ['utf-8', 'application/soap+xml'] for param, value in zip(params, values): self.rootpkg.set_param(param, value) def set_payload_headers(self): # Content-Transfer-Encoding is set to 7bit by default del(self.rootpkg['Content-Transfer-Encoding']) headers = ['Content-ID', 'Content-Transfer-Encoding'] values = ['<SOAP-ENV:Envelope>', 'binary'] for header, value in zip(headers, values): self.rootpkg.add_header(header, value) def set_multipart_headers(self): headers = ['Host', 'Content-Length', 'Connection'] values = [self.url, str(len(self.get_body())), 'close'] for header, value in zip(headers, values): self.mtompkg.add_header(header, value) def set_multipart_params(self): params = ['charset', 'type', 'start', 'start-info', 'boundary'] values = ['utf-8', 'application/xop+xml', '<SOAP-ENV:Envelope>', 'application/soap+xml', '==========boundary========'] for param, value in zip(params, values): self.mtompkg.set_param(param, value) def set_soap_envelope(self, **kwargs): """ Format SOAP envelope to be attached in HTTP POST request. """ # The number of keys in the SOAP envelope depends on the command and # the message type (Request/Response) keys = '' # osc-data keys # Only Request messages have data, but the template has a reference to # it in both cases. data = '' if 'command' in kwargs.keys(): keys += '<osc-data:key>{}</osc-data:key>'.format(kwargs['command']) message_type = 'Request' data += """<osc-data:data> <osc-data:item xmime5:contentType="*/*"> <xop:Include href="cid:request_zip"/> </osc-data:item> </osc-data:data>""" else: message_type = 'Response' for key in kwargs['keys']: keys += '<osc-data:key>{}</osc-data:key>'.format(key) envelope = """<?xml version="1.0" encoding="UTF-8"?> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xop="http://www.w3.org/2004/08/xop/include" xmlns:xmime5="http://www.w3.org/2005/05/xmlmime" xmlns:osc-data="http://oracc.org/wsdl/ows.xsd" xmlns:osc-meth="http://oracc.org/wsdl/ows.wsdl"> <SOAP-ENV:Body> <osc-meth:{type}> <osc-data:keys> {keys} </osc-data:keys> {data} </osc-meth:{type}> </SOAP-ENV:Body> </SOAP-ENV:Envelope>""".format(type=message_type, keys=keys, data=data) self.envelope = envelope def get_soap_envelope(self): return self.envelope def get_headers(self): """ Return dict with message headers - ready to use by requests module. """ return dict(self.mtompkg.items()) def get_body(self): """ Returns the body of the HTTP POST request containing the soap envelope including the encoded compressed ATF. """ # Header and body on a HTTP SOAP/MTOM message is separated by \n\n body = self.mtompkg.as_string().split('\n\n', 1)[1] # This returns the randomly created boundary string, which separates # the different elements in the request (document, envelope and # payload) bound = self.mtompkg.get_boundary() # There's two different kinds of SOAP HTTP POST requests handled, due # to the communication with the server being asynchronous: # * One that contains an ATF attachment submitted for validation/lemm # * One that contains only a request ID to ask for the validated/lemm'd # ATF. # The boundary will be empty in the second case. if bound is not None: # Encoded zip files start with "\n\nPK" and end with a new line # follwed by the ending boundary (in the form "--random_boundary--" attachment = body.split("\n\nPK")[1].split("\n--" + str(bound))[0] # Some line endings are \n and some are \r\n. The email module # automatically replaces some of them causing the server not to # understand line endings correctly. For this, we need to first # make sure all of the line endings are \n, then convert them all # to \r\n (to avoid having '\r\n' converted to '\r\r\n'. # Note the encoding of the zipped ATF might coincidentally contain # a '\n' substring. We have to avoid replacing those, otherwise # the zipped ATF will be corrupt and imposible to open by the # server. body = body.replace(attachment, '<attachment>') body = body.replace('\r\n', '\n').replace('\n', '\r\n') body = body.replace("<attachment>", attachment) else: body = body.replace('\r\n', '\n').replace('\n', '\r\n') return body