Skip to content
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

environ['CONTENT_LENGTH'] should be str #23

Open
lemon24 opened this issue Jun 8, 2023 · 0 comments
Open

environ['CONTENT_LENGTH'] should be str #23

lemon24 opened this issue Jun 8, 2023 · 0 comments

Comments

@lemon24
Copy link

lemon24 commented Jun 8, 2023

environ['CONTENT_LENGTH'] should be str, because PEP 3333 says:

It is a violation of this specification for any CGI variable’s value to be of any type other than str.

WSGIAdapter sets it to an int, which causes recent versions of Werkzeug to fail (example below), likely because of stricter number parsing introduced in 2.3.5:

When parsing numbers in HTTP request headers such as Content-Length, only ASCII digits are accepted rather than any format that Python’s int and float accept.

repro.py:

from flask import Flask, request
import requests
from wsgiadapter import WSGIAdapter

app = Flask(__name__)
app.config['DEBUG'] = True

@app.route("/", methods=['POST'])
def hello_world():
    print('in hello_world')
    request.form['key']
    return 'ok'
    
session = requests.Session()
session.mount('http://app/', WSGIAdapter(app))

print(session.post('http://app/', data={'key': 'value'}))

Environment:

$ python -V
Python 3.11.1
$ pip install flask==2.3.2 werkzeug==2.3.5 requests==2.31.0 requests-wsgi-adapter==0.4.1
...
Output (click to expand):
$ python repro.py 
in hello_world
Traceback (most recent call last):
  File ".../repro.py", line 17, in <module>
    print(session.post('http://app/', data={'key': 'value'}))
  File ".../.venv/lib/python3.11/site-packages/requests/sessions.py", line 637, in post
    return self.request("POST", url, data=data, json=json, **kwargs)
  File ".../.venv/lib/python3.11/site-packages/requests/sessions.py", line 589, in request
    resp = self.send(prep, **send_kwargs)
  File ".../.venv/lib/python3.11/site-packages/requests/sessions.py", line 703, in send
    r = adapter.send(request, **kwargs)
  File ".../.venv/lib/python3.11/site-packages/wsgiadapter.py", line 163, in send
    response.raw = Content(b''.join(self.app(environ, start_response)))
  File ".../.venv/lib/python3.11/site-packages/flask/app.py", line 2213, in __call__
    return self.wsgi_app(environ, start_response)
  File ".../.venv/lib/python3.11/site-packages/flask/app.py", line 2193, in wsgi_app
    response = self.handle_exception(e)
  File ".../.venv/lib/python3.11/site-packages/flask/app.py", line 2190, in wsgi_app
    response = self.full_dispatch_request()
  File ".../.venv/lib/python3.11/site-packages/flask/app.py", line 1486, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File ".../.venv/lib/python3.11/site-packages/flask/app.py", line 1484, in full_dispatch_request
    rv = self.dispatch_request()
  File ".../.venv/lib/python3.11/site-packages/flask/app.py", line 1469, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
  File ".../repro.py", line 11, in hello_world
    request.form['key']
  File ".../.venv/lib/python3.11/site-packages/werkzeug/utils.py", line 106, in __get__
    value = self.fget(obj)  # type: ignore
  File ".../.venv/lib/python3.11/site-packages/werkzeug/wrappers/request.py", line 450, in form
    self._load_form_data()
  File ".../.venv/lib/python3.11/site-packages/flask/wrappers.py", line 114, in _load_form_data
    super()._load_form_data()
  File ".../.venv/lib/python3.11/site-packages/werkzeug/wrappers/request.py", line 275, in _load_form_data
    self._get_stream_for_parsing(),
  File ".../.venv/lib/python3.11/site-packages/werkzeug/wrappers/request.py", line 302, in _get_stream_for_parsing
    return self.stream
  File ".../.venv/lib/python3.11/site-packages/werkzeug/utils.py", line 106, in __get__
    value = self.fget(obj)  # type: ignore
  File ".../.venv/lib/python3.11/site-packages/werkzeug/wrappers/request.py", line 354, in stream
    return get_input_stream(
  File ".../.venv/lib/python3.11/site-packages/werkzeug/wsgi.py", line 175, in get_input_stream
    content_length = get_content_length(environ)
  File ".../.venv/lib/python3.11/site-packages/werkzeug/wsgi.py", line 129, in get_content_length
    return _sansio_utils.get_content_length(
  File ".../.venv/lib/python3.11/site-packages/werkzeug/sansio/utils.py", line 157, in get_content_length
    return max(0, _plain_int(http_content_length))
  File ".../.venv/lib/python3.11/site-packages/werkzeug/_internal.py", line 325, in _plain_int
    if _plain_int_re.fullmatch(value) is None:
TypeError: expected string or bytes-like object, got 'int'

This workaround shows that CONTENT_LENGTH being set to a string fixes it:

class WSGIAdapter(WSGIAdapter):
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        original_app = self.app
        
        def new_app(environ, start_response):
            environ['CONTENT_LENGTH'] = str(environ['CONTENT_LENGTH'])
            return original_app(environ, start_response)
        
        self.app = new_app
$ python repro.py
in hello_world
<Response [200]>
lemon24 added a commit to lemon24/reader that referenced this issue Jun 8, 2023
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