Skip to content

Commit

Permalink
Enable response mimetype validation for non-error responses (#1654)
Browse files Browse the repository at this point in the history
Addresses a todo left in the code.

We only validate the mimetype if the spec defines which mime-types it
produces, and only for non-error responses.
  • Loading branch information
RobbeSneyders committed Mar 2, 2023
1 parent 50cfc83 commit b28cf09
Show file tree
Hide file tree
Showing 6 changed files with 23 additions and 11 deletions.
2 changes: 1 addition & 1 deletion connexion/decorators/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def _deduct_content_type(data: t.Any, headers: dict) -> str:
if not produces:
# Produces can be empty/ for empty responses
pass
if len(produces) == 1:
elif len(produces) == 1:
content_type = produces[0]
elif isinstance(data, str) and "text/plain" in produces:
content_type = "text/plain"
Expand Down
5 changes: 4 additions & 1 deletion connexion/middleware/request_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,13 @@ def extract_content_type(
return mime_type, encoding

def validate_mime_type(self, mime_type: str) -> None:
"""Validate the mime type against the spec.
"""Validate the mime type against the spec if it defines which mime types are accepted.
:param mime_type: mime type from content type header
"""
if not self._operation.consumes:
return

# Convert to MediaTypeDict to handle media-ranges
media_type_dict = MediaTypeDict(
[(c.lower(), None) for c in self._operation.consumes]
Expand Down
19 changes: 14 additions & 5 deletions connexion/middleware/response_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from starlette.types import ASGIApp, Receive, Scope, Send

from connexion import utils
from connexion.datastructures import MediaTypeDict
from connexion.exceptions import NonConformingResponseHeaders
from connexion.middleware.abstract import RoutedAPI, RoutedMiddleware
from connexion.operations import AbstractOperation
Expand Down Expand Up @@ -50,11 +51,17 @@ def extract_content_type(
return mime_type, encoding

def validate_mime_type(self, mime_type: str) -> None:
"""Validate the mime type against the spec.
"""Validate the mime type against the spec if it defines which mime types are produced.
:param mime_type: mime type from content type header
"""
if mime_type.lower() not in [c.lower() for c in self._operation.produces]:
if not self._operation.produces:
return

media_type_dict = MediaTypeDict(
[(p.lower(), None) for p in self._operation.produces]
)
if mime_type.lower() not in media_type_dict:
raise NonConformingResponseHeaders(
detail=f"Invalid Response Content-type ({mime_type}), "
f"expected {self._operation.produces}",
Expand Down Expand Up @@ -83,11 +90,13 @@ async def wrapped_send(message: t.MutableMapping[str, t.Any]) -> None:
nonlocal send

if message["type"] == "http.response.start":
status = str(message["status"])
headers = message["headers"]

mime_type, encoding = self.extract_content_type(headers)
# TODO: Add produces to all tests and fix response content types
# self.validate_mime_type(mime_type)
if message["status"] < 400:
self.validate_mime_type(mime_type)

status = str(message["status"])
response_definition = self._operation.response_definition(
status, mime_type
)
Expand Down
4 changes: 2 additions & 2 deletions connexion/operations/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def __init__(
response_content_types = []
for _, defn in self._responses.items():
response_content_types += defn.get("content", {}).keys()
self._produces = response_content_types or ["application/json"]
self._produces = response_content_types
self._consumes = None

logger.debug("consumes: %s" % self.consumes)
Expand Down Expand Up @@ -128,7 +128,7 @@ def parameters(self):
def consumes(self):
if self._consumes is None:
request_content = self.request_body.get("content", {})
self._consumes = list(request_content.keys()) or ["application/json"]
self._consumes = list(request_content.keys())
return self._consumes

@property
Expand Down
2 changes: 1 addition & 1 deletion tests/api/test_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ def test_get_bad_default_response(simple_app):
def test_streaming_response(simple_app):
app_client = simple_app.test_client()
resp = app_client.get("/v1.0/get_streaming_response")
assert resp.status_code == 200
assert resp.status_code == 200, resp.text


def test_oneof(simple_openapi_app):
Expand Down
2 changes: 1 addition & 1 deletion tests/fixtures/simple/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1269,7 +1269,7 @@ paths:
'200':
description: OK
content:
application/octet-stream:
text/x-python:
schema:
type: string
format: binary
Expand Down

0 comments on commit b28cf09

Please sign in to comment.