Example #1
0
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))
Example #2
0
  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)
Example #3
0
  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)
Example #4
0
    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 
Example #5
0
    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)
Example #6
0
  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)
Example #7
0
 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
Example #8
0
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
Example #9
0
  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))
Example #10
0
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)]
Example #11
0
    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'])
Example #12
0
    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
Example #14
0
    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'])
Example #15
0
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
Example #16
0
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) ]
Example #17
0
    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
Example #18
0
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()	
Example #19
0
  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)
Example #20
0
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)
Example #22
0
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()
Example #23
0
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()
Example #24
0
    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)
Example #25
0
File: soap.py Project: caot/soaplib
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)
Example #26
0
    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
Example #27
0
    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)])
Example #28
0
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
Example #29
0
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
Example #30
0
        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,
            )
Example #31
0
    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)
Example #32
0
File: mime.py Project: Bernie/spyne
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])
Example #33
0
        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)
Example #35
0
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]
Example #36
0
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
Example #37
0
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()
Example #38
0
    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)
Example #39
0
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,
        )
Example #41
0
	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
Example #42
0
        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)
Example #44
0
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)