def _UploadInstanceToHealthcareAPI(sop_instance_uid, inst): # type: (str, str) -> None """Uploads instances in Healthcare API.""" http = httplib2.Http(timeout=60) http = _CREDENTIALS.authorize(http) related = MIMEMultipart("related", boundary="boundary") setattr(related, "_write_headers", lambda self: None) mime_attach = MIMEApplication(inst, "dicom", _encoder=encoders.encode_noop) related.attach(mime_attach) multipart_boundary = related.get_boundary() headers = {} headers["content-type"] = ( 'multipart/related; type="application/dicom"; boundary="%s"' ) % multipart_boundary body = related.as_string() path = "https://healthcare.googleapis.com/v1alpha/projects/{}/locations/{}/datasets/{}/dicomStores/{}/dicomWeb/studies".format( FLAGS.project_id, FLAGS.location, FLAGS.dataset_id, FLAGS.dicom_store_id) resp, content = http.request(path, method="POST", headers=headers, body=body) if resp.status == 409: logger.warning("Instance with SOP Instance UID already exists: %s", sop_instance_uid) return assert resp.status == 200, ( "Failed to store instance %s, reason: %s, response: %s" % (sop_instance_uid, resp.reason, content))
def _execute(self, http, order, requests): """Serialize batch request, send to server, process response. Args: http: httplib2.Http, an http object to be used to make the request with. order: list, list of request ids in the order they were added to the batch. request: list, list of request objects to send. Raises: httplib2.Error if a transport error has occured. apiclient.errors.BatchError if the response is the wrong format. """ message = MIMEMultipart('mixed') # Message should not write out it's own headers. setattr(message, '_write_headers', lambda self: None) # Add all the individual requests. for request_id in order: request = requests[request_id] msg = MIMENonMultipart('application', 'http') msg['Content-Transfer-Encoding'] = 'binary' msg['Content-ID'] = self._id_to_header(request_id) body = self._serialize_request(request) msg.set_payload(body) message.attach(msg) body = message.as_string() headers = {} headers['content-type'] = ('multipart/mixed; ' 'boundary="%s"') % message.get_boundary() resp, content = http.request(self._batch_uri, 'POST', body=body, headers=headers) if resp.status >= 300: raise HttpError(resp, content, self._batch_uri) # Now break out the individual responses and store each one. boundary, _ = content.split(None, 1) # Prepend with a content-type header so FeedParser can handle it. header = 'content-type: %s\r\n\r\n' % resp['content-type'] for_parser = header + content parser = FeedParser() parser.feed(for_parser) mime_response = parser.close() if not mime_response.is_multipart(): raise BatchError("Response not in multipart/mixed format.", resp, content) for part in mime_response.get_payload(): request_id = self._header_to_id(part['Content-ID']) headers, content = self._deserialize_response(part.get_payload()) self._responses[request_id] = (headers, content)
def _execute(self, http, order, requests): """Serialize batch request, send to server, process response. Args: http: httplib2.Http, an http object to be used to make the request with. order: list, list of request ids in the order they were added to the batch. request: list, list of request objects to send. Raises: httplib2.HttpLib2Error if a transport error has occured. apiclient.errors.BatchError if the response is the wrong format. """ message = MIMEMultipart('mixed') # Message should not write out it's own headers. setattr(message, '_write_headers', lambda self: None) # Add all the individual requests. for request_id in order: request = requests[request_id] msg = MIMENonMultipart('application', 'http') msg['Content-Transfer-Encoding'] = 'binary' msg['Content-ID'] = self._id_to_header(request_id) body = self._serialize_request(request) msg.set_payload(body) message.attach(msg) body = message.as_string() headers = {} headers['content-type'] = ('multipart/mixed; ' 'boundary="%s"') % message.get_boundary() resp, content = http.request(self._batch_uri, 'POST', body=body, headers=headers) if resp.status >= 300: raise HttpError(resp, content, uri=self._batch_uri) # Now break out the individual responses and store each one. boundary, _ = content.split(None, 1) # Prepend with a content-type header so FeedParser can handle it. header = 'content-type: %s\r\n\r\n' % resp['content-type'] for_parser = header + content parser = FeedParser() parser.feed(for_parser) mime_response = parser.close() if not mime_response.is_multipart(): raise BatchError("Response not in multipart/mixed format.", resp=resp, content=content) for part in mime_response.get_payload(): request_id = self._header_to_id(part['Content-ID']) response, content = self._deserialize_response(part.get_payload()) self._responses[request_id] = (response, content)
def upload(self, filename): name = os.path.basename(filename) size = os.path.getsize(filename) cype = guess_type(name) if not cype or not cype[0]: cype = "text/plain" else: cype = cype[0] url = ("https://www.googleapis.com/upload/drive/v2/files?" "uploadType=multipart") message = MIMEMultipart('mixed') # Message should not write out it's own headers. setattr(message, '_write_headers', lambda self: None) msg = MIMENonMultipart('application', 'json; charset=UTF-8') msg.set_payload(json.dumps({"title": name, #.replace("'", r"\'"), "mimeType": cype})) message.attach(msg) msg = MIMENonMultipart(*cype.split('/')) msg.add_header("Content-Transfer-Encoding", "binary") msg.set_payload(open(filename).read()) message.attach(msg) body = message.as_string() bd = message.get_boundary() hd = {"Content-Type": 'multipart/related; boundary="%s"' % bd} res = self.auth_http("POST", url, body, hd) ret = res.read() pprint(ret) return
def upload(self, filename): name = os.path.basename(filename) size = os.path.getsize(filename) cype = guess_type(name) if not cype or not cype[0]: cype = "text/plain" else: cype = cype[0] url = ("https://www.googleapis.com/upload/drive/v2/files?" "uploadType=multipart") message = MIMEMultipart('mixed') # Message should not write out it's own headers. setattr(message, '_write_headers', lambda self: None) msg = MIMENonMultipart('application', 'json; charset=UTF-8') msg.set_payload(json.dumps({"title": name, #.replace("'", r"\'"), "mimeType": cype})) message.attach(msg) msg = MIMENonMultipart(*cype.split('/')) msg.add_header("Content-Transfer-Encoding", "binary") msg.set_payload(open(filename).read()) message.attach(msg) body = message.as_string() bd = message.get_boundary() hd = {"Content-Type": 'multipart/related; boundary="%s"' % bd} res = self.auth_http("POST", url, body, hd) ret = res.read() pprint(ret)
def test_get_activities_extra_fetches_fail(self): """Sometimes the extras fetches return errors. Ignore that.""" self.init() batch = MIMEMultipart() for i in range(3): msg = Message() msg.set_payload('HTTP/1.1 500 Foo Bar\n\r\n\r\n') msg['Content-ID'] = '<response-abc+%d>' % (i + 1) batch.attach(msg) # as_string() must be called before get_boundary() to generate the # boundaries between parts, but can't be called again, so we capture the # result. batch_str = batch.as_string() self.auth_entity.http = lambda: http.HttpMockSequence( [({'status': '200'}, json.dumps({'items': [ACTIVITY_GP_EXTRAS]})), ({'status': '200', 'content-type': 'multipart/mixed; boundary="%s"' % batch.get_boundary()}, batch_str), ]) cache = util.CacheDict() self.assert_equals([ACTIVITY_AS], self.googleplus.get_activities( fetch_replies=True, fetch_likes=True, fetch_shares=True, cache=cache)) for prefix in 'AGC ', 'AGL ', 'AGS ': self.assertNotIn(prefix + '001', cache)
def upload_attachment(self, page_id, filename, file_content, **kwargs): """Uploads attachment.""" mime = MIMEMultipart("form-data") part = MIMEApplication(file_content, "octet-stream", encode_noop) part.add_header("Content-Disposition", "form-data", name="Filedata", filename=filename.encode("utf-8")) part.add_header("Content-Transfer-Encoding", "binary") mime.attach(part) # we don't want mime.as_string(), because the format is different # from the one we should use in HTTP requests # all we wanted is a proper boundary string boundary = mime.get_boundary() body = "\r\n".join( [ "--" + boundary, ('Content-Disposition: form-data; name="Filedata"; ' 'filename="{}"').format(filename.encode("utf-8")), # 'MIME-Version: 1.0', "Content-Type: application/octet-stream", # 'Content-Transfer-Encoding: binary', "", file_content, "--" + boundary + "--", "", ] ) path = "/pages/%d/attachments%s" % (page_id, self._create_argument(**kwargs)) headers = { "Authorization": "Basic %s" % self.auth_key, "Content-Type": "multipart/form-data; boundary=%s" % boundary, "Content-Length": str(len(body)), } try: return self._request("POST", path, body, headers) except SpringnoteException as ex: if ex.status >= 300 and ex.status < 400: # it's a redirect return True # TODO
def encrypt_all_payloads(message, gpg_to_cmdline): encrypted_payloads = list() if type(message.get_payload()) == str: if ( cfg.has_key("default") and cfg["default"].has_key("mime_conversion") and cfg["default"]["mime_conversion"] == "yes" ): # Convert a plain text email into PGP/MIME attachment style. Modeled after enigmail. submsg1 = email.message.Message() submsg1.set_payload("Version: 1\n") submsg1.set_type("application/pgp-encrypted") submsg1.set_param("PGP/MIME version identification", "", "Content-Description") submsg2 = email.message.Message() submsg2.set_type("application/octet-stream") submsg2.set_param("name", "encrypted.asc") submsg2.set_param("OpenPGP encrypted message", "", "Content-Description") submsg2.set_param("inline", "", "Content-Disposition") submsg2.set_param("filename", "encrypted.asc", "Content-Disposition") # WTF! It seems to swallow the first line. Not sure why. Perhaps # it's skipping an imaginary blank line someplace. (ie skipping a header) # Workaround it here by prepending a blank line. submsg2.set_payload("\n" + message.get_payload()) message.preamble = "This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)" # Use this just to generate a MIME boundary string. junk_msg = MIMEMultipart() junk_str = junk_msg.as_string() # WTF! Without this, get_boundary() will return 'None'! boundary = junk_msg.get_boundary() # This also modifies the boundary in the body of the message, ie it gets parsed. if message.has_key("Content-Type"): message.replace_header( "Content-Type", 'multipart/encrypted; protocol="application/pgp-encrypted";\nboundary="%s"\n' % boundary, ) else: message["Content-Type"] = ( 'multipart/encrypted; protocol="application/pgp-encrypted";\nboundary="%s"\n' % boundary ) return [submsg1, encrypt_payload(submsg2, gpg_to_cmdline)] else: # Do a simple in-line PGP conversion of a plain text email. return encrypt_payload(message, gpg_to_cmdline).get_payload() for payload in message.get_payload(): if type(payload.get_payload()) == list: encrypted_payloads.extend(encrypt_all_payloads(payload, gpg_to_cmdline)) else: encrypted_payloads.append(encrypt_payload(payload, gpg_to_cmdline)) return encrypted_payloads
def test_get_activities_fetch_extras(self): self.init() # Generate minimal fake responses for each request in the batch. # # Test with multiple activities to cover the bug described in # https://github.com/snarfed/bridgy/issues/22#issuecomment-56329848 : # util.CacheDict.get_multi() didn't originally handle generator args. batch = MIMEMultipart() for i, item in enumerate((COMMENT_GP, PLUSONER, RESHARER) * 2): msg = Message() msg.set_payload('HTTP/1.1 200 OK\n\r\n\r\n' + json.dumps({'items': [item]})) msg['Content-ID'] = '<response-abc+%d>' % (i + 1) batch.attach(msg) # as_string() must be called before get_boundary() to generate the # boundaries between parts, but can't be called again, so we capture the # result. batch_str = batch.as_string() gpe_1 = ACTIVITY_GP_EXTRAS gpe_2 = copy.deepcopy(gpe_1) gpe_2['id'] = '002' http_seq = http.HttpMockSequence( [({'status': '200'}, json.dumps({'items': [gpe_1, gpe_2]})), ({'status': '200', 'content-type': 'multipart/mixed; boundary="%s"' % batch.get_boundary()}, batch_str), ({'status': '200'}, json.dumps({'items': [gpe_1, gpe_2]})), ]) self.auth_entity.http = lambda: http_seq ase_1 = ACTIVITY_AS_EXTRAS ase_2 = copy.deepcopy(ase_1) ase_2['id'] = tag_uri('002') ase_2['object']['tags'][0]['id'] = tag_uri('002_liked_by_222') ase_2['object']['tags'][1]['id'] = tag_uri('002_shared_by_444') cache = util.CacheDict() self.assert_equals([ase_1, ase_2], self.googleplus.get_activities( fetch_replies=True, fetch_likes=True, fetch_shares=True, cache=cache)) for id in '001', '002': for prefix in 'AGL ', 'AGS ': self.assertEquals(1, cache[prefix + id]) # no new extras, so another request won't fill them in as_1 = copy.deepcopy(ACTIVITY_AS) for field in 'replies', 'plusoners', 'resharers': as_1['object'][field] = {'totalItems': 1} as_2 = copy.deepcopy(as_1) as_2['id'] = tag_uri('002') self.assert_equals([as_1, as_2], self.googleplus.get_activities( fetch_replies=True, fetch_likes=True, fetch_shares=True, cache=cache))
def encrypt_all_payloads_mime(message, gpg_to_cmdline): # Convert a plain text email into PGP/MIME attachment style. Modeled after enigmail. submsg1 = email.message.Message() submsg1.set_payload("Version: 1\n") submsg1.set_type("application/pgp-encrypted") submsg1.set_param('PGP/MIME version identification', "", 'Content-Description') submsg2 = email.message.Message() submsg2.set_type("application/octet-stream") submsg2.set_param('name', "encrypted.asc") submsg2.set_param('OpenPGP encrypted message', "", 'Content-Description') submsg2.set_param('inline', "", 'Content-Disposition') submsg2.set_param('filename', "encrypted.asc", 'Content-Disposition') if type(message.get_payload()) == str: # WTF! It seems to swallow the first line. Not sure why. Perhaps # it's skipping an imaginary blank line someplace. (ie skipping a header) # Workaround it here by prepending a blank line. # This happens only on text only messages. submsg2.set_payload("\n" + message.get_payload()) check_nested = True else: processed_payloads = generate_message_from_payloads(message) submsg2.set_payload(processed_payloads.as_string()) check_nested = False message.preamble = "This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)" # Use this just to generate a MIME boundary string. junk_msg = MIMEMultipart() junk_str = junk_msg.as_string( ) # WTF! Without this, get_boundary() will return 'None'! boundary = junk_msg.get_boundary() # This also modifies the boundary in the body of the message, ie it gets parsed. if message.has_key('Content-Type'): message.replace_header( 'Content-Type', "multipart/encrypted; protocol=\"application/pgp-encrypted\";\nboundary=\"%s\"\n" % boundary) else: message[ 'Content-Type'] = "multipart/encrypted; protocol=\"application/pgp-encrypted\";\nboundary=\"%s\"\n" % boundary return [submsg1, encrypt_payload(submsg2, gpg_to_cmdline, check_nested)]
def upload(self, filename): name = os.path.basename(filename) entry = self.get_info(name) if entry: self._delete(entry) #print "after check" size = os.path.getsize(filename) cype = guess_type(name) if not cype or not cype[0]: cype = "text/plain" else: cype = cype[0] url = "https://api.box.com/2.0/files/content" message = MIMEMultipart('mixed') # Message should not write out it's own headers. setattr(message, '_write_headers', lambda self: None) msg = MIMENonMultipart(*cype.split('/')) msg.add_header('Content-Disposition', 'form-data; name="f"; filename="%s"' % name) del msg["MIME-Version"] msg.set_payload(open(filename).read()) message.attach(msg) msg = MIMENonMultipart("text", "plain") msg.add_header('Content-Disposition', 'form-data; name="folder_id"') del msg["Content-Type"] del msg["MIME-Version"] msg.set_payload('0') message.attach(msg) body = message.as_string() #print body #return bd = message.get_boundary() hd = {"Content-Type": "multipart/form-data; boundary=%s" % bd} res = self.auth_http("POST", url, body, hd) ret = res.read() print(json.loads(ret)["entries"][0]['name'])
def bind(self, param_values, **kw_param_values): """Bind the definition to parameter values, creating a document. :return: A 2-tuple (media_type, document). """ definition = self.resolve_definition() params = definition.params(self.resource) validated_values = self.validate_param_values(params, param_values, **kw_param_values) media_type = self.media_type if media_type == 'application/x-www-form-urlencoded': doc = urlencode(sorted(validated_values.items())) elif media_type == 'multipart/form-data': outer = MIMEMultipart() outer.set_type('multipart/form-data') missing = object() for param in params: value = validated_values.get(param.name, missing) if value is not missing: if param.type == 'binary': maintype, subtype = 'application', 'octet-stream' params = {} else: maintype, subtype = 'text', 'plain' params = {'charset': 'utf-8'} inner = MIMENonMultipart(maintype, subtype, **params) inner.set_payload(value) inner['Content-Disposition'] = ('form-data; name="%s"' % param.name) outer.attach(inner) doc = str(outer) # Chop off the 'From' line, which only makes sense in an # email. (Python 3 does not include it.) if doc.startswith('From '): doc = doc[doc.find('\n') + 1:] media_type = (outer.get_content_type() + '; boundary="%s"' % outer.get_boundary()) elif media_type == 'application/json': doc = json.dumps(validated_values) else: raise ValueError("Unsupported media type: '%s'" % media_type) return media_type, doc
def bind(self, param_values, **kw_param_values): """Bind the definition to parameter values, creating a document. :return: A 2-tuple (media_type, document). """ definition = self.resolve_definition() params = definition.params(self.resource) validated_values = self.validate_param_values( params, param_values, **kw_param_values) media_type = self.media_type if media_type == 'application/x-www-form-urlencoded': doc = urlencode(sorted(validated_values.items())) elif media_type == 'multipart/form-data': outer = MIMEMultipart() outer.set_type('multipart/form-data') missing = object() for param in params: value = validated_values.get(param.name, missing) if value is not missing: if param.type == 'binary': maintype, subtype = 'application', 'octet-stream' params = {} else: maintype, subtype = 'text', 'plain' params = {'charset' : 'utf-8'} inner = MIMENonMultipart(maintype, subtype, **params) inner.set_payload(value) inner['Content-Disposition'] = ( 'form-data; name="%s"' % param.name) outer.attach(inner) doc = str(outer) # Chop off the 'From' line, which only makes sense in an # email. (Python 3 does not include it.) if doc.startswith('From '): doc = doc[doc.find('\n')+1:] media_type = (outer.get_content_type() + '; boundary="%s"' % outer.get_boundary()) elif media_type == 'application/json': doc = json.dumps(validated_values) else: raise ValueError("Unsupported media type: '%s'" % media_type) return media_type, doc
def upload(self, filename): name = os.path.basename(filename) entry = self.get_info(name) if entry: self._delete(entry) #print "after check" size = os.path.getsize(filename) cype = guess_type(name) if not cype or not cype[0]: cype = "text/plain" else: cype = cype[0] url = "https://api.box.com/2.0/files/content" message = MIMEMultipart('mixed') # Message should not write out it's own headers. setattr(message, '_write_headers', lambda self: None) msg = MIMENonMultipart(*cype.split('/')) msg.add_header('Content-Disposition', 'form-data; name="f"; filename="%s"' % name) del msg["MIME-Version"] msg.set_payload(open(filename).read()) message.attach(msg) msg = MIMENonMultipart("text", "plain") msg.add_header('Content-Disposition', 'form-data; name="folder_id"') del msg["Content-Type"] del msg["MIME-Version"] msg.set_payload('0') message.attach(msg) body = message.as_string() #print body #return bd = message.get_boundary() hd = {"Content-Type": "multipart/form-data; boundary=%s" % bd} res = self.auth_http("POST", url, body, hd) ret = res.read() print(json.loads(ret)["entries"][0]['name'])
def get_message_multipart(entries): #outer = MIMEMultipart() outer = MIMEMultipart('form-data') for entry in entries: if 'file' in entry: name = entry['name'] path = entry['file'] if not os.path.isfile(path): continue msg = get_message_file(name, path) else: name = entry['name'] value = entry['value'] msg = get_message_variable(name, value) # Set the filename parameter del msg['MIME-Version'] outer.attach(msg) #print dir(outer) del outer['MIME-Version'] content_type = outer['Content-Type'] composed = outer.as_string() #print "composed:\n", composed replstr = '' for sep in ['\n\t', ' ']: replstr = 'Content-Type: %s;%sboundary="%s"' % \ (content_type, sep, outer.get_boundary()) #print "replstr:", replstr composed = composed.replace(replstr, '') content_type = replstr composed = composed.lstrip() return content_type.split(' ', 1)[1], composed
def encrypt_all_payloads_mime( message, gpg_to_cmdline ): # Convert a plain text email into PGP/MIME attachment style. Modeled after enigmail. submsg1 = email.message.Message() submsg1.set_payload("Version: 1\n") submsg1.set_type("application/pgp-encrypted") submsg1.set_param('PGP/MIME version identification', "", 'Content-Description' ) submsg2 = email.message.Message() submsg2.set_type("application/octet-stream") submsg2.set_param('name', "encrypted.asc") submsg2.set_param('OpenPGP encrypted message', "", 'Content-Description' ) submsg2.set_param('inline', "", 'Content-Disposition' ) submsg2.set_param('filename', "encrypted.asc", 'Content-Disposition' ) if type ( message.get_payload() ) == str: # WTF! It seems to swallow the first line. Not sure why. Perhaps # it's skipping an imaginary blank line someplace. (ie skipping a header) # Workaround it here by prepending a blank line. # This happens only on text only messages. submsg2.set_payload("\n" + message.get_payload()) check_nested = True else: processed_payloads = generate_message_from_payloads(message) submsg2.set_payload(processed_payloads.as_string()) check_nested = False message.preamble = "This is an OpenPGP/MIME encrypted message (RFC 2440 and 3156)" # Use this just to generate a MIME boundary string. junk_msg = MIMEMultipart() junk_str = junk_msg.as_string() # WTF! Without this, get_boundary() will return 'None'! boundary = junk_msg.get_boundary() # This also modifies the boundary in the body of the message, ie it gets parsed. if message.has_key('Content-Type'): message.replace_header('Content-Type', "multipart/encrypted; protocol=\"application/pgp-encrypted\";\nboundary=\"%s\"\n" % boundary) else: message['Content-Type'] = "multipart/encrypted; protocol=\"application/pgp-encrypted\";\nboundary=\"%s\"\n" % boundary return [ submsg1, encrypt_payload(submsg2, gpg_to_cmdline, check_nested) ]
def upload_attachment(self, page_id, filename, file_content, **kwargs): """Uploads attachment.""" mime = MIMEMultipart('form-data') part = MIMEApplication(file_content, 'octet-stream', encode_noop) part.add_header('Content-Disposition', 'form-data', name='Filedata', filename=filename.encode('utf-8')) part.add_header('Content-Transfer-Encoding', 'binary') mime.attach(part) # we don't want mime.as_string(), because the format is different # from the one we should use in HTTP requests # all we wanted is a proper boundary string boundary = mime.get_boundary() body = '\r\n'.join([ '--' + boundary, ('Content-Disposition: form-data; name="Filedata"; ' 'filename="{}"').format(filename.encode('utf-8')), # 'MIME-Version: 1.0', 'Content-Type: application/octet-stream', # 'Content-Transfer-Encoding: binary', '', file_content, '--' + boundary + '--', '', ]) path = '/pages/%d/attachments%s' % ( page_id, self._create_argument(**kwargs)) headers = { 'Authorization': 'Basic %s' % self.auth_key, 'Content-Type': 'multipart/form-data; boundary=%s' % boundary, 'Content-Length': str(len(body)), } try: return self._request('POST', path, body, headers) except SpringnoteException as ex: if ex.status >= 300 and ex.status < 400: # it's a redirect return True # TODO
def build_mdn(message, status, **kwargs): ''' Function builds AS2 MDN report ''' try: hparser = HeaderParser() message_header = hparser.parsestr(message.headers) text = str() # check for default orginization message if message.organization.confirmation_message: text = message.organization.confirmation_message # overwrite with partner specific message if message.partner.confirmation_message: text = message.partner.confirmation_message # default message if text.strip() == '': text = _(u'The AS2 message has been processed. Thank you for exchanging AS2 messages with Pyas2.') if status != 'success': #### Send mail here as2utils.senderrorreport(message, _(u'Failure in processing message from partner,\n Basic status : %s \n Advanced Status: %s'%(kwargs['adv_status'],kwargs['status_message']))) text = _(u'The AS2 message could not be processed. The disposition-notification report has additional details.') models.Log.objects.create(message=message, status='E', text = kwargs['status_message']) message.status = 'E' else: message.status = 'S' if not message_header.get('disposition-notification-to'): models.Log.objects.create(message=message, status='S', text=_(u'MDN not requested by partner, closing request.')) return None, None models.Log.objects.create(message=message, status='S', text=_(u'Building the MDN response to the request')) main = MIMEMultipart('report', report_type="disposition-notification") textmessage = email.Message.Message() textmessage.set_payload("%s\n"%text) textmessage.set_type('text/plain') textmessage.set_charset('us-ascii') del textmessage['MIME-Version'] main.attach(textmessage) mdnbase = email.Message.Message() mdnbase.set_type('message/disposition-notification') mdnbase.set_charset('us-ascii') mdn = 'Reporting-UA: Bots Opensource EDI Translator\n' mdn = mdn + 'Original-Recipient: rfc822; %s\n'%message_header.get('as2-to') mdn = mdn + 'Final-Recipient: rfc822; %s\n'%message_header.get('as2-to') mdn = mdn + 'Original-Message-ID: <%s>\n'%message.message_id if status != 'success': mdn = mdn + 'Disposition: automatic-action/MDN-sent-automatically; processed/%s: %s\n'%(status, kwargs['adv_status']) else: mdn = mdn + 'Disposition: automatic-action/MDN-sent-automatically; processed\n' if message.mic: mdn = mdn + 'Received-content-MIC: %s\n'%message.mic mdnbase.set_payload(mdn) del mdnbase['MIME-Version'] main.attach(mdnbase) del main['MIME-Version'] pyas2init.logger.debug('MDN for message %s created:\n%s'%(message.message_id,main.as_string())) mdnsigned = False if message_header.get('disposition-notification-options') and message.organization and message.organization.signature_key: models.Log.objects.create(message=message, status='S', text=_(u'Signing the MDN using private key %s'%message.organization.signature_key)) mdnsigned = True options = message_header.get('disposition-notification-options').split(";") algorithm = options[1].split(",")[1].strip() signed = MIMEMultipart('signed', protocol="application/pkcs7-signature") signed.attach(main) micalg, signature = as2utils.sign_payload( as2utils.canonicalize(as2utils.mimetostring(main, 0)+'\n'), str(message.organization.signature_key.certificate.path), str(message.organization.signature_key.certificate_passphrase) ) pyas2init.logger.debug('Signature for MDN created:\n%s'%signature.as_string()) signed.set_param('micalg',micalg) signed.attach(signature) mdnmessage = signed else: mdnmessage = main ### Add new line between the MDN message and the signature mdnbody = as2utils.extractpayload(mdnmessage) mainboundary = '--' + main.get_boundary() + '--' mdnbody = as2utils.canonicalize(mdnbody.replace(mainboundary, mainboundary + '\n')) mdnmessage.add_header('ediint-features', 'CEM') mdnmessage.add_header('as2-from', message_header.get('as2-to')) mdnmessage.add_header('as2-to', message_header.get('as2-from')) mdnmessage.add_header('AS2-Version', '1.2') mdnmessage.add_header('date', email.Utils.formatdate(localtime=True)) mdnmessage.add_header('Message-ID', email.utils.make_msgid()) mdnmessage.add_header('user-agent', 'PYAS2, A pythonic AS2 server') filename = mdnmessage.get('message-id').strip('<>') + '.mdn' fullfilename = as2utils.storefile(pyas2init.gsettings['mdn_send_store'],filename,mdnbody,True) mdn_headers = '' for key in mdnmessage.keys(): mdn_headers = mdn_headers + '%s: %s\n'%(key, mdnmessage[key]) if message_header.get('receipt-delivery-option'): message.mdn = models.MDN.objects.create(message_id=filename, file=fullfilename, status='P', signed=mdnsigned, headers=mdn_headers, return_url= message_header['receipt-delivery-option']) message.mdn_mode = 'ASYNC' mdnbody, mdnmessage = None, None models.Log.objects.create(message=message, status='S', text=_(u'Asynchronous MDN requested, setting status to pending')) else: message.mdn = models.MDN.objects.create(message_id=filename,file=fullfilename, status='S', signed=mdnsigned, headers=mdn_headers) message.mdn_mode = 'SYNC' models.Log.objects.create(message=message, status='S', text=_(u'MDN created successfully and sent to partner')) return mdnbody, mdnmessage finally: message.save()
def execute(self, http=None): """Execute all the requests as a single batched HTTP request. Args: http: httplib2.Http, an http object to be used in place of the one the HttpRequest request object was constructed with. If one isn't supplied then use a http object from the requests in this batch. Returns: None Raises: apiclient.errors.HttpError if the response was not a 2xx. httplib2.Error if a transport error has occured. apiclient.errors.BatchError if the response is the wrong format. """ if http is None: for request_id in self._order: request, callback = self._requests[request_id] if request is not None: http = request.http break if http is None: raise ValueError("Missing a valid http object.") msgRoot = MIMEMultipart('mixed') # msgRoot should not write out it's own headers setattr(msgRoot, '_write_headers', lambda self: None) # Add all the individual requests. for request_id in self._order: request, callback = self._requests[request_id] msg = MIMENonMultipart('application', 'http') msg['Content-Transfer-Encoding'] = 'binary' msg['Content-ID'] = self._id_to_header(request_id) body = self._serialize_request(request) msg.set_payload(body) msgRoot.attach(msg) body = msgRoot.as_string() headers = {} headers['content-type'] = ('multipart/mixed; ' 'boundary="%s"') % msgRoot.get_boundary() resp, content = http.request(self._batch_uri, 'POST', body=body, headers=headers) if resp.status >= 300: raise HttpError(resp, content, self._batch_uri) # Now break up the response and process each one with the correct postproc # and trigger the right callbacks. boundary, _ = content.split(None, 1) # Prepend with a content-type header so FeedParser can handle it. header = 'content-type: %s\r\n\r\n' % resp['content-type'] for_parser = header + content parser = FeedParser() parser.feed(for_parser) respRoot = parser.close() if not respRoot.is_multipart(): raise BatchError("Response not in multipart/mixed format.", resp, content) parts = respRoot.get_payload() for part in parts: request_id = self._header_to_id(part['Content-ID']) headers, content = self._deserialize_response(part.get_payload()) # TODO(jcgregorio) Remove this temporary hack once the server stops # gzipping individual response bodies. if content[0] != '{': gzipped_content = content content = gzip.GzipFile( fileobj=StringIO.StringIO(gzipped_content)).read() request, cb = self._requests[request_id] postproc = request.postproc response = postproc(resp, content) if cb is not None: cb(request_id, response) if self._callback is not None: self._callback(request_id, response)
def composeEmail(path): outer = MIMEMultipart() #Writing FROM content extractedContent = "\n" fileHandler = open(path + os.sep + "From") fromName = str(fileHandler.readline()).replace("\n", "") fromEmail = str(fileHandler.readline()).replace("\n", "") if str(fromEmail).find("@") != -1: extractedContent += "From: \"%(fromName)s\" <%(fromEmail)s> \n" % locals() else: extractedContent += "From: \"%(fromName)s\" \n" % locals() fileHandler.close() #outer['From'] = extractedContent #Writing Sender content if os.path.exists(path + os.sep + "Sender"): fileHandler = open(path + os.sep + "Sender") senderName = str(fileHandler.readline()).replace("\n", "") senderEmail = str(fileHandler.readline()).replace("\n", "") extractedContent += "Sender: \"%(senderName)s\" <%(senderEmail)s> \n" % locals() fileHandler.close() #Writing TO content dirPath = path + os.sep + "To" + os.sep dirList = os.listdir(dirPath) toContacts = "To: " for file in dirList: if str(file).find(FSTemplates._suffix) == -1: fileHandler = open(dirPath + file) toName = str(fileHandler.readline()).replace("\n", "") toEmail = str(fileHandler.readline()).replace("\n", "") if str(toEmail).find("@") != -1: toContacts += "\"%(toName)s\" <%(toEmail)s>, " % locals() else: toContacts += "\"%(toName)s\", " % locals() fileHandler.close() #outer['To'] = toContacts[:-2] extractedContent += toContacts[:-2] + "\n" #Writing CC content dirPath = path + os.sep + "Cc" + os.sep if os.path.exists(dirPath): dirList = os.listdir(dirPath) ccContacts = "Cc: " for file in dirList: print dirPath + file if str(file).find(FSTemplates._suffix) == -1: fileHandler = open(dirPath + file) ccName = str(fileHandler.readline()).replace("\n", "") ccEmail = str(fileHandler.readline()).replace("\n", "") if str(ccEmail).find("@") != -1: ccContacts += "\"%(ccName)s\" <%(ccEmail)s>, " % locals() else: ccContacts += "\"%(ccName)s\", " % locals() fileHandler.close() #outer['Cc'] = ccContacts[:-2] + "\n" extractedContent += ccContacts[:-2] + "\n" #Writing BCC content dirPath = path + os.sep + "Bcc" + os.sep if os.path.exists(dirPath): dirList = os.listdir(dirPath) bccContacts = "Bcc" for file in dirList: if str(file).find(FSTemplates._suffix) == -1: fileHandler = open(dirPath + file) bccName = str(fileHandler.readline()).replace("\n", "") bccEmail = str(fileHandler.readline()).replace("\n", "") if str(bccEmail).find("@") != -1: bccContacts += "\"%(bccName)s\" <%(bccEmail)s>, " % locals() else: bccContacts += "\"%(bccName)s\", " % locals() fileHandler.close() #outer['Bcc'] = bccContacts[:-2] + "\n" extractedContent += bccContacts[:-2] + "\n" #Writing SUBJECT content fileHandler = open(path + os.sep + "Subject") #outer['Subject'] = fileHandler.readline() extractedContent += ("Subject: " + fileHandler.readline() + "\n") fileHandler.close() #Writing DATE content to EML file if os.path.exists(path + os.sep + "Date"): fileHandler = open(path + os.sep + "Date") #outer['Date'] = fileHandler.readline() extractedContent += ("Date: " + fileHandler.readline() + "\n") fileHandler.close() else: fileHandler = open(path + os.sep + "Date", TransformerTemplate.fileWrite) fileHandler.write(time.strftime("%a, %d %b %Y %H:%M:%S %Z")) fileHandler.close() #Writing MESSAGE ID content if os.path.exists(path + os.sep + "Message-ID"): fileHandler = open(path + os.sep + "Message-ID") #outer['Message-ID'] = fileHandler.readline() extractedContent += ("Message-ID: " + fileHandler.readline() + "\n") fileHandler.close() #Writing Body content fileHandler = open(path + os.sep + "Body") #outer['Body'] = fileHandler.read() body = "\n" + fileHandler.read() fileHandler.close() #Writing Attachment(s) dirPath = path + os.sep + "Attachments" + os.sep if os.path.exists(dirPath): for filename in os.listdir(dirPath): path = os.path.join(dirPath, filename) #print "Attachment Path: " + path if not os.path.isfile(path): #print "Continuing ..." continue # Guess the content type based on the file's extension. Encoding # will be ignored, although we should check for simple things like # gzip'd or compressed files. ctype, encoding = mimetypes.guess_type(path) #print "Attachment encoding: " + encoding if ctype is None or encoding is not None: # No guess could be made, or the file is encoded (compressed), so # use a generic bag-of-bits type. ctype = 'application/octet-stream' maintype, subtype = ctype.split('/', 1) #print "Processing file ..." if maintype == 'text': fp = open(path) # Note: we should handle calculating the charset msg = MIMEText(fp.read(), _subtype=subtype) fp.close() elif maintype == 'image': fp = open(path, 'rb') msg = MIMEImage(fp.read(), _subtype=subtype) fp.close() elif maintype == 'audio': fp = open(path, 'rb') msg = MIMEAudio(fp.read(), _subtype=subtype) fp.close() else: fp = open(path, 'rb') msg = MIMEBase(maintype, subtype) msg.set_payload(fp.read()) fp.close() # Encode the payload using Base64 encoders.encode_base64(msg) # Set the filename parameter msg.add_header('Content-Disposition', 'attachment', filename=filename) #print "Message Length: " + str(len(msg.as_string())) outer.attach(msg) #print "Attachment Length: " + str(len(outer.as_string())) # Now return the message extractedAttachment = outer.as_string() boundary = str(outer.get_boundary()) mimePosition = str(extractedAttachment).find("MIME-Version: ") insertionPoint = str(extractedAttachment[mimePosition:]).find("\n") composedMessage = extractedAttachment[:mimePosition + insertionPoint] + extractedContent composedMessage += "\n--" + boundary + "\n" composedMessage += "Content-Type: text/plain; charset=UTF-8" composedMessage += body composedMessage += "\n--" + boundary + "--\n" if not (len(extractedAttachment[mimePosition + insertionPoint+1:]) <= (len(2*boundary)+9) and len(extractedAttachment[mimePosition + insertionPoint+1:]) >= (len(2*boundary)+6)): composedMessage += extractedAttachment[mimePosition + insertionPoint+1:] #print composedMessage #str(composedMessage).replace("Body: ", "\n") return composedMessage
def method(self, **kwargs): # Don't bother with doc string, it will be over-written by createMethod. for name in six.iterkeys(kwargs): if name not in parameters.argmap: raise TypeError('Got an unexpected keyword argument "%s"' % name) # Remove args that have a value of None. keys = list(kwargs.keys()) for name in keys: if kwargs[name] is None: del kwargs[name] for name in parameters.required_params: if name not in kwargs: # temporary workaround for non-paging methods incorrectly requiring # page token parameter (cf. drive.changes.watch vs. drive.changes.list) if name not in _PAGE_TOKEN_NAMES or _findPageTokenName( _methodProperties(methodDesc, schema, 'response')): raise TypeError('Missing required parameter "%s"' % name) for name, regex in six.iteritems(parameters.pattern_params): if name in kwargs: if isinstance(kwargs[name], six.string_types): pvalues = [kwargs[name]] else: pvalues = kwargs[name] for pvalue in pvalues: if re.match(regex, pvalue) is None: raise TypeError( 'Parameter "%s" value "%s" does not match the pattern "%s"' % (name, pvalue, regex)) for name, enums in six.iteritems(parameters.enum_params): if name in kwargs: # We need to handle the case of a repeated enum # name differently, since we want to handle both # arg='value' and arg=['value1', 'value2'] if (name in parameters.repeated_params and not isinstance(kwargs[name], six.string_types)): values = kwargs[name] else: values = [kwargs[name]] for value in values: if value not in enums: raise TypeError( 'Parameter "%s" value "%s" is not an allowed value in "%s"' % (name, value, str(enums))) actual_query_params = {} actual_path_params = {} for key, value in six.iteritems(kwargs): to_type = parameters.param_types.get(key, 'string') # For repeated parameters we cast each member of the list. if key in parameters.repeated_params and type(value) == type([]): cast_value = [_cast(x, to_type) for x in value] else: cast_value = _cast(value, to_type) if key in parameters.query_params: actual_query_params[parameters.argmap[key]] = cast_value if key in parameters.path_params: actual_path_params[parameters.argmap[key]] = cast_value body_value = kwargs.get('body', None) media_filename = kwargs.get('media_body', None) media_mime_type = kwargs.get('media_mime_type', None) if self._developerKey: actual_query_params['key'] = self._developerKey model = self._model if methodName.endswith('_media'): model = MediaModel() elif 'response' not in methodDesc: model = RawModel() headers = {} headers, params, query, body = model.request(headers, actual_path_params, actual_query_params, body_value) expanded_url = uritemplate.expand(pathUrl, params) url = _urljoin(self._baseUrl, expanded_url + query) resumable = None multipart_boundary = '' if media_filename: # Ensure we end up with a valid MediaUpload object. if isinstance(media_filename, six.string_types): if media_mime_type is None: logger.warning( 'media_mime_type argument not specified: trying to auto-detect for %s', media_filename) media_mime_type, _ = mimetypes.guess_type(media_filename) if media_mime_type is None: raise UnknownFileType(media_filename) if not mimeparse.best_match([media_mime_type], ','.join(accept)): raise UnacceptableMimeTypeError(media_mime_type) media_upload = MediaFileUpload(media_filename, mimetype=media_mime_type) elif isinstance(media_filename, MediaUpload): media_upload = media_filename else: raise TypeError('media_filename must be str or MediaUpload.') # Check the maxSize if media_upload.size( ) is not None and media_upload.size() > maxSize > 0: raise MediaUploadSizeError("Media larger than: %s" % maxSize) # Use the media path uri for media uploads expanded_url = uritemplate.expand(mediaPathUrl, params) url = _urljoin(self._baseUrl, expanded_url + query) if media_upload.resumable(): url = _add_query_parameter(url, 'uploadType', 'resumable') if media_upload.resumable(): # This is all we need to do for resumable, if the body exists it gets # sent in the first request, otherwise an empty body is sent. resumable = media_upload else: # A non-resumable upload if body is None: # This is a simple media upload headers['content-type'] = media_upload.mimetype() body = media_upload.getbytes(0, media_upload.size()) url = _add_query_parameter(url, 'uploadType', 'media') else: # This is a multipart/related upload. msgRoot = MIMEMultipart('related') # msgRoot should not write out it's own headers setattr(msgRoot, '_write_headers', lambda self: None) # attach the body as one part msg = MIMENonMultipart(*headers['content-type'].split('/')) msg.set_payload(body) msgRoot.attach(msg) # attach the media as the second part msg = MIMENonMultipart(*media_upload.mimetype().split('/')) msg['Content-Transfer-Encoding'] = 'binary' payload = media_upload.getbytes(0, media_upload.size()) msg.set_payload(payload) msgRoot.attach(msg) # encode the body: note that we can't use `as_string`, because # it plays games with `From ` lines. fp = BytesIO() g = _BytesGenerator(fp, mangle_from_=False) g.flatten(msgRoot, unixfrom=False) body = fp.getvalue() multipart_boundary = msgRoot.get_boundary() headers['content-type'] = ( 'multipart/related; ' 'boundary="%s"') % multipart_boundary url = _add_query_parameter(url, 'uploadType', 'multipart') logger.info('URL being requested: %s %s' % (httpMethod, url)) return self._requestBuilder(self._http, model.response, url, method=httpMethod, body=body, headers=headers, methodId=methodId, resumable=resumable)
def build_mdn(message, status, **kwargs): """ Function builds AS2 MDN report """ try: hparser = HeaderParser() message_header = hparser.parsestr(message.headers) text = _(u"The AS2 message has been processed. Thank you for exchanging AS2 messages with Pyas2.") if status != "success": #### Send mail here as2utils.senderrorreport( message, _( u"Failure in processing message from partner,\n Basic status : %s \n Advanced Status: %s" % (kwargs["adv_status"], kwargs["status_message"]) ), ) text = _( u"The AS2 message could not be processed. The disposition-notification report has additional details." ) models.Log.objects.create(message=message, status="E", text=kwargs["status_message"]) message.status = "E" else: message.status = "S" if not message_header.get("disposition-notification-to"): models.Log.objects.create( message=message, status="S", text=_(u"MDN not requested by partner, closing request.") ) return None, None models.Log.objects.create(message=message, status="S", text=_(u"Building the MDN response to the request")) main = MIMEMultipart("report", report_type="disposition-notification") textmessage = email.Message.Message() textmessage.set_payload("%s\n" % text) textmessage.set_type("text/plain") textmessage.set_charset("us-ascii") del textmessage["MIME-Version"] main.attach(textmessage) mdnbase = email.Message.Message() mdnbase.set_type("message/disposition-notification") mdnbase.set_charset("us-ascii") mdn = "Reporting-UA: Bots Opensource EDI Translator\n" mdn = mdn + "Original-Recipient: rfc822; %s\n" % message_header.get("as2-to") mdn = mdn + "Final-Recipient: rfc822; %s\n" % message_header.get("as2-to") mdn = mdn + "Original-Message-ID: <%s>\n" % message.message_id if status != "success": mdn = mdn + "Disposition: automatic-action/MDN-sent-automatically; processed/%s: %s\n" % ( status, kwargs["adv_status"], ) else: mdn = mdn + "Disposition: automatic-action/MDN-sent-automatically; processed\n" if message.mic: mdn = mdn + "Received-content-MIC: %s\n" % message.mic mdnbase.set_payload(mdn) del mdnbase["MIME-Version"] main.attach(mdnbase) del main["MIME-Version"] pyas2init.logger.debug("MDN for message %s created:\n%s" % (message.message_id, main.as_string())) mdnsigned = False if ( message_header.get("disposition-notification-options") and message.organization and message.organization.signature_key ): models.Log.objects.create( message=message, status="S", text=_(u"Signing the MDN using private key %s" % message.organization.signature_key), ) mdnsigned = True options = message_header.get("disposition-notification-options").split(";") algorithm = options[1].split(",")[1].strip() signed = MIMEMultipart("signed", protocol="application/pkcs7-signature") signed.attach(main) micalg, signature = as2utils.sign_payload( as2utils.canonicalize(as2utils.mimetostring(main, 0) + "\n"), str(message.organization.signature_key.certificate.path), str(message.organization.signature_key.certificate_passphrase), ) pyas2init.logger.debug("Signature for MDN created:\n%s" % signature.as_string()) signed.set_param("micalg", micalg) signed.attach(signature) mdnmessage = signed else: mdnmessage = main ### Add new line between the MDN message and the signature mdnbody = as2utils.extractpayload(mdnmessage) mainboundary = "--" + main.get_boundary() + "--" mdnbody = as2utils.canonicalize(mdnbody.replace(mainboundary, mainboundary + "\n")) mdnmessage.add_header("ediint-features", "CEM") mdnmessage.add_header("as2-from", message_header.get("as2-to")) mdnmessage.add_header("as2-to", message_header.get("as2-from")) mdnmessage.add_header("AS2-Version", "1.2") mdnmessage.add_header("date", email.Utils.formatdate(localtime=True)) mdnmessage.add_header("Message-ID", email.utils.make_msgid()) mdnmessage.add_header("user-agent", "PYAS2, A pythonic AS2 server") filename = mdnmessage.get("message-id").strip("<>") + ".mdn" fullfilename = as2utils.storefile(pyas2init.gsettings["mdn_send_store"], filename, mdnbody, True) mdn_headers = "" for key in mdnmessage.keys(): mdn_headers = mdn_headers + "%s: %s\n" % (key, mdnmessage[key]) if message_header.get("receipt-delivery-option"): message.mdn = models.MDN.objects.create( message_id=filename, file=fullfilename, status="P", signed=mdnsigned, headers=mdn_headers, return_url=message_header["receipt-delivery-option"], ) message.mdn_mode = "ASYNC" mdnbody, mdnmessage = None, None models.Log.objects.create( message=message, status="S", text=_(u"Asynchronous MDN requested, setting status to pending") ) else: message.mdn = models.MDN.objects.create( message_id=filename, file=fullfilename, status="S", signed=mdnsigned, headers=mdn_headers ) message.mdn_mode = "SYNC" models.Log.objects.create( message=message, status="S", text=_(u"MDN created successfully and sent to partner") ) return mdnbody, mdnmessage finally: message.save()
def build_mdn(message, status, **kwargs): """ Function builds AS2 MDN report for the received message. Takes message status as input and returns the mdn content.""" try: # Initialize variables mdn_body, mdn_message = None, None # Set the confirmation text message here confirmation_text = str() if message.organization and message.organization.confirmation_message: confirmation_text = message.organization.confirmation_message # overwrite with partner specific message if message.partner and message.partner.confirmation_message: confirmation_text = message.partner.confirmation_message # default message if confirmation_text.strip() == '': confirmation_text = _( u'The AS2 message has been processed. ' u'Thank you for exchanging AS2 messages with Pyas2.') # Update message status and send mail here based on the created MDN if status != 'success': as2utils.senderrorreport( message, _(u'Failure in processing message from partner,\n ' u'Basic status : %s \n Advanced Status: %s' % (kwargs['adv_status'], kwargs['status_message']))) confirmation_text = _( u'The AS2 message could not be processed. ' u'The disposition-notification report has additional details.') models.Log.objects.create(message=message, status='E', text=kwargs['status_message']) message.status = 'E' message.adv_status = kwargs['status_message'] else: message.status = 'S' # In case no MDN is requested exit from process header_parser = HeaderParser() message_header = header_parser.parsestr(message.headers) if not message_header.get('disposition-notification-to'): models.Log.objects.create( message=message, status='S', text=_(u'MDN not requested by partner, closing request.')) return mdn_body, mdn_message # Build the MDN report models.Log.objects.create( message=message, status='S', text=_(u'Building the MDN response to the request')) mdn_report = MIMEMultipart('report', report_type="disposition-notification") # Build the text message with confirmation text and add to report mdn_text = email.Message.Message() mdn_text.set_payload("%s\n" % confirmation_text) mdn_text.set_type('text/plain') mdn_text.set_charset('us-ascii') del mdn_text['MIME-Version'] mdn_report.attach(mdn_text) # Build the MDN message and add to report mdn_base = email.Message.Message() mdn_base.set_type('message/disposition-notification') mdn_base.set_charset('us-ascii') mdn = 'Reporting-UA: Bots Opensource EDI Translator\n' mdn += 'Original-Recipient: rfc822; %s\n' % message_header.get( 'as2-to') mdn += 'Final-Recipient: rfc822; %s\n' % message_header.get('as2-to') mdn += 'Original-Message-ID: <%s>\n' % message.message_id if status != 'success': mdn += 'Disposition: automatic-action/MDN-sent-automatically; ' \ 'processed/%s: %s\n' % (status, kwargs['adv_status']) else: mdn += 'Disposition: automatic-action/MDN-sent-automatically; processed\n' if message.mic: mdn += 'Received-content-MIC: %s\n' % message.mic mdn_base.set_payload(mdn) del mdn_base['MIME-Version'] mdn_report.attach(mdn_base) del mdn_report['MIME-Version'] # If signed MDN is requested by partner then sign the MDN and attach to report pyas2init.logger.debug('MDN for message %s created:\n%s' % (message.message_id, mdn_report.as_string())) mdn_signed = False if message_header.get('disposition-notification-options') and message.organization \ and message.organization.signature_key: models.Log.objects.create( message=message, status='S', text=_(u'Signing the MDN using private key {0:s}'.format( message.organization.signature_key))) mdn_signed = True # options = message_header.get('disposition-notification-options').split(";") # algorithm = options[1].split(",")[1].strip() signed_report = MIMEMultipart( 'signed', protocol="application/pkcs7-signature") signed_report.attach(mdn_report) mic_alg, signature = as2utils.sign_payload( as2utils.canonicalize( as2utils.mimetostring(mdn_report, 0) + '\n'), str(message.organization.signature_key.certificate.path), str(message.organization.signature_key.certificate_passphrase)) pyas2init.logger.debug('Signature for MDN created:\n%s' % signature.as_string()) signed_report.set_param('micalg', mic_alg) signed_report.attach(signature) mdn_message = signed_report else: mdn_message = mdn_report # Extract the MDN boy from the mdn message. # Add new line between the MDN message and the signature, # Found that without this MDN signature verification fails on Mendelson AS2 main_boundary = '--' + mdn_report.get_boundary() + '--' mdn_body = as2utils.canonicalize( as2utils.extractpayload(mdn_message).replace( main_boundary, main_boundary + '\n')) # Add the relevant headers to the MDN message mdn_message.add_header('ediint-features', 'CEM') mdn_message.add_header('as2-from', message_header.get('as2-to')) mdn_message.add_header('as2-to', message_header.get('as2-from')) mdn_message.add_header('AS2-Version', '1.2') mdn_message.add_header('date', email.Utils.formatdate(localtime=True)) mdn_message.add_header('Message-ID', email.utils.make_msgid()) mdn_message.add_header('user-agent', 'PYAS2, A pythonic AS2 server') # Save the MDN to the store filename = mdn_message.get('message-id').strip('<>') + '.mdn' full_filename = as2utils.storefile( pyas2init.gsettings['mdn_send_store'], filename, mdn_body, True) # Extract the MDN headers as string mdn_headers = '' for key in mdn_message.keys(): mdn_headers += '%s: %s\n' % (key, mdn_message[key]) # Is Async mdn is requested mark MDN as pending and return None if message_header.get('receipt-delivery-option'): message.mdn = models.MDN.objects.create( message_id=filename, file=full_filename, status='P', signed=mdn_signed, headers=mdn_headers, return_url=message_header['receipt-delivery-option']) message.mdn_mode = 'ASYNC' mdn_body, mdn_message = None, None models.Log.objects.create( message=message, status='S', text=_( u'Asynchronous MDN requested, setting status to pending')) # Else mark MDN as sent and return the MDN message else: message.mdn = models.MDN.objects.create(message_id=filename, file=full_filename, status='S', signed=mdn_signed, headers=mdn_headers) message.mdn_mode = 'SYNC' models.Log.objects.create( message=message, status='S', text=_(u'MDN created successfully and sent to partner')) return mdn_body, mdn_message finally: message.save()
def method(self, **kwargs): for name in six.iterkeys(kwargs): if name not in argmap: raise TypeError('Got an unexpected keyword argument "%s"' % name) for name in required_params: if name not in kwargs: raise TypeError('Missing required parameter "%s"' % name) for name, regex in six.iteritems(pattern_params): if name in kwargs: if isinstance(kwargs[name], six.string_types): pvalues = [kwargs[name]] else: pvalues = kwargs[name] for pvalue in pvalues: if re.match(regex, pvalue) is None: raise TypeError( 'Parameter "%s" value "%s" does not match the pattern "%s"' % (name, pvalue, regex)) for name, enums in six.iteritems(enum_params): if name in kwargs: if kwargs[name] not in enums: raise TypeError( 'Parameter "%s" value "%s" is not an allowed value in "%s"' % (name, kwargs[name], str(enums))) actual_query_params = {} actual_path_params = {} for key, value in six.iteritems(kwargs): to_type = param_type.get(key, 'string') # For repeated parameters we cast each member of the list. if key in repeated_params and type(value) == type([]): cast_value = [_cast(x, to_type) for x in value] else: cast_value = _cast(value, to_type) if key in query_params: actual_query_params[argmap[key]] = cast_value if key in path_params: actual_path_params[argmap[key]] = cast_value body_value = kwargs.get('body', None) media_filename = kwargs.get('media_body', None) if self._developerKey: actual_query_params['key'] = self._developerKey model = self._model # If there is no schema for the response then presume a binary blob. if 'response' not in methodDesc: model = RawModel() headers = {} headers, params, query, body = model.request(headers, actual_path_params, actual_query_params, body_value) expanded_url = uritemplate.expand(pathUrl, params) url = six.moves.urllib.parse.urljoin(self._baseUrl, expanded_url + query) resumable = None multipart_boundary = '' if media_filename: # Ensure we end up with a valid MediaUpload object. if isinstance(media_filename, six.string_types): (media_mime_type, encoding) = mimetypes.guess_type(media_filename) if media_mime_type is None: raise UnknownFileType(media_filename) if not mimeparse.best_match([media_mime_type], ','.join(accept)): raise UnacceptableMimeTypeError(media_mime_type) media_upload = MediaFileUpload(media_filename, media_mime_type) elif isinstance(media_filename, MediaUpload): media_upload = media_filename else: raise TypeError('media_filename must be str or MediaUpload.') # Check the maxSize if maxSize > 0 and media_upload.size() > maxSize: raise MediaUploadSizeError("Media larger than: %s" % maxSize) # Use the media path uri for media uploads if media_upload.resumable(): expanded_url = uritemplate.expand(mediaResumablePathUrl, params) else: expanded_url = uritemplate.expand(mediaPathUrl, params) url = six.moves.urllib.parse.urljoin(self._baseUrl, expanded_url + query) if media_upload.resumable(): # This is all we need to do for resumable, if the body exists it gets # sent in the first request, otherwise an empty body is sent. resumable = media_upload else: # A non-resumable upload if body is None: # This is a simple media upload headers['content-type'] = media_upload.mimetype() body = media_upload.getbytes(0, media_upload.size()) else: # This is a multipart/related upload. msgRoot = MIMEMultipart('related') # msgRoot should not write out it's own headers setattr(msgRoot, '_write_headers', lambda self: None) # attach the body as one part msg = MIMENonMultipart(*headers['content-type'].split('/')) msg.set_payload(body) msgRoot.attach(msg) # attach the media as the second part msg = MIMENonMultipart(*media_upload.mimetype().split('/')) msg['Content-Transfer-Encoding'] = 'binary' payload = media_upload.getbytes(0, media_upload.size()) msg.set_payload(payload) msgRoot.attach(msg) body = msgRoot.as_string() multipart_boundary = msgRoot.get_boundary() headers['content-type'] = ('multipart/related; ' 'boundary="%s"') % multipart_boundary logging.info('URL being requested: %s' % url) return self._requestBuilder(self._http, model.response, url, method=httpMethod, body=body, headers=headers, methodId=methodId, resumable=resumable)
def apply_mtom(headers, envelope, params, paramvals): ''' Apply MTOM to a SOAP envelope, separating attachments into a MIME multipart message. References: XOP http://www.w3.org/TR/xop10/ MTOM http://www.w3.org/TR/soap12-mtom/ http://www.w3.org/Submission/soap11mtom10/ @param headers Headers dictionary of the SOAP message that would originally be sent. @param envelope SOAP envelope string that would have originally been sent. @param params params attribute from the Message object used for the SOAP @param paramvals values of the params, passed to Message.to_xml @return tuple of length 2 with dictionary of headers and string of body that can be sent with HTTPConnection ''' # grab the XML element of the message in the SOAP body soapmsg = StringIO(envelope) soaptree = ElementTree.parse(soapmsg) soapns = soaptree.getroot().tag.split('}')[0].strip('{') soapbody = soaptree.getroot().find("{%s}Body" % soapns) message = None for child in list(soapbody): if child.tag != "%sFault" % (soapns, ): message = child break # Get additional parameters from original Content-Type ctarray = [] for n, v in headers.items(): if n.lower() == 'content-type': ctarray = v.split(';') break roottype = ctarray[0].strip() rootparams = {} for ctparam in ctarray[1:]: n, v = ctparam.strip().split('=') rootparams[n] = v.strip("\"'") # Set up initial MIME parts mtompkg = MIMEMultipart('related', boundary='?//<><>soaplib_MIME_boundary<>') rootpkg = None try: rootpkg = MIMEApplication(envelope, 'xop+xml', encode_7or8bit) except NameError: rootpkg = MIMENonMultipart("application", "xop+xml") rootpkg.set_payload(envelope) encode_7or8bit(rootpkg) # Set up multipart headers. del (mtompkg['mime-version']) mtompkg.set_param('start-info', roottype) mtompkg.set_param('start', '<soaplibEnvelope>') if 'SOAPAction' in headers: mtompkg.add_header('SOAPAction', headers.get('SOAPAction')) # Set up root SOAP part headers. del (rootpkg['mime-version']) rootpkg.add_header('Content-ID', '<soaplibEnvelope>') for n, v in rootparams.items(): rootpkg.set_param(n, v) rootpkg.set_param('type', roottype) mtompkg.attach(rootpkg) # Extract attachments from SOAP envelope. for i in range(len(params)): name, typ = params[i] if typ == Attachment: id = "soaplibAttachment_%s" % (len(mtompkg.get_payload()), ) param = message[i] param.text = "" incl = create_xml_subelement( param, "{http://www.w3.org/2004/08/xop/include}Include") incl.attrib["href"] = "cid:%s" % id if paramvals[i].fileName and not paramvals[i].data: paramvals[i].load_from_file() data = paramvals[i].data attachment = None try: attachment = MIMEApplication(data, _encoder=encode_7or8bit) except NameError: attachment = MIMENonMultipart("application", "octet-stream") attachment.set_payload(data) encode_7or8bit(attachment) del (attachment['mime-version']) attachment.add_header('Content-ID', '<%s>' % (id, )) mtompkg.attach(attachment) # Update SOAP envelope. soapmsg.close() soapmsg = StringIO() soaptree.write(soapmsg) rootpkg.set_payload(soapmsg.getvalue()) soapmsg.close() # extract body string from MIMEMultipart message bound = '--%s' % (mtompkg.get_boundary(), ) marray = mtompkg.as_string().split(bound) mtombody = bound mtombody += bound.join(marray[1:]) # set Content-Length mtompkg.add_header("Content-Length", str(len(mtombody))) # extract dictionary of headers from MIMEMultipart message mtomheaders = {} for name, value in mtompkg.items(): mtomheaders[name] = value if len(mtompkg.get_payload()) <= 1: return (headers, envelope) return (mtomheaders, mtombody)
def attach(self, pagepath, filename, description=None, replace=False): """Attaches a file to a wiki page. Parameters ---------- pagepath : :class:`str` Wiki page to attach to. filename : :class:`str` or :func:`tuple` Name of the file to attach. If a tuple is passed, the first item should be the name of the file, & the second item should be the data that the file should contain. description : :class:`str`, optional If supplied, this description will be added as a comment on the attachment. replace : :class:`bool`, optional Set this to ``True`` if the file is replacing an existing file. """ from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from httplib import HTTPConnection, HTTPSConnection from os.path import basename if self._realm is not None: response = self.opener.open(self.url + "/attachment/wiki/" + pagepath + "/?action=new") if self._debug: print(response.info()) print(response.read()) response.close() # # Read and examine the file # if isinstance(filename, tuple): fname = basename(filename[0]) fbytes = filename[1] else: with open(filename, 'r') as f: fbytes = f.read() fname = basename(filename) # # Create the mime sections to hold the form data # postdata = MIMEMultipart('form-data', boundary='TracRemoteAttachmentBoundary') postdict = {'__FORM_TOKEN': self._form_token, 'action': 'new', 'realm': 'wiki', 'id': pagepath, } if description is not None: postdict['description'] = description if replace: postdict['replace'] = 'on' for k in postdict: mime = MIMEText(postdict[k]) mime['Content-Disposition'] = 'form-data; name="{0}"'.format(k) del mime['MIME-Version'] del mime['Content-Type'] del mime['Content-Transfer-Encoding'] postdata.attach(mime) del postdata['MIME-Version'] body = postdata.as_string().split('\n') # # We have to hack the boundary definition because Apache's mod_security # considers " and = to be invalid characters and will reject POST # requests. To eliminate the = characters we explicitly set the # boundary string above. # body[0] = body[0].replace('"', '') if self._debug: print(body) # # Create a separate mime section for the file by hand. # payload = ['--'+postdata.get_boundary()] payload.append(('Content-Disposition: form-data; ' + 'name="attachment"; ' + 'filename="{0}"').format(unquote(fname))) payload.append('Content-type: application/octet-stream') payload.append('') payload.append(fbytes) payload.append('--'+postdata.get_boundary()+'--') content_header = (body[0]+body[1]).split(': ')[1] if self._debug: print(content_header) crlf_body = '\r\n'.join(body[2:len(body)-2] + payload)+'\r\n' if self._debug: print(crlf_body) # # Have to use a raw httplib connection becuase urlopen will # try to encode the data as application/x-www-form-urlencoded # if 'https' in self.url: if self._debug: HTTPSConnection.debuglevel = 1 hostname = self.url[self.url.index('//')+2:] if hostname.find('/') > 0: foo = hostname.split('/') hostname = foo[0] extra = '/'+'/'.join(foo[1:]) else: extra = '' http = HTTPSConnection(hostname) else: if self._debug: HTTPConnection.debuglevel = 1 hostname = self.url[self.url.index('//')+2:] if hostname.find('/') > 0: foo = hostname.split('/') hostname = foo[0] extra = '/'+'/'.join(foo[1:]) else: extra = '' http = HTTPConnection(hostname) headers = {'Cookie': '; '.join(['='.join(c) for c in self._cookies]), 'Content-Type': content_header} if self._realm is not None: auth_handler = 0 while not isinstance(self.opener.handlers[auth_handler], HTTPDigestAuthHandler): auth_handler += 1 req = Request((self.url + "/attachment/wiki/" + pagepath + "/?action=new"), 'foo=bar') req_data = {'realm': self._realm, 'nonce': self.opener.handlers[auth_handler].last_nonce, 'qop': 'auth'} auth_string = self.opener.handlers[auth_handler].get_authorization( req, req_data) if self._debug: print(auth_string) # http.putheader('Authorization', 'Digest '+auth_string) headers['Authorization'] = 'Digest ' + auth_string # print("ACTUAL Content-Length: {0:d}".format(len(crlf_body))) http.request('POST', extra+"/attachment/wiki/"+pagepath+"/?action=new", crlf_body, headers) # # If successful, the initial response should be a redirect. # response = http.getresponse() if self._debug: print(response.getheaders()) print(response.status) print(response.read()) http.close() return
text = lsb_release + '\n' + '\n'.join(env) attachment = MIMEText(text, _charset='UTF-8') attachment.add_header('Content-Disposition', 'inline') message = MIMEMultipart() message.add_header('Tags', 'snap') message.attach(attachment) blob = message.as_string().encode('UTF-8') url = 'https://{}/+storeblob'.format(LAUNCHPAD) data = MIMEMultipart() submit = MIMEText('1') submit.add_header('Content-Disposition', 'form-data; name="FORM_SUBMIT"') data.attach(submit) form_blob = MIMEBase('application', 'octet-stream') form_blob.add_header('Content-Disposition', 'form-data; name="field.blob"; filename="x"') form_blob.set_payload(blob.decode('ascii')) data.attach(form_blob) data_flat = BytesIO() gen = BytesGenerator(data_flat, mangle_from_=False) gen.flatten(data) request = Request(url, data_flat.getvalue()) request.add_header('Content-Type', 'multipart/form-data; boundary=' + data.get_boundary()) opener = build_opener(HTTPSHandler) result = opener.open(request) handle = result.info().get('X-Launchpad-Blob-Token') summary = '[snap] SUMMARY HERE'.encode('UTF-8') params = urlencode({'field.title': summary}) filebug_url = 'https://bugs.{}/ubuntu/+source/libreoffice/+filebug/{}?{}' subprocess.run(["xdg-open", filebug_url.format(LAUNCHPAD, handle, params)])
class Mdn(object): """Class for handling AS2 MDNs. Includes functions for both parsing and building them. """ def __init__(self, mdn_mode=None, digest_alg=None, mdn_url=None): self.message_id = None self.orig_message_id = None self.payload = None self.mdn_mode = mdn_mode self.digest_alg = digest_alg self.mdn_url = mdn_url @property def content(self): """Function returns the body of the mdn message as a byte string""" if self.payload: message_bytes = mime_to_bytes( self.payload, 0).replace(b'\n', b'\r\n') boundary = b'--' + self.payload.get_boundary().encode('utf-8') temp = message_bytes.split(boundary) temp.pop(0) return boundary + boundary.join(temp) else: return '' @property def headers(self): if self.payload: return dict(self.payload.items()) else: return {} @property def headers_str(self): message_header = '' if self.payload: for k, v in self.headers.items(): message_header += '{}: {}\r\n'.format(k, v) return message_header.encode('utf-8') def build(self, message, status, detailed_status=None): """Function builds and signs an AS2 MDN message. :param message: The received AS2 message for which this is an MDN. :param status: The status of processing of the received AS2 message. :param detailed_status: The optional detailed status of processing of the received AS2 message. Used to give additional error info (default "None") """ # Generate message id using UUID 1 as it uses both hostname and time self.message_id = email_utils.make_msgid().lstrip('<').rstrip('>') self.orig_message_id = message.message_id # Set up the message headers mdn_headers = { 'AS2-Version': AS2_VERSION, 'ediint-features': EDIINT_FEATURES, 'Message-ID': '<{}>'.format(self.message_id), 'AS2-From': quote_as2name(message.headers.get('as2-to')), 'AS2-To': quote_as2name(message.headers.get('as2-from')), 'Date': email_utils.formatdate(localtime=True), 'user-agent': 'pyAS2 Open Source AS2 Software' } # Set the confirmation text message here confirmation_text = MDN_CONFIRM_TEXT # overwrite with organization specific message if message.receiver and message.receiver.mdn_confirm_text: confirmation_text = message.receiver.mdn_confirm_text # overwrite with partner specific message if message.sender and message.sender.mdn_confirm_text: confirmation_text = message.sender.mdn_confirm_text if status != 'processed': confirmation_text = MDN_FAILED_TEXT self.payload = MIMEMultipart( 'report', report_type='disposition-notification') # Create and attach the MDN Text Message mdn_text = email_message.Message() mdn_text.set_payload('%s\n' % confirmation_text) mdn_text.set_type('text/plain') del mdn_text['MIME-Version'] encoders.encode_7or8bit(mdn_text) self.payload.attach(mdn_text) # Create and attache the MDN Report Message mdn_base = email_message.Message() mdn_base.set_type('message/disposition-notification') mdn_report = 'Reporting-UA: pyAS2 Open Source AS2 Software\n' mdn_report += 'Original-Recipient: rfc822; {}\n'.format( message.headers.get('as2-to')) mdn_report += 'Final-Recipient: rfc822; {}\n'.format( message.headers.get('as2-to')) mdn_report += 'Original-Message-ID: <{}>\n'.format(message.message_id) mdn_report += 'Disposition: automatic-action/' \ 'MDN-sent-automatically; {}'.format(status) if detailed_status: mdn_report += ': {}'.format(detailed_status) mdn_report += '\n' if message.mic: mdn_report += 'Received-content-MIC: {}, {}\n'.format( message.mic.decode(), message.digest_alg) mdn_base.set_payload(mdn_report) del mdn_base['MIME-Version'] encoders.encode_7or8bit(mdn_base) self.payload.attach(mdn_base) # logger.debug('MDN for message %s created:\n%s' % ( # message.message_id, mdn_base.as_string())) # Sign the MDN if it is requested by the sender if message.headers.get('disposition-notification-options') and \ message.receiver and message.receiver.sign_key: self.digest_alg = \ message.headers['disposition-notification-options'].split( ';')[-1].split(',')[-1].strip().replace('-', '') signed_mdn = MIMEMultipart( 'signed', protocol="application/pkcs7-signature") del signed_mdn['MIME-Version'] signed_mdn.attach(self.payload) # Create the signature mime message signature = email_message.Message() signature.set_type('application/pkcs7-signature') signature.set_param('name', 'smime.p7s') signature.set_param('smime-type', 'signed-data') signature.add_header( 'Content-Disposition', 'attachment', filename='smime.p7s') del signature['MIME-Version'] signature.set_payload(sign_message( canonicalize(self.payload), self.digest_alg, message.receiver.sign_key )) encoders.encode_base64(signature) # logger.debug( # 'Signature for MDN created:\n%s' % signature.as_string()) signed_mdn.set_param('micalg', self.digest_alg) signed_mdn.attach(signature) self.payload = signed_mdn # Update the headers of the final payload and set message boundary for k, v in mdn_headers.items(): if self.payload.get(k): self.payload.replace_header(k, v) else: self.payload.add_header(k, v) if self.payload.is_multipart(): self.payload.set_boundary(make_mime_boundary()) def parse(self, raw_content, find_message_cb): """Function parses the RAW AS2 MDN, verifies it and extracts the processing status of the orginal AS2 message. :param raw_content: A byte string of the received HTTP headers followed by the body. :param find_message_cb: A callback the must returns the original Message Object. The original message-id and original recipient AS2 ID are passed as arguments to it. :returns: A two element tuple containing (status, detailed_status). The status is a string indicating the status of the transaction. The optional detailed_status gives additional information about the processing status. """ status, detailed_status = None, None self.payload = parse_mime(raw_content) self.orig_message_id, orig_recipient = self.detect_mdn() # Call the find message callback which should return a Message instance orig_message = find_message_cb(self.orig_message_id, orig_recipient) # Extract the headers and save it mdn_headers = {} for k, v in self.payload.items(): k = k.lower() if k == 'message-id': self.message_id = v.lstrip('<').rstrip('>') mdn_headers[k] = v if orig_message.receiver.mdn_digest_alg \ and self.payload.get_content_type() != 'multipart/signed': status = 'failed/Failure' detailed_status = 'Expected signed MDN but unsigned MDN returned' return status, detailed_status if self.payload.get_content_type() == 'multipart/signed': signature = None message_boundary = ( '--' + self.payload.get_boundary()).encode('utf-8') for part in self.payload.walk(): if part.get_content_type() == 'application/pkcs7-signature': signature = part.get_payload(decode=True) elif part.get_content_type() == 'multipart/report': self.payload = part # Verify the message, first using raw message and if it fails # then convert to canonical form and try again mic_content = extract_first_part(raw_content, message_boundary) verify_cert = orig_message.receiver.load_verify_cert() try: self.digest_alg = verify_message( mic_content, signature, verify_cert) except IntegrityError: mic_content = canonicalize(self.payload) self.digest_alg = verify_message( mic_content, signature, verify_cert) for part in self.payload.walk(): if part.get_content_type() == 'message/disposition-notification': # logger.debug('Found MDN report for message %s:\n%s' % ( # orig_message.message_id, part.as_string())) mdn = part.get_payload()[-1] mdn_status = mdn['Disposition'].split( ';').pop().strip().split(':') status = mdn_status[0] if status == 'processed': mdn_mic = mdn.get('Received-Content-MIC', '').split(',')[0] # TODO: Check MIC for all cases if mdn_mic and orig_message.mic \ and mdn_mic != orig_message.mic.decode(): status = 'processed/warning' detailed_status = 'Message Integrity check failed.' else: detailed_status = ' '.join(mdn_status[1:]).strip() return status, detailed_status def detect_mdn(self): """ Function checks if the received raw message is an AS2 MDN or not. :raises MDNNotFound: If the received payload is not an MDN then this exception is raised. :return: A two element tuple containing (message_id, message_recipient). The message_id is the original AS2 message id and the message_recipient is the original AS2 message recipient. """ mdn_message = None if self.payload.get_content_type() == 'multipart/report': mdn_message = self.payload elif self.payload.get_content_type() == 'multipart/signed': for part in self.payload.walk(): if part.get_content_type() == 'multipart/report': mdn_message = self.payload if not mdn_message: raise MDNNotFound('No MDN found in the received message') message_id, message_recipient = None, None for part in mdn_message.walk(): if part.get_content_type() == 'message/disposition-notification': mdn = part.get_payload()[0] message_id = mdn.get('Original-Message-ID').strip('<>') message_recipient = mdn.get( 'Original-Recipient').split(';')[1].strip() return message_id, message_recipient
def signEmailFrom(msg, pubcert, privkey, sign_detached=False): """ Signs the provided email message object with the from address cert (.crt) & private key (.pem) from current dir. :type msg: email.message.Message :type pubcert: str :type privkey: str :type sign_detached: bool Default - Returns signed message object, in format Content-Type: application/x-pkcs7-mime; smime-type=signed-data; name="smime.p7m" Content-Disposition: attachment; filename="smime.p7m" Content-Transfer-Encoding: base64 See RFC5751 section 3.4 The reason for choosing this format, rather than multipart/signed, is that it prevents the delivery service from applying open/link tracking or other manipulations on the body. sign_detached == true:, Returns signed message object, in format Content-Type: multipart/signed; protocol="application/x-pkcs7-signature"; The reason for choosing this format is for transparency on mail clients that do not understand S/MIME. """ assert isinstance(msg, email.message.Message) assert isinstance(pubcert, str) assert isinstance(privkey, str) assert isinstance(sign_detached, bool) if sign_detached: # Need to fix up the header order and formatting here rawMsg = msg.as_bytes() sgn = create_embedded_pkcs7_signature(rawMsg, pubcert, privkey, PKCS7_DETACHED) # Wrap message with multipart/signed header msg2 = MIMEMultipart() # this makes a new boundary bound = msg2.get_boundary( ) # keep for later as we have to rewrite the header msg2.set_default_type('multipart/signed') copyHeaders(msg, msg2) del msg2['Content-Language'] # These don't apply to multipart/signed del msg2['Content-Transfer-Encoding'] msg2.attach(msg) sgn_part = MIMEApplication(sgn, 'x-pkcs7-signature; name="smime.p7s"', _encoder=email.encoders.encode_base64) sgn_part.add_header('Content-Disposition', 'attachment; filename="smime.p7s"') msg2.attach(sgn_part) # Fix up Content-Type headers, as default class methods don't allow passing in protocol etc. msg2.replace_header( 'Content-Type', 'multipart/signed; protocol="application/x-pkcs7-signature"; micalg="sha1"; boundary="{}"' .format(bound)) return msg2 else: rawMsg = msg.as_bytes() sgn = create_embedded_pkcs7_signature(rawMsg, pubcert, privkey, PKCS7_NOSIGS) msg.set_payload(base64.encodebytes(sgn)) hdrList = { 'Content-Type': 'application/x-pkcs7-mime; smime-type=signed-data; name="smime.p7m"', 'Content-Transfer-Encoding': 'base64', 'Content-Disposition': 'attachment; filename="smime.p7m"' } copyHeaders(hdrList, msg) return msg
def method(self, **kwargs): for name in kwargs.iterkeys(): if name not in argmap: raise TypeError('Got an unexpected keyword argument "%s"' % name) for name in required_params: if name not in kwargs: raise TypeError('Missing required parameter "%s"' % name) for name, regex in pattern_params.iteritems(): if name in kwargs: if isinstance(kwargs[name], basestring): pvalues = [kwargs[name]] else: pvalues = kwargs[name] for pvalue in pvalues: if re.match(regex, pvalue) is None: raise TypeError( 'Parameter "%s" value "%s" does not match the pattern "%s"' % (name, pvalue, regex) ) for name, enums in enum_params.iteritems(): if name in kwargs: if kwargs[name] not in enums: raise TypeError( 'Parameter "%s" value "%s" is not an allowed value in "%s"' % (name, kwargs[name], str(enums)) ) actual_query_params = {} actual_path_params = {} for key, value in kwargs.iteritems(): to_type = param_type.get(key, "string") # For repeated parameters we cast each member of the list. if key in repeated_params and type(value) == type([]): cast_value = [_cast(x, to_type) for x in value] else: cast_value = _cast(value, to_type) if key in query_params: actual_query_params[argmap[key]] = cast_value if key in path_params: actual_path_params[argmap[key]] = cast_value body_value = kwargs.get("body", None) media_filename = kwargs.get("media_body", None) if self._developerKey: actual_query_params["key"] = self._developerKey model = self._model # If there is no schema for the response then presume a binary blob. if "response" not in methodDesc: model = RawModel() headers = {} headers, params, query, body = model.request(headers, actual_path_params, actual_query_params, body_value) expanded_url = uritemplate.expand(pathUrl, params) url = urlparse.urljoin(self._baseUrl, expanded_url + query) resumable = None multipart_boundary = "" if media_filename: # Ensure we end up with a valid MediaUpload object. if isinstance(media_filename, basestring): (media_mime_type, encoding) = mimetypes.guess_type(media_filename) if media_mime_type is None: raise UnknownFileType(media_filename) if not mimeparse.best_match([media_mime_type], ",".join(accept)): raise UnacceptableMimeTypeError(media_mime_type) media_upload = MediaFileUpload(media_filename, media_mime_type) elif isinstance(media_filename, MediaUpload): media_upload = media_filename else: raise TypeError("media_filename must be str or MediaUpload.") # Check the maxSize if maxSize > 0 and media_upload.size() > maxSize: raise MediaUploadSizeError("Media larger than: %s" % maxSize) # Use the media path uri for media uploads if media_upload.resumable(): expanded_url = uritemplate.expand(mediaResumablePathUrl, params) else: expanded_url = uritemplate.expand(mediaPathUrl, params) url = urlparse.urljoin(self._baseUrl, expanded_url + query) if media_upload.resumable(): # This is all we need to do for resumable, if the body exists it gets # sent in the first request, otherwise an empty body is sent. resumable = media_upload else: # A non-resumable upload if body is None: # This is a simple media upload headers["content-type"] = media_upload.mimetype() body = media_upload.getbytes(0, media_upload.size()) else: # This is a multipart/related upload. msgRoot = MIMEMultipart("related") # msgRoot should not write out it's own headers setattr(msgRoot, "_write_headers", lambda self: None) # attach the body as one part msg = MIMENonMultipart(*headers["content-type"].split("/")) msg.set_payload(body) msgRoot.attach(msg) # attach the media as the second part msg = MIMENonMultipart(*media_upload.mimetype().split("/")) msg["Content-Transfer-Encoding"] = "binary" payload = media_upload.getbytes(0, media_upload.size()) msg.set_payload(payload) msgRoot.attach(msg) body = msgRoot.as_string() multipart_boundary = msgRoot.get_boundary() headers["content-type"] = ("multipart/related; " 'boundary="%s"') % multipart_boundary logging.info("URL being requested: %s" % url) return self._requestBuilder( self._http, model.response, url, method=httpMethod, body=body, headers=headers, methodId=methodId, resumable=resumable, )
def execute(self, http=None): """Execute all the requests as a single batched HTTP request. Args: http: httplib2.Http, an http object to be used in place of the one the HttpRequest request object was constructed with. If one isn't supplied then use a http object from the requests in this batch. Returns: None Raises: apiclient.errors.HttpError if the response was not a 2xx. httplib2.Error if a transport error has occured. """ if http is None: for request_id in self._order: request, callback = self._requests[request_id] if request is not None: http = request.http break if http is None: raise ValueError("Missing a valid http object.") msgRoot = MIMEMultipart('mixed') # msgRoot should not write out it's own headers setattr(msgRoot, '_write_headers', lambda self: None) # Add all the individual requests. for request_id in self._order: request, callback = self._requests[request_id] msg = MIMENonMultipart('application', 'http') msg['Content-Transfer-Encoding'] = 'binary' msg['Content-ID'] = self._id_to_header(request_id) body = self._serialize_request(request) msg.set_payload(body) msgRoot.attach(msg) body = msgRoot.as_string() headers = {} headers['content-type'] = ('multipart/mixed; ' 'boundary="%s"') % msgRoot.get_boundary() resp, content = http.request(self._batch_uri, 'POST', body=body, headers=headers) if resp.status >= 300: raise HttpError(resp, content, self._batch_uri) # Now break up the response and process each one with the correct postproc # and trigger the right callbacks. boundary, _ = content.split(None, 1) # Prepend with a content-type header so FeedParser can handle it. header = 'Content-Type: %s\r\n\r\n' % resp['content-type'] content = header + content parser = FeedParser() parser.feed(content) respRoot = parser.close() if not respRoot.is_multipart(): raise BatchError("Response not in multipart/mixed format.") parts = respRoot.get_payload() for part in parts: request_id = self._header_to_id(part['Content-ID']) headers, content = self._deserialize_response(part.get_payload()) # TODO(jcgregorio) Remove this temporary hack once the server stops # gzipping individual response bodies. if content[0] != '{': gzipped_content = content content = gzip.GzipFile( fileobj=StringIO.StringIO(gzipped_content)).read() request, cb = self._requests[request_id] postproc = request.postproc response = postproc(resp, content) if cb is not None: cb(request_id, response) if self._callback is not None: self._callback(request_id, response)
def apply_mtom(headers, envelope, params, paramvals): '''Apply MTOM to a SOAP envelope, separating attachments into a MIME multipart message. Returns a tuple of length 2 with dictionary of headers and string of body that can be sent with HTTPConnection References: XOP http://www.w3.org/TR/xop10/ MTOM http://www.w3.org/TR/soap12-mtom/ http://www.w3.org/Submission/soap11mtom10/ :param headers Headers dictionary of the SOAP message that would originally be sent. :param envelope Iterable containing SOAP envelope string that would have originally been sent. :param params params attribute from the Message object used for the SOAP :param paramvals values of the params, passed to Message.to_parent_element ''' # grab the XML element of the message in the SOAP body envelope = ''.join(envelope) soaptree = etree.fromstring(envelope) soapbody = soaptree.find("{%s}Body" % _ns_soap_env) message = None for child in list(soapbody): if child.tag == ("{%s}Fault" % _ns_soap_env): return (headers, envelope) else: message = child break # Get additional parameters from original Content-Type ctarray = [] for n, v in headers.items(): if n.lower() == 'content-type': ctarray = v.split(';') break roottype = ctarray[0].strip() rootparams = {} for ctparam in ctarray[1:]: n, v = ctparam.strip().split('=') rootparams[n] = v.strip("\"'") # Set up initial MIME parts. mtompkg = MIMEMultipart('related', boundary='?//<><>spyne_MIME_boundary<>') rootpkg = MIMEApplication(envelope, 'xop+xml', encode_7or8bit) # Set up multipart headers. del(mtompkg['mime-version']) mtompkg.set_param('start-info', roottype) mtompkg.set_param('start', '<spyneEnvelope>') if 'SOAPAction' in headers: mtompkg.add_header('SOAPAction', headers.get('SOAPAction')) # Set up root SOAP part headers. del(rootpkg['mime-version']) rootpkg.add_header('Content-ID', '<spyneEnvelope>') for n, v in rootparams.items(): rootpkg.set_param(n, v) rootpkg.set_param('type', roottype) mtompkg.attach(rootpkg) # Extract attachments from SOAP envelope. for i in range(len(params)): name, typ = params[i] if typ in (ByteArray, Attachment): id = "spyneAttachment_%s" % (len(mtompkg.get_payload()), ) param = message[i] param.text = "" incl = etree.SubElement(param, "{%s}Include" % _ns_xop) incl.attrib["href"] = "cid:%s" % id if paramvals[i].fileName and not paramvals[i].data: paramvals[i].load_from_file() if type == Attachment: data = paramvals[i].data else: data = ''.join(paramvals[i]) attachment = None attachment = MIMEApplication(data, _encoder=encode_7or8bit) del(attachment['mime-version']) attachment.add_header('Content-ID', '<%s>' % (id, )) mtompkg.attach(attachment) # Update SOAP envelope. rootpkg.set_payload(etree.tostring(soaptree)) # extract body string from MIMEMultipart message bound = '--%s' % (mtompkg.get_boundary(), ) marray = mtompkg.as_string().split(bound) mtombody = bound mtombody += bound.join(marray[1:]) # set Content-Length mtompkg.add_header("Content-Length", str(len(mtombody))) # extract dictionary of headers from MIMEMultipart message mtomheaders = {} for name, value in mtompkg.items(): mtomheaders[name] = value if len(mtompkg.get_payload()) <= 1: return (headers, envelope) return (mtomheaders, [mtombody])
def method(self, **kwargs): for name in kwargs.iterkeys(): if name not in argmap: raise TypeError('Got an unexpected keyword argument "%s"' % name) for name in required_params: if name not in kwargs: raise TypeError('Missing required parameter "%s"' % name) for name, regex in pattern_params.iteritems(): if name in kwargs: if isinstance(kwargs[name], basestring): pvalues = [kwargs[name]] else: pvalues = kwargs[name] for pvalue in pvalues: if re.match(regex, pvalue) is None: raise TypeError( 'Parameter "%s" value "%s" does not match the pattern "%s"' % (name, pvalue, regex)) for name, enums in enum_params.iteritems(): if name in kwargs: if kwargs[name] not in enums: raise TypeError( 'Parameter "%s" value "%s" is not an allowed value in "%s"' % (name, kwargs[name], str(enums))) actual_query_params = {} actual_path_params = {} for key, value in kwargs.iteritems(): to_type = param_type.get(key, 'string') # For repeated parameters we cast each member of the list. if key in repeated_params and type(value) == type([]): cast_value = [_cast(x, to_type) for x in value] else: cast_value = _cast(value, to_type) if key in query_params: actual_query_params[argmap[key]] = cast_value if key in path_params: actual_path_params[argmap[key]] = cast_value body_value = kwargs.get('body', None) media_filename = kwargs.get('media_body', None) if self._developerKey: actual_query_params['key'] = self._developerKey headers = {} headers, params, query, body = self._model.request( headers, actual_path_params, actual_query_params, body_value) expanded_url = uritemplate.expand(pathUrl, params) url = urlparse.urljoin(self._baseUrl, expanded_url + query) resumable = None multipart_boundary = '' if media_filename: # Convert a simple filename into a MediaUpload object. if isinstance(media_filename, basestring): (media_mime_type, encoding) = mimetypes.guess_type(media_filename) if media_mime_type is None: raise UnknownFileType(media_filename) if not mimeparse.best_match([media_mime_type], ','.join(accept)): raise UnacceptableMimeTypeError(media_mime_type) media_upload = MediaFileUpload(media_filename, media_mime_type) elif isinstance(media_filename, MediaUpload): media_upload = media_filename else: raise TypeError( 'media_filename must be str or MediaUpload. Got %s' % type(media_upload)) if media_upload.resumable(): resumable = media_upload # Check the maxSize if maxSize > 0 and media_upload.size() > maxSize: raise MediaUploadSizeError("Media larger than: %s" % maxSize) # Use the media path uri for media uploads if media_upload.resumable(): expanded_url = uritemplate.expand(mediaResumablePathUrl, params) else: expanded_url = uritemplate.expand(mediaPathUrl, params) url = urlparse.urljoin(self._baseUrl, expanded_url + query) if body is None: # This is a simple media upload headers['content-type'] = media_upload.mimetype() expanded_url = uritemplate.expand(mediaResumablePathUrl, params) if not media_upload.resumable(): body = media_upload.getbytes(0, media_upload.size()) else: # This is a multipart/related upload. msgRoot = MIMEMultipart('related') # msgRoot should not write out it's own headers setattr(msgRoot, '_write_headers', lambda self: None) # attach the body as one part msg = MIMENonMultipart(*headers['content-type'].split('/')) msg.set_payload(body) msgRoot.attach(msg) # attach the media as the second part msg = MIMENonMultipart(*media_upload.mimetype().split('/')) msg['Content-Transfer-Encoding'] = 'binary' if media_upload.resumable(): # This is a multipart resumable upload, where a multipart payload # looks like this: # # --===============1678050750164843052== # Content-Type: application/json # MIME-Version: 1.0 # # {'foo': 'bar'} # --===============1678050750164843052== # Content-Type: image/png # MIME-Version: 1.0 # Content-Transfer-Encoding: binary # # <BINARY STUFF> # --===============1678050750164843052==-- # # In the case of resumable multipart media uploads, the <BINARY # STUFF> is large and will be spread across multiple PUTs. What we # do here is compose the multipart message with a random payload in # place of <BINARY STUFF> and then split the resulting content into # two pieces, text before <BINARY STUFF> and text after <BINARY # STUFF>. The text after <BINARY STUFF> is the multipart boundary. # In apiclient.http the HttpRequest will send the text before # <BINARY STUFF>, then send the actual binary media in chunks, and # then will send the multipart delimeter. payload = hex(random.getrandbits(300)) msg.set_payload(payload) msgRoot.attach(msg) body = msgRoot.as_string() body, _ = body.split(payload) resumable = media_upload else: payload = media_upload.getbytes(0, media_upload.size()) msg.set_payload(payload) msgRoot.attach(msg) body = msgRoot.as_string() multipart_boundary = msgRoot.get_boundary() headers['content-type'] = ( 'multipart/related; ' 'boundary="%s"') % multipart_boundary logging.info('URL being requested: %s' % url) return self._requestBuilder(self._http, self._model.response, url, method=httpMethod, body=body, headers=headers, methodId=methodId, resumable=resumable)
def method(self, **kwargs): for name in kwargs.iterkeys(): if name not in argmap: raise TypeError('Got an unexpected keyword argument "%s"' % name) for name in required_params: if name not in kwargs: raise TypeError('Missing required parameter "%s"' % name) for name, regex in pattern_params.iteritems(): if name in kwargs: if re.match(regex, kwargs[name]) is None: raise TypeError( 'Parameter "%s" value "%s" does not match the pattern "%s"' % (name, kwargs[name], regex)) for name, enums in enum_params.iteritems(): if name in kwargs: if kwargs[name] not in enums: raise TypeError( 'Parameter "%s" value "%s" is not an allowed value in "%s"' % (name, kwargs[name], str(enums))) actual_query_params = {} actual_path_params = {} for key, value in kwargs.iteritems(): to_type = param_type.get(key, 'string') # For repeated parameters we cast each member of the list. if key in repeated_params and type(value) == type([]): cast_value = [_cast(x, to_type) for x in value] else: cast_value = _cast(value, to_type) if key in query_params: actual_query_params[argmap[key]] = cast_value if key in path_params: actual_path_params[argmap[key]] = cast_value body_value = kwargs.get('body', None) media_filename = kwargs.get('media_body', None) if self._developerKey: actual_query_params['key'] = self._developerKey headers = {} headers, params, query, body = self._model.request(headers, actual_path_params, actual_query_params, body_value) expanded_url = uritemplate.expand(pathUrl, params) url = urlparse.urljoin(self._baseUrl, expanded_url + query) if media_filename: (media_mime_type, encoding) = mimetypes.guess_type(media_filename) if media_mime_type is None: raise UnknownFileType(media_filename) if not mimeparse.best_match([media_mime_type], ','.join(accept)): raise UnacceptableMimeTypeError(media_mime_type) # Check the maxSize if maxSize > 0 and os.path.getsize(media_filename) > maxSize: raise MediaUploadSizeError(media_filename) # Use the media path uri for media uploads expanded_url = uritemplate.expand(mediaPathUrl, params) url = urlparse.urljoin(self._baseUrl, expanded_url + query) if body is None: headers['content-type'] = media_mime_type # make the body the contents of the file f = file(media_filename, 'rb') body = f.read() f.close() else: msgRoot = MIMEMultipart('related') # msgRoot should not write out it's own headers setattr(msgRoot, '_write_headers', lambda self: None) # attach the body as one part msg = MIMENonMultipart(*headers['content-type'].split('/')) msg.set_payload(body) msgRoot.attach(msg) # attach the media as the second part msg = MIMENonMultipart(*media_mime_type.split('/')) msg['Content-Transfer-Encoding'] = 'binary' f = file(media_filename, 'rb') msg.set_payload(f.read()) f.close() msgRoot.attach(msg) body = msgRoot.as_string() # must appear after the call to as_string() to get the right boundary headers['content-type'] = ('multipart/related; ' 'boundary="%s"') % msgRoot.get_boundary() logging.info('URL being requested: %s' % url) return self._requestBuilder(self._http, self._model.response, url, method=httpMethod, body=body, headers=headers, methodId=methodId)
def apply_mtom(headers, envelope, params, paramvals): """Apply MTOM to a SOAP envelope, separating attachments into a MIME multipart message. Returns a tuple of length 2 with dictionary of headers and string of body that can be sent with HTTPConnection References: XOP http://www.w3.org/TR/xop10/ MTOM http://www.w3.org/TR/soap12-mtom/ http://www.w3.org/Submission/soap11mtom10/ :param headers Headers dictionary of the SOAP message that would originally be sent. :param envelope Iterable containing SOAP envelope string that would have originally been sent. :param params params attribute from the Message object used for the SOAP :param paramvals values of the params, passed to Message.to_parent """ # grab the XML element of the message in the SOAP body envelope = ''.join(envelope) soaptree = etree.fromstring(envelope) soapbody = soaptree.find("{%s}Body" % _ns_soap_env) message = None for child in list(soapbody): if child.tag == ("{%s}Fault" % _ns_soap_env): return headers, envelope else: message = child break # Get additional parameters from original Content-Type ctarray = [] for n, v in headers.items(): if n.lower() == 'content-type': ctarray = v.split(';') break roottype = ctarray[0].strip() rootparams = {} for ctparam in ctarray[1:]: n, v = ctparam.strip().split('=') rootparams[n] = v.strip("\"'") # Set up initial MIME parts. mtompkg = MIMEMultipart('related', boundary='?//<><>spyne_MIME_boundary<>') rootpkg = MIMEApplication(envelope, 'xop+xml', encode_7or8bit) # Set up multipart headers. del mtompkg['mime-version'] mtompkg.set_param('start-info', roottype) mtompkg.set_param('start', '<spyneEnvelope>') if 'SOAPAction' in headers: mtompkg.add_header('SOAPAction', headers.get('SOAPAction')) # Set up root SOAP part headers. del rootpkg['mime-version'] rootpkg.add_header('Content-ID', '<spyneEnvelope>') for n, v in rootparams.items(): rootpkg.set_param(n, v) rootpkg.set_param('type', roottype) mtompkg.attach(rootpkg) # Extract attachments from SOAP envelope. for i in range(len(params)): name, typ = params[i] if issubclass(typ, (ByteArray, File)): id = "SpyneAttachment_%s" % (len(mtompkg.get_payload()), ) param = message[i] param.text = "" incl = etree.SubElement(param, "{%s}Include" % _ns_xop) incl.attrib["href"] = "cid:%s" % id if paramvals[i].fileName and not paramvals[i].data: paramvals[i].load_from_file() if issubclass(type, File): data = paramvals[i].data else: data = ''.join(paramvals[i]) attachment = MIMEApplication(data, _encoder=encode_7or8bit) del attachment['mime-version'] attachment.add_header('Content-ID', '<%s>' % (id, )) mtompkg.attach(attachment) # Update SOAP envelope. rootpkg.set_payload(etree.tostring(soaptree)) # extract body string from MIMEMultipart message bound = '--%s' % (mtompkg.get_boundary(), ) marray = mtompkg.as_string().split(bound) mtombody = bound mtombody += bound.join(marray[1:]) # set Content-Length mtompkg.add_header("Content-Length", str(len(mtombody))) # extract dictionary of headers from MIMEMultipart message mtomheaders = {} for name, value in mtompkg.items(): mtomheaders[name] = value if len(mtompkg.get_payload()) <= 1: return headers, envelope return mtomheaders, [mtombody]
class Mdn: """Class for handling AS2 MDNs. Includes functions for both parsing and building them. """ def __init__(self, mdn_mode=None, digest_alg=None, mdn_url=None): self.message_id = None self.orig_message_id = None self.payload = None self.mdn_mode = mdn_mode self.digest_alg = digest_alg self.mdn_url = mdn_url @property def content(self): """Function returns the body of the mdn message as a byte string""" if self.payload is not None: message_bytes = mime_to_bytes(self.payload) boundary = b"--" + self.payload.get_boundary().encode("utf-8") temp = message_bytes.split(boundary) temp.pop(0) return boundary + boundary.join(temp) return "" @property def headers(self): """Return the headers in the payload as a dictionary.""" if self.payload: return dict(self.payload.items()) return {} @property def headers_str(self): """Return the headers in the payload as a string.""" message_header = "" if self.payload: for k, v in self.headers.items(): message_header += f"{k}: {v}\r\n" return message_header.encode("utf-8") def build( self, message, status, detailed_status=None, confirmation_text=MDN_CONFIRM_TEXT, failed_text=MDN_FAILED_TEXT, ): """Function builds and signs an AS2 MDN message. :param message: The received AS2 message for which this is an MDN. :param status: The status of processing of the received AS2 message. :param detailed_status: The optional detailed status of processing of the received AS2 message. Used to give additional error info (default "None") :param confirmation_text: The confirmation message sent in the first part of the MDN. :param failed_text: The failure message sent in the first part of the failed MDN. """ # Generate message id using UUID 1 as it uses both hostname and time self.message_id = email_utils.make_msgid().lstrip("<").rstrip(">") self.orig_message_id = message.message_id # Set up the message headers mdn_headers = { "AS2-Version": AS2_VERSION, "ediint-features": EDIINT_FEATURES, "Message-ID": f"<{self.message_id}>", "AS2-From": quote_as2name(message.headers.get("as2-to")), "AS2-To": quote_as2name(message.headers.get("as2-from")), "Date": email_utils.formatdate(localtime=True), "user-agent": "pyAS2 Open Source AS2 Software", } # Set the confirmation text message here # overwrite with organization specific message if message.receiver and message.receiver.mdn_confirm_text: confirmation_text = message.receiver.mdn_confirm_text # overwrite with partner specific message if message.sender and message.sender.mdn_confirm_text: confirmation_text = message.sender.mdn_confirm_text if status != "processed": confirmation_text = failed_text self.payload = MIMEMultipart("report", report_type="disposition-notification") # Create and attach the MDN Text Message mdn_text = email_message.Message() mdn_text.set_payload(f"{confirmation_text}\r\n") mdn_text.set_type("text/plain") del mdn_text["MIME-Version"] encoders.encode_7or8bit(mdn_text) self.payload.attach(mdn_text) # Create and attache the MDN Report Message mdn_base = email_message.Message() mdn_base.set_type("message/disposition-notification") mdn_report = "Reporting-UA: pyAS2 Open Source AS2 Software\r\n" mdn_report += f'Original-Recipient: rfc822; {message.headers.get("as2-to")}\r\n' mdn_report += f'Final-Recipient: rfc822; {message.headers.get("as2-to")}\r\n' mdn_report += f"Original-Message-ID: <{message.message_id}>\r\n" mdn_report += f"Disposition: automatic-action/MDN-sent-automatically; {status}" if detailed_status: mdn_report += f": {detailed_status}" mdn_report += "\r\n" if message.mic: mdn_report += f"Received-content-MIC: {message.mic.decode()}, {message.digest_alg}\r\n" mdn_base.set_payload(mdn_report) del mdn_base["MIME-Version"] encoders.encode_7or8bit(mdn_base) self.payload.attach(mdn_base) logger.debug( f"MDN report for message {message.message_id} created:\n{mime_to_bytes(mdn_base)}" ) # Sign the MDN if it is requested by the sender if (message.headers.get("disposition-notification-options") and message.receiver and message.receiver.sign_key): self.digest_alg = ( message.headers["disposition-notification-options"].split( ";")[-1].split(",")[-1].strip().replace("-", "")) signed_mdn = MIMEMultipart("signed", protocol="application/pkcs7-signature") del signed_mdn["MIME-Version"] signed_mdn.attach(self.payload) # Create the signature mime message signature = email_message.Message() signature.set_type("application/pkcs7-signature") signature.set_param("name", "smime.p7s") signature.set_param("smime-type", "signed-data") signature.add_header("Content-Disposition", "attachment", filename="smime.p7s") del signature["MIME-Version"] signed_data = sign_message(canonicalize(self.payload), self.digest_alg, message.receiver.sign_key) signature.set_payload(signed_data) encoders.encode_base64(signature) signed_mdn.set_param("micalg", self.digest_alg) signed_mdn.attach(signature) self.payload = signed_mdn logger.debug(f"Signing the MDN for message {message.message_id}") # Update the headers of the final payload and set message boundary for k, v in mdn_headers.items(): if self.payload.get(k): self.payload.replace_header(k, v) else: self.payload.add_header(k, v) self.payload.set_boundary(make_mime_boundary()) logger.debug(f"MDN generated for message {message.message_id} with " f"content:\n {mime_to_bytes(self.payload)}") def parse(self, raw_content, find_message_cb): """Function parses the RAW AS2 MDN, verifies it and extracts the processing status of the orginal AS2 message. :param raw_content: A byte string of the received HTTP headers followed by the body. :param find_message_cb: A callback the must returns the original Message Object. The original message-id and original recipient AS2 ID are passed as arguments to it. :returns: A two element tuple containing (status, detailed_status). The status is a string indicating the status of the transaction. The optional detailed_status gives additional information about the processing status. """ status, detailed_status = None, None try: self.payload = parse_mime(raw_content) self.orig_message_id, orig_recipient = self.detect_mdn() # Call the find message callback which should return a Message instance orig_message = find_message_cb(self.orig_message_id, orig_recipient) # Extract the headers and save it mdn_headers = {} for k, v in self.payload.items(): k = k.lower() if k == "message-id": self.message_id = v.lstrip("<").rstrip(">") mdn_headers[k] = v if (orig_message.receiver.mdn_digest_alg and self.payload.get_content_type() != "multipart/signed"): status = "failed/Failure" detailed_status = "Expected signed MDN but unsigned MDN returned" return status, detailed_status if self.payload.get_content_type() == "multipart/signed": logger.debug( f"Verifying signed MDN: \n{mime_to_bytes(self.payload)}") message_boundary = ( "--" + self.payload.get_boundary()).encode("utf-8") # Extract the signature and the signed payload signature = None signature_types = [ "application/pkcs7-signature", "application/x-pkcs7-signature", ] for part in self.payload.walk(): if part.get_content_type() in signature_types: signature = part.get_payload(decode=True) elif part.get_content_type() == "multipart/report": self.payload = part # Verify the message, first using raw message and if it fails # then convert to canonical form and try again mic_content = extract_first_part(raw_content, message_boundary) verify_cert = orig_message.receiver.load_verify_cert() try: self.digest_alg = verify_message(mic_content, signature, verify_cert) except IntegrityError: mic_content = canonicalize(self.payload) self.digest_alg = verify_message(mic_content, signature, verify_cert) for part in self.payload.walk(): if part.get_content_type( ) == "message/disposition-notification": logger.debug( f"MDN report for message {orig_message.message_id}:\n{part.as_string()}" ) mdn = part.get_payload()[-1] mdn_status = mdn["Disposition"].split( ";").pop().strip().split(":") status = mdn_status[0] if status == "processed": # Compare the original mic with the received mic mdn_mic = mdn.get("Received-Content-MIC", "").split(",")[0] if (mdn_mic and orig_message.mic and mdn_mic != orig_message.mic.decode()): status = "processed/warning" detailed_status = "Message Integrity check failed." else: detailed_status = " ".join(mdn_status[1:]).strip() except MDNNotFound: status = "failed/Failure" detailed_status = "mdn-not-found" except Exception as e: # pylint: disable=W0703 status = "failed/Failure" detailed_status = f"Failed to parse received MDN. {e}" logger.error( f"Failed to parse AS2 MDN\n: {traceback.format_exc()}") return status, detailed_status def detect_mdn(self): """Function checks if the received raw message is an AS2 MDN or not. :raises MDNNotFound: If the received payload is not an MDN then this exception is raised. :return: A two element tuple containing (message_id, message_recipient). The message_id is the original AS2 message id and the message_recipient is the original AS2 message recipient. """ mdn_message = None if self.payload.get_content_type() == "multipart/report": mdn_message = self.payload elif self.payload.get_content_type() == "multipart/signed": for part in self.payload.walk(): if part.get_content_type() == "multipart/report": mdn_message = self.payload if not mdn_message: raise MDNNotFound("No MDN found in the received message") message_id, message_recipient = None, None for part in mdn_message.walk(): if part.get_content_type() == "message/disposition-notification": mdn = part.get_payload()[0] message_id = mdn.get("Original-Message-ID").strip("<>") message_recipient = None if "Original-Recipient" in mdn: message_recipient = mdn["Original-Recipient"].split( ";")[1].strip() elif "Final-Recipient" in mdn: message_recipient = mdn["Final-Recipient"].split( ";")[1].strip() return message_id, message_recipient
def build_mdn(message, status, **kwargs): """ Function builds AS2 MDN report for the received message. Takes message status as input and returns the mdn content.""" try: # Initialize variables mdn_body, mdn_message = None, None # Set the confirmation text message here confirmation_text = str() if message.organization and message.organization.confirmation_message: confirmation_text = message.organization.confirmation_message # overwrite with partner specific message if message.partner and message.partner.confirmation_message: confirmation_text = message.partner.confirmation_message # default message if confirmation_text.strip() == '': confirmation_text = _(u'The AS2 message has been processed. ' u'Thank you for exchanging AS2 messages with Pyas2.') # Update message status and send mail here based on the created MDN if status != 'success': as2utils.senderrorreport(message, _(u'Failure in processing message from partner,\n ' u'Basic status : %s \n Advanced Status: %s' % (kwargs['adv_status'], kwargs['status_message']))) confirmation_text = _(u'The AS2 message could not be processed. ' u'The disposition-notification report has additional details.') models.Log.objects.create(message=message, status='E', text=kwargs['status_message']) message.status = 'E' else: message.status = 'S' # In case no MDN is requested exit from process header_parser = HeaderParser() message_header = header_parser.parsestr(message.headers) if not message_header.get('disposition-notification-to'): models.Log.objects.create(message=message, status='S', text=_(u'MDN not requested by partner, closing request.')) return mdn_body, mdn_message # Build the MDN report models.Log.objects.create(message=message, status='S', text=_(u'Building the MDN response to the request')) mdn_report = MIMEMultipart('report', report_type="disposition-notification") # Build the text message with confirmation text and add to report mdn_text = email.Message.Message() mdn_text.set_payload("%s\n" % confirmation_text) mdn_text.set_type('text/plain') mdn_text.set_charset('us-ascii') del mdn_text['MIME-Version'] mdn_report.attach(mdn_text) # Build the MDN message and add to report mdn_base = email.Message.Message() mdn_base.set_type('message/disposition-notification') mdn_base.set_charset('us-ascii') mdn = 'Reporting-UA: Bots Opensource EDI Translator\n' mdn += 'Original-Recipient: rfc822; %s\n' % message_header.get('as2-to') mdn += 'Final-Recipient: rfc822; %s\n' % message_header.get('as2-to') mdn += 'Original-Message-ID: <%s>\n' % message.message_id if status != 'success': mdn += 'Disposition: automatic-action/MDN-sent-automatically; ' \ 'processed/%s: %s\n' % (status, kwargs['adv_status']) else: mdn += 'Disposition: automatic-action/MDN-sent-automatically; processed\n' if message.mic: mdn += 'Received-content-MIC: %s\n' % message.mic mdn_base.set_payload(mdn) del mdn_base['MIME-Version'] mdn_report.attach(mdn_base) del mdn_report['MIME-Version'] # If signed MDN is requested by partner then sign the MDN and attach to report pyas2init.logger.debug('MDN for message %s created:\n%s' % (message.message_id, mdn_report.as_string())) mdn_signed = False if message_header.get('disposition-notification-options') and message.organization \ and message.organization.signature_key: models.Log.objects.create(message=message, status='S', text=_(u'Signing the MDN using private key {0:s}'.format( message.organization.signature_key))) mdn_signed = True # options = message_header.get('disposition-notification-options').split(";") # algorithm = options[1].split(",")[1].strip() signed_report = MIMEMultipart('signed', protocol="application/pkcs7-signature") signed_report.attach(mdn_report) mic_alg, signature = as2utils.sign_payload( as2utils.canonicalize(as2utils.mimetostring(mdn_report, 0)+'\n'), str(message.organization.signature_key.certificate.path), str(message.organization.signature_key.certificate_passphrase) ) pyas2init.logger.debug('Signature for MDN created:\n%s' % signature.as_string()) signed_report.set_param('micalg', mic_alg) signed_report.attach(signature) mdn_message = signed_report else: mdn_message = mdn_report # Extract the MDN boy from the mdn message. # Add new line between the MDN message and the signature, # Found that without this MDN signature verification fails on Mendelson AS2 main_boundary = '--' + mdn_report.get_boundary() + '--' mdn_body = as2utils.canonicalize( as2utils.extractpayload(mdn_message).replace(main_boundary, main_boundary+'\n')) # Add the relevant headers to the MDN message mdn_message.add_header('ediint-features', 'CEM') mdn_message.add_header('as2-from', message_header.get('as2-to')) mdn_message.add_header('as2-to', message_header.get('as2-from')) mdn_message.add_header('AS2-Version', '1.2') mdn_message.add_header('date', email.Utils.formatdate(localtime=True)) mdn_message.add_header('Message-ID', email.utils.make_msgid()) mdn_message.add_header('user-agent', 'PYAS2, A pythonic AS2 server') # Save the MDN to the store filename = mdn_message.get('message-id').strip('<>') + '.mdn' full_filename = as2utils.storefile(pyas2init.gsettings['mdn_send_store'], filename, mdn_body, True) # Extract the MDN headers as string mdn_headers = '' for key in mdn_message.keys(): mdn_headers += '%s: %s\n' % (key, mdn_message[key]) # Is Async mdn is requested mark MDN as pending and return None if message_header.get('receipt-delivery-option'): message.mdn = models.MDN.objects.create(message_id=filename, file=full_filename, status='P', signed=mdn_signed, headers=mdn_headers, return_url=message_header['receipt-delivery-option']) message.mdn_mode = 'ASYNC' mdn_body, mdn_message = None, None models.Log.objects.create(message=message, status='S', text=_(u'Asynchronous MDN requested, setting status to pending')) # Else mark MDN as sent and return the MDN message else: message.mdn = models.MDN.objects.create(message_id=filename, file=full_filename, status='S', signed=mdn_signed, headers=mdn_headers) message.mdn_mode = 'SYNC' models.Log.objects.create(message=message, status='S', text=_(u'MDN created successfully and sent to partner')) return mdn_body, mdn_message finally: message.save()
def method(self, **kwargs): for name in kwargs.iterkeys(): if name not in argmap: raise TypeError('Got an unexpected keyword argument "%s"' % name) for name in required_params: if name not in kwargs: raise TypeError('Missing required parameter "%s"' % name) for name, regex in pattern_params.iteritems(): if name in kwargs: if isinstance(kwargs[name], basestring): pvalues = [kwargs[name]] else: pvalues = kwargs[name] for pvalue in pvalues: if re.match(regex, pvalue) is None: raise TypeError( 'Parameter "%s" value "%s" does not match the pattern "%s"' % (name, pvalue, regex)) for name, enums in enum_params.iteritems(): if name in kwargs: if kwargs[name] not in enums: raise TypeError( 'Parameter "%s" value "%s" is not an allowed value in "%s"' % (name, kwargs[name], str(enums))) actual_query_params = {} actual_path_params = {} for key, value in kwargs.iteritems(): to_type = param_type.get(key, 'string') # For repeated parameters we cast each member of the list. if key in repeated_params and type(value) == type([]): cast_value = [_cast(x, to_type) for x in value] else: cast_value = _cast(value, to_type) if key in query_params: actual_query_params[argmap[key]] = cast_value if key in path_params: actual_path_params[argmap[key]] = cast_value body_value = kwargs.get('body', None) media_filename = kwargs.get('media_body', None) if self._developerKey: actual_query_params['key'] = self._developerKey model = self._model # If there is no schema for the response then presume a binary blob. if 'response' not in methodDesc: model = RawModel() headers = {} headers, params, query, body = model.request(headers, actual_path_params, actual_query_params, body_value) expanded_url = uritemplate.expand(pathUrl, params) url = urlparse.urljoin(self._baseUrl, expanded_url + query) resumable = None multipart_boundary = '' if media_filename: # Convert a simple filename into a MediaUpload object. if isinstance(media_filename, basestring): (media_mime_type, encoding) = mimetypes.guess_type(media_filename) if media_mime_type is None: raise UnknownFileType(media_filename) if not mimeparse.best_match([media_mime_type], ','.join(accept)): raise UnacceptableMimeTypeError(media_mime_type) media_upload = MediaFileUpload(media_filename, media_mime_type) elif isinstance(media_filename, MediaUpload): media_upload = media_filename else: raise TypeError('media_filename must be str or MediaUpload.') if media_upload.resumable(): resumable = media_upload # Check the maxSize if maxSize > 0 and media_upload.size() > maxSize: raise MediaUploadSizeError("Media larger than: %s" % maxSize) # Use the media path uri for media uploads if media_upload.resumable(): expanded_url = uritemplate.expand(mediaResumablePathUrl, params) else: expanded_url = uritemplate.expand(mediaPathUrl, params) url = urlparse.urljoin(self._baseUrl, expanded_url + query) if body is None: # This is a simple media upload headers['content-type'] = media_upload.mimetype() expanded_url = uritemplate.expand(mediaResumablePathUrl, params) if not media_upload.resumable(): body = media_upload.getbytes(0, media_upload.size()) else: # This is a multipart/related upload. msgRoot = MIMEMultipart('related') # msgRoot should not write out it's own headers setattr(msgRoot, '_write_headers', lambda self: None) # attach the body as one part msg = MIMENonMultipart(*headers['content-type'].split('/')) msg.set_payload(body) msgRoot.attach(msg) # attach the media as the second part msg = MIMENonMultipart(*media_upload.mimetype().split('/')) msg['Content-Transfer-Encoding'] = 'binary' if media_upload.resumable(): # This is a multipart resumable upload, where a multipart payload # looks like this: # # --===============1678050750164843052== # Content-Type: application/json # MIME-Version: 1.0 # # {'foo': 'bar'} # --===============1678050750164843052== # Content-Type: image/png # MIME-Version: 1.0 # Content-Transfer-Encoding: binary # # <BINARY STUFF> # --===============1678050750164843052==-- # # In the case of resumable multipart media uploads, the <BINARY # STUFF> is large and will be spread across multiple PUTs. What we # do here is compose the multipart message with a random payload in # place of <BINARY STUFF> and then split the resulting content into # two pieces, text before <BINARY STUFF> and text after <BINARY # STUFF>. The text after <BINARY STUFF> is the multipart boundary. # In apiclient.http the HttpRequest will send the text before # <BINARY STUFF>, then send the actual binary media in chunks, and # then will send the multipart delimeter. payload = hex(random.getrandbits(300)) msg.set_payload(payload) msgRoot.attach(msg) body = msgRoot.as_string() body, _ = body.split(payload) resumable = media_upload else: payload = media_upload.getbytes(0, media_upload.size()) msg.set_payload(payload) msgRoot.attach(msg) body = msgRoot.as_string() multipart_boundary = msgRoot.get_boundary() headers['content-type'] = ('multipart/related; ' 'boundary="%s"') % multipart_boundary logging.info('URL being requested: %s' % url) return self._requestBuilder(self._http, model.response, url, method=httpMethod, body=body, headers=headers, methodId=methodId, resumable=resumable)
def apply_mtom(headers, envelope, params, paramvals): ''' Apply MTOM to a SOAP envelope, separating attachments into a MIME multipart message. References: XOP http://www.w3.org/TR/xop10/ MTOM http://www.w3.org/TR/soap12-mtom/ http://www.w3.org/Submission/soap11mtom10/ @param headers Headers dictionary of the SOAP message that would originally be sent. @param envelope SOAP envelope string that would have originally been sent. @param params params attribute from the Message object used for the SOAP @param paramvals values of the params, passed to Message.to_xml @return tuple of length 2 with dictionary of headers and string of body that can be sent with HTTPConnection ''' # grab the XML element of the message in the SOAP body soapmsg = StringIO(envelope) soaptree = ElementTree.parse(soapmsg) soapns = soaptree.getroot().tag.split('}')[0].strip('{') soapbody = soaptree.getroot().find("{%s}Body" % soapns) message = None for child in list(soapbody): if child.tag != "%sFault" % (soapns, ): message = child break # Get additional parameters from original Content-Type ctarray = [] for n, v in headers.items(): if n.lower() == 'content-type': ctarray = v.split(';') break roottype = ctarray[0].strip() rootparams = {} for ctparam in ctarray[1:]: n, v = ctparam.strip().split('=') rootparams[n] = v.strip("\"'") # Set up initial MIME parts mtompkg = MIMEMultipart('related', boundary='?//<><>soaplib_MIME_boundary<>') rootpkg = None try: rootpkg = MIMEApplication(envelope, 'xop+xml', encode_7or8bit) except NameError: rootpkg = MIMENonMultipart("application", "xop+xml") rootpkg.set_payload(envelope) encode_7or8bit(rootpkg) # Set up multipart headers. del(mtompkg['mime-version']) mtompkg.set_param('start-info', roottype) mtompkg.set_param('start', '<soaplibEnvelope>') if 'SOAPAction' in headers: mtompkg.add_header('SOAPAction', headers.get('SOAPAction')) # Set up root SOAP part headers. del(rootpkg['mime-version']) rootpkg.add_header('Content-ID', '<soaplibEnvelope>') for n, v in rootparams.items(): rootpkg.set_param(n, v) rootpkg.set_param('type', roottype) mtompkg.attach(rootpkg) # Extract attachments from SOAP envelope. for i in range(len(params)): name, typ = params[i] if typ == Attachment: id = "soaplibAttachment_%s" % (len(mtompkg.get_payload()), ) param = message[i] param.text = "" incl = create_xml_subelement(param, "{http://www.w3.org/2004/08/xop/include}Include") incl.attrib["href"] = "cid:%s" % id if paramvals[i].fileName and not paramvals[i].data: paramvals[i].load_from_file() data = paramvals[i].data attachment = None try: attachment = MIMEApplication(data, _encoder=encode_7or8bit) except NameError: attachment = MIMENonMultipart("application", "octet-stream") attachment.set_payload(data) encode_7or8bit(attachment) del(attachment['mime-version']) attachment.add_header('Content-ID', '<%s>' % (id, )) mtompkg.attach(attachment) # Update SOAP envelope. soapmsg.close() soapmsg = StringIO() soaptree.write(soapmsg) rootpkg.set_payload(soapmsg.getvalue()) soapmsg.close() # extract body string from MIMEMultipart message bound = '--%s' % (mtompkg.get_boundary(), ) marray = mtompkg.as_string().split(bound) mtombody = bound mtombody += bound.join(marray[1:]) # set Content-Length mtompkg.add_header("Content-Length", str(len(mtombody))) # extract dictionary of headers from MIMEMultipart message mtomheaders = {} for name, value in mtompkg.items(): mtomheaders[name] = value if len(mtompkg.get_payload()) <= 1: return (headers, envelope) return (mtomheaders, mtombody)
def method(self, **kwargs): # Don't bother with doc string, it will be over-written by createMethod. for name in six.iterkeys(kwargs): if name not in parameters.argmap: raise TypeError('Got an unexpected keyword argument "{0!s}"'.format(name)) # Remove args that have a value of None. keys = list(kwargs.keys()) for name in keys: if kwargs[name] is None: del kwargs[name] for name in parameters.required_params: if name not in kwargs: raise TypeError('Missing required parameter "{0!s}"'.format(name)) for name, regex in six.iteritems(parameters.pattern_params): if name in kwargs: if isinstance(kwargs[name], six.string_types): pvalues = [kwargs[name]] else: pvalues = kwargs[name] for pvalue in pvalues: if re.match(regex, pvalue) is None: raise TypeError( 'Parameter "{0!s}" value "{1!s}" does not match the pattern "{2!s}"'.format( name, pvalue, regex ) ) for name, enums in six.iteritems(parameters.enum_params): if name in kwargs: # We need to handle the case of a repeated enum # name differently, since we want to handle both # arg='value' and arg=['value1', 'value2'] if name in parameters.repeated_params and not isinstance(kwargs[name], six.string_types): values = kwargs[name] else: values = [kwargs[name]] for value in values: if value not in enums: raise TypeError( 'Parameter "{0!s}" value "{1!s}" is not an allowed value in "{2!s}"'.format( name, value, str(enums) ) ) actual_query_params = {} actual_path_params = {} for key, value in six.iteritems(kwargs): to_type = parameters.param_types.get(key, "string") # For repeated parameters we cast each member of the list. if key in parameters.repeated_params and type(value) == type([]): cast_value = [_cast(x, to_type) for x in value] else: cast_value = _cast(value, to_type) if key in parameters.query_params: actual_query_params[parameters.argmap[key]] = cast_value if key in parameters.path_params: actual_path_params[parameters.argmap[key]] = cast_value body_value = kwargs.get("body", None) media_filename = kwargs.get("media_body", None) if self._developerKey: actual_query_params["key"] = self._developerKey model = self._model if methodName.endswith("_media"): model = MediaModel() elif "response" not in methodDesc: model = RawModel() headers = {} headers, params, query, body = model.request(headers, actual_path_params, actual_query_params, body_value) expanded_url = uritemplate.expand(pathUrl, params) url = _urljoin(self._baseUrl, expanded_url + query) resumable = None multipart_boundary = "" if media_filename: # Ensure we end up with a valid MediaUpload object. if isinstance(media_filename, six.string_types): (media_mime_type, encoding) = mimetypes.guess_type(media_filename) if media_mime_type is None: raise UnknownFileType(media_filename) if not mimeparse.best_match([media_mime_type], ",".join(accept)): raise UnacceptableMimeTypeError(media_mime_type) media_upload = MediaFileUpload(media_filename, mimetype=media_mime_type) elif isinstance(media_filename, MediaUpload): media_upload = media_filename else: raise TypeError("media_filename must be str or MediaUpload.") # Check the maxSize if media_upload.size() is not None and media_upload.size() > maxSize > 0: raise MediaUploadSizeError("Media larger than: {0!s}".format(maxSize)) # Use the media path uri for media uploads expanded_url = uritemplate.expand(mediaPathUrl, params) url = _urljoin(self._baseUrl, expanded_url + query) if media_upload.resumable(): url = _add_query_parameter(url, "uploadType", "resumable") if media_upload.resumable(): # This is all we need to do for resumable, if the body exists it gets # sent in the first request, otherwise an empty body is sent. resumable = media_upload else: # A non-resumable upload if body is None: # This is a simple media upload headers["content-type"] = media_upload.mimetype() body = media_upload.getbytes(0, media_upload.size()) url = _add_query_parameter(url, "uploadType", "media") else: # This is a multipart/related upload. msgRoot = MIMEMultipart("related") # msgRoot should not write out it's own headers setattr(msgRoot, "_write_headers", lambda self: None) # attach the body as one part msg = MIMENonMultipart(*headers["content-type"].split("/")) msg.set_payload(body) msgRoot.attach(msg) # attach the media as the second part msg = MIMENonMultipart(*media_upload.mimetype().split("/")) msg["Content-Transfer-Encoding"] = "binary" payload = media_upload.getbytes(0, media_upload.size()) msg.set_payload(payload) msgRoot.attach(msg) # encode the body: note that we can't use `as_string`, because # it plays games with `From ` lines. fp = BytesIO() g = _BytesGenerator(fp, mangle_from_=False) g.flatten(msgRoot, unixfrom=False) body = fp.getvalue() multipart_boundary = msgRoot.get_boundary() headers["content-type"] = ("multipart/related; " 'boundary="%s"') % multipart_boundary url = _add_query_parameter(url, "uploadType", "multipart") logger.info("URL being requested: {0!s} {1!s}".format(httpMethod, url)) return self._requestBuilder( self._http, model.response, url, method=httpMethod, body=body, headers=headers, methodId=methodId, resumable=resumable, )
def sendMMS(self,senderAddress,address,message,attachments,clientCorrelator,notifyURL,senderName,callbackData): """ sendMMS : Send an SMS from your Web application Parameters: senderAddress : MSISDN or allocated code of the sender address : MSISDN or ACR of the mobile terminal to send to message : text part of the message to send to the recipient(s) attachments : file attachments (see the Attachments class) to send as part of the MMS clientCorrelator : (string) uniquely identifies this create MMS request. If there is a communication failure during the request, using the same clientCorrelator when retrying the request allows the operator to avoid sending the same MMS twice. notifyURL : (anyURI) is the URL-escaped URL to which you would like a notification of delivery sent. The format of this notification is shown below. senderName : (string) is the name to appear on the user's terminal as the sender of the message. callbackData : (string) is any meaningful data you woul like send back in the notification, for example to identify the message or pass a function name, etc. """ baseurl=self.endpoints.getSendMMSEndpoint() requestProcessor=JSONRequest() formparameters=FormParameters() formparameters.put('senderAddress',senderAddress) if '{senderAddress}' in baseurl: baseurl=baseurl.replace('{senderAddress}',str(senderAddress)) if address is not None: for item in address: formparameters.put('address',item) formparameters.put('message',message) formparameters.put('clientCorrelator',clientCorrelator) formparameters.put('notifyURL',notifyURL) formparameters.put('senderName',senderName) formparameters.put('callbackData',callbackData) postdata=formparameters.encodeParameters() outer=MIMEMultipart() jsonpart=MIMEBase('application', 'x-www-form-urlencoded') jsonpart.set_payload(postdata) jsonpart.add_header('Content-Disposition', 'form-data; name="root-fields"') outer.attach(jsonpart) if attachments is not None: for item in attachments: if item is not None: attachmentName=item.getName() attachmentContentType=item.getContentType() attachmentData=item.getData() if attachmentData is not None: if attachmentContentType is not None: maintype, subtype = attachmentContentType.split('/', 1) msga = MIMEBase(maintype, subtype) else: msga = MIMEBase('application','octet-stream') msga.set_payload(base64.b64encode(attachmentData)) msga.add_header('Content-Disposition', 'form-data', name='attachments', filename=attachmentName) msga.add_header('Content-Transfer-Encoding', 'base64') outer.attach(msga) payload=outer.as_string(False) boundary=outer.get_boundary() trimmed=re.sub('Content-Type: multipart/mixed\\; [A-Za-z0-9\\=\\"]*','',payload) reformatted=trimmed.replace('\r\n','\n').replace('\n','\r\n') rawresponse=requestProcessor.postMultipart(baseurl,reformatted,'application/json', self.username, self.password, boundary) response=SendMMSResponse() if rawresponse is not None and rawresponse.getContent() is not None: jsondata=json.loads(rawresponse.getContent()) if jsondata is not None and jsondata['resourceReference'] is not None: response.setResourceReferenceJSON(jsondata['resourceReference']) if rawresponse.getCode() is not None: response.setHTTPResponseCode(rawresponse.getCode()) if rawresponse.getLocation() is not None: response.setLocation(rawresponse.getLocation()) if rawresponse.getContentType() is not None: response.setContentType(rawresponse.getContentType()) return response
def method(self, **kwargs): for name in kwargs.iterkeys(): if name not in argmap: raise TypeError('Got an unexpected keyword argument "%s"' % name) for name in required_params: if name not in kwargs: raise TypeError('Missing required parameter "%s"' % name) for name, regex in pattern_params.iteritems(): if name in kwargs: if re.match(regex, kwargs[name]) is None: raise TypeError( 'Parameter "%s" value "%s" does not match the pattern "%s"' % (name, kwargs[name], regex)) for name, enums in enum_params.iteritems(): if name in kwargs: if kwargs[name] not in enums: raise TypeError( 'Parameter "%s" value "%s" is not an allowed value in "%s"' % (name, kwargs[name], str(enums))) actual_query_params = {} actual_path_params = {} for key, value in kwargs.iteritems(): to_type = param_type.get(key, 'string') # For repeated parameters we cast each member of the list. if key in repeated_params and type(value) == type([]): cast_value = [_cast(x, to_type) for x in value] else: cast_value = _cast(value, to_type) if key in query_params: actual_query_params[argmap[key]] = cast_value if key in path_params: actual_path_params[argmap[key]] = cast_value body_value = kwargs.get('body', None) media_filename = kwargs.get('media_body', None) if self._developerKey: actual_query_params['key'] = self._developerKey headers = {} headers, params, query, body = self._model.request( headers, actual_path_params, actual_query_params, body_value) expanded_url = uritemplate.expand(pathUrl, params) url = urlparse.urljoin(self._baseUrl, expanded_url + query) if media_filename: (media_mime_type, encoding) = mimetypes.guess_type(media_filename) if media_mime_type is None: raise UnknownFileType(media_filename) if not mimeparse.best_match([media_mime_type], ','.join(accept)): raise UnacceptableMimeTypeError(media_mime_type) # Check the maxSize if maxSize > 0 and os.path.getsize(media_filename) > maxSize: raise MediaUploadSizeError(media_filename) # Use the media path uri for media uploads expanded_url = uritemplate.expand(mediaPathUrl, params) url = urlparse.urljoin(self._baseUrl, expanded_url + query) if body is None: headers['content-type'] = media_mime_type # make the body the contents of the file f = file(media_filename, 'rb') body = f.read() f.close() else: msgRoot = MIMEMultipart('related') # msgRoot should not write out it's own headers setattr(msgRoot, '_write_headers', lambda self: None) # attach the body as one part msg = MIMENonMultipart(*headers['content-type'].split('/')) msg.set_payload(body) msgRoot.attach(msg) # attach the media as the second part msg = MIMENonMultipart(*media_mime_type.split('/')) msg['Content-Transfer-Encoding'] = 'binary' f = file(media_filename, 'rb') msg.set_payload(f.read()) f.close() msgRoot.attach(msg) body = msgRoot.as_string() # must appear after the call to as_string() to get the right boundary headers['content-type'] = ( 'multipart/related; ' 'boundary="%s"') % msgRoot.get_boundary() logging.info('URL being requested: %s' % url) return self._requestBuilder(self._http, self._model.response, url, method=httpMethod, body=body, headers=headers, methodId=methodId)
def method(self, **kwargs): # Don't bother with doc string, it will be over-written by createMethod. for name in kwargs.iterkeys(): if name not in parameters.argmap: raise TypeError('Got an unexpected keyword argument "%s"' % name) # Remove args that have a value of None. keys = kwargs.keys() for name in keys: if kwargs[name] is None: del kwargs[name] for name in parameters.required_params: if name not in kwargs: raise TypeError('Missing required parameter "%s"' % name) for name, regex in parameters.pattern_params.iteritems(): if name in kwargs: if isinstance(kwargs[name], basestring): pvalues = [kwargs[name]] else: pvalues = kwargs[name] for pvalue in pvalues: if re.match(regex, pvalue) is None: raise TypeError( 'Parameter "%s" value "%s" does not match the pattern "%s"' % (name, pvalue, regex)) for name, enums in parameters.enum_params.iteritems(): if name in kwargs: # We need to handle the case of a repeated enum # name differently, since we want to handle both # arg='value' and arg=['value1', 'value2'] if (name in parameters.repeated_params and not isinstance(kwargs[name], basestring)): values = kwargs[name] else: values = [kwargs[name]] for value in values: if value not in enums: raise TypeError( 'Parameter "%s" value "%s" is not an allowed value in "%s"' % (name, value, str(enums))) actual_query_params = {} actual_path_params = {} for key, value in kwargs.iteritems(): to_type = parameters.param_types.get(key, 'string') # For repeated parameters we cast each member of the list. if key in parameters.repeated_params and type(value) == type([]): cast_value = [_cast(x, to_type) for x in value] else: cast_value = _cast(value, to_type) if key in parameters.query_params: actual_query_params[parameters.argmap[key]] = cast_value if key in parameters.path_params: actual_path_params[parameters.argmap[key]] = cast_value body_value = kwargs.get('body', None) media_filename = kwargs.get('media_body', None) if self._developerKey: actual_query_params['key'] = self._developerKey model = self._model if methodName.endswith('_media'): model = MediaModel() elif 'response' not in methodDesc: model = RawModel() headers = {} headers, params, query, body = model.request(headers, actual_path_params, actual_query_params, body_value) expanded_url = uritemplate.expand(pathUrl, params) url = urlparse.urljoin(self._baseUrl, expanded_url + query) resumable = None multipart_boundary = '' if media_filename: # Ensure we end up with a valid MediaUpload object. if isinstance(media_filename, basestring): (media_mime_type, encoding) = mimetypes.guess_type(media_filename) if media_mime_type is None: raise UnknownFileType(media_filename) if not mimeparse.best_match([media_mime_type], ','.join(accept)): raise UnacceptableMimeTypeError(media_mime_type) media_upload = MediaFileUpload(media_filename, mimetype=media_mime_type) elif isinstance(media_filename, MediaUpload): media_upload = media_filename else: raise TypeError('media_filename must be str or MediaUpload.') # Check the maxSize if maxSize > 0 and media_upload.size() > maxSize: raise MediaUploadSizeError("Media larger than: %s" % maxSize) # Use the media path uri for media uploads expanded_url = uritemplate.expand(mediaPathUrl, params) url = urlparse.urljoin(self._baseUrl, expanded_url + query) if media_upload.resumable(): url = _add_query_parameter(url, 'uploadType', 'resumable') if media_upload.resumable(): # This is all we need to do for resumable, if the body exists it gets # sent in the first request, otherwise an empty body is sent. resumable = media_upload else: # A non-resumable upload if body is None: # This is a simple media upload headers['content-type'] = media_upload.mimetype() body = media_upload.getbytes(0, media_upload.size()) url = _add_query_parameter(url, 'uploadType', 'media') else: # This is a multipart/related upload. msgRoot = MIMEMultipart('related') # msgRoot should not write out it's own headers setattr(msgRoot, '_write_headers', lambda self: None) # attach the body as one part msg = MIMENonMultipart(*headers['content-type'].split('/')) msg.set_payload(body) msgRoot.attach(msg) # attach the media as the second part msg = MIMENonMultipart(*media_upload.mimetype().split('/')) msg['Content-Transfer-Encoding'] = 'binary' payload = media_upload.getbytes(0, media_upload.size()) msg.set_payload(payload) msgRoot.attach(msg) body = msgRoot.as_string() multipart_boundary = msgRoot.get_boundary() headers['content-type'] = ('multipart/related; ' 'boundary="%s"') % multipart_boundary url = _add_query_parameter(url, 'uploadType', 'multipart') logger.info('URL being requested: %s %s' % (httpMethod,url)) return self._requestBuilder(self._http, model.response, url, method=httpMethod, body=body, headers=headers, methodId=methodId, resumable=resumable)
def apply_mtom(headers, envelope, params, paramvals): """ Apply MTOM to a SOAP envelope, separating attachments into a MIME multipart message. References: XOP http://www.w3.org/TR/xop10/ MTOM http://www.w3.org/TR/soap12-mtom/ http://www.w3.org/Submission/soap11mtom10/ @param headers Headers dictionary of the SOAP message that would originally be sent. @param envelope SOAP envelope string that would have originally been sent. @param params params attribute from the Message object used for the SOAP @param paramvals values of the params, passed to Message.to_xml @return tuple of length 2 with dictionary of headers and string of body that can be sent with HTTPConnection """ # grab the XML element of the message in the SOAP body soaptree = etree.fromstring(envelope) soapbody = soaptree.find("{%s}Body" % soaplib.ns_soap_env) message = None for child in list(soapbody): if child.tag != "%sFault" % (soaplib.ns_soap_env,): message = child break # Get additional parameters from original Content-Type ctarray = [] for n, v in headers.items(): if n.lower() == "content-type": ctarray = v.split(";") break roottype = ctarray[0].strip() rootparams = {} for ctparam in ctarray[1:]: n, v = ctparam.strip().split("=") rootparams[n] = v.strip("\"'") # Set up initial MIME parts. mtompkg = MIMEMultipart("related", boundary="?//<><>soaplib_MIME_boundary<>") rootpkg = None try: rootpkg = MIMEApplication(envelope, "xop+xml", encode_7or8bit) except NameError: rootpkg = MIMENonMultipart("application", "xop+xml") rootpkg.set_payload(envelope) encode_7or8bit(rootpkg) # Set up multipart headers. del (mtompkg["mime-version"]) mtompkg.set_param("start-info", roottype) mtompkg.set_param("start", "<soaplibEnvelope>") if "SOAPAction" in headers: mtompkg.add_header("SOAPAction", headers.get("SOAPAction")) # Set up root SOAP part headers. del (rootpkg["mime-version"]) rootpkg.add_header("Content-ID", "<soaplibEnvelope>") for n, v in rootparams.items(): rootpkg.set_param(n, v) rootpkg.set_param("type", roottype) mtompkg.attach(rootpkg) # Extract attachments from SOAP envelope. for i in range(len(params)): name, typ = params[i] if typ == Attachment: id = "soaplibAttachment_%s" % (len(mtompkg.get_payload()),) param = message[i] param.text = "" incl = etree.SubElement(param, "{%s}Include" % soaplib.ns_xop) incl.attrib["href"] = "cid:%s" % id if paramvals[i].fileName and not paramvals[i].data: paramvals[i].load_from_file() data = paramvals[i].data attachment = None try: attachment = MIMEApplication(data, _encoder=encode_7or8bit) except NameError: attachment = MIMENonMultipart("application", "octet-stream") attachment.set_payload(data) encode_7or8bit(attachment) del (attachment["mime-version"]) attachment.add_header("Content-ID", "<%s>" % (id,)) mtompkg.attach(attachment) # Update SOAP envelope. rootpkg.set_payload(etree.tostring(soaptree)) # extract body string from MIMEMultipart message bound = "--%s" % (mtompkg.get_boundary(),) marray = mtompkg.as_string().split(bound) mtombody = bound mtombody += bound.join(marray[1:]) # set Content-Length mtompkg.add_header("Content-Length", str(len(mtombody))) # extract dictionary of headers from MIMEMultipart message mtomheaders = {} for name, value in mtompkg.items(): mtomheaders[name] = value if len(mtompkg.get_payload()) <= 1: return (headers, envelope) return (mtomheaders, mtombody)