Parses a ``multipart/form-data`` body. The ``boundary`` and ``data`` parameters are both byte strings. The dictionaries given in the arguments and files parameters will be updated with the contents of the body. .. versionchanged:: 5.1 Now recognizes non-ASCII filenames in R
(
boundary: bytes,
data: bytes,
arguments: Dict[str, List[bytes]],
files: Dict[str, List[HTTPFile]],
)
| 792 | |
| 793 | |
| 794 | def parse_multipart_form_data( |
| 795 | boundary: bytes, |
| 796 | data: bytes, |
| 797 | arguments: Dict[str, List[bytes]], |
| 798 | files: Dict[str, List[HTTPFile]], |
| 799 | ) -> None: |
| 800 | """Parses a ``multipart/form-data`` body. |
| 801 | |
| 802 | The ``boundary`` and ``data`` parameters are both byte strings. |
| 803 | The dictionaries given in the arguments and files parameters |
| 804 | will be updated with the contents of the body. |
| 805 | |
| 806 | .. versionchanged:: 5.1 |
| 807 | |
| 808 | Now recognizes non-ASCII filenames in RFC 2231/5987 |
| 809 | (``filename*=``) format. |
| 810 | """ |
| 811 | # The standard allows for the boundary to be quoted in the header, |
| 812 | # although it's rare (it happens at least for google app engine |
| 813 | # xmpp). I think we're also supposed to handle backslash-escapes |
| 814 | # here but I'll save that until we see a client that uses them |
| 815 | # in the wild. |
| 816 | if boundary.startswith(b'"') and boundary.endswith(b'"'): |
| 817 | boundary = boundary[1:-1] |
| 818 | final_boundary_index = data.rfind(b"--" + boundary + b"--") |
| 819 | if final_boundary_index == -1: |
| 820 | gen_log.warning("Invalid multipart/form-data: no final boundary") |
| 821 | return |
| 822 | parts = data[:final_boundary_index].split(b"--" + boundary + b"\r\n") |
| 823 | for part in parts: |
| 824 | if not part: |
| 825 | continue |
| 826 | eoh = part.find(b"\r\n\r\n") |
| 827 | if eoh == -1: |
| 828 | gen_log.warning("multipart/form-data missing headers") |
| 829 | continue |
| 830 | headers = HTTPHeaders.parse(part[:eoh].decode("utf-8")) |
| 831 | disp_header = headers.get("Content-Disposition", "") |
| 832 | disposition, disp_params = _parse_header(disp_header) |
| 833 | if disposition != "form-data" or not part.endswith(b"\r\n"): |
| 834 | gen_log.warning("Invalid multipart/form-data") |
| 835 | continue |
| 836 | value = part[eoh + 4 : -2] |
| 837 | if not disp_params.get("name"): |
| 838 | gen_log.warning("multipart/form-data value missing name") |
| 839 | continue |
| 840 | name = disp_params["name"] |
| 841 | if disp_params.get("filename"): |
| 842 | ctype = headers.get("Content-Type", "application/unknown") |
| 843 | files.setdefault(name, []).append( |
| 844 | HTTPFile( |
| 845 | filename=disp_params["filename"], body=value, content_type=ctype |
| 846 | ) |
| 847 | ) |
| 848 | else: |
| 849 | arguments.setdefault(name, []).append(value) |
| 850 | |
| 851 |