def parse(content_type: str, data: bytes) -> "HTTPMultipartBody": """ Parse HTTP v1 Multipart Body. It will raise an Error during the parse period if parse failed. """ body_args = HTTPMultipartBody() if not content_type.lower().startswith("multipart/form-data"): raise ProtocolError("Unknown content-type.") for field in content_type.split(";"): # Search Boundary if field.find("boundary=") == -1: continue boundary = ensure_bytes(field.split("=")[1]) if boundary.startswith(b'"') and boundary.endswith(b'"'): boundary = boundary[1:-1] break else: raise ProtocolError("Cannot Find Boundary.") full_boundary = b"--" + boundary body_content = data.split(full_boundary + b"--")[0] full_boundary += _CRLF_BYTES_MARK splitted_body_content = body_content.split(full_boundary) for part in splitted_body_content: if not part: continue initial, content = part.split(_CRLF_BYTES_MARK * 2) headers = HTTPHeaders.parse(initial) disposition = headers.get_first("content-disposition") disposition_list = [] disposition_dict = TolerantMagicDict() for field in disposition.split(";"): # Split Disposition field = field.strip() # Remove Useless Spaces. if field.find("=") == -1: # This is not a key-value pair. disposition_list.append(field) continue key, value = field.split("=") if value.startswith('"') and value.endswith('"'): value = value[1:-1] disposition_dict.add(key.strip().lower(), value.strip()) if disposition_list[0] != "form-data": raise ProtocolError("Cannot Parse Body.") # Mixed form-data will be supported later. content = content[:-2] # Drop CRLF Mark if "filename" in disposition_dict.keys(): body_args.files.add( disposition_dict.get_first("name", ""), HTTPMultipartFileField( fieldname=disposition_dict.get_first("name", ""), filename=disposition_dict.get_first("filename", ""), content=content, content_type=headers.get_first( "content-type", "application/octet-stream"), headers=headers, encoding=headers.get_first("content-transfer-encoding", "binary"))) else: try: content = content.decode() except UnicodeDecodeError: pass body_args.add(disposition_dict.get_first("name", ""), content) return body_args