Repository: pallets/werkzeug

Issue:
[BUG] Multipart data parse regression
Seems that there is a regression in the new MultiPartPaser implementation

The following does not work in `Werkzeug==2.0.0`; but, works in  `Werkzeug<2.0.0`.

```
from werkzeug.formparser import MultiPartParser
from io import BytesIO

parser = MultiPartParser()

boundary = b'----------a_BoUnDaRy9009049739267083$'

f = BytesIO(b'------------a_BoUnDaRy9009049739267083$\r\nContent-Disposition: form-data; name="file"; filename="/home/marc/workspace/data-governance/tests/functional_tests/test_data_governance_api/controllers/work_unit_data/NaValues.csv"\r\nContent-Type: text/csv\r\n\r\n"I was told that I was not able to receive a pay increase because I was at the top of the pay range for my level. This indicates to me that I need to either be promoted to the next level, or look elsewhere for employment."\r\nProject Management seems inconsistent some are well run some are not transparent and all over the place. Hiring contractors also are not managed well and may have resulted to project overruns.\r\nNA\r\n"1. How can diversity and inclusion efforts just be tailored to minority group?2. Is it appropriate to use the term ""blacks"" when sending out a company email around diversity and inclusion?3. Again, an actual HR department would resolve this"\r\nNaN\r\n------------a_BoUnDaRy9009049739267083$--\r\n')

form, files = parser.parse(f, boundary, 965)
```

It results in the following error:

```

Traceback (most recent call last):
  File "/home/*/flaskapp_fail.py", line 57, in <module>
    form, files = parser.parse(f, boundary, 965)
  File "/home/*/lib/python3.8/site-packages/werkzeug/formparser.py", line 459, in parse
    event = parser.next_event()
  File "/home/*/lib/python3.8/site-packages/werkzeug/sansio/multipart.py", line 213, in next_event
    raise ValueError(f"Invalid form-data cannot parse beyond {self.state}")
ValueError: Invalid form-data cannot parse beyond State.PREAMBLE
```


Environment:

- Python version: 3.8.5
- Werkzeug version: 2.0.0



Discussion:
@pgjones see anything here?
Ah, I think this is the in the boundary `$` which is being interpreted in a regex sense.

Resolving patch:
diff --git a/src/werkzeug/sansio/multipart.py b/src/werkzeug/sansio/multipart.py
--- a/src/werkzeug/sansio/multipart.py
+++ b/src/werkzeug/sansio/multipart.py
@@ -101,7 +101,7 @@ def __init__(
         # group to understand if it is an epilogue boundary.
         self.preamble_re = re.compile(
             br"%s?--%s(--[^\S\n\r]*%s?|[^\S\n\r]*%s)"
-            % (LINE_BREAK, boundary, LINE_BREAK, LINE_BREAK),
+            % (LINE_BREAK, re.escape(boundary), LINE_BREAK, LINE_BREAK),
             re.MULTILINE,
         )
         # A boundary must include a line break prefix and suffix, and
@@ -110,7 +110,7 @@ def __init__(
         # understand if it is an epilogue boundary.
         self.boundary_re = re.compile(
             br"%s--%s(--[^\S\n\r]*%s?|[^\S\n\r]*%s)"
-            % (LINE_BREAK, boundary, LINE_BREAK, LINE_BREAK),
+            % (LINE_BREAK, re.escape(boundary), LINE_BREAK, LINE_BREAK),
             re.MULTILINE,
         )
 


Test patch:
diff --git a/tests/sansio/test_multipart.py b/tests/sansio/test_multipart.py
--- a/tests/sansio/test_multipart.py
+++ b/tests/sansio/test_multipart.py
@@ -10,18 +10,18 @@
 
 
 def test_decoder_simple() -> None:
-    boundary = b"---------------------------9704338192090380615194531385"
+    boundary = b"---------------------------9704338192090380615194531385$"
     decoder = MultipartDecoder(boundary)
     data = """
------------------------------9704338192090380615194531385
+-----------------------------9704338192090380615194531385$
 Content-Disposition: form-data; name="fname"
 
 ß∑œß∂ƒå∂
------------------------------9704338192090380615194531385
+-----------------------------9704338192090380615194531385$
 Content-Disposition: form-data; name="lname"; filename="bob"
 
 asdasd
------------------------------9704338192090380615194531385--
+-----------------------------9704338192090380615194531385$--
     """.replace(
         "\n", "\r\n"
     ).encode(