129 lines
4.1 KiB
Python
129 lines
4.1 KiB
Python
from dataclasses import dataclass
|
|
from http.server import SimpleHTTPRequestHandler
|
|
|
|
from byteb4rb1e.utils.io import ChunksIO
|
|
|
|
|
|
@dataclass
|
|
class HandlerOptions:
|
|
"""configuration options of the HTTP POST method handler
|
|
"""
|
|
max_chunk_size: int = ChunksIO.max_chunk_size
|
|
# default (in memory) buffer size in bytes (from KiB) of the sliding buffer
|
|
# reading from the pure (unchunked) client read stream
|
|
buffer_size: int = 512 * 1024
|
|
|
|
|
|
@dataclass
|
|
class ServerOptions:
|
|
"""configuration options of the HTTP server
|
|
"""
|
|
handler: HandlerOptions
|
|
hostname: str = ''
|
|
port: int = 8000
|
|
|
|
|
|
class MultipartUploadHandler(SimpleHTTPRequestHandler):
|
|
"""Simple, yet compliant HTTP/1.0 MIME Multipart Upload Handler
|
|
|
|
Implementation of a RFC1341 & RFC7578 compliant server for handling
|
|
multipart uploads.
|
|
|
|
This is meant as a utility for debugging MIME Multipart upload clients
|
|
|
|
Support for:
|
|
- client 'Expect' header
|
|
- chunked transfer-encoding
|
|
"""
|
|
media_subtypes = [
|
|
'mixed',
|
|
'alternative',
|
|
'parallel',
|
|
'digest',
|
|
'form-data'
|
|
]
|
|
|
|
def do_POST(self):
|
|
h_content_type = self.headers.get('Content-Type')
|
|
h_expect = self.headers.get('Expect')
|
|
h_transfer_encoding = self.headers.get('Transfer-Encoding')
|
|
|
|
if h_content_type == None:
|
|
self.send_error(400, 'Missing \'Content-Type\' header')
|
|
|
|
content_type_segments = [s.strip() for s in h_content_type.split(';')]
|
|
try:
|
|
media_type, media_subtype = content_type_segments[0].split('/', 1)
|
|
except IndexError:
|
|
self.send_error(
|
|
400,
|
|
'no value was supplied for \'Content-Type\' header'
|
|
)
|
|
except ValueError:
|
|
self.send_error(
|
|
400,
|
|
'unable to parse media type and subtype from ' +
|
|
'first (semicolon-delimited) segment of \'Content-Type\' ' +
|
|
f'header value: {content_type_segments[0]}'
|
|
)
|
|
|
|
if media_type != 'multipart':
|
|
self.send_error(
|
|
400,
|
|
'unsupported media type in \'Content-Type\' header value: ' +
|
|
f'{media_type}'
|
|
)
|
|
elif media_subtype not in self.media_subtypes:
|
|
self.send_error(
|
|
400,
|
|
'unsupported media sub-type in \'Content-Type\' header value: ' +
|
|
f'{media_type}. Must be one of {", ".join(self.media_subtypes)}'
|
|
)
|
|
|
|
if h_transfer_encoding:
|
|
if h_transfer_encoding != 'chunked':
|
|
self.send_error(
|
|
501,
|
|
f'unable to handle transfer-encoding: {h_transfer_encoding}'
|
|
)
|
|
|
|
content_type_params = {v[0].strip():v[1].strip() for v in [
|
|
s.split('=', 1) for s in content_type_segments[1:]
|
|
]}
|
|
|
|
boundary = content_type_params.get('boundary', '')
|
|
boundary_len = len(boundary)
|
|
if boundary == '':
|
|
self.send_error(
|
|
400,
|
|
'missing \'boundary\' parameter in \'Content-Type\' header field'
|
|
)
|
|
elif boundary_len > 70:
|
|
self.send_error(
|
|
400,
|
|
'\'boundary\' parameter value in \'Content-Type\' too long. ' +
|
|
f'Is {boundary_len} characters long, must be less than 70.'
|
|
)
|
|
|
|
del content_type_params['boundary']
|
|
content_type_params_keys = content_type_params.keys()
|
|
if len(content_type_params_keys) > 0:
|
|
self.send_error(
|
|
400,
|
|
'None other than \'boundary\' parameter in \'Content-Type\'' +
|
|
'header expected. Also received ' +
|
|
'{\', \'.join(content_type_param_keys)}'
|
|
)
|
|
|
|
|
|
self.handle_expect_100()
|
|
|
|
# read the first 4-bytes of the body to check if it has a preamble
|
|
# indication
|
|
|
|
# well great... curl is not RFC 1341 compliant. And RFC 1341 is asking
|
|
# for tolerance towards non-compliant clients...
|
|
|
|
self.send_response(200, 'OK')
|
|
self.end_headers()
|
|
|