multipart parsing for file uploads exposes one class multipartparser which feeds chunks of uploaded data to file upload handlers for processing no more reads are allowed from this device an rfc 7578 multipartformdata parser multivaluedict parse reads the input stream in chunksize chunks and returns a tuple of multivaluedictpost multivaluedictfiles initialize the multipartparser object meta the standard meta dictionary in django request objects inputdata the raw post data as a filelike object uploadhandlers a list of uploadhandler instances that perform operations on the uploaded data encoding the encoding with which to treat the incoming data contenttype should contain multipart and the boundary information parse the header to get the boundary to split the parts contentlength should contain the length of the body we are about to receive this means we shouldn t continue raise an error for compatibility with lowlevel network apis with 32bit integers the chunk size should be 231 but still divisible by 4 call the actual parse routine and close all open files in case of errors this is needed because if exceptions are thrown the multipartparser will not be garbage collected immediately and resources would be kept alive this is only needed for errors because the request object closes all uploaded files at the end of the request parse the post data and break it into a files multivaluedict and a post multivaluedict return a tuple containing the post and files dictionary respectively http spec says that contentlength 0 is valid handling contentlength 0 before continuing see if any of the handlers take care of the parsing this allows overriding everything if need be check to see if it was handled create the data structures to be used later instantiate the parser and stream whether or not to signal a filecompletion at the beginning of the loop number of bytes that have been read to count the number of keys in the request to count the number of files in the request to limit the amount of data read from the request whether a file upload is finished we run this at the beginning of the next loop since we cannot be sure a file is complete until we hit the next boundarypart of the multipart content avoid storing more than datauploadmaxnumberfields 2 accounts for empty raw fields before and after the last boundary avoid reading more than datauploadmaxmemorysize this is a post field we can just set it in the post add two here to make the check consistent with the xwwwformurlencoded check that includes avoid storing more than datauploadmaxnumberfiles this is a file use the handler we only specialcase base64 transfer encoding we should always decode base64 chunks by multiple of 4 ignoring whitespace since this is only a chunk any error is an unfixable error don t continue if the chunk received by the handler is none just use up the rest of this file handle file upload completions on next iteration if this is neither a field nor a file exhaust the field stream note there could be an error here at some point but there will be at least two raw types before and after the other boundaries this branch is usually not reached at all because a missing contentdisposition header will skip the whole boundary make sure that the request data is all fed signal that the upload has completed any shortcircuits if a handler s uploadcomplete returns a value handle all the signaling that takes place when a file is complete if it returns a file object then set the files dict sanitize the filename of an upload remove all possible path separators even though that might remove more than actually required by the target system filenames that could potentially cause problems currentparent dir are also discarded it should be noted that this function could still return a filepath like c somefile txt which is handled later on by the storage layer so while this function does sanitize filenames to some extent the resulting filename should still be considered as untrusted user input remove nonprintable characters free up all file handles fixme this currently assumes that upload handlers store the file as file we should document that maybe add handler freefile to complement newfile the lazystream wrapper allows one to get and unget bytes from a stream given a producer object an iterator that yields bytestrings the lazystream object will support iteration reading and keeping a lookback variable in case you need to unget some bytes every lazystream must have a producer when instantiated a producer is an iterable that returns a string each time it is called do the whole thing in one shot if no limit was provided otherwise do some bookkeeping to return exactly enough of the stream and stashing any extra content we get from the producer used when the exact number of bytes to read is unimportant return whatever chunk is conveniently returned from the iterator useful to avoid unnecessary bookkeeping if performance is an issue used to invalidatedisable this lazy stream replace the producer with an empty list any leftover bytes that have already been read will still be reported upon read andor next place bytes back onto the front of the lazy stream future calls to read will return those bytes first the stream position and thus tell will be rewound update the unget history as a sanity check to see if we ve pushed back the same number of bytes in one chunk if we keep ungetting the same number of bytes many times here 50 we re mostly likely in an infinite loop of some sort this is usually caused by a maliciouslymalformed mime request an iterable that will yield chunks of data given a filelike object as the constructor yield chunks of read operations from that object a producer that will iterate over boundaries a producer that is sensitive to boundaries will happily yield bytes until a boundary is found will yield the bytes before the boundary throw away the boundary bytes themselves and push the postboundary bytes back on the stream the future calls to next after locating the boundary will raise a stopiteration exception rollback an additional six bytes because the format is like this crlfboundarycrlf try to use mx fast string search if available otherwise use python find wrap the latter for consistency make sure we don t treat a partial boundary and its separators as data there s nothing left we should just return and mark as done find a multipart boundary in data should no boundary exist in the data return none otherwise return a tuple containing the indices of the following the end of current encapsulation the start of the next encapsulation backup over crlf exhaust an iterator or stream try iterator iterstreamoriterable except typeerror iterator chunkiterstreamoriterable 16384 collections dequeiterator maxlen0 consume iterator quickly def parseboundarystreamstream maxheadersize look for the end of headers and if not found extend the search to double the size up to the maxtotalheadersize headerschunksize 1024 while true if headerschunksize maxheadersize raise multipartparsererrorrequest max total header size exceeded stream at beginning of header look for end of header and parse it if found the header must fit within one chunk chunk stream readheaderschunksize find returns the top of these four bytes so munch them later to prevent them from polluting the payload headerend chunk findbrnrn if headerend 1 break find no header mark this fact and pass on the stream verbatim stream ungetchunk no more data to read if lenchunk headerschunksize return raw stream double the chunk size headerschunksize 2 header chunk headerend here we place any excess chunk back onto the stream as well as throwing away the crlfcrlf bytes from above stream ungetchunkheaderend 4 type raw outdict eliminate blank lines for line in header splitbrn this terminology main value and dictionary of parameters is from the python docs try mainvaluepair params parseheaderparametersline decode name value mainvaluepair split 1 params k v encode for k v in params items except valueerror invalid header continue if name contentdisposition type field if params getfilename type file outdictname value params if type raw stream ungetchunk return type outdict stream class parser def initself stream boundary self stream stream self separator b boundary def iterself boundarystream interboundaryiterself stream self separator for substream in boundarystream iterate over each part yield parseboundarystreamsubstream maxtotalheadersize no more reads are allowed from this device an rfc 7578 multipart form data parser multivaluedict parse reads the input stream in chunk_size chunks and returns a tuple of multivaluedict post multivaluedict files initialize the multipartparser object meta the standard meta dictionary in django request objects input_data the raw post data as a file like object upload_handlers a list of uploadhandler instances that perform operations on the uploaded data encoding the encoding with which to treat the incoming data content type should contain multipart and the boundary information parse the header to get the boundary to split the parts content length should contain the length of the body we are about to receive this means we shouldn t continue raise an error for compatibility with low level network apis with 32 bit integers the chunk size should be 2 31 but still divisible by 4 call the actual parse routine and close all open files in case of errors this is needed because if exceptions are thrown the multipartparser will not be garbage collected immediately and resources would be kept alive this is only needed for errors because the request object closes all uploaded files at the end of the request parse the post data and break it into a files multivaluedict and a post multivaluedict return a tuple containing the post and files dictionary respectively http spec says that content length 0 is valid handling content length 0 before continuing see if any of the handlers take care of the parsing this allows overriding everything if need be check to see if it was handled create the data structures to be used later instantiate the parser and stream whether or not to signal a file completion at the beginning of the loop number of bytes that have been read to count the number of keys in the request to count the number of files in the request to limit the amount of data read from the request whether a file upload is finished we run this at the beginning of the next loop since we cannot be sure a file is complete until we hit the next boundary part of the multipart content avoid storing more than data_upload_max_number_fields 2 accounts for empty raw fields before and after the last boundary avoid reading more than data_upload_max_memory_size this is a post field we can just set it in the post add two here to make the check consistent with the x www form urlencoded check that includes avoid storing more than data_upload_max_number_files this is a file use the handler we only special case base64 transfer encoding we should always decode base64 chunks by multiple of 4 ignoring whitespace since this is only a chunk any error is an unfixable error don t continue if the chunk received by the handler is none just use up the rest of this file handle file upload completions on next iteration if this is neither a field nor a file exhaust the field stream note there could be an error here at some point but there will be at least two raw types before and after the other boundaries this branch is usually not reached at all because a missing content disposition header will skip the whole boundary make sure that the request data is all fed signal that the upload has completed any shortcircuits if a handler s upload_complete returns a value handle all the signaling that takes place when a file is complete if it returns a file object then set the files dict sanitize the filename of an upload remove all possible path separators even though that might remove more than actually required by the target system filenames that could potentially cause problems current parent dir are also discarded it should be noted that this function could still return a filepath like c some_file txt which is handled later on by the storage layer so while this function does sanitize filenames to some extent the resulting filename should still be considered as untrusted user input remove non printable characters free up all file handles fixme this currently assumes that upload handlers store the file as file we should document that maybe add handler free_file to complement new_file the lazystream wrapper allows one to get and unget bytes from a stream given a producer object an iterator that yields bytestrings the lazystream object will support iteration reading and keeping a look back variable in case you need to unget some bytes every lazystream must have a producer when instantiated a producer is an iterable that returns a string each time it is called do the whole thing in one shot if no limit was provided otherwise do some bookkeeping to return exactly enough of the stream and stashing any extra content we get from the producer used when the exact number of bytes to read is unimportant return whatever chunk is conveniently returned from the iterator useful to avoid unnecessary bookkeeping if performance is an issue used to invalidate disable this lazy stream replace the producer with an empty list any leftover bytes that have already been read will still be reported upon read and or next place bytes back onto the front of the lazy stream future calls to read will return those bytes first the stream position and thus tell will be rewound update the unget history as a sanity check to see if we ve pushed back the same number of bytes in one chunk if we keep ungetting the same number of bytes many times here 50 we re mostly likely in an infinite loop of some sort this is usually caused by a maliciously malformed mime request an iterable that will yield chunks of data given a file like object as the constructor yield chunks of read operations from that object a producer that will iterate over boundaries a producer that is sensitive to boundaries will happily yield bytes until a boundary is found will yield the bytes before the boundary throw away the boundary bytes themselves and push the post boundary bytes back on the stream the future calls to next after locating the boundary will raise a stopiteration exception rollback an additional six bytes because the format is like this crlf boundary crlf try to use mx fast string search if available otherwise use python find wrap the latter for consistency make sure we don t treat a partial boundary and its separators as data and len chunk len self _boundary 6 there s nothing left we should just return and mark as done find a multipart boundary in data should no boundary exist in the data return none otherwise return a tuple containing the indices of the following the end of current encapsulation the start of the next encapsulation backup over crlf exhaust an iterator or stream consume iterator quickly parse one and exactly one stream that encapsulates a boundary look for the end of headers and if not found extend the search to double the size up to the max_total_header_size stream at beginning of header look for end of header and parse it if found the header must fit within one chunk find returns the top of these four bytes so munch them later to prevent them from polluting the payload find no header mark this fact and pass on the stream verbatim no more data to read double the chunk size here we place any excess chunk back onto the stream as well as throwing away the crlfcrlf bytes from above eliminate blank lines this terminology main value and dictionary of parameters is from the python docs invalid header iterate over each part
import base64
import binascii
import collections
import html

from django.conf import settings
from django.core.exceptions import (
    RequestDataTooBig,
    SuspiciousMultipartForm,
    TooManyFieldsSent,
    TooManyFilesSent,
)
from django.core.files.uploadhandler import SkipFile, StopFutureHandlers, StopUpload
from django.utils.datastructures import MultiValueDict
from django.utils.encoding import force_str
from django.utils.http import parse_header_parameters
from django.utils.regex_helper import _lazy_re_compile

__all__ = ("MultiPartParser", "MultiPartParserError", "InputStreamExhausted")


class MultiPartParserError(Exception):
    pass


class InputStreamExhausted(Exception):
    

    pass


RAW = "raw"
FILE = "file"
FIELD = "field"
FIELD_TYPES = frozenset([FIELD, RAW])
MAX_TOTAL_HEADER_SIZE = 1024


class MultiPartParser:
    

    boundary_re = _lazy_re_compile(r"[ -~]{0,200}[!-~]")

    def __init__(self, META, input_data, upload_handlers, encoding=None):
        
        
        content_type = META.get("CONTENT_TYPE", "")
        if not content_type.startswith("multipart/"):
            raise MultiPartParserError("Invalid Content-Type: %s" % content_type)

        try:
            content_type.encode("ascii")
        except UnicodeEncodeError:
            raise MultiPartParserError(
                "Invalid non-ASCII Content-Type in multipart: %s"
                % force_str(content_type)
            )

        
        _, opts = parse_header_parameters(content_type)
        boundary = opts.get("boundary")
        if not boundary or not self.boundary_re.fullmatch(boundary):
            raise MultiPartParserError(
                "Invalid boundary in multipart: %s" % force_str(boundary)
            )

        
        
        try:
            content_length = int(META.get("CONTENT_LENGTH", 0))
        except (ValueError, TypeError):
            content_length = 0

        if content_length < 0:
            
            raise MultiPartParserError("Invalid content length: %r" % content_length)

        self._boundary = boundary.encode("ascii")
        self._input_data = input_data

        
        
        possible_sizes = [x.chunk_size for x in upload_handlers if x.chunk_size]
        self._chunk_size = min([2**31 - 4] + possible_sizes)

        self._meta = META
        self._encoding = encoding or settings.DEFAULT_CHARSET
        self._content_length = content_length
        self._upload_handlers = upload_handlers

    def parse(self):
        
        
        
        
        
        
        try:
            return self._parse()
        except Exception:
            if hasattr(self, "_files"):
                for _, files in self._files.lists():
                    for fileobj in files:
                        fileobj.close()
            raise

    def _parse(self):
        
        from django.http import QueryDict

        encoding = self._encoding
        handlers = self._upload_handlers

        
        
        if self._content_length == 0:
            return QueryDict(encoding=self._encoding), MultiValueDict()

        
        
        for handler in handlers:
            result = handler.handle_raw_input(
                self._input_data,
                self._meta,
                self._content_length,
                self._boundary,
                encoding,
            )
            
            if result is not None:
                return result[0], result[1]

        
        self._post = QueryDict(mutable=True)
        self._files = MultiValueDict()

        
        stream = LazyStream(ChunkIter(self._input_data, self._chunk_size))

        
        old_field_name = None
        counters = [0] * len(handlers)

        
        num_bytes_read = 0
        
        num_post_keys = 0
        
        num_files = 0
        
        read_size = None
        
        uploaded_file = True

        try:
            for item_type, meta_data, field_stream in Parser(stream, self._boundary):
                if old_field_name:
                    
                    
                    
                    self.handle_file_complete(old_field_name, counters)
                    old_field_name = None
                    uploaded_file = True

                if (
                    item_type in FIELD_TYPES
                    and settings.DATA_UPLOAD_MAX_NUMBER_FIELDS is not None
                ):
                    
                    num_post_keys += 1
                    
                    
                    if settings.DATA_UPLOAD_MAX_NUMBER_FIELDS + 2 < num_post_keys:
                        raise TooManyFieldsSent(
                            "The number of GET/POST parameters exceeded "
                            "settings.DATA_UPLOAD_MAX_NUMBER_FIELDS."
                        )

                try:
                    disposition = meta_data["content-disposition"][1]
                    field_name = disposition["name"].strip()
                except (KeyError, IndexError, AttributeError):
                    continue

                transfer_encoding = meta_data.get("content-transfer-encoding")
                if transfer_encoding is not None:
                    transfer_encoding = transfer_encoding[0].strip()
                field_name = force_str(field_name, encoding, errors="replace")

                if item_type == FIELD:
                    
                    if settings.DATA_UPLOAD_MAX_MEMORY_SIZE is not None:
                        read_size = (
                            settings.DATA_UPLOAD_MAX_MEMORY_SIZE - num_bytes_read
                        )

                    
                    if transfer_encoding == "base64":
                        raw_data = field_stream.read(size=read_size)
                        num_bytes_read += len(raw_data)
                        try:
                            data = base64.b64decode(raw_data)
                        except binascii.Error:
                            data = raw_data
                    else:
                        data = field_stream.read(size=read_size)
                        num_bytes_read += len(data)

                    
                    
                    num_bytes_read += len(field_name) + 2
                    if (
                        settings.DATA_UPLOAD_MAX_MEMORY_SIZE is not None
                        and num_bytes_read > settings.DATA_UPLOAD_MAX_MEMORY_SIZE
                    ):
                        raise RequestDataTooBig(
                            "Request body exceeded "
                            "settings.DATA_UPLOAD_MAX_MEMORY_SIZE."
                        )

                    self._post.appendlist(
                        field_name, force_str(data, encoding, errors="replace")
                    )
                elif item_type == FILE:
                    
                    num_files += 1
                    if (
                        settings.DATA_UPLOAD_MAX_NUMBER_FILES is not None
                        and num_files > settings.DATA_UPLOAD_MAX_NUMBER_FILES
                    ):
                        raise TooManyFilesSent(
                            "The number of files exceeded "
                            "settings.DATA_UPLOAD_MAX_NUMBER_FILES."
                        )
                    
                    file_name = disposition.get("filename")
                    if file_name:
                        file_name = force_str(file_name, encoding, errors="replace")
                        file_name = self.sanitize_file_name(file_name)
                    if not file_name:
                        continue

                    content_type, content_type_extra = meta_data.get(
                        "content-type", ("", {})
                    )
                    content_type = content_type.strip()
                    charset = content_type_extra.get("charset")

                    try:
                        content_length = int(meta_data.get("content-length")[0])
                    except (IndexError, TypeError, ValueError):
                        content_length = None

                    counters = [0] * len(handlers)
                    uploaded_file = False
                    try:
                        for handler in handlers:
                            try:
                                handler.new_file(
                                    field_name,
                                    file_name,
                                    content_type,
                                    content_length,
                                    charset,
                                    content_type_extra,
                                )
                            except StopFutureHandlers:
                                break

                        for chunk in field_stream:
                            if transfer_encoding == "base64":
                                
                                
                                

                                stripped_chunk = b"".join(chunk.split())

                                remaining = len(stripped_chunk) % 4
                                while remaining != 0:
                                    over_chunk = field_stream.read(4 - remaining)
                                    if not over_chunk:
                                        break
                                    stripped_chunk += b"".join(over_chunk.split())
                                    remaining = len(stripped_chunk) % 4

                                try:
                                    chunk = base64.b64decode(stripped_chunk)
                                except Exception as exc:
                                    
                                    
                                    raise MultiPartParserError(
                                        "Could not decode base64 data."
                                    ) from exc

                            for i, handler in enumerate(handlers):
                                chunk_length = len(chunk)
                                chunk = handler.receive_data_chunk(chunk, counters[i])
                                counters[i] += chunk_length
                                if chunk is None:
                                    
                                    
                                    break

                    except SkipFile:
                        self._close_files()
                        
                        exhaust(field_stream)
                    else:
                        
                        old_field_name = field_name
                else:
                    
                    
                    
                    
                    
                    
                    exhaust(field_stream)
        except StopUpload as e:
            self._close_files()
            if not e.connection_reset:
                exhaust(self._input_data)
        else:
            if not uploaded_file:
                for handler in handlers:
                    handler.upload_interrupted()
            
            exhaust(self._input_data)

        
        
        any(handler.upload_complete() for handler in handlers)
        self._post._mutable = False
        return self._post, self._files

    def handle_file_complete(self, old_field_name, counters):
        
        for i, handler in enumerate(self._upload_handlers):
            file_obj = handler.file_complete(counters[i])
            if file_obj:
                
                self._files.appendlist(
                    force_str(old_field_name, self._encoding, errors="replace"),
                    file_obj,
                )
                break

    def sanitize_file_name(self, file_name):
        
        file_name = html.unescape(file_name)
        file_name = file_name.rsplit("/")[-1]
        file_name = file_name.rsplit("\\")[-1]
        
        file_name = "".join([char for char in file_name if char.isprintable()])

        if file_name in {"", ".", ".."}:
            return None
        return file_name

    IE_sanitize = sanitize_file_name

    def _close_files(self):
        
        
        
        
        for handler in self._upload_handlers:
            if hasattr(handler, "file"):
                handler.file.close()


class LazyStream:
    

    def __init__(self, producer, length=None):
        
        self._producer = producer
        self._empty = False
        self._leftover = b""
        self.length = length
        self.position = 0
        self._remaining = length
        self._unget_history = []

    def tell(self):
        return self.position

    def read(self, size=None):
        def parts():
            remaining = self._remaining if size is None else size
            
            if remaining is None:
                yield b"".join(self)
                return

            
            
            
            while remaining != 0:
                assert remaining > 0, "remaining bytes to read should never go negative"

                try:
                    chunk = next(self)
                except StopIteration:
                    return
                else:
                    emitting = chunk[:remaining]
                    self.unget(chunk[remaining:])
                    remaining -= len(emitting)
                    yield emitting

        return b"".join(parts())

    def __next__(self):
        
        if self._leftover:
            output = self._leftover
            self._leftover = b""
        else:
            output = next(self._producer)
            self._unget_history = []
        self.position += len(output)
        return output

    def close(self):
        
        self._producer = []

    def __iter__(self):
        return self

    def unget(self, bytes):
        
        if not bytes:
            return
        self._update_unget_history(len(bytes))
        self.position -= len(bytes)
        self._leftover = bytes + self._leftover

    def _update_unget_history(self, num_bytes):
        
        self._unget_history = [num_bytes] + self._unget_history[:49]
        number_equal = len(
            [
                current_number
                for current_number in self._unget_history
                if current_number == num_bytes
            ]
        )

        if number_equal > 40:
            raise SuspiciousMultipartForm(
                "The multipart parser got stuck, which shouldn't happen with"
                " normal uploaded files. Check for malicious upload activity;"
                " if there is none, report this to the Django developers."
            )


class ChunkIter:
    

    def __init__(self, flo, chunk_size=64 * 1024):
        self.flo = flo
        self.chunk_size = chunk_size

    def __next__(self):
        try:
            data = self.flo.read(self.chunk_size)
        except InputStreamExhausted:
            raise StopIteration()
        if data:
            return data
        else:
            raise StopIteration()

    def __iter__(self):
        return self


class InterBoundaryIter:
    

    def __init__(self, stream, boundary):
        self._stream = stream
        self._boundary = boundary

    def __iter__(self):
        return self

    def __next__(self):
        try:
            return LazyStream(BoundaryIter(self._stream, self._boundary))
        except InputStreamExhausted:
            raise StopIteration()


class BoundaryIter:
    

    def __init__(self, stream, boundary):
        self._stream = stream
        self._boundary = boundary
        self._done = False
        
        
        self._rollback = len(boundary) + 6

        
        
        unused_char = self._stream.read(1)
        if not unused_char:
            raise InputStreamExhausted()
        self._stream.unget(unused_char)

    def __iter__(self):
        return self

    def __next__(self):
        if self._done:
            raise StopIteration()

        stream = self._stream
        rollback = self._rollback

        bytes_read = 0
        chunks = []
        for bytes in stream:
            bytes_read += len(bytes)
            chunks.append(bytes)
            if bytes_read > rollback:
                break
            if not bytes:
                break
        else:
            self._done = True

        if not chunks:
            raise StopIteration()

        chunk = b"".join(chunks)
        boundary = self._find_boundary(chunk)

        if boundary:
            end, next = boundary
            stream.unget(chunk[next:])
            self._done = True
            return chunk[:end]
        else:
            
            
            if not chunk[:-rollback]:  
                
                self._done = True
                return chunk
            else:
                stream.unget(chunk[-rollback:])
                return chunk[:-rollback]

    def _find_boundary(self, data):
        
        index = data.find(self._boundary)
        if index < 0:
            return None
        else:
            end = index
            next = index + len(self._boundary)
            
            last = max(0, end - 1)
            if data[last : last + 1] == b"\n":
                end -= 1
            last = max(0, end - 1)
            if data[last : last + 1] == b"\r":
                end -= 1
            return end, next


def exhaust(stream_or_iterable):
    
    try:
        iterator = iter(stream_or_iterable)
    except TypeError:
        iterator = ChunkIter(stream_or_iterable, 16384)
    collections.deque(iterator, maxlen=0)  


def parse_boundary_stream(stream, max_header_size):
    

    
    
    headers_chunk_size = 1024
    while True:
        if headers_chunk_size > max_header_size:
            raise MultiPartParserError("Request max total header size exceeded.")

        
        
        chunk = stream.read(headers_chunk_size)
        
        
        header_end = chunk.find(b"\r\n\r\n")
        if header_end != -1:
            break

        
        stream.unget(chunk)
        
        if len(chunk) < headers_chunk_size:
            return (RAW, {}, stream)
        
        headers_chunk_size *= 2

    header = chunk[:header_end]

    
    
    stream.unget(chunk[header_end + 4 :])

    TYPE = RAW
    outdict = {}

    
    for line in header.split(b"\r\n"):
        
        
        try:
            main_value_pair, params = parse_header_parameters(line.decode())
            name, value = main_value_pair.split(":", 1)
            params = {k: v.encode() for k, v in params.items()}
        except ValueError:  
            continue

        if name == "content-disposition":
            TYPE = FIELD
            if params.get("filename"):
                TYPE = FILE

        outdict[name] = value, params

    if TYPE == RAW:
        stream.unget(chunk)

    return (TYPE, outdict, stream)


class Parser:
    def __init__(self, stream, boundary):
        self._stream = stream
        self._separator = b"--" + boundary

    def __iter__(self):
        boundarystream = InterBoundaryIter(self._stream, self._separator)
        for sub_stream in boundarystream:
            
            yield parse_boundary_stream(sub_stream, MAX_TOTAL_HEADER_SIZE)