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

werkzeug.test.stream_encode_multipart raises ValueError if the data contains empty stream #2740

Closed
pandabear opened this issue Jun 27, 2023 · 3 comments
Milestone

Comments

@pandabear
Copy link

This was discovered in tests pipeline upon upgrading to latest Flask and during pytest run. FlaskClient was used to upload an empty bytes file with something like

resp = test_client.post(
    '/my/route',
    data={'file': (b'', 'test.json')}
)

It will trigger a ValueError with the following stack trace:

venv39/lib64/python3.9/site-packages/flask/testing.py:184: in _request_from_builder_args
    return builder.get_request()
venv39/lib64/python3.9/site-packages/werkzeug/test.py:795: in get_request
    return cls(self.get_environ())
venv39/lib64/python3.9/site-packages/werkzeug/test.py:719: in get_environ
    input_stream, content_length, boundary = stream_encode_multipart(
venv39/lib64/python3.9/site-packages/werkzeug/test.py:131: in stream_encode_multipart
    encoder.send_event(

self = <werkzeug.sansio.multipart.MultipartEncoder object at 0x7f23c813b220>, event = File(name='info', filename='test.json', headers=Headers([('Content-Type', 'application/json')]))

    def send_event(self, event: Event) -> bytes:
        if isinstance(event, Preamble) and self.state == State.PREAMBLE:
            self.state = State.PART
            return event.data
        elif isinstance(event, (Field, File)) and self.state in {
            State.PREAMBLE,
            State.PART,
            State.DATA,
        }:
            data = b"\r\n--" + self.boundary + b"\r\n"
            data += b'Content-Disposition: form-data; name="%s"' % event.name.encode()
            if isinstance(event, File):
                data += b'; filename="%s"' % event.filename.encode()
            data += b"\r\n"
            for name, value in t.cast(Field, event).headers:
                if name.lower() != "content-disposition":
                    data += f"{name}: {value}\r\n".encode()
            self.state = State.DATA_START
            return data
        elif isinstance(event, Data) and self.state == State.DATA_START:
            self.state = State.DATA
            if len(event.data) > 0:
                return b"\r\n" + event.data
            else:
                return event.data
        elif isinstance(event, Data) and self.state == State.DATA:
            return event.data
        elif isinstance(event, Epilogue):
            self.state = State.COMPLETE
            return b"\r\n--" + self.boundary + b"--\r\n" + event.data
        else:
>           raise ValueError(f"Cannot generate {event} in state: {self.state}")
E           ValueError: Cannot generate File(name='info', filename='test.json', headers=Headers([('Content-Type', 'application/json')])) in state: State.DATA_START

venv39/lib64/python3.9/site-packages/werkzeug/sansio/multipart.py:313: ValueError

I would've expected that an empty file could've been uploaded as multipart just fine.

Upon further examination, the cause seem to be this line:

while True:
chunk = reader(16384)
if not chunk:
break
write_binary(encoder.send_event(Data(data=chunk, more_data=True)))
. The encoder's state never gets updated from State.DATA_START, since the chunk always returns Falsey and thus never gets to conduct the necessary encoder.send_event-call.

So importing from .sansio.multipart import State and the modifying the line if not chunk: -> if not chunk and encoder.state != State.DATA_START: seemed to have solved this.

Environment:

  • Python version: 3.9
  • Werkzeug version: 2.3.6
@davidism
Copy link
Member

Happy to review a PR

@davidism davidism added this to the 2.3.7 milestone Jun 27, 2023
pandabear pushed a commit to pandabear/werkzeug that referenced this issue Jun 28, 2023
pandabear pushed a commit to pandabear/werkzeug that referenced this issue Jun 28, 2023
pandabear pushed a commit to pandabear/werkzeug that referenced this issue Jun 28, 2023
pandabear pushed a commit to pandabear/werkzeug that referenced this issue Jun 28, 2023
pandabear pushed a commit to pandabear/werkzeug that referenced this issue Jun 28, 2023
pandabear pushed a commit to pandabear/werkzeug that referenced this issue Jun 28, 2023
pandabear pushed a commit to pandabear/werkzeug that referenced this issue Jun 28, 2023
@pandabear
Copy link
Author

PR created here: #2742 !

benmwebb added a commit to salilab/multifoxs that referenced this issue Jul 27, 2023
Current werkzeug fails if given an empty file as
input; hold at 2.2 for now. See also
pallets/werkzeug#2740.
benmwebb added a commit to salilab/foxsdock that referenced this issue Jul 27, 2023
Current werkzeug fails if given an empty file as
input; hold at 2.2 for now. See also
pallets/werkzeug#2740.
benmwebb added a commit to salilab/multifit-web that referenced this issue Jul 27, 2023
Current werkzeug fails if given an empty file as
input; hold at 2.2 for now. See also
pallets/werkzeug#2740.
@pgjones
Copy link
Member

pgjones commented Aug 12, 2023

Closed by #2765.

@pgjones pgjones closed this as completed Aug 12, 2023
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 27, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants