Skip to content

[bug] gunicorn re-chunks responses which were already chunked #3322

@twitchyliquid64

Description

@twitchyliquid64

Heya! I think I found a bug in gunicorn's handling of responses which were already chunked.

The issue

If a handler upstream to gunicorn:

  1. Does not set Content-Length
  2. Sets Transfer-Encoding: chunked
  3. Streams back data which is already chunked (i.e. response data is already framed as <length>\r\n<data>\r\n)

gunicorn wraps the already-chunked content in chunks, corrupting the data stream with erroneous framing.

Suspected code

is_chunked is used to turn on gunicorn's own chunking, but does not consider if the response is already chunked.

def is_chunked(self):
# Only use chunked responses when the client is
# speaking HTTP/1.1 or newer and there was
# no Content-Length header set.
if self.response_length is not None:
return False
elif self.req.version <= (1, 0):
return False
elif self.req.method == 'HEAD':
# Responses to a HEAD request MUST NOT contain a response body.
return False
elif self.status_code in (204, 304):
# Do not use chunked responses when the response is guaranteed to
# not have a response body.
return False
return True

Repro steps

A flask app which streams its own response like this. Works fine with other WSGI wrappers:

@some_blueprint.route('/stream', methods=['POST'])
def stream():
  r = Response(chunk_generator(), content_type='application/chunked-json')
  r.automatically_set_content_length = False
  r.direct_passthrough = True
  r.headers['Transfer-Encoding'] = 'chunked'
  return r

def chunk_generator() -> Generator[bytes, None, None]:
  for x in range(50):
    c = wire_chunk('some bytes'.encode('utf-8'))
    yield c
  yield '0\r\n\r\n'.encode('utf-8') # Symbolizes end of stream

def wire_chunk(data: bytes) -> bytes:
    data_len = len(data)
    return f'{data_len:x}'.encode('utf-8') + '\r\n'.encode('utf-8') + data + '\r\n'.encode('utf-8')

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions