We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Insufficient input validation of content-type 'multipart/form-data'
### Checklist

- [x] The bug is reproducible against the latest release and/or `master`.
- [x] There are no similar issues or pull requests to fix it yet.

### Describe the bug

Not actually a bug, but insufficient input validation.

### To reproduce

Provide POST input (e.g. with postman) with content-type 'multipart/form-data' but without _boundary_ parameter.

### Expected behavior

Shall return 4xx error code. According to rfc7578 boundary is a required parameter.

### Actual behavior

_None_ boundary is passed to multipart library and an exception is raised there.

### Debugging material

```
TypeError: can't concat NoneType to bytes
  File "starlette/middleware/trustedhost.py", line 51, in __call__
    await self.app(scope, receive, send)
  File "starlette/middleware/sessions.py", line 75, in __call__
    await self.app(scope, receive, send_wrapper)
  File "cpms/middleware.py", line 55, in __call__
    await self.app(scope, receive, send)
  File "starlette/middleware/authentication.py", line 48, in __call__
    await self.app(scope, receive, send)
  File "starlette/exceptions.py", line 82, in __call__
    raise exc from None
  File "starlette/exceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
  File "starlette/routing.py", line 582, in __call__
    await route.handle(scope, receive, send)
  File "starlette/routing.py", line 243, in handle
    await self.app(scope, receive, send)
  File "starlette/routing.py", line 54, in app
    response = await func(request)
  File "starlette/authentication.py", line 69, in async_wrapper
    return await func(*args, **kwargs)
  File "cpms/views/files.py", line 90, in decorator
    return await endpoint(request)
  File "cpms/views/files.py", line 133, in upload_files
    for f_name, f_value in (await request.form()).items():
  File "starlette/requests.py", line 240, in form
    self._form = await multipart_parser.parse()
  File "starlette/formparsers.py", line 181, in parse
    parser = multipart.MultipartParser(boundary, callbacks)
  File "multipart/multipart.py", line 1042, in __init__
    self.boundary = b'\r\n--' + boundary
```

### Environment

- OS: OpenBSD 6.7
- Python version: 3.7.9
- Starlette version: 0.14.1

### Additional context

The problem occurs due insufficient checking of the input on line 166 of starlette/formparsers.py
`      boundary = params.get(b"boundary")`

It is better to replace it with:
`      boundary = params[b"boundary"]`
so exception at early stage will be raised.

Or make an additional input validation.
```
      boundary = params.get(b"boundary")
      if not boundary:
          raise HTTPException(status_code=400)
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `starlette/formparsers.py`
Content:
```
1 import typing
2 from enum import Enum
3 from urllib.parse import unquote_plus
4 
5 from starlette.datastructures import FormData, Headers, UploadFile
6 
7 try:
8     import multipart
9     from multipart.multipart import parse_options_header
10 except ImportError:  # pragma: nocover
11     parse_options_header = None
12     multipart = None
13 
14 
15 class FormMessage(Enum):
16     FIELD_START = 1
17     FIELD_NAME = 2
18     FIELD_DATA = 3
19     FIELD_END = 4
20     END = 5
21 
22 
23 class MultiPartMessage(Enum):
24     PART_BEGIN = 1
25     PART_DATA = 2
26     PART_END = 3
27     HEADER_FIELD = 4
28     HEADER_VALUE = 5
29     HEADER_END = 6
30     HEADERS_FINISHED = 7
31     END = 8
32 
33 
34 def _user_safe_decode(src: bytes, codec: str) -> str:
35     try:
36         return src.decode(codec)
37     except (UnicodeDecodeError, LookupError):
38         return src.decode("latin-1")
39 
40 
41 class FormParser:
42     def __init__(
43         self, headers: Headers, stream: typing.AsyncGenerator[bytes, None]
44     ) -> None:
45         assert (
46             multipart is not None
47         ), "The `python-multipart` library must be installed to use form parsing."
48         self.headers = headers
49         self.stream = stream
50         self.messages: typing.List[typing.Tuple[FormMessage, bytes]] = []
51 
52     def on_field_start(self) -> None:
53         message = (FormMessage.FIELD_START, b"")
54         self.messages.append(message)
55 
56     def on_field_name(self, data: bytes, start: int, end: int) -> None:
57         message = (FormMessage.FIELD_NAME, data[start:end])
58         self.messages.append(message)
59 
60     def on_field_data(self, data: bytes, start: int, end: int) -> None:
61         message = (FormMessage.FIELD_DATA, data[start:end])
62         self.messages.append(message)
63 
64     def on_field_end(self) -> None:
65         message = (FormMessage.FIELD_END, b"")
66         self.messages.append(message)
67 
68     def on_end(self) -> None:
69         message = (FormMessage.END, b"")
70         self.messages.append(message)
71 
72     async def parse(self) -> FormData:
73         # Callbacks dictionary.
74         callbacks = {
75             "on_field_start": self.on_field_start,
76             "on_field_name": self.on_field_name,
77             "on_field_data": self.on_field_data,
78             "on_field_end": self.on_field_end,
79             "on_end": self.on_end,
80         }
81 
82         # Create the parser.
83         parser = multipart.QuerystringParser(callbacks)
84         field_name = b""
85         field_value = b""
86 
87         items: typing.List[typing.Tuple[str, typing.Union[str, UploadFile]]] = []
88 
89         # Feed the parser with data from the request.
90         async for chunk in self.stream:
91             if chunk:
92                 parser.write(chunk)
93             else:
94                 parser.finalize()
95             messages = list(self.messages)
96             self.messages.clear()
97             for message_type, message_bytes in messages:
98                 if message_type == FormMessage.FIELD_START:
99                     field_name = b""
100                     field_value = b""
101                 elif message_type == FormMessage.FIELD_NAME:
102                     field_name += message_bytes
103                 elif message_type == FormMessage.FIELD_DATA:
104                     field_value += message_bytes
105                 elif message_type == FormMessage.FIELD_END:
106                     name = unquote_plus(field_name.decode("latin-1"))
107                     value = unquote_plus(field_value.decode("latin-1"))
108                     items.append((name, value))
109 
110         return FormData(items)
111 
112 
113 class MultiPartParser:
114     def __init__(
115         self, headers: Headers, stream: typing.AsyncGenerator[bytes, None]
116     ) -> None:
117         assert (
118             multipart is not None
119         ), "The `python-multipart` library must be installed to use form parsing."
120         self.headers = headers
121         self.stream = stream
122         self.messages: typing.List[typing.Tuple[MultiPartMessage, bytes]] = []
123 
124     def on_part_begin(self) -> None:
125         message = (MultiPartMessage.PART_BEGIN, b"")
126         self.messages.append(message)
127 
128     def on_part_data(self, data: bytes, start: int, end: int) -> None:
129         message = (MultiPartMessage.PART_DATA, data[start:end])
130         self.messages.append(message)
131 
132     def on_part_end(self) -> None:
133         message = (MultiPartMessage.PART_END, b"")
134         self.messages.append(message)
135 
136     def on_header_field(self, data: bytes, start: int, end: int) -> None:
137         message = (MultiPartMessage.HEADER_FIELD, data[start:end])
138         self.messages.append(message)
139 
140     def on_header_value(self, data: bytes, start: int, end: int) -> None:
141         message = (MultiPartMessage.HEADER_VALUE, data[start:end])
142         self.messages.append(message)
143 
144     def on_header_end(self) -> None:
145         message = (MultiPartMessage.HEADER_END, b"")
146         self.messages.append(message)
147 
148     def on_headers_finished(self) -> None:
149         message = (MultiPartMessage.HEADERS_FINISHED, b"")
150         self.messages.append(message)
151 
152     def on_end(self) -> None:
153         message = (MultiPartMessage.END, b"")
154         self.messages.append(message)
155 
156     async def parse(self) -> FormData:
157         # Parse the Content-Type header to get the multipart boundary.
158         content_type, params = parse_options_header(self.headers["Content-Type"])
159         charset = params.get(b"charset", "utf-8")
160         if type(charset) == bytes:
161             charset = charset.decode("latin-1")
162         boundary = params.get(b"boundary")
163 
164         # Callbacks dictionary.
165         callbacks = {
166             "on_part_begin": self.on_part_begin,
167             "on_part_data": self.on_part_data,
168             "on_part_end": self.on_part_end,
169             "on_header_field": self.on_header_field,
170             "on_header_value": self.on_header_value,
171             "on_header_end": self.on_header_end,
172             "on_headers_finished": self.on_headers_finished,
173             "on_end": self.on_end,
174         }
175 
176         # Create the parser.
177         parser = multipart.MultipartParser(boundary, callbacks)
178         header_field = b""
179         header_value = b""
180         content_disposition = None
181         content_type = b""
182         field_name = ""
183         data = b""
184         file: typing.Optional[UploadFile] = None
185 
186         items: typing.List[typing.Tuple[str, typing.Union[str, UploadFile]]] = []
187         item_headers: typing.List[typing.Tuple[bytes, bytes]] = []
188 
189         # Feed the parser with data from the request.
190         async for chunk in self.stream:
191             parser.write(chunk)
192             messages = list(self.messages)
193             self.messages.clear()
194             for message_type, message_bytes in messages:
195                 if message_type == MultiPartMessage.PART_BEGIN:
196                     content_disposition = None
197                     content_type = b""
198                     data = b""
199                     item_headers = []
200                 elif message_type == MultiPartMessage.HEADER_FIELD:
201                     header_field += message_bytes
202                 elif message_type == MultiPartMessage.HEADER_VALUE:
203                     header_value += message_bytes
204                 elif message_type == MultiPartMessage.HEADER_END:
205                     field = header_field.lower()
206                     if field == b"content-disposition":
207                         content_disposition = header_value
208                     elif field == b"content-type":
209                         content_type = header_value
210                     item_headers.append((field, header_value))
211                     header_field = b""
212                     header_value = b""
213                 elif message_type == MultiPartMessage.HEADERS_FINISHED:
214                     disposition, options = parse_options_header(content_disposition)
215                     field_name = _user_safe_decode(options[b"name"], charset)
216                     if b"filename" in options:
217                         filename = _user_safe_decode(options[b"filename"], charset)
218                         file = UploadFile(
219                             filename=filename,
220                             content_type=content_type.decode("latin-1"),
221                             headers=Headers(raw=item_headers),
222                         )
223                     else:
224                         file = None
225                 elif message_type == MultiPartMessage.PART_DATA:
226                     if file is None:
227                         data += message_bytes
228                     else:
229                         await file.write(message_bytes)
230                 elif message_type == MultiPartMessage.PART_END:
231                     if file is None:
232                         items.append((field_name, _user_safe_decode(data, charset)))
233                     else:
234                         await file.seek(0)
235                         items.append((field_name, file))
236 
237         parser.finalize()
238         return FormData(items)
239
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/starlette/formparsers.py b/starlette/formparsers.py
--- a/starlette/formparsers.py
+++ b/starlette/formparsers.py
@@ -159,7 +159,7 @@
         charset = params.get(b"charset", "utf-8")
         if type(charset) == bytes:
             charset = charset.decode("latin-1")
-        boundary = params.get(b"boundary")
+        boundary = params[b"boundary"]
 
         # Callbacks dictionary.
         callbacks = {

{"golden_diff": "diff --git a/starlette/formparsers.py b/starlette/formparsers.py\n--- a/starlette/formparsers.py\n+++ b/starlette/formparsers.py\n@@ -159,7 +159,7 @@\n         charset = params.get(b\"charset\", \"utf-8\")\n         if type(charset) == bytes:\n             charset = charset.decode(\"latin-1\")\n-        boundary = params.get(b\"boundary\")\n+        boundary = params[b\"boundary\"]\n \n         # Callbacks dictionary.\n         callbacks = {\n", "issue": "Insufficient input validation of content-type 'multipart/form-data'\n### Checklist\r\n\r\n- [x] The bug is reproducible against the latest release and/or `master`.\r\n- [x] There are no similar issues or pull requests to fix it yet.\r\n\r\n### Describe the bug\r\n\r\nNot actually a bug, but insufficient input validation.\r\n\r\n### To reproduce\r\n\r\nProvide POST input (e.g. with postman) with content-type 'multipart/form-data' but without _boundary_ parameter.\r\n\r\n### Expected behavior\r\n\r\nShall return 4xx error code. According to rfc7578 boundary is a required parameter.\r\n\r\n### Actual behavior\r\n\r\n_None_ boundary is passed to multipart library and an exception is raised there.\r\n\r\n### Debugging material\r\n\r\n```\r\nTypeError: can't concat NoneType to bytes\r\n  File \"starlette/middleware/trustedhost.py\", line 51, in __call__\r\n    await self.app(scope, receive, send)\r\n  File \"starlette/middleware/sessions.py\", line 75, in __call__\r\n    await self.app(scope, receive, send_wrapper)\r\n  File \"cpms/middleware.py\", line 55, in __call__\r\n    await self.app(scope, receive, send)\r\n  File \"starlette/middleware/authentication.py\", line 48, in __call__\r\n    await self.app(scope, receive, send)\r\n  File \"starlette/exceptions.py\", line 82, in __call__\r\n    raise exc from None\r\n  File \"starlette/exceptions.py\", line 71, in __call__\r\n    await self.app(scope, receive, sender)\r\n  File \"starlette/routing.py\", line 582, in __call__\r\n    await route.handle(scope, receive, send)\r\n  File \"starlette/routing.py\", line 243, in handle\r\n    await self.app(scope, receive, send)\r\n  File \"starlette/routing.py\", line 54, in app\r\n    response = await func(request)\r\n  File \"starlette/authentication.py\", line 69, in async_wrapper\r\n    return await func(*args, **kwargs)\r\n  File \"cpms/views/files.py\", line 90, in decorator\r\n    return await endpoint(request)\r\n  File \"cpms/views/files.py\", line 133, in upload_files\r\n    for f_name, f_value in (await request.form()).items():\r\n  File \"starlette/requests.py\", line 240, in form\r\n    self._form = await multipart_parser.parse()\r\n  File \"starlette/formparsers.py\", line 181, in parse\r\n    parser = multipart.MultipartParser(boundary, callbacks)\r\n  File \"multipart/multipart.py\", line 1042, in __init__\r\n    self.boundary = b'\\r\\n--' + boundary\r\n```\r\n\r\n### Environment\r\n\r\n- OS: OpenBSD 6.7\r\n- Python version: 3.7.9\r\n- Starlette version: 0.14.1\r\n\r\n### Additional context\r\n\r\nThe problem occurs due insufficient checking of the input on line 166 of starlette/formparsers.py\r\n`      boundary = params.get(b\"boundary\")`\r\n\r\nIt is better to replace it with:\r\n`      boundary = params[b\"boundary\"]`\r\nso exception at early stage will be raised.\r\n\r\nOr make an additional input validation.\r\n```\r\n      boundary = params.get(b\"boundary\")\r\n      if not boundary:\r\n          raise HTTPException(status_code=400)\r\n```\r\n\n", "before_files": [{"content": "import typing\nfrom enum import Enum\nfrom urllib.parse import unquote_plus\n\nfrom starlette.datastructures import FormData, Headers, UploadFile\n\ntry:\n    import multipart\n    from multipart.multipart import parse_options_header\nexcept ImportError:  # pragma: nocover\n    parse_options_header = None\n    multipart = None\n\n\nclass FormMessage(Enum):\n    FIELD_START = 1\n    FIELD_NAME = 2\n    FIELD_DATA = 3\n    FIELD_END = 4\n    END = 5\n\n\nclass MultiPartMessage(Enum):\n    PART_BEGIN = 1\n    PART_DATA = 2\n    PART_END = 3\n    HEADER_FIELD = 4\n    HEADER_VALUE = 5\n    HEADER_END = 6\n    HEADERS_FINISHED = 7\n    END = 8\n\n\ndef _user_safe_decode(src: bytes, codec: str) -> str:\n    try:\n        return src.decode(codec)\n    except (UnicodeDecodeError, LookupError):\n        return src.decode(\"latin-1\")\n\n\nclass FormParser:\n    def __init__(\n        self, headers: Headers, stream: typing.AsyncGenerator[bytes, None]\n    ) -> None:\n        assert (\n            multipart is not None\n        ), \"The `python-multipart` library must be installed to use form parsing.\"\n        self.headers = headers\n        self.stream = stream\n        self.messages: typing.List[typing.Tuple[FormMessage, bytes]] = []\n\n    def on_field_start(self) -> None:\n        message = (FormMessage.FIELD_START, b\"\")\n        self.messages.append(message)\n\n    def on_field_name(self, data: bytes, start: int, end: int) -> None:\n        message = (FormMessage.FIELD_NAME, data[start:end])\n        self.messages.append(message)\n\n    def on_field_data(self, data: bytes, start: int, end: int) -> None:\n        message = (FormMessage.FIELD_DATA, data[start:end])\n        self.messages.append(message)\n\n    def on_field_end(self) -> None:\n        message = (FormMessage.FIELD_END, b\"\")\n        self.messages.append(message)\n\n    def on_end(self) -> None:\n        message = (FormMessage.END, b\"\")\n        self.messages.append(message)\n\n    async def parse(self) -> FormData:\n        # Callbacks dictionary.\n        callbacks = {\n            \"on_field_start\": self.on_field_start,\n            \"on_field_name\": self.on_field_name,\n            \"on_field_data\": self.on_field_data,\n            \"on_field_end\": self.on_field_end,\n            \"on_end\": self.on_end,\n        }\n\n        # Create the parser.\n        parser = multipart.QuerystringParser(callbacks)\n        field_name = b\"\"\n        field_value = b\"\"\n\n        items: typing.List[typing.Tuple[str, typing.Union[str, UploadFile]]] = []\n\n        # Feed the parser with data from the request.\n        async for chunk in self.stream:\n            if chunk:\n                parser.write(chunk)\n            else:\n                parser.finalize()\n            messages = list(self.messages)\n            self.messages.clear()\n            for message_type, message_bytes in messages:\n                if message_type == FormMessage.FIELD_START:\n                    field_name = b\"\"\n                    field_value = b\"\"\n                elif message_type == FormMessage.FIELD_NAME:\n                    field_name += message_bytes\n                elif message_type == FormMessage.FIELD_DATA:\n                    field_value += message_bytes\n                elif message_type == FormMessage.FIELD_END:\n                    name = unquote_plus(field_name.decode(\"latin-1\"))\n                    value = unquote_plus(field_value.decode(\"latin-1\"))\n                    items.append((name, value))\n\n        return FormData(items)\n\n\nclass MultiPartParser:\n    def __init__(\n        self, headers: Headers, stream: typing.AsyncGenerator[bytes, None]\n    ) -> None:\n        assert (\n            multipart is not None\n        ), \"The `python-multipart` library must be installed to use form parsing.\"\n        self.headers = headers\n        self.stream = stream\n        self.messages: typing.List[typing.Tuple[MultiPartMessage, bytes]] = []\n\n    def on_part_begin(self) -> None:\n        message = (MultiPartMessage.PART_BEGIN, b\"\")\n        self.messages.append(message)\n\n    def on_part_data(self, data: bytes, start: int, end: int) -> None:\n        message = (MultiPartMessage.PART_DATA, data[start:end])\n        self.messages.append(message)\n\n    def on_part_end(self) -> None:\n        message = (MultiPartMessage.PART_END, b\"\")\n        self.messages.append(message)\n\n    def on_header_field(self, data: bytes, start: int, end: int) -> None:\n        message = (MultiPartMessage.HEADER_FIELD, data[start:end])\n        self.messages.append(message)\n\n    def on_header_value(self, data: bytes, start: int, end: int) -> None:\n        message = (MultiPartMessage.HEADER_VALUE, data[start:end])\n        self.messages.append(message)\n\n    def on_header_end(self) -> None:\n        message = (MultiPartMessage.HEADER_END, b\"\")\n        self.messages.append(message)\n\n    def on_headers_finished(self) -> None:\n        message = (MultiPartMessage.HEADERS_FINISHED, b\"\")\n        self.messages.append(message)\n\n    def on_end(self) -> None:\n        message = (MultiPartMessage.END, b\"\")\n        self.messages.append(message)\n\n    async def parse(self) -> FormData:\n        # Parse the Content-Type header to get the multipart boundary.\n        content_type, params = parse_options_header(self.headers[\"Content-Type\"])\n        charset = params.get(b\"charset\", \"utf-8\")\n        if type(charset) == bytes:\n            charset = charset.decode(\"latin-1\")\n        boundary = params.get(b\"boundary\")\n\n        # Callbacks dictionary.\n        callbacks = {\n            \"on_part_begin\": self.on_part_begin,\n            \"on_part_data\": self.on_part_data,\n            \"on_part_end\": self.on_part_end,\n            \"on_header_field\": self.on_header_field,\n            \"on_header_value\": self.on_header_value,\n            \"on_header_end\": self.on_header_end,\n            \"on_headers_finished\": self.on_headers_finished,\n            \"on_end\": self.on_end,\n        }\n\n        # Create the parser.\n        parser = multipart.MultipartParser(boundary, callbacks)\n        header_field = b\"\"\n        header_value = b\"\"\n        content_disposition = None\n        content_type = b\"\"\n        field_name = \"\"\n        data = b\"\"\n        file: typing.Optional[UploadFile] = None\n\n        items: typing.List[typing.Tuple[str, typing.Union[str, UploadFile]]] = []\n        item_headers: typing.List[typing.Tuple[bytes, bytes]] = []\n\n        # Feed the parser with data from the request.\n        async for chunk in self.stream:\n            parser.write(chunk)\n            messages = list(self.messages)\n            self.messages.clear()\n            for message_type, message_bytes in messages:\n                if message_type == MultiPartMessage.PART_BEGIN:\n                    content_disposition = None\n                    content_type = b\"\"\n                    data = b\"\"\n                    item_headers = []\n                elif message_type == MultiPartMessage.HEADER_FIELD:\n                    header_field += message_bytes\n                elif message_type == MultiPartMessage.HEADER_VALUE:\n                    header_value += message_bytes\n                elif message_type == MultiPartMessage.HEADER_END:\n                    field = header_field.lower()\n                    if field == b\"content-disposition\":\n                        content_disposition = header_value\n                    elif field == b\"content-type\":\n                        content_type = header_value\n                    item_headers.append((field, header_value))\n                    header_field = b\"\"\n                    header_value = b\"\"\n                elif message_type == MultiPartMessage.HEADERS_FINISHED:\n                    disposition, options = parse_options_header(content_disposition)\n                    field_name = _user_safe_decode(options[b\"name\"], charset)\n                    if b\"filename\" in options:\n                        filename = _user_safe_decode(options[b\"filename\"], charset)\n                        file = UploadFile(\n                            filename=filename,\n                            content_type=content_type.decode(\"latin-1\"),\n                            headers=Headers(raw=item_headers),\n                        )\n                    else:\n                        file = None\n                elif message_type == MultiPartMessage.PART_DATA:\n                    if file is None:\n                        data += message_bytes\n                    else:\n                        await file.write(message_bytes)\n                elif message_type == MultiPartMessage.PART_END:\n                    if file is None:\n                        items.append((field_name, _user_safe_decode(data, charset)))\n                    else:\n                        await file.seek(0)\n                        items.append((field_name, file))\n\n        parser.finalize()\n        return FormData(items)\n", "path": "starlette/formparsers.py"}], "after_files": [{"content": "import typing\nfrom enum import Enum\nfrom urllib.parse import unquote_plus\n\nfrom starlette.datastructures import FormData, Headers, UploadFile\n\ntry:\n    import multipart\n    from multipart.multipart import parse_options_header\nexcept ImportError:  # pragma: nocover\n    parse_options_header = None\n    multipart = None\n\n\nclass FormMessage(Enum):\n    FIELD_START = 1\n    FIELD_NAME = 2\n    FIELD_DATA = 3\n    FIELD_END = 4\n    END = 5\n\n\nclass MultiPartMessage(Enum):\n    PART_BEGIN = 1\n    PART_DATA = 2\n    PART_END = 3\n    HEADER_FIELD = 4\n    HEADER_VALUE = 5\n    HEADER_END = 6\n    HEADERS_FINISHED = 7\n    END = 8\n\n\ndef _user_safe_decode(src: bytes, codec: str) -> str:\n    try:\n        return src.decode(codec)\n    except (UnicodeDecodeError, LookupError):\n        return src.decode(\"latin-1\")\n\n\nclass FormParser:\n    def __init__(\n        self, headers: Headers, stream: typing.AsyncGenerator[bytes, None]\n    ) -> None:\n        assert (\n            multipart is not None\n        ), \"The `python-multipart` library must be installed to use form parsing.\"\n        self.headers = headers\n        self.stream = stream\n        self.messages: typing.List[typing.Tuple[FormMessage, bytes]] = []\n\n    def on_field_start(self) -> None:\n        message = (FormMessage.FIELD_START, b\"\")\n        self.messages.append(message)\n\n    def on_field_name(self, data: bytes, start: int, end: int) -> None:\n        message = (FormMessage.FIELD_NAME, data[start:end])\n        self.messages.append(message)\n\n    def on_field_data(self, data: bytes, start: int, end: int) -> None:\n        message = (FormMessage.FIELD_DATA, data[start:end])\n        self.messages.append(message)\n\n    def on_field_end(self) -> None:\n        message = (FormMessage.FIELD_END, b\"\")\n        self.messages.append(message)\n\n    def on_end(self) -> None:\n        message = (FormMessage.END, b\"\")\n        self.messages.append(message)\n\n    async def parse(self) -> FormData:\n        # Callbacks dictionary.\n        callbacks = {\n            \"on_field_start\": self.on_field_start,\n            \"on_field_name\": self.on_field_name,\n            \"on_field_data\": self.on_field_data,\n            \"on_field_end\": self.on_field_end,\n            \"on_end\": self.on_end,\n        }\n\n        # Create the parser.\n        parser = multipart.QuerystringParser(callbacks)\n        field_name = b\"\"\n        field_value = b\"\"\n\n        items: typing.List[typing.Tuple[str, typing.Union[str, UploadFile]]] = []\n\n        # Feed the parser with data from the request.\n        async for chunk in self.stream:\n            if chunk:\n                parser.write(chunk)\n            else:\n                parser.finalize()\n            messages = list(self.messages)\n            self.messages.clear()\n            for message_type, message_bytes in messages:\n                if message_type == FormMessage.FIELD_START:\n                    field_name = b\"\"\n                    field_value = b\"\"\n                elif message_type == FormMessage.FIELD_NAME:\n                    field_name += message_bytes\n                elif message_type == FormMessage.FIELD_DATA:\n                    field_value += message_bytes\n                elif message_type == FormMessage.FIELD_END:\n                    name = unquote_plus(field_name.decode(\"latin-1\"))\n                    value = unquote_plus(field_value.decode(\"latin-1\"))\n                    items.append((name, value))\n\n        return FormData(items)\n\n\nclass MultiPartParser:\n    def __init__(\n        self, headers: Headers, stream: typing.AsyncGenerator[bytes, None]\n    ) -> None:\n        assert (\n            multipart is not None\n        ), \"The `python-multipart` library must be installed to use form parsing.\"\n        self.headers = headers\n        self.stream = stream\n        self.messages: typing.List[typing.Tuple[MultiPartMessage, bytes]] = []\n\n    def on_part_begin(self) -> None:\n        message = (MultiPartMessage.PART_BEGIN, b\"\")\n        self.messages.append(message)\n\n    def on_part_data(self, data: bytes, start: int, end: int) -> None:\n        message = (MultiPartMessage.PART_DATA, data[start:end])\n        self.messages.append(message)\n\n    def on_part_end(self) -> None:\n        message = (MultiPartMessage.PART_END, b\"\")\n        self.messages.append(message)\n\n    def on_header_field(self, data: bytes, start: int, end: int) -> None:\n        message = (MultiPartMessage.HEADER_FIELD, data[start:end])\n        self.messages.append(message)\n\n    def on_header_value(self, data: bytes, start: int, end: int) -> None:\n        message = (MultiPartMessage.HEADER_VALUE, data[start:end])\n        self.messages.append(message)\n\n    def on_header_end(self) -> None:\n        message = (MultiPartMessage.HEADER_END, b\"\")\n        self.messages.append(message)\n\n    def on_headers_finished(self) -> None:\n        message = (MultiPartMessage.HEADERS_FINISHED, b\"\")\n        self.messages.append(message)\n\n    def on_end(self) -> None:\n        message = (MultiPartMessage.END, b\"\")\n        self.messages.append(message)\n\n    async def parse(self) -> FormData:\n        # Parse the Content-Type header to get the multipart boundary.\n        content_type, params = parse_options_header(self.headers[\"Content-Type\"])\n        charset = params.get(b\"charset\", \"utf-8\")\n        if type(charset) == bytes:\n            charset = charset.decode(\"latin-1\")\n        boundary = params[b\"boundary\"]\n\n        # Callbacks dictionary.\n        callbacks = {\n            \"on_part_begin\": self.on_part_begin,\n            \"on_part_data\": self.on_part_data,\n            \"on_part_end\": self.on_part_end,\n            \"on_header_field\": self.on_header_field,\n            \"on_header_value\": self.on_header_value,\n            \"on_header_end\": self.on_header_end,\n            \"on_headers_finished\": self.on_headers_finished,\n            \"on_end\": self.on_end,\n        }\n\n        # Create the parser.\n        parser = multipart.MultipartParser(boundary, callbacks)\n        header_field = b\"\"\n        header_value = b\"\"\n        content_disposition = None\n        content_type = b\"\"\n        field_name = \"\"\n        data = b\"\"\n        file: typing.Optional[UploadFile] = None\n\n        items: typing.List[typing.Tuple[str, typing.Union[str, UploadFile]]] = []\n        item_headers: typing.List[typing.Tuple[bytes, bytes]] = []\n\n        # Feed the parser with data from the request.\n        async for chunk in self.stream:\n            parser.write(chunk)\n            messages = list(self.messages)\n            self.messages.clear()\n            for message_type, message_bytes in messages:\n                if message_type == MultiPartMessage.PART_BEGIN:\n                    content_disposition = None\n                    content_type = b\"\"\n                    data = b\"\"\n                    item_headers = []\n                elif message_type == MultiPartMessage.HEADER_FIELD:\n                    header_field += message_bytes\n                elif message_type == MultiPartMessage.HEADER_VALUE:\n                    header_value += message_bytes\n                elif message_type == MultiPartMessage.HEADER_END:\n                    field = header_field.lower()\n                    if field == b\"content-disposition\":\n                        content_disposition = header_value\n                    elif field == b\"content-type\":\n                        content_type = header_value\n                    item_headers.append((field, header_value))\n                    header_field = b\"\"\n                    header_value = b\"\"\n                elif message_type == MultiPartMessage.HEADERS_FINISHED:\n                    disposition, options = parse_options_header(content_disposition)\n                    field_name = _user_safe_decode(options[b\"name\"], charset)\n                    if b\"filename\" in options:\n                        filename = _user_safe_decode(options[b\"filename\"], charset)\n                        file = UploadFile(\n                            filename=filename,\n                            content_type=content_type.decode(\"latin-1\"),\n                            headers=Headers(raw=item_headers),\n                        )\n                    else:\n                        file = None\n                elif message_type == MultiPartMessage.PART_DATA:\n                    if file is None:\n                        data += message_bytes\n                    else:\n                        await file.write(message_bytes)\n                elif message_type == MultiPartMessage.PART_END:\n                    if file is None:\n                        items.append((field_name, _user_safe_decode(data, charset)))\n                    else:\n                        await file.seek(0)\n                        items.append((field_name, file))\n\n        parser.finalize()\n        return FormData(items)\n", "path": "starlette/formparsers.py"}]}