We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Form field names do not escape perfect encoding
I switched one microservice from Flask to Starlette and got unexpected form parsing problem.
This code will return %20 in places where spaces were supposed to be:
```
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.requests import Request as StarletteRequest

SERVER = Starlette()

@SERVER.route("/send", methods=["POST"])
async def send_endpoint(request: StarletteRequest) -> PlainTextResponse:
    form = await request.form()
    if "number" in form and "name" in form:
        number = form["number"]
        name = form["name"]
        return PlainTextResponse("Number: " + number + " ; Name: " + name)
```

Maybe there are other issues with form parsing (x-www-form-urlencoded).

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

{"golden_diff": "diff --git a/starlette/formparsers.py b/starlette/formparsers.py\n--- a/starlette/formparsers.py\n+++ b/starlette/formparsers.py\n@@ -1,9 +1,11 @@\n-from enum import Enum\n-from starlette.datastructures import Headers\n-import asyncio\n import io\n-import tempfile\n import typing\n+import asyncio\n+import tempfile\n+from enum import Enum\n+from urllib.parse import unquote\n+\n+from starlette.datastructures import Headers\n \n try:\n     from multipart.multipart import parse_options_header\n@@ -69,27 +71,22 @@\n         self.messages = []  # type: typing.List[typing.Tuple[FormMessage, bytes]]\n \n     def on_field_start(self) -> None:\n-        print(\"on_field_start\")\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-        print(\"on_field_name\")\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-        print(\"on_field_data\")\n         message = (FormMessage.FIELD_DATA, data[start:end])\n         self.messages.append(message)\n \n     def on_field_end(self) -> None:\n-        print(\"on_field_end\")\n         message = (FormMessage.FIELD_END, b\"\")\n         self.messages.append(message)\n \n     def on_end(self) -> None:\n-        print(\"on_end\")\n         message = (FormMessage.END, b\"\")\n         self.messages.append(message)\n \n@@ -127,7 +124,9 @@\n                 elif message_type == FormMessage.FIELD_DATA:\n                     field_value += message_bytes\n                 elif message_type == FormMessage.FIELD_END:\n-                    result[field_name.decode(\"latin-1\")] = field_value.decode(\"latin-1\")\n+                    result[field_name.decode(\"latin-1\")] = unquote(\n+                        field_value.decode(\"latin-1\")\n+                    )\n                 elif message_type == FormMessage.END:\n                     pass\n", "issue": "Form field names do not escape perfect encoding\nI switched one microservice from Flask to Starlette and got unexpected form parsing problem.\r\nThis code will return %20 in places where spaces were supposed to be:\r\n```\r\nfrom starlette.applications import Starlette\r\nfrom starlette.responses import PlainTextResponse\r\nfrom starlette.requests import Request as StarletteRequest\r\n\r\nSERVER = Starlette()\r\n\r\n@SERVER.route(\"/send\", methods=[\"POST\"])\r\nasync def send_endpoint(request: StarletteRequest) -> PlainTextResponse:\r\n    form = await request.form()\r\n    if \"number\" in form and \"name\" in form:\r\n        number = form[\"number\"]\r\n        name = form[\"name\"]\r\n        return PlainTextResponse(\"Number: \" + number + \" ; Name: \" + name)\r\n```\r\n\r\nMaybe there are other issues with form parsing (x-www-form-urlencoded).\n", "before_files": [{"content": "from enum import Enum\nfrom starlette.datastructures import Headers\nimport asyncio\nimport io\nimport tempfile\nimport typing\n\ntry:\n    from multipart.multipart import parse_options_header\n    import multipart\nexcept ImportError:  # pragma: nocover\n    parse_options_header = None  # type: ignore\n    multipart = None  # type: ignore\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\nclass UploadFile:\n    def __init__(self, filename: str) -> None:\n        self.filename = filename\n        self._file = io.BytesIO()  # type: typing.IO[typing.Any]\n        self._loop = asyncio.get_event_loop()\n\n    def create_tempfile(self) -> None:\n        self._file = tempfile.SpooledTemporaryFile()\n\n    async def setup(self) -> None:\n        await self._loop.run_in_executor(None, self.create_tempfile)\n\n    async def write(self, data: bytes) -> None:\n        await self._loop.run_in_executor(None, self._file.write, data)\n\n    async def read(self, size: int = None) -> bytes:\n        return await self._loop.run_in_executor(None, self._file.read, size)\n\n    async def seek(self, offset: int) -> None:\n        await self._loop.run_in_executor(None, self._file.seek, offset)\n\n    async def close(self) -> None:\n        await self._loop.run_in_executor(None, self._file.close)\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 = []  # type: typing.List[typing.Tuple[FormMessage, bytes]]\n\n    def on_field_start(self) -> None:\n        print(\"on_field_start\")\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        print(\"on_field_name\")\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        print(\"on_field_data\")\n        message = (FormMessage.FIELD_DATA, data[start:end])\n        self.messages.append(message)\n\n    def on_field_end(self) -> None:\n        print(\"on_field_end\")\n        message = (FormMessage.FIELD_END, b\"\")\n        self.messages.append(message)\n\n    def on_end(self) -> None:\n        print(\"on_end\")\n        message = (FormMessage.END, b\"\")\n        self.messages.append(message)\n\n    async def parse(self) -> typing.Dict[str, typing.Union[str, UploadFile]]:\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        result = {}  # type: typing.Dict[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                    result[field_name.decode(\"latin-1\")] = field_value.decode(\"latin-1\")\n                elif message_type == FormMessage.END:\n                    pass\n\n        return result\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 = []  # type: 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) -> typing.Dict[str, typing.Union[str, UploadFile]]:\n        # Parse the Content-Type header to get the multipart boundary.\n        content_type, params = parse_options_header(self.headers[\"Content-Type\"])\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        raw_headers = []  # type: typing.List[typing.Tuple[bytes, bytes]]\n        field_name = \"\"\n        data = b\"\"\n        file = None  # type: typing.Optional[UploadFile]\n\n        result = {}  # type: typing.Dict[str, typing.Union[str, UploadFile]]\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                    raw_headers = []\n                    data = b\"\"\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                    raw_headers.append((header_field.lower(), header_value))\n                    header_field = b\"\"\n                    header_value = b\"\"\n                elif message_type == MultiPartMessage.HEADERS_FINISHED:\n                    headers = Headers(raw=raw_headers)\n                    content_disposition = headers.get(\"Content-Disposition\")\n                    disposition, options = parse_options_header(content_disposition)\n                    field_name = options[b\"name\"].decode(\"latin-1\")\n                    if b\"filename\" in options:\n                        filename = options[b\"filename\"].decode(\"latin-1\")\n                        file = UploadFile(filename=filename)\n                        await file.setup()\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                        result[field_name] = data.decode(\"latin-1\")\n                    else:\n                        await file.seek(0)\n                        result[field_name] = file\n                elif message_type == MultiPartMessage.END:\n                    pass\n\n        parser.finalize()\n        return result\n", "path": "starlette/formparsers.py"}], "after_files": [{"content": "import io\nimport typing\nimport asyncio\nimport tempfile\nfrom enum import Enum\nfrom urllib.parse import unquote\n\nfrom starlette.datastructures import Headers\n\ntry:\n    from multipart.multipart import parse_options_header\n    import multipart\nexcept ImportError:  # pragma: nocover\n    parse_options_header = None  # type: ignore\n    multipart = None  # type: ignore\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\nclass UploadFile:\n    def __init__(self, filename: str) -> None:\n        self.filename = filename\n        self._file = io.BytesIO()  # type: typing.IO[typing.Any]\n        self._loop = asyncio.get_event_loop()\n\n    def create_tempfile(self) -> None:\n        self._file = tempfile.SpooledTemporaryFile()\n\n    async def setup(self) -> None:\n        await self._loop.run_in_executor(None, self.create_tempfile)\n\n    async def write(self, data: bytes) -> None:\n        await self._loop.run_in_executor(None, self._file.write, data)\n\n    async def read(self, size: int = None) -> bytes:\n        return await self._loop.run_in_executor(None, self._file.read, size)\n\n    async def seek(self, offset: int) -> None:\n        await self._loop.run_in_executor(None, self._file.seek, offset)\n\n    async def close(self) -> None:\n        await self._loop.run_in_executor(None, self._file.close)\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 = []  # type: 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) -> typing.Dict[str, typing.Union[str, UploadFile]]:\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        result = {}  # type: typing.Dict[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                    result[field_name.decode(\"latin-1\")] = unquote(\n                        field_value.decode(\"latin-1\")\n                    )\n                elif message_type == FormMessage.END:\n                    pass\n\n        return result\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 = []  # type: 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) -> typing.Dict[str, typing.Union[str, UploadFile]]:\n        # Parse the Content-Type header to get the multipart boundary.\n        content_type, params = parse_options_header(self.headers[\"Content-Type\"])\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        raw_headers = []  # type: typing.List[typing.Tuple[bytes, bytes]]\n        field_name = \"\"\n        data = b\"\"\n        file = None  # type: typing.Optional[UploadFile]\n\n        result = {}  # type: typing.Dict[str, typing.Union[str, UploadFile]]\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                    raw_headers = []\n                    data = b\"\"\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                    raw_headers.append((header_field.lower(), header_value))\n                    header_field = b\"\"\n                    header_value = b\"\"\n                elif message_type == MultiPartMessage.HEADERS_FINISHED:\n                    headers = Headers(raw=raw_headers)\n                    content_disposition = headers.get(\"Content-Disposition\")\n                    disposition, options = parse_options_header(content_disposition)\n                    field_name = options[b\"name\"].decode(\"latin-1\")\n                    if b\"filename\" in options:\n                        filename = options[b\"filename\"].decode(\"latin-1\")\n                        file = UploadFile(filename=filename)\n                        await file.setup()\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                        result[field_name] = data.decode(\"latin-1\")\n                    else:\n                        await file.seek(0)\n                        result[field_name] = file\n                elif message_type == MultiPartMessage.END:\n                    pass\n\n        parser.finalize()\n        return result\n", "path": "starlette/formparsers.py"}]}