Skip to content

Fix/multiple content type headers lead to 415 consistently #2070

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
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

Lewiscowles1986
Copy link

@Lewiscowles1986 Lewiscowles1986 commented Jul 25, 2025

I'm pleased to submit a pull request that I believe fixes #2040 on GitHub. The proposed changes aim to improve consistency of the behavior of content-type header inspection in Connexion.

Key modifications include:

  • Ensuring that searching for content types does not short-circuit, aligning with other header inspections.
  • Adding a test to safeguard against potential regressions.

The reason this fix is effective (tested in an interactive debugger with multiple clients and original tests) is that
the modified method was previously returning 'application/json', while subsequent implementations downstream returned
'application/json,application/json'. This resulted in three parts when splitting by '/' instead of two, which caused
issues.

I did investigate alternative approaches to fixing the '/'-split issue mentioned in the original report. However, this
led me to modify more code, which didn't resolve the 500 error and 415 response from Safari. I opted for a more precise
approach that focused on the specific method modification, as it yielded better results.

Please let me know if you have any questions or concerns about this pull request.

@chrisinmtown
Copy link
Contributor

Took me too long to see the whole picture here. Tell me if I describe the current request processing correctly:

  • something 1 calls extract_content_type which stops at the first Content-Type and returns it; in this test, the value is OK and processing continues.
  • something 2 calls a different function (?) that extracts the two Content-Type values, builds a CSV list, then calls is_json_mimetype; in this test, the value is NOT OK, the function blows up

If I have that right, I think that means there are at least two functions that extract the content type?

Finally returning to this PR, it changes extract_content_type to gather all Content-Type values and return a list; in this test case, the "something 1" caller validates the CSV in the result, doesn't like it, rejects with 415. And "something 2" never is reached.

@Lewiscowles1986
Copy link
Author

Hi @chrisinmtown you 100% got it 💯

Apologies I had not made this clearer. My storytelling chip is clearly on the fritz.
So this is about aligning the outputs to be the same

@chrisinmtown
Copy link
Contributor

This may be a case of belt-and-suspenders, but please hear me out. Would you consider including this minor change to the is_json_mimetype function to ensure the split never gives back more than 2 values:

diff --git a/connexion/utils.py b/connexion/utils.py
index 5458ca8..fa5f0d6 100644
--- a/connexion/utils.py
+++ b/connexion/utils.py
@@ -158,7 +158,7 @@ def is_json_mimetype(mimetype):
     if mimetype is None:
         return False
 
-    maintype, subtype = mimetype.split("/")  # type: str, str
+    maintype, subtype = mimetype.split("/", maxsplit=1)  # type: str, str
     if ";" in subtype:
         subtype, parameter = subtype.split(";", maxsplit=1)
     return maintype == "application" and (

@chrisinmtown
Copy link
Contributor

I agree that this PR makes Connexion behave similarly to how Flask/Werkzeug handles multiple Content-Type headers. For me the interesting question is, why does the Connexion code re-implement a function to fetch Content-Type from a request? Why can't it just reuse a Flask/Werkzeug-provided value, and drop the problematic code patched by this PR? I have not yet figured that out.

@Lewiscowles1986
Copy link
Author

@chrisinmtown I think that because Connexion uses multiple potential backends, one Starlette and one Flask, this is what leads to the situation.

@Lewiscowles1986
Copy link
Author

@chrisinmtown can I ask if we think that

% python3
Python 3.13.3 (main, Apr  8 2025, 13:54:08) [Clang 16.0.0 (clang-1600.0.26.6)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> "a;b;c".split(";", maxsplit=1)
['a', 'b;c']
>>>

might cause other issues?

@chrisinmtown
Copy link
Contributor

might cause other issues?

Of course it might :) Still, that line of code is inherently fragile, it assumes the split will always yield a list of exactly length 2. Adding the maxsplit parameter makes the function more robust, for when the next clever person thinks of sending a malformed content-type value "foo/bar/baz" or what have you.

@Lewiscowles1986
Copy link
Author

So it looks like I was confusing the charset detection, from the mime and subtype. 😊

Change made, and I've changed tests to use pytest.mark.parametrize.

If one mime-type check fails, full visibility about impact of the other cases is there. I believe this is a developer experience win, but I'm open to feedback if this is less clear.

@chrisinmtown
Copy link
Contributor

LGTM. Now we wait for the maintainers to notice this PR.

Copy link
Member

@Ruwann Ruwann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite see what you mean with "while subsequent implementations downstream returned
'application/json,application/json'. "
Can we instead fix those?



def test_multiple_json_content_type(json_validation_spec_dir, spec):
"""ensure that defaults applied that modify the body"""
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring is the one from the test above, it should be about multiple content type headers?

class MyDefaultsJSONBodyValidator(DefaultsJSONRequestBodyValidator):
pass

validator_map = {"body": {"application/json": MyDefaultsJSONBodyValidator}}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to update the custom body validator in this test

content_type = value
break
value = value.decode("latin-1")
content_type = ",".join([content_type, value] if content_type else [value])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be only one content-type header. If there are multiple, I believe it is more appropriate to raise a BadRequest error to return a 400 status code

@pytest.mark.parametrize(
"mime_type",
[
"application/json,application/json",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a valid mimetype

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

Successfully merging this pull request may close these issues.

Passing in invalid headers to an API causes 500 error
3 participants