Repository: pallets/werkzeug (python)

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


Pull request:
Multipart fix
Closes #2125

Checklist:

- [x] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change.
- [x] Add or update relevant docs, in the docs folder and in code.
- [x] Add an entry in `CHANGES.rst` summarizing the change and linking to the issue.
- [x] Add `.. versionchanged::` entries in any relevant code docs.
- [x] Run `pre-commit` hooks and fix any issues.
- [x] Run `pytest` and `tox`, no tests failed.


Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 5340da9f..85ccae87 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -12,6 +12,8 @@ Unreleased
 -   Fix some types that weren't available in Python 3.6.0. :issue:`2123`
 -   ``cached_property`` is generic over its return type, properties
     decorated with it report the correct type. :issue:`2113`
+-   Fix multipart parsing bug when boundary contains special regex
+    characters. :issue:`2125`
 
 
 Version 2.0.0
diff --git a/src/werkzeug/sansio/multipart.py b/src/werkzeug/sansio/multipart.py
index d96cc159..bb8ab345 100644
--- a/src/werkzeug/sansio/multipart.py
+++ b/src/werkzeug/sansio/multipart.py
@@ -101,7 +101,7 @@ class MultipartDecoder:
         # 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 @@ class MultipartDecoder:
         # 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
index 22ba7555..f9c48b47 100644
--- a/tests/sansio/test_multipart.py
+++ b/tests/sansio/test_multipart.py
@@ -10,18 +10,18 @@ from werkzeug.sansio.multipart import Preamble
 
 
 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(