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()