multipart-decoder 0.0.1__tar.gz → 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {multipart_decoder-0.0.1/src/multipart_decoder.egg-info → multipart_decoder-1.0.0}/PKG-INFO +47 -30
- {multipart_decoder-0.0.1 → multipart_decoder-1.0.0}/README.md +36 -28
- multipart_decoder-1.0.0/pyproject.toml +48 -0
- multipart_decoder-1.0.0/src/multipart_decoder/__init__.py +8 -0
- multipart_decoder-1.0.0/src/multipart_decoder/decoder.py +99 -0
- {multipart_decoder-0.0.1 → multipart_decoder-1.0.0/src/multipart_decoder.egg-info}/PKG-INFO +47 -30
- {multipart_decoder-0.0.1 → multipart_decoder-1.0.0}/src/multipart_decoder.egg-info/SOURCES.txt +1 -0
- multipart_decoder-1.0.0/src/multipart_decoder.egg-info/requires.txt +1 -0
- multipart_decoder-1.0.0/tests/test_decoder.py +75 -0
- multipart_decoder-0.0.1/pyproject.toml +0 -23
- multipart_decoder-0.0.1/src/multipart_decoder/__init__.py +0 -8
- multipart_decoder-0.0.1/src/multipart_decoder/decoder.py +0 -71
- multipart_decoder-0.0.1/tests/test_decoder.py +0 -72
- {multipart_decoder-0.0.1 → multipart_decoder-1.0.0}/LICENSE +0 -0
- {multipart_decoder-0.0.1 → multipart_decoder-1.0.0}/setup.cfg +0 -0
- {multipart_decoder-0.0.1 → multipart_decoder-1.0.0}/src/multipart_decoder.egg-info/dependency_links.txt +0 -0
- {multipart_decoder-0.0.1 → multipart_decoder-1.0.0}/src/multipart_decoder.egg-info/top_level.txt +0 -0
|
@@ -1,21 +1,34 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multipart-decoder
|
|
3
|
-
Version: 0.0
|
|
4
|
-
Summary: A lightweight Python package for
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A lightweight Python package for parsing multipart/form-data content
|
|
5
5
|
Author-email: Satya Tulasi Jalandhar C H <satyatulasijalandharch2@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/satyatulasijalandharch/multipart-decoder
|
|
8
8
|
Project-URL: Issues, https://github.com/satyatulasijalandharch/multipart-decoder/issues
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
10
18
|
Classifier: Operating System :: OS Independent
|
|
11
19
|
Requires-Python: >=3.8
|
|
12
20
|
Description-Content-Type: text/markdown
|
|
13
21
|
License-File: LICENSE
|
|
22
|
+
Requires-Dist: requests_toolbelt==1.0.0
|
|
14
23
|
Dynamic: license-file
|
|
15
24
|
|
|
16
|
-
#
|
|
25
|
+
# Multipart Decoder
|
|
17
26
|
|
|
18
|
-
|
|
27
|
+
[](https://pypi.org/project/multipart-decoder/)
|
|
28
|
+
[](https://pypi.org/project/multipart-decoder)
|
|
29
|
+
[](https://pepy.tech/project/multipart-decoder)
|
|
30
|
+
|
|
31
|
+
**Multipart Decoder** is a lightweight Python package for parsing multipart/form-data content.
|
|
19
32
|
|
|
20
33
|
## Installation
|
|
21
34
|
|
|
@@ -30,7 +43,7 @@ This example demonstrates how to parse simple form data with text fields.
|
|
|
30
43
|
|
|
31
44
|
```python
|
|
32
45
|
|
|
33
|
-
from multipart_decoder import
|
|
46
|
+
from multipart_decoder import MultipartFormParser
|
|
34
47
|
|
|
35
48
|
# Example with raw form data
|
|
36
49
|
def basic_form_parsing():
|
|
@@ -39,18 +52,19 @@ def basic_form_parsing():
|
|
|
39
52
|
body = "LS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5ZFZUbDJFQXAxc2NYWXNXWg0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJ1c2VybmFtZSINCg0KZXhhbXBsZV91c2VyDQotLS0tLS1XZWJLaXRGb3JtQm91bmRhcnlkVlRsMkVBcDFzY1hZc1daDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9ImVtYWlsIg0KDQp1c2VyQGV4YW1wbGUuY29tDQotLS0tLS1XZWJLaXRGb3JtQm91bmRhcnlkVlRsMkVBcDFzY1hZc1daLS0NCg=="
|
|
40
53
|
|
|
41
54
|
# Parse the multipart form data
|
|
42
|
-
form_data =
|
|
43
|
-
|
|
55
|
+
form_data = MultipartFormParser(body, content_type)
|
|
56
|
+
|
|
44
57
|
# Access the form fields
|
|
45
|
-
print(f"Username: {form_data['username']}")
|
|
46
|
-
print(f"Email: {form_data['email']}")
|
|
47
|
-
|
|
58
|
+
print(f"Username: {form_data['fields']['username']}")
|
|
59
|
+
print(f"Email: {form_data['fields']['email']}")
|
|
60
|
+
|
|
48
61
|
# Show the entire parsed data structure
|
|
49
62
|
print("\nComplete form data dictionary:")
|
|
50
63
|
print(form_data)
|
|
51
64
|
|
|
52
65
|
if __name__ == "__main__":
|
|
53
66
|
basic_form_parsing()
|
|
67
|
+
|
|
54
68
|
```
|
|
55
69
|
## Expected Output
|
|
56
70
|
|
|
@@ -61,9 +75,13 @@ if __name__ == "__main__":
|
|
|
61
75
|
#### Complete Form Data Dictionary:
|
|
62
76
|
```json
|
|
63
77
|
{
|
|
64
|
-
"
|
|
65
|
-
|
|
78
|
+
"fields": {
|
|
79
|
+
"username": "example_user",
|
|
80
|
+
"email": "user@example.com"
|
|
81
|
+
},
|
|
82
|
+
"files": {}
|
|
66
83
|
}
|
|
84
|
+
|
|
67
85
|
```
|
|
68
86
|
|
|
69
87
|
|
|
@@ -72,39 +90,38 @@ if __name__ == "__main__":
|
|
|
72
90
|
This example demonstrates how to handle file uploads in multipart form data.
|
|
73
91
|
|
|
74
92
|
```python
|
|
75
|
-
from multipart_decoder import
|
|
93
|
+
from multipart_decoder import MultipartFormParser
|
|
76
94
|
import os
|
|
77
95
|
|
|
78
96
|
def process_file_upload():
|
|
79
|
-
|
|
80
|
-
content_type = (
|
|
81
|
-
"multipart/form-data; boundary=----WebKitFormBoundary4AQwYOUUCEUw9hsX"
|
|
82
|
-
)
|
|
97
|
+
content_type = "multipart/form-data; boundary=----WebKitFormBoundary4AQwYOUUCEUw9hsX"
|
|
83
98
|
body = "LS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5NEFRd1lPVVVDRVV3OWhzWA0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWVsZDEiDQoNCnZhbHVlMQ0KLS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5NEFRd1lPVVVDRVV3OWhzWA0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWVsZDIiDQoNCnZhbHVlMg0KLS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5NEFRd1lPVVVDRVV3OWhzWA0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWxlMSI7IGZpbGVuYW1lPSJleGFtcGxlLnR4dCINCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbg0KDQpIZWxsbywgd29ybGQhDQotLS0tLS1XZWJLaXRGb3JtQm91bmRhcnk0QVF3WU9VVUNFVXc5aHNYLS0NCg=="
|
|
84
99
|
|
|
85
|
-
form_data =
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
print(f"Field
|
|
100
|
+
form_data = MultipartFormParser(body, content_type)
|
|
101
|
+
|
|
102
|
+
# Access fields
|
|
103
|
+
print(f"Field 1: {form_data['fields']['field1']}")
|
|
104
|
+
print(f"Field 2: {form_data['fields']['field2']}")
|
|
89
105
|
|
|
90
|
-
#
|
|
91
|
-
if 'file1' in form_data:
|
|
92
|
-
|
|
93
|
-
print(f" - Filename: {filename}")
|
|
94
|
-
print(f" - Content type: {content_type}")
|
|
95
|
-
print(f" - File size: {
|
|
106
|
+
# Access files
|
|
107
|
+
if 'file1' in form_data['files']:
|
|
108
|
+
file_info = form_data['files']['file1']
|
|
109
|
+
print(f" - Filename: {file_info['filename']}")
|
|
110
|
+
print(f" - Content type: {file_info['content_type']}")
|
|
111
|
+
print(f" - File size: {file_info['size']} bytes")
|
|
96
112
|
|
|
97
113
|
# Example: save the file
|
|
98
|
-
save_path = f"saved_{filename}"
|
|
114
|
+
save_path = f"saved_{file_info['filename']}"
|
|
99
115
|
with open(save_path, "wb") as f:
|
|
100
|
-
f.write(
|
|
116
|
+
f.write(file_info['content'])
|
|
101
117
|
print(f" - File saved to: {save_path}")
|
|
102
118
|
|
|
103
|
-
#
|
|
119
|
+
# Cleanup (for demonstration)
|
|
104
120
|
os.remove(save_path)
|
|
105
121
|
|
|
106
122
|
if __name__ == "__main__":
|
|
107
123
|
process_file_upload()
|
|
124
|
+
|
|
108
125
|
```
|
|
109
126
|
# Expected Output:
|
|
110
127
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Multipart Decoder
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://pypi.org/project/multipart-decoder/)
|
|
4
|
+
[](https://pypi.org/project/multipart-decoder)
|
|
5
|
+
[](https://pepy.tech/project/multipart-decoder)
|
|
6
|
+
|
|
7
|
+
**Multipart Decoder** is a lightweight Python package for parsing multipart/form-data content.
|
|
4
8
|
|
|
5
9
|
## Installation
|
|
6
10
|
|
|
@@ -15,7 +19,7 @@ This example demonstrates how to parse simple form data with text fields.
|
|
|
15
19
|
|
|
16
20
|
```python
|
|
17
21
|
|
|
18
|
-
from multipart_decoder import
|
|
22
|
+
from multipart_decoder import MultipartFormParser
|
|
19
23
|
|
|
20
24
|
# Example with raw form data
|
|
21
25
|
def basic_form_parsing():
|
|
@@ -24,18 +28,19 @@ def basic_form_parsing():
|
|
|
24
28
|
body = "LS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5ZFZUbDJFQXAxc2NYWXNXWg0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJ1c2VybmFtZSINCg0KZXhhbXBsZV91c2VyDQotLS0tLS1XZWJLaXRGb3JtQm91bmRhcnlkVlRsMkVBcDFzY1hZc1daDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9ImVtYWlsIg0KDQp1c2VyQGV4YW1wbGUuY29tDQotLS0tLS1XZWJLaXRGb3JtQm91bmRhcnlkVlRsMkVBcDFzY1hZc1daLS0NCg=="
|
|
25
29
|
|
|
26
30
|
# Parse the multipart form data
|
|
27
|
-
form_data =
|
|
28
|
-
|
|
31
|
+
form_data = MultipartFormParser(body, content_type)
|
|
32
|
+
|
|
29
33
|
# Access the form fields
|
|
30
|
-
print(f"Username: {form_data['username']}")
|
|
31
|
-
print(f"Email: {form_data['email']}")
|
|
32
|
-
|
|
34
|
+
print(f"Username: {form_data['fields']['username']}")
|
|
35
|
+
print(f"Email: {form_data['fields']['email']}")
|
|
36
|
+
|
|
33
37
|
# Show the entire parsed data structure
|
|
34
38
|
print("\nComplete form data dictionary:")
|
|
35
39
|
print(form_data)
|
|
36
40
|
|
|
37
41
|
if __name__ == "__main__":
|
|
38
42
|
basic_form_parsing()
|
|
43
|
+
|
|
39
44
|
```
|
|
40
45
|
## Expected Output
|
|
41
46
|
|
|
@@ -46,9 +51,13 @@ if __name__ == "__main__":
|
|
|
46
51
|
#### Complete Form Data Dictionary:
|
|
47
52
|
```json
|
|
48
53
|
{
|
|
49
|
-
"
|
|
50
|
-
|
|
54
|
+
"fields": {
|
|
55
|
+
"username": "example_user",
|
|
56
|
+
"email": "user@example.com"
|
|
57
|
+
},
|
|
58
|
+
"files": {}
|
|
51
59
|
}
|
|
60
|
+
|
|
52
61
|
```
|
|
53
62
|
|
|
54
63
|
|
|
@@ -57,39 +66,38 @@ if __name__ == "__main__":
|
|
|
57
66
|
This example demonstrates how to handle file uploads in multipart form data.
|
|
58
67
|
|
|
59
68
|
```python
|
|
60
|
-
from multipart_decoder import
|
|
69
|
+
from multipart_decoder import MultipartFormParser
|
|
61
70
|
import os
|
|
62
71
|
|
|
63
72
|
def process_file_upload():
|
|
64
|
-
|
|
65
|
-
content_type = (
|
|
66
|
-
"multipart/form-data; boundary=----WebKitFormBoundary4AQwYOUUCEUw9hsX"
|
|
67
|
-
)
|
|
73
|
+
content_type = "multipart/form-data; boundary=----WebKitFormBoundary4AQwYOUUCEUw9hsX"
|
|
68
74
|
body = "LS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5NEFRd1lPVVVDRVV3OWhzWA0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWVsZDEiDQoNCnZhbHVlMQ0KLS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5NEFRd1lPVVVDRVV3OWhzWA0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWVsZDIiDQoNCnZhbHVlMg0KLS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5NEFRd1lPVVVDRVV3OWhzWA0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWxlMSI7IGZpbGVuYW1lPSJleGFtcGxlLnR4dCINCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbg0KDQpIZWxsbywgd29ybGQhDQotLS0tLS1XZWJLaXRGb3JtQm91bmRhcnk0QVF3WU9VVUNFVXc5aHNYLS0NCg=="
|
|
69
75
|
|
|
70
|
-
form_data =
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
print(f"Field
|
|
76
|
+
form_data = MultipartFormParser(body, content_type)
|
|
77
|
+
|
|
78
|
+
# Access fields
|
|
79
|
+
print(f"Field 1: {form_data['fields']['field1']}")
|
|
80
|
+
print(f"Field 2: {form_data['fields']['field2']}")
|
|
74
81
|
|
|
75
|
-
#
|
|
76
|
-
if 'file1' in form_data:
|
|
77
|
-
|
|
78
|
-
print(f" - Filename: {filename}")
|
|
79
|
-
print(f" - Content type: {content_type}")
|
|
80
|
-
print(f" - File size: {
|
|
82
|
+
# Access files
|
|
83
|
+
if 'file1' in form_data['files']:
|
|
84
|
+
file_info = form_data['files']['file1']
|
|
85
|
+
print(f" - Filename: {file_info['filename']}")
|
|
86
|
+
print(f" - Content type: {file_info['content_type']}")
|
|
87
|
+
print(f" - File size: {file_info['size']} bytes")
|
|
81
88
|
|
|
82
89
|
# Example: save the file
|
|
83
|
-
save_path = f"saved_{filename}"
|
|
90
|
+
save_path = f"saved_{file_info['filename']}"
|
|
84
91
|
with open(save_path, "wb") as f:
|
|
85
|
-
f.write(
|
|
92
|
+
f.write(file_info['content'])
|
|
86
93
|
print(f" - File saved to: {save_path}")
|
|
87
94
|
|
|
88
|
-
#
|
|
95
|
+
# Cleanup (for demonstration)
|
|
89
96
|
os.remove(save_path)
|
|
90
97
|
|
|
91
98
|
if __name__ == "__main__":
|
|
92
99
|
process_file_upload()
|
|
100
|
+
|
|
93
101
|
```
|
|
94
102
|
# Expected Output:
|
|
95
103
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools >= 77.0.3"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "multipart-decoder"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Satya Tulasi Jalandhar C H", email="satyatulasijalandharch2@gmail.com" },
|
|
10
|
+
]
|
|
11
|
+
description = "A lightweight Python package for parsing multipart/form-data content"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.8"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"requests_toolbelt==1.0.0"
|
|
16
|
+
]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.8",
|
|
20
|
+
"Programming Language :: Python :: 3.9",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Programming Language :: Python :: 3.14",
|
|
26
|
+
"Programming Language :: Python :: 3.15",
|
|
27
|
+
"Operating System :: OS Independent",
|
|
28
|
+
]
|
|
29
|
+
license = "MIT"
|
|
30
|
+
license-files = ["LICENSE*"]
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.dynamic]
|
|
33
|
+
version = {attr = "multipart_decoder.__version__"}
|
|
34
|
+
|
|
35
|
+
[[tool.uv.index]]
|
|
36
|
+
name = "pypi"
|
|
37
|
+
url = "https://pypi.org/simple/"
|
|
38
|
+
publish-url = "https://upload.pypi.org/legacy/"
|
|
39
|
+
|
|
40
|
+
[[tool.uv.index]]
|
|
41
|
+
name = "testpypi"
|
|
42
|
+
url = "https://test.pypi.org/simple/"
|
|
43
|
+
publish-url = "https://test.pypi.org/legacy/"
|
|
44
|
+
explicit = true
|
|
45
|
+
|
|
46
|
+
[project.urls]
|
|
47
|
+
Homepage = "https://github.com/satyatulasijalandharch/multipart-decoder"
|
|
48
|
+
Issues = "https://github.com/satyatulasijalandharch/multipart-decoder/issues"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Decoder module for parsing multipart/form-data content.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import logging
|
|
7
|
+
from base64 import b64decode
|
|
8
|
+
from binascii import Error as BinasciiError
|
|
9
|
+
from requests_toolbelt.multipart import decoder
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def MultipartFormParser(body, content_type):
|
|
13
|
+
"""
|
|
14
|
+
Decode a multipart/form-data request body.
|
|
15
|
+
:param body: The body of the request, typically a byte string.
|
|
16
|
+
:param content_type: The content type of the request, typically 'multipart/form-data; boundary=...'.
|
|
17
|
+
:return: A dictionary containing the extracted fields and files.
|
|
18
|
+
:raises ValueError: If input parameters are invalid
|
|
19
|
+
:raises Exception: For various parsing errors
|
|
20
|
+
"""
|
|
21
|
+
extracted_data = {"fields": {}, "files": {}}
|
|
22
|
+
|
|
23
|
+
# Validate input parameters
|
|
24
|
+
if not body:
|
|
25
|
+
raise ValueError("Request body cannot be empty")
|
|
26
|
+
if not content_type or "boundary=" not in content_type:
|
|
27
|
+
raise ValueError(
|
|
28
|
+
"Invalid content type: must be multipart/form-data with boundary"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
# Decode the base64 encoded body
|
|
33
|
+
try:
|
|
34
|
+
data = b64decode(body)
|
|
35
|
+
except BinasciiError as e:
|
|
36
|
+
raise ValueError(f"Invalid base64 encoded body: {str(e)}")
|
|
37
|
+
|
|
38
|
+
# Parse multipart data
|
|
39
|
+
try:
|
|
40
|
+
multipart_data = decoder.MultipartDecoder(data, content_type)
|
|
41
|
+
except Exception as e:
|
|
42
|
+
raise ValueError(f"Failed to decode multipart form data: {str(e)}")
|
|
43
|
+
|
|
44
|
+
# Process parts
|
|
45
|
+
for part in multipart_data.parts:
|
|
46
|
+
try:
|
|
47
|
+
# Get content disposition header
|
|
48
|
+
if b"Content-Disposition" not in part.headers:
|
|
49
|
+
logging.warning("Skipping part without Content-Disposition header")
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
content_disposition = part.headers[b"Content-Disposition"].decode()
|
|
53
|
+
|
|
54
|
+
# Extract the field name
|
|
55
|
+
name_match = re.search(r'name="([^"]+)"', content_disposition)
|
|
56
|
+
if not name_match:
|
|
57
|
+
logging.warning("Skipping part without field name")
|
|
58
|
+
continue
|
|
59
|
+
|
|
60
|
+
field_name = name_match.group(1)
|
|
61
|
+
|
|
62
|
+
# Check if it's a file
|
|
63
|
+
filename_match = re.search(r'filename="([^"]+)"', content_disposition)
|
|
64
|
+
|
|
65
|
+
if filename_match:
|
|
66
|
+
# Handle file upload
|
|
67
|
+
filename = filename_match.group(1)
|
|
68
|
+
content_type = part.headers.get(
|
|
69
|
+
b"Content-Type", b"application/octet-stream"
|
|
70
|
+
).decode()
|
|
71
|
+
file_data = part.content
|
|
72
|
+
|
|
73
|
+
# Store file information
|
|
74
|
+
extracted_data["files"][field_name] = {
|
|
75
|
+
"filename": filename,
|
|
76
|
+
"content_type": content_type,
|
|
77
|
+
"content": file_data,
|
|
78
|
+
"size": len(file_data),
|
|
79
|
+
}
|
|
80
|
+
else:
|
|
81
|
+
# Handle regular form fields
|
|
82
|
+
try:
|
|
83
|
+
field_value = part.text.strip()
|
|
84
|
+
except UnicodeDecodeError:
|
|
85
|
+
logging.warning(
|
|
86
|
+
f"Could not decode text for field {field_name}, storing as binary"
|
|
87
|
+
)
|
|
88
|
+
field_value = part.content
|
|
89
|
+
extracted_data["fields"][field_name] = field_value
|
|
90
|
+
|
|
91
|
+
except Exception as e:
|
|
92
|
+
logging.error(f"Error processing part: {str(e)}")
|
|
93
|
+
# Continue processing other parts instead of failing completely
|
|
94
|
+
|
|
95
|
+
except Exception as e:
|
|
96
|
+
# Re-raise with more context
|
|
97
|
+
raise Exception(f"Failed to parse multipart form data: {str(e)}") from e
|
|
98
|
+
|
|
99
|
+
return extracted_data
|
|
@@ -1,21 +1,34 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multipart-decoder
|
|
3
|
-
Version: 0.0
|
|
4
|
-
Summary: A lightweight Python package for
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A lightweight Python package for parsing multipart/form-data content
|
|
5
5
|
Author-email: Satya Tulasi Jalandhar C H <satyatulasijalandharch2@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/satyatulasijalandharch/multipart-decoder
|
|
8
8
|
Project-URL: Issues, https://github.com/satyatulasijalandharch/multipart-decoder/issues
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
10
18
|
Classifier: Operating System :: OS Independent
|
|
11
19
|
Requires-Python: >=3.8
|
|
12
20
|
Description-Content-Type: text/markdown
|
|
13
21
|
License-File: LICENSE
|
|
22
|
+
Requires-Dist: requests_toolbelt==1.0.0
|
|
14
23
|
Dynamic: license-file
|
|
15
24
|
|
|
16
|
-
#
|
|
25
|
+
# Multipart Decoder
|
|
17
26
|
|
|
18
|
-
|
|
27
|
+
[](https://pypi.org/project/multipart-decoder/)
|
|
28
|
+
[](https://pypi.org/project/multipart-decoder)
|
|
29
|
+
[](https://pepy.tech/project/multipart-decoder)
|
|
30
|
+
|
|
31
|
+
**Multipart Decoder** is a lightweight Python package for parsing multipart/form-data content.
|
|
19
32
|
|
|
20
33
|
## Installation
|
|
21
34
|
|
|
@@ -30,7 +43,7 @@ This example demonstrates how to parse simple form data with text fields.
|
|
|
30
43
|
|
|
31
44
|
```python
|
|
32
45
|
|
|
33
|
-
from multipart_decoder import
|
|
46
|
+
from multipart_decoder import MultipartFormParser
|
|
34
47
|
|
|
35
48
|
# Example with raw form data
|
|
36
49
|
def basic_form_parsing():
|
|
@@ -39,18 +52,19 @@ def basic_form_parsing():
|
|
|
39
52
|
body = "LS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5ZFZUbDJFQXAxc2NYWXNXWg0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJ1c2VybmFtZSINCg0KZXhhbXBsZV91c2VyDQotLS0tLS1XZWJLaXRGb3JtQm91bmRhcnlkVlRsMkVBcDFzY1hZc1daDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9ImVtYWlsIg0KDQp1c2VyQGV4YW1wbGUuY29tDQotLS0tLS1XZWJLaXRGb3JtQm91bmRhcnlkVlRsMkVBcDFzY1hZc1daLS0NCg=="
|
|
40
53
|
|
|
41
54
|
# Parse the multipart form data
|
|
42
|
-
form_data =
|
|
43
|
-
|
|
55
|
+
form_data = MultipartFormParser(body, content_type)
|
|
56
|
+
|
|
44
57
|
# Access the form fields
|
|
45
|
-
print(f"Username: {form_data['username']}")
|
|
46
|
-
print(f"Email: {form_data['email']}")
|
|
47
|
-
|
|
58
|
+
print(f"Username: {form_data['fields']['username']}")
|
|
59
|
+
print(f"Email: {form_data['fields']['email']}")
|
|
60
|
+
|
|
48
61
|
# Show the entire parsed data structure
|
|
49
62
|
print("\nComplete form data dictionary:")
|
|
50
63
|
print(form_data)
|
|
51
64
|
|
|
52
65
|
if __name__ == "__main__":
|
|
53
66
|
basic_form_parsing()
|
|
67
|
+
|
|
54
68
|
```
|
|
55
69
|
## Expected Output
|
|
56
70
|
|
|
@@ -61,9 +75,13 @@ if __name__ == "__main__":
|
|
|
61
75
|
#### Complete Form Data Dictionary:
|
|
62
76
|
```json
|
|
63
77
|
{
|
|
64
|
-
"
|
|
65
|
-
|
|
78
|
+
"fields": {
|
|
79
|
+
"username": "example_user",
|
|
80
|
+
"email": "user@example.com"
|
|
81
|
+
},
|
|
82
|
+
"files": {}
|
|
66
83
|
}
|
|
84
|
+
|
|
67
85
|
```
|
|
68
86
|
|
|
69
87
|
|
|
@@ -72,39 +90,38 @@ if __name__ == "__main__":
|
|
|
72
90
|
This example demonstrates how to handle file uploads in multipart form data.
|
|
73
91
|
|
|
74
92
|
```python
|
|
75
|
-
from multipart_decoder import
|
|
93
|
+
from multipart_decoder import MultipartFormParser
|
|
76
94
|
import os
|
|
77
95
|
|
|
78
96
|
def process_file_upload():
|
|
79
|
-
|
|
80
|
-
content_type = (
|
|
81
|
-
"multipart/form-data; boundary=----WebKitFormBoundary4AQwYOUUCEUw9hsX"
|
|
82
|
-
)
|
|
97
|
+
content_type = "multipart/form-data; boundary=----WebKitFormBoundary4AQwYOUUCEUw9hsX"
|
|
83
98
|
body = "LS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5NEFRd1lPVVVDRVV3OWhzWA0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWVsZDEiDQoNCnZhbHVlMQ0KLS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5NEFRd1lPVVVDRVV3OWhzWA0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWVsZDIiDQoNCnZhbHVlMg0KLS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5NEFRd1lPVVVDRVV3OWhzWA0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWxlMSI7IGZpbGVuYW1lPSJleGFtcGxlLnR4dCINCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbg0KDQpIZWxsbywgd29ybGQhDQotLS0tLS1XZWJLaXRGb3JtQm91bmRhcnk0QVF3WU9VVUNFVXc5aHNYLS0NCg=="
|
|
84
99
|
|
|
85
|
-
form_data =
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
print(f"Field
|
|
100
|
+
form_data = MultipartFormParser(body, content_type)
|
|
101
|
+
|
|
102
|
+
# Access fields
|
|
103
|
+
print(f"Field 1: {form_data['fields']['field1']}")
|
|
104
|
+
print(f"Field 2: {form_data['fields']['field2']}")
|
|
89
105
|
|
|
90
|
-
#
|
|
91
|
-
if 'file1' in form_data:
|
|
92
|
-
|
|
93
|
-
print(f" - Filename: {filename}")
|
|
94
|
-
print(f" - Content type: {content_type}")
|
|
95
|
-
print(f" - File size: {
|
|
106
|
+
# Access files
|
|
107
|
+
if 'file1' in form_data['files']:
|
|
108
|
+
file_info = form_data['files']['file1']
|
|
109
|
+
print(f" - Filename: {file_info['filename']}")
|
|
110
|
+
print(f" - Content type: {file_info['content_type']}")
|
|
111
|
+
print(f" - File size: {file_info['size']} bytes")
|
|
96
112
|
|
|
97
113
|
# Example: save the file
|
|
98
|
-
save_path = f"saved_{filename}"
|
|
114
|
+
save_path = f"saved_{file_info['filename']}"
|
|
99
115
|
with open(save_path, "wb") as f:
|
|
100
|
-
f.write(
|
|
116
|
+
f.write(file_info['content'])
|
|
101
117
|
print(f" - File saved to: {save_path}")
|
|
102
118
|
|
|
103
|
-
#
|
|
119
|
+
# Cleanup (for demonstration)
|
|
104
120
|
os.remove(save_path)
|
|
105
121
|
|
|
106
122
|
if __name__ == "__main__":
|
|
107
123
|
process_file_upload()
|
|
124
|
+
|
|
108
125
|
```
|
|
109
126
|
# Expected Output:
|
|
110
127
|
|
{multipart_decoder-0.0.1 → multipart_decoder-1.0.0}/src/multipart_decoder.egg-info/SOURCES.txt
RENAMED
|
@@ -6,5 +6,6 @@ src/multipart_decoder/decoder.py
|
|
|
6
6
|
src/multipart_decoder.egg-info/PKG-INFO
|
|
7
7
|
src/multipart_decoder.egg-info/SOURCES.txt
|
|
8
8
|
src/multipart_decoder.egg-info/dependency_links.txt
|
|
9
|
+
src/multipart_decoder.egg-info/requires.txt
|
|
9
10
|
src/multipart_decoder.egg-info/top_level.txt
|
|
10
11
|
tests/test_decoder.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests_toolbelt==1.0.0
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for the multipart_decoder package.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import unittest
|
|
6
|
+
from multipart_decoder import MultipartFormParser
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestMultipartFormParser(unittest.TestCase):
|
|
10
|
+
def test_basic_form_fields(self):
|
|
11
|
+
"""Test decoding of basic form fields."""
|
|
12
|
+
content_type = (
|
|
13
|
+
"multipart/form-data; boundary=----WebKitFormBoundaryp0mBfJEEcQtmxFCF"
|
|
14
|
+
)
|
|
15
|
+
body = "LS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5cDBtQmZKRUVjUXRteEZDRg0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWVsZDEiDQoNCnZhbHVlMQ0KLS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5cDBtQmZKRUVjUXRteEZDRg0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWVsZDIiDQoNCnZhbHVlMg0KLS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5cDBtQmZKRUVjUXRteEZDRi0tDQo="
|
|
16
|
+
|
|
17
|
+
result = MultipartFormParser(body, content_type)
|
|
18
|
+
self.assertEqual(result["fields"]["field1"], "value1")
|
|
19
|
+
self.assertEqual(result["fields"]["field2"], "value2")
|
|
20
|
+
|
|
21
|
+
def test_file_upload(self):
|
|
22
|
+
"""Test decoding of file uploads."""
|
|
23
|
+
content_type = (
|
|
24
|
+
"multipart/form-data; boundary=----WebKitFormBoundaryI6YbOCiSAMjU6wK2"
|
|
25
|
+
)
|
|
26
|
+
body = "LS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5STZZYk9DaVNBTWpVNndLMg0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWxlMSI7IGZpbGVuYW1lPSJleGFtcGxlLnR4dCINCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbg0KDQpIZWxsbywgd29ybGQhDQotLS0tLS1XZWJLaXRGb3JtQm91bmRhcnlJNlliT0NpU0FNalU2d0syLS0NCg=="
|
|
27
|
+
|
|
28
|
+
result = MultipartFormParser(body, content_type)
|
|
29
|
+
self.assertIn("file1", result["files"])
|
|
30
|
+
|
|
31
|
+
file_info = result["files"]["file1"]
|
|
32
|
+
self.assertEqual(file_info["filename"], "example.txt")
|
|
33
|
+
self.assertEqual(file_info["content_type"], "text/plain")
|
|
34
|
+
self.assertEqual(file_info["content"], b"Hello, world!")
|
|
35
|
+
self.assertEqual(file_info["size"], len(b"Hello, world!"))
|
|
36
|
+
|
|
37
|
+
def test_mixed_content(self):
|
|
38
|
+
"""Test decoding of mixed content (form fields and file uploads)."""
|
|
39
|
+
content_type = (
|
|
40
|
+
"multipart/form-data; boundary=----WebKitFormBoundary4AQwYOUUCEUw9hsX"
|
|
41
|
+
)
|
|
42
|
+
body = "LS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5NEFRd1lPVVVDRVV3OWhzWA0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWVsZDEiDQoNCnZhbHVlMQ0KLS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5NEFRd1lPVVVDRVV3OWhzWA0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWVsZDIiDQoNCnZhbHVlMg0KLS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5NEFRd1lPVVVDRVV3OWhzWA0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWxlMSI7IGZpbGVuYW1lPSJleGFtcGxlLnR4dCINCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbg0KDQpIZWxsbywgd29ybGQhDQotLS0tLS1XZWJLaXRGb3JtQm91bmRhcnk0QVF3WU9VVUNFVXc5aHNYLS0NCg=="
|
|
43
|
+
|
|
44
|
+
result = MultipartFormParser(body, content_type)
|
|
45
|
+
self.assertEqual(result["fields"]["field1"], "value1")
|
|
46
|
+
self.assertEqual(result["fields"]["field2"], "value2")
|
|
47
|
+
|
|
48
|
+
file_info = result["files"]["file1"]
|
|
49
|
+
self.assertEqual(file_info["filename"], "example.txt")
|
|
50
|
+
self.assertEqual(file_info["content_type"], "text/plain")
|
|
51
|
+
self.assertEqual(file_info["content"], b"Hello, world!")
|
|
52
|
+
self.assertEqual(file_info["size"], len(b"Hello, world!"))
|
|
53
|
+
|
|
54
|
+
def test_empty_body(self):
|
|
55
|
+
"""Test validation of empty request body."""
|
|
56
|
+
content_type = "multipart/form-data; boundary=something"
|
|
57
|
+
body = ""
|
|
58
|
+
|
|
59
|
+
with self.assertRaises(ValueError) as context:
|
|
60
|
+
MultipartFormParser(body, content_type)
|
|
61
|
+
self.assertIn("Request body cannot be empty", str(context.exception))
|
|
62
|
+
|
|
63
|
+
def test_malformed_multipart_data(self):
|
|
64
|
+
"""Test handling of malformed multipart data."""
|
|
65
|
+
content_type = "multipart/form-data; boundary=boundary"
|
|
66
|
+
# Base64 encoded partial/malformed multipart data
|
|
67
|
+
body = "LS0tYm91bmRhcnkNCkNvbnRlbnQtRGlzcG9zaXRpb246IGZvcm0tZGF0YTsNCg=="
|
|
68
|
+
|
|
69
|
+
with self.assertRaises(Exception) as context:
|
|
70
|
+
MultipartFormParser(body, content_type)
|
|
71
|
+
self.assertIn("Failed to", str(context.exception))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if __name__ == "__main__":
|
|
75
|
+
unittest.main()
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["setuptools >= 77.0.3"]
|
|
3
|
-
build-backend = "setuptools.build_meta"
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = "multipart-decoder"
|
|
7
|
-
version = "0.0.1"
|
|
8
|
-
authors = [
|
|
9
|
-
{ name="Satya Tulasi Jalandhar C H", email="satyatulasijalandharch2@gmail.com" },
|
|
10
|
-
]
|
|
11
|
-
description = "A lightweight Python package for decoding multipart/form-data content"
|
|
12
|
-
readme = "README.md"
|
|
13
|
-
requires-python = ">=3.8"
|
|
14
|
-
classifiers = [
|
|
15
|
-
"Programming Language :: Python :: 3",
|
|
16
|
-
"Operating System :: OS Independent",
|
|
17
|
-
]
|
|
18
|
-
license = "MIT"
|
|
19
|
-
license-files = ["LICENSE*"]
|
|
20
|
-
|
|
21
|
-
[project.urls]
|
|
22
|
-
Homepage = "https://github.com/satyatulasijalandharch/multipart-decoder"
|
|
23
|
-
Issues = "https://github.com/satyatulasijalandharch/multipart-decoder/issues"
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Decoder module for parsing multipart/form-data content.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import re
|
|
6
|
-
import base64
|
|
7
|
-
import email.parser
|
|
8
|
-
from io import BytesIO
|
|
9
|
-
from email.policy import default
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def MultipartDecoder(content_bytes, content_type):
|
|
13
|
-
"""
|
|
14
|
-
Decode multipart form-data from bytes content.
|
|
15
|
-
|
|
16
|
-
Args:
|
|
17
|
-
content_bytes (bytes): The raw multipart form-data content
|
|
18
|
-
content_type (str): The Content-Type header value (must include boundary)
|
|
19
|
-
|
|
20
|
-
Returns:
|
|
21
|
-
dict: A dictionary where keys are field names and values are either:
|
|
22
|
-
- strings for regular form fields
|
|
23
|
-
- tuples of (filename, content_type, data) for file uploads
|
|
24
|
-
|
|
25
|
-
Raises:
|
|
26
|
-
ValueError: If the content is not multipart or lacks required headers
|
|
27
|
-
"""
|
|
28
|
-
if not content_type or 'boundary=' not in content_type:
|
|
29
|
-
raise ValueError("Content-Type header must include a boundary parameter")
|
|
30
|
-
|
|
31
|
-
try:
|
|
32
|
-
content_bytes = base64.b64decode(content_bytes)
|
|
33
|
-
except (base64.binascii.Error, TypeError) as e:
|
|
34
|
-
raise ValueError("Failed to decode base64 content") from e
|
|
35
|
-
# Prepare the content with proper headers for the email parser
|
|
36
|
-
content = BytesIO()
|
|
37
|
-
content.write(b'Content-Type: ' + content_type.encode('utf-8') + b'\r\n\r\n')
|
|
38
|
-
content.write(content_bytes)
|
|
39
|
-
content.seek(0)
|
|
40
|
-
|
|
41
|
-
# Parse the multipart content
|
|
42
|
-
parser = email.parser.BytesParser(policy=default)
|
|
43
|
-
message = parser.parse(content)
|
|
44
|
-
|
|
45
|
-
if not message.is_multipart():
|
|
46
|
-
raise ValueError("Content is not multipart")
|
|
47
|
-
|
|
48
|
-
form_data = {}
|
|
49
|
-
|
|
50
|
-
# Process each part of the multipart message
|
|
51
|
-
for part in message.get_payload():
|
|
52
|
-
# Get the Content-Disposition header to extract field name and filename
|
|
53
|
-
content_disposition = part.get("Content-Disposition", "")
|
|
54
|
-
# Use regex to extract field name from content disposition
|
|
55
|
-
name_match = re.search(r'name="([^"]*)"', content_disposition)
|
|
56
|
-
|
|
57
|
-
if name_match:
|
|
58
|
-
field_name = name_match.group(1)
|
|
59
|
-
filename_match = re.search(r'filename="([^"]*)"', content_disposition)
|
|
60
|
-
|
|
61
|
-
if filename_match:
|
|
62
|
-
# This is a file upload
|
|
63
|
-
filename = filename_match.group(1)
|
|
64
|
-
part_content_type = part.get_content_type()
|
|
65
|
-
data = part.get_payload(decode=True) # Binary file content
|
|
66
|
-
form_data[field_name] = (filename, part_content_type, data)
|
|
67
|
-
else:
|
|
68
|
-
# This is a regular form field
|
|
69
|
-
form_data[field_name] = part.get_payload(decode=True).decode(part.get_content_charset('utf-8'))
|
|
70
|
-
|
|
71
|
-
return form_data
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Unit tests for the multipart_decoder package.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import unittest
|
|
6
|
-
from multipart_decoder import MultipartDecoder
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class TestMultipartDecoder(unittest.TestCase):
|
|
10
|
-
def test_basic_form_fields(self):
|
|
11
|
-
"""Test decoding of basic form fields."""
|
|
12
|
-
content_type = (
|
|
13
|
-
"multipart/form-data; boundary=----WebKitFormBoundaryp0mBfJEEcQtmxFCF"
|
|
14
|
-
)
|
|
15
|
-
body = "LS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5cDBtQmZKRUVjUXRteEZDRg0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWVsZDEiDQoNCnZhbHVlMQ0KLS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5cDBtQmZKRUVjUXRteEZDRg0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWVsZDIiDQoNCnZhbHVlMg0KLS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5cDBtQmZKRUVjUXRteEZDRi0tDQo="
|
|
16
|
-
|
|
17
|
-
result = MultipartDecoder(body, content_type)
|
|
18
|
-
self.assertEqual(result["field1"], "value1")
|
|
19
|
-
self.assertEqual(result["field2"], "value2")
|
|
20
|
-
|
|
21
|
-
def test_file_upload(self):
|
|
22
|
-
"""Test decoding of file uploads."""
|
|
23
|
-
content_type = (
|
|
24
|
-
"multipart/form-data; boundary=----WebKitFormBoundaryI6YbOCiSAMjU6wK2"
|
|
25
|
-
)
|
|
26
|
-
body = "LS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5STZZYk9DaVNBTWpVNndLMg0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWxlMSI7IGZpbGVuYW1lPSJleGFtcGxlLnR4dCINCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbg0KDQpIZWxsbywgd29ybGQhDQotLS0tLS1XZWJLaXRGb3JtQm91bmRhcnlJNlliT0NpU0FNalU2d0syLS0NCg=="
|
|
27
|
-
|
|
28
|
-
result = MultipartDecoder(body, content_type)
|
|
29
|
-
self.assertTrue("file1" in result)
|
|
30
|
-
self.assertTrue(isinstance(result["file1"], tuple))
|
|
31
|
-
|
|
32
|
-
filename, content_type, data = result["file1"]
|
|
33
|
-
self.assertEqual(filename, "example.txt")
|
|
34
|
-
self.assertEqual(content_type, "text/plain")
|
|
35
|
-
self.assertEqual(data, b"Hello, world!")
|
|
36
|
-
|
|
37
|
-
def test_mixed_content(self):
|
|
38
|
-
"""Test decoding of mixed content (form fields and file uploads)."""
|
|
39
|
-
content_type = (
|
|
40
|
-
"multipart/form-data; boundary=----WebKitFormBoundary4AQwYOUUCEUw9hsX"
|
|
41
|
-
)
|
|
42
|
-
body = "LS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5NEFRd1lPVVVDRVV3OWhzWA0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWVsZDEiDQoNCnZhbHVlMQ0KLS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5NEFRd1lPVVVDRVV3OWhzWA0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWVsZDIiDQoNCnZhbHVlMg0KLS0tLS0tV2ViS2l0Rm9ybUJvdW5kYXJ5NEFRd1lPVVVDRVV3OWhzWA0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJmaWxlMSI7IGZpbGVuYW1lPSJleGFtcGxlLnR4dCINCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFpbg0KDQpIZWxsbywgd29ybGQhDQotLS0tLS1XZWJLaXRGb3JtQm91bmRhcnk0QVF3WU9VVUNFVXc5aHNYLS0NCg=="
|
|
43
|
-
|
|
44
|
-
result = MultipartDecoder(body, content_type)
|
|
45
|
-
self.assertEqual(result["field1"], "value1")
|
|
46
|
-
|
|
47
|
-
filename, content_type, data = result["file1"]
|
|
48
|
-
self.assertEqual(filename, "example.txt")
|
|
49
|
-
self.assertEqual(content_type, "text/plain")
|
|
50
|
-
self.assertEqual(data, b"Hello, world!")
|
|
51
|
-
|
|
52
|
-
def test_invalid_content(self):
|
|
53
|
-
"""Test handling of invalid content."""
|
|
54
|
-
content_type = (
|
|
55
|
-
"multipart/form-data; boundary=----WebKitFormBoundaryI6YbOCiSAMjU6wK2"
|
|
56
|
-
)
|
|
57
|
-
body = "LS0tWxsbCg=="
|
|
58
|
-
|
|
59
|
-
with self.assertRaises(ValueError):
|
|
60
|
-
MultipartDecoder(body, content_type)
|
|
61
|
-
|
|
62
|
-
def test_missing_boundary(self):
|
|
63
|
-
"""Test handling of missing boundary parameter."""
|
|
64
|
-
content_type = "multipart/form-data"
|
|
65
|
-
body = "LS0tWxsbCg=="
|
|
66
|
-
|
|
67
|
-
with self.assertRaises(ValueError):
|
|
68
|
-
MultipartDecoder(body, content_type)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if __name__ == "__main__":
|
|
72
|
-
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{multipart_decoder-0.0.1 → multipart_decoder-1.0.0}/src/multipart_decoder.egg-info/top_level.txt
RENAMED
|
File without changes
|