Skip to content

[python] Support binary data in body #10861

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
zerodayz opened this issue Jan 19, 2021 · 1 comment
Open

[python] Support binary data in body #10861

zerodayz opened this issue Jan 19, 2021 · 1 comment

Comments

@zerodayz
Copy link

zerodayz commented Jan 19, 2021

Description

API expects data send as application/octet-stream in the body of the request.

Swagger-codegen version

latest, it has never been working.

Swagger declaration file content or url

There are body sanitization checks that do not allow application/octet-stream

Command line used for generation

java -jar swagger-codegen-latest.jar generate -i <path to openapi spec> -l python

Steps to reproduce
  1. Generate api_client.py and rest.py as part of generate command.
  2. Try to use API that accepts Content-Type: application/octet-stream and send data in body
Related issues/PRs

There is similar problem reported on JAVA language as well #669

Suggest a fix/enhancement

At the moment we don't have any working patch.

@zerodayz
Copy link
Author

The files I am using is our own project that uses swagger-codegen, and it is only to help you understand where the problem is:

We sanitize body here:

https://github.com/unofficial-memsource/memsource-cli-client/blob/master/memsource_cli/api_client.py#L151-L152

        # body
        if body:
            body = self.sanitize_for_serialization(body)

Which checks body content here:

https://github.com/unofficial-memsource/memsource-cli-client/blob/master/memsource_cli/api_client.py#L180-L221

    def sanitize_for_serialization(self, obj):
        """Builds a JSON POST object.
        If obj is None, return None.
        If obj is str, int, long, float, bool, return directly.
        If obj is datetime.datetime, datetime.date
            convert to string in iso8601 format.
        If obj is list, sanitize each element in the list.
        If obj is dict, return the dict.
        If obj is swagger model, return the properties dict.
        :param obj: The data to serialize.
        :return: The serialized form of data.
        """
        if obj is None:
            return None
        elif isinstance(obj, self.PRIMITIVE_TYPES):
            return obj
        elif isinstance(obj, list):
            return [self.sanitize_for_serialization(sub_obj)
                    for sub_obj in obj]
        elif isinstance(obj, tuple):
            return tuple(self.sanitize_for_serialization(sub_obj)
                         for sub_obj in obj)
        elif isinstance(obj, (datetime.datetime, datetime.date)):
            return obj.isoformat()

        if isinstance(obj, dict):
            obj_dict = obj
        else:
            # Convert model obj to dict except
            # attributes `swagger_types`, `attribute_map`
            # and attributes which value is not None.
            # Convert attribute name to json key in
            # model definition for request.
            obj_dict = {obj.attribute_map[attr]: getattr(obj, attr)
                        for attr, _ in six.iteritems(obj.swagger_types)
                        if getattr(obj, attr) is not None}

        return {key: self.sanitize_for_serialization(val)
                for key, val in six.iteritems(obj_dict)}

This wouldn't allow for data in body.

Also this will also needs to be fixed in rest.py, here we would normally fail with ApiException as unknown Content-Type:

https://github.com/unofficial-memsource/memsource-cli-client/blob/master/memsource_cli/rest.py#L153-L204

      try:
           # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE`
           if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']:
               if query_params:
                   url += '?' + urlencode(query_params)
               if re.search('json', headers['Content-Type'], re.IGNORECASE):
                   request_body = None
                   if body is not None:
                       request_body = json.dumps(body)
                   r = self.pool_manager.request(
                       method, url,
                       body=request_body,
                       preload_content=_preload_content,
                       timeout=timeout,
                       headers=headers)
               elif headers['Content-Type'] == 'application/x-www-form-urlencoded':  # noqa: E501
                   r = self.pool_manager.request(
                       method, url,
                       fields=post_params,
                       encode_multipart=False,
                       preload_content=_preload_content,
                       timeout=timeout,
                       headers=headers)
               elif headers['Content-Type'] == 'multipart/form-data':
                   # must del headers['Content-Type'], or the correct
                   # Content-Type which generated by urllib3 will be
                   # overwritten.
                   del headers['Content-Type']
                   r = self.pool_manager.request(
                       method, url,
                       fields=post_params,
                       encode_multipart=True,
                       preload_content=_preload_content,
                       timeout=timeout,
                       headers=headers)
               # Pass a `string` parameter directly in the body to support
               # other content types than Json when `body` argument is
               # provided in serialized form
               elif isinstance(body, str):
                   request_body = body
                   r = self.pool_manager.request(
                       method, url,
                       body=request_body,
                       preload_content=_preload_content,
                       timeout=timeout,
                       headers=headers)
               else:
                   # Cannot generate the request from given parameters
                   msg = """Cannot prepare a request message for provided
                            arguments. Please check that your arguments match
                            declared content type."""
                   raise ApiException(status=0, reason=msg)

And we also need to fix it in https://github.com/unofficial-memsource/memsource-cli-client/blob/master/memsource_cli/rest.py#L270-L278

  def POST(self, url, headers=None, query_params=None, post_params=None,
            body=None, _preload_content=True, _request_timeout=None):
       return self.request("POST", url,
                           headers=headers,
                           query_params=query_params,
                           post_params=post_params,
                           _preload_content=_preload_content,
                           _request_timeout=_request_timeout,
                           body=body)

It's just an idea, I think i might have missed something.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant