Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions python-multipart-master/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Changelog

## 0.0.21 (2025-12-17)

* Add support for Python 3.14 and drop EOL 3.8 and 3.9 [#216](https://github.com/Kludex/python-multipart/pull/216).

## 0.0.20 (2024-12-16)

* Handle messages containing only end boundary [#142](https://github.com/Kludex/python-multipart/pull/142).

## 0.0.19 (2024-11-30)

* Don't warn when CRLF is found after last boundary on `MultipartParser` [#193](https://github.com/Kludex/python-multipart/pull/193).

## 0.0.18 (2024-11-28)

* Hard break if found data after last boundary on `MultipartParser` [#189](https://github.com/Kludex/python-multipart/pull/189).

## 0.0.17 (2024-10-31)

* Handle PermissionError in fallback code for old import name [#182](https://github.com/Kludex/python-multipart/pull/182).

## 0.0.16 (2024-10-27)

* Add dunder attributes to `multipart` package [#177](https://github.com/Kludex/python-multipart/pull/177).

## 0.0.15 (2024-10-27)

* Replace `FutureWarning` to `PendingDeprecationWarning` [#174](https://github.com/Kludex/python-multipart/pull/174).
* Add missing files to SDist [#171](https://github.com/Kludex/python-multipart/pull/171).

## 0.0.14 (2024-10-24)

* Fix import scheme for `multipart` module ([#168](https://github.com/Kludex/python-multipart/pull/168)).

## 0.0.13 (2024-10-20)

* Rename import to `python_multipart` [#166](https://github.com/Kludex/python-multipart/pull/166).

## 0.0.12 (2024-09-29)

* Improve error message when boundary character does not match [#124](https://github.com/Kludex/python-multipart/pull/124).
* Add mypy strict typing [#140](https://github.com/Kludex/python-multipart/pull/140).
* Enforce 100% coverage [#159](https://github.com/Kludex/python-multipart/pull/159).

## 0.0.11 (2024-09-28)

* Improve performance, especially in data with many CR-LF [#137](https://github.com/Kludex/python-multipart/pull/137).
* Handle invalid CRLF in header name [#141](https://github.com/Kludex/python-multipart/pull/141).

## 0.0.10 (2024-09-21)

* Support `on_header_begin` [#103](https://github.com/Kludex/python-multipart/pull/103).
* Improve type hints on `FormParser` [#104](https://github.com/Kludex/python-multipart/pull/104).
* Fix `OnFileCallback` type [#106](https://github.com/Kludex/python-multipart/pull/106).
* Improve type hints [#110](https://github.com/Kludex/python-multipart/pull/110).
* Improve type hints on `File` [#111](https://github.com/Kludex/python-multipart/pull/111).
* Add type hint to helper functions [#112](https://github.com/Kludex/python-multipart/pull/112).
* Minor fix for Field.__repr__ [#114](https://github.com/Kludex/python-multipart/pull/114).
* Fix use of chunk_size parameter [#136](https://github.com/Kludex/python-multipart/pull/136).
* Allow digits and valid token chars in headers [#134](https://github.com/Kludex/python-multipart/pull/134).
* Fix headers being carried between parts [#135](https://github.com/Kludex/python-multipart/pull/135).

## 0.0.9 (2024-02-10)

* Add support for Python 3.12 [#85](https://github.com/Kludex/python-multipart/pull/85).
* Drop support for Python 3.7 [#95](https://github.com/Kludex/python-multipart/pull/95).
* Add `MultipartState(IntEnum)` [#96](https://github.com/Kludex/python-multipart/pull/96).
* Add `QuerystringState` [#97](https://github.com/Kludex/python-multipart/pull/97).
* Add `TypedDict` callbacks [#98](https://github.com/Kludex/python-multipart/pull/98).
* Add config `TypedDict`s [#99](https://github.com/Kludex/python-multipart/pull/99).

## 0.0.8 (2024-02-09)

* Check if Message.get_params return 3-tuple instead of str on parse_options_header [#79](https://github.com/Kludex/python-multipart/pull/79).
* Cleanup unused regex patterns [#82](https://github.com/Kludex/python-multipart/pull/82).

## 0.0.7 (2024-02-03)

* Refactor header option parser to use the standard library instead of a custom RegEx [#75](https://github.com/andrew-d/python-multipart/pull/75).

## 0.0.6 (2023-02-27)

* Migrate package installation to `pyproject.toml` (PEP 621) [#54](https://github.com/andrew-d/python-multipart/pull/54).
* Use yaml.safe_load instead of yaml.load [#46](https://github.com/andrew-d/python-multipart/pull/46).
* Add support for Python 3.11, drop EOL 3.6 [#51](https://github.com/andrew-d/python-multipart/pull/51).
* Add support for Python 3.8-3.10, drop EOL 2.7-3.5 [#42](https://github.com/andrew-d/python-multipart/pull/42).
* `QuerystringParser`: don't raise an AttributeError in `__repr__` [#30](https://github.com/andrew-d/python-multipart/pull/30).
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.2.2] - 2026-01-09

### Added

- Enhanced `parse_options_header` function to handle complex HTTP Header formats
- Added `test_header_robustness.py` test module for edge cases

### Fixed

- Fixed parsing issue with semicolons inside quoted parameter values
- Fixed escaped quotes handling in filenames
- Improved Windows path compatibility (IE6 format)

### Changed

- Refactored header parsing to use state machine pattern
- Improved performance by avoiding regex-based parsing

### Security

- Eliminated potential ReDoS vulnerabilities in header parsing

## [0.2.1] - 2023-XX-XX

[Previous entries...]

14 changes: 14 additions & 0 deletions python-multipart-master/LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Copyright 2012, Andrew Dunham

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

13 changes: 13 additions & 0 deletions python-multipart-master/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# [Python-Multipart](https://kludex.github.io/python-multipart/)

[![Package version](https://badge.fury.io/py/python-multipart.svg)](https://pypi.python.org/pypi/python-multipart)
[![Supported Python Version](https://img.shields.io/pypi/pyversions/python-multipart.svg?color=%2334D058)](https://pypi.org/project/python-multipart)

---

`python-multipart` is an Apache2-licensed streaming multipart parser for Python.
Test coverage is currently 100%.

## Why?

Because streaming uploads are awesome for large files.
11 changes: 11 additions & 0 deletions python-multipart-master/SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Security Policy

If you think you have identified a security issue with `python-multipart`, **do not open a public issue**.

To responsibly report a security issue, please navigate to the Security tab for the repo and click "Report a vulnerability."

![Screenshot of repo security tab showing "Report a vulnerability" button](https://github.com/encode/.github/raw/master/img/github-demos-private-vulnerability-reporting.png)

Be sure to include as much detail as necessary in your report. As with reporting normal issues, a minimal reproducible example will help the maintainers address the issue faster.

Thank you.
1 change: 1 addition & 0 deletions python-multipart-master/docs/CNAME
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
multipart.fastapiexpert.com
3 changes: 3 additions & 0 deletions python-multipart-master/docs/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
::: python_multipart

::: python_multipart.exceptions
1 change: 1 addition & 0 deletions python-multipart-master/docs/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--8<-- "CHANGELOG.md"
186 changes: 186 additions & 0 deletions python-multipart-master/docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# Python-Multipart

Python-Multipart is a streaming multipart parser for Python.

## Quickstart

### Simple Example

The following example shows a quick example of parsing an incoming request body in a simple WSGI application:

```python
import python_multipart

def simple_app(environ, start_response):
ret = []

# The following two callbacks just append the name to the return value.
def on_field(field):
ret.append(b"Parsed value parameter named: %s" % (field.field_name,))

def on_file(file):
ret.append(b"Parsed file parameter named: %s" % (file.field_name,))

# Create headers object. We need to convert from WSGI to the actual
# name of the header, since this library does not assume that you are
# using WSGI.
headers = {'Content-Type': environ['CONTENT_TYPE']}
if 'HTTP_X_FILE_NAME' in environ:
headers['X-File-Name'] = environ['HTTP_X_FILE_NAME']
if 'CONTENT_LENGTH' in environ:
headers['Content-Length'] = environ['CONTENT_LENGTH']

# Parse the form.
python_multipart.parse_form(headers, environ['wsgi.input'], on_field, on_file)

# Return something.
start_response('200 OK', [('Content-type', 'text/plain')])
ret.append(b'\n')
return ret

from wsgiref.simple_server import make_server
from wsgiref.validate import validator

httpd = make_server('', 8123, simple_app)
print("Serving on port 8123...")
httpd.serve_forever()
```

If you test this with curl, you can see that the parser works:

```console
$ curl -ik -F "foo=bar" http://localhost:8123/
HTTP/1.0 200 OK
Date: Sun, 07 Apr 2013 01:40:52 GMT
Server: WSGIServer/0.1 Python/2.7.3
Content-type: text/plain

Parsed value parameter named: foo
```

For a more in-depth example showing how the various parts fit together, check out the next section.

### In-Depth Example

In this section, we’ll build an application that computes the SHA-256 hash of all uploaded files in a streaming manner.

To start, we need a simple WSGI application. We could do this with a framework like Flask, Django, or Tornado, but for now let’s stick to plain WSGI:

```python
import python_multipart

def simple_app(environ, start_response):
start_response('200 OK', [('Content-type', 'text/plain')])
return ['Hashes:\n']

from wsgiref.simple_server import make_server
httpd = make_server('', 8123, simple_app)
print("Serving on port 8123...")
httpd.serve_forever()
```

You can run this and check with curl that it works properly:

```console
$ curl -ik http://localhost:8123/
HTTP/1.0 200 OK
Date: Sun, 07 Apr 2013 01:49:03 GMT
Server: WSGIServer/0.1 Python/2.7.3
Content-type: text/plain
Content-Length: 8

Hashes:
```

Good! It works. Now, let’s add some of the code that we need. What we need to do, essentially, is set up the appropriate parser and callbacks so that we can access each portion of the request as it arrives, without needing to store any parts in memory.

We can start off by checking if we need to create the parser at all - if the Content-Type isn’t multipart/form-data, then we’re not going to do anything.

The final code should look like this:

```python
import hashlib
import python_multipart
from python_multipart.multipart import parse_options_header

def simple_app(environ, start_response):
ret = []

# Python 2 doesn't have the "nonlocal" keyword from Python 3, so we get
# around it by setting attributes on a dummy object.
class g(object):
hash = None

# This is called when a new part arrives. We create a new hash object
# in this callback.
def on_part_begin():
g.hash = hashlib.sha256()

# We got some data! Update our hash.
def on_part_data(data, start, end):
g.hash.update(data[start:end])

# Our current part is done, so we can finish the hash.
def on_part_end():
ret.append("Part hash: %s" % (g.hash.hexdigest(),))

# Parse the Content-Type header to get the multipart boundary.
content_type, params = parse_options_header(environ['CONTENT_TYPE'])
boundary = params.get(b'boundary')

# Callbacks dictionary.
callbacks = {
'on_part_begin': on_part_begin,
'on_part_data': on_part_data,
'on_part_end': on_part_end,
}

# Create the parser.
parser = python_multipart.MultipartParser(boundary, callbacks)

# The input stream is from the WSGI environ.
inp = environ['wsgi.input']

# Feed the parser with data from the request.
size = int(environ['CONTENT_LENGTH'])
while size > 0:
to_read = min(size, 1024 * 1024)
data = inp.read(to_read)
parser.write(data)

size -= len(data)
if len(data) != to_read:
break

start_response('200 OK', [('Content-type', 'text/plain')])
return ret

from wsgiref.simple_server import make_server
httpd = make_server('', 8123, simple_app)
print("Serving on port 8123...")
httpd.serve_forever()
```

And you can see that this works:

```console
$ echo "Foo bar" > /tmp/test.txt
$ shasum -a 256 /tmp/test.txt
0b64696c0f7ddb9e3435341720988d5455b3b0f0724688f98ec8e6019af3d931 /tmp/test.txt
$ curl -ik -F file=@/tmp/test.txt http://localhost:8123/
HTTP/1.0 200 OK
Date: Sun, 07 Apr 2013 02:09:10 GMT
Server: WSGIServer/0.1 Python/2.7.3
Content-type: text/plain

Hashes:
Part hash: 0b64696c0f7ddb9e3435341720988d5455b3b0f0724688f98ec8e6019af3d931
```


## Historical note

This package used to be accessed via `import multipart`. This still works for
now (with a warning) as long as the Python package `multipart` is not also
installed. If both are installed, you need to use the full PyPI name
`python_multipart` for this package.
37 changes: 37 additions & 0 deletions python-multipart-master/fuzz/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Fuzz Testing

Fuzz testing is:

> An automated software testing technique that involves providing invalid, unexpected, or random data as inputs to a program.

We use coverage guided fuzz testing to automatically discover bugs in python-multipart.

This `fuzz/` directory contains the configuration and the fuzz tests for python-multipart.
To generate and run fuzz tests, we use the [Atheris](https://github.com/google/atheris) library.

## Running a fuzzer

This directory contains fuzzers like for example `fuzz_form.py`. You can run it with:

Run fuzz target:
```sh
$ python fuzz/fuzz_form.py
```

You should see output that looks something like this:

```
#2 INITED cov: 32 ft: 32 corp: 1/1b exec/s: 0 rss: 49Mb
#3 NEW cov: 33 ft: 33 corp: 2/2b lim: 4 exec/s: 0 rss: 49Mb L: 1/1 MS: 1 ChangeByte-
#4 NEW cov: 97 ft: 97 corp: 3/4b lim: 4 exec/s: 0 rss: 49Mb L: 2/2 MS: 1 InsertByte-
#11 NEW cov: 116 ft: 119 corp: 4/5b lim: 4 exec/s: 0 rss: 49Mb L: 1/2 MS: 2 ChangeBinInt-EraseBytes-
#30 NEW cov: 131 ft: 134 corp: 5/8b lim: 4 exec/s: 0 rss: 49Mb L: 3/3 MS: 4 ChangeByte-ChangeBit-InsertByte-CopyPart-
#31 NEW cov: 135 ft: 138 corp: 6/11b lim: 4 exec/s: 0 rss: 49Mb L: 3/3 MS: 1 CrossOver-
#39 NEW cov: 135 ft: 142 corp: 7/15b lim: 4 exec/s: 0 rss: 49Mb L: 4/4 MS: 3 ChangeBit-CrossOver-CopyPart-
```

It will continue to generate random inputs forever, until it finds a
bug or is terminated. The testcases for bugs it finds can be seen in
the form of `crash-*` or `timeout-*` at the place from where command is run.
You can rerun the fuzzer on a single input by passing it on the
command line `python fuzz/fuzz_form.py /path/to/testcase`.
Empty file.
Empty file.
Loading
Loading