Skip to content

Commit f6161cc

Browse files
authored
fix: handle incoming invalid dynamic metadata (#17390)
1 parent e951ec1 commit f6161cc

File tree

2 files changed

+32
-4
lines changed

2 files changed

+32
-4
lines changed

tests/unit/forklift/test_metadata.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,20 @@ def test_invalid_dists(self, field_name):
274274
metadata.parse(None, form_data=data)
275275
_assert_invalid_metadata(excinfo.value, field_name.replace("_", "-"))
276276

277+
def test_valid_dynamic(self):
278+
data = MultiDict(metadata_version="2.2", name="spam", version="2.0")
279+
data.add("dynamic", "keywords")
280+
data.add("dynamic", "author")
281+
meta = metadata.parse(None, form_data=data)
282+
assert meta.dynamic == ["keywords", "author"]
283+
284+
def test_invalid_dynamic(self):
285+
data = MultiDict(metadata_version="2.2", name="spam", version="2.0")
286+
data.add("dynamic", "Requires")
287+
with pytest.raises(ExceptionGroup) as excinfo:
288+
metadata.parse(None, form_data=data)
289+
_assert_invalid_metadata(excinfo.value, "dynamic")
290+
277291

278292
class TestFromFormData:
279293
def test_valid(self):

warehouse/forklift/metadata.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from trove_classifiers import all_classifiers, deprecated_classifiers
3131
from webob.multidict import MultiDict
3232

33+
from warehouse.packaging.models import DynamicFieldsEnum
3334
from warehouse.utils import http
3435

3536
SUPPORTED_METADATA_VERSIONS = {"1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4"}
@@ -141,7 +142,7 @@ def _validate_metadata(metadata: Metadata, *, backfill: bool = False):
141142
InvalidMetadata("classifier", f"{classifier!r} is not a valid classifier.")
142143
)
143144

144-
# Validate that no deprecated classifers are being used.
145+
# Validate that no deprecated classifiers are being used.
145146
# NOTE: We only check this is we're not doing a backfill, because backfill
146147
# operations may legitimately use deprecated classifiers.
147148
if not backfill:
@@ -235,6 +236,19 @@ def _validate_metadata(metadata: Metadata, *, backfill: bool = False):
235236
)
236237
)
237238

239+
# Validate that any `dynamic` fields passed are in the allowed list
240+
# TODO: This probably should be lifted up to packaging.metadata
241+
for field in {"dynamic"}:
242+
if (value := getattr(metadata, field)) is not None:
243+
for key in value:
244+
if key not in map(str.lower, DynamicFieldsEnum.enums):
245+
errors.append(
246+
InvalidMetadata(
247+
_RAW_TO_EMAIL_MAPPING.get(field, field),
248+
f"Dynamic field {key!r} is not a valid dynamic field.",
249+
)
250+
)
251+
238252
# Ensure that License and License-Expression are mutually exclusive
239253
# See https://peps.python.org/pep-0639/#deprecate-license-field
240254
if metadata.license and metadata.license_expression:
@@ -263,12 +277,12 @@ def _validate_metadata(metadata: Metadata, *, backfill: bool = False):
263277

264278

265279
def parse_form_metadata(data: MultiDict) -> Metadata:
266-
# We construct a RawMetdata using the form data, which we will later pass
280+
# We construct a RawMetadata using the form data, which we will later pass
267281
# to Metadata to get a validated metadata.
268282
#
269-
# NOTE: Form data is very similiar to the email format where the only difference
283+
# NOTE: Form data is very similar to the email format where the only difference
270284
# between a list and a single value is whether or not the same key is used
271-
# multiple times. Thus we will handle things in a similiar way, always
285+
# multiple times. Thus, we will handle things in a similar way, always
272286
# fetching things as a list and then determining what to do based on the
273287
# field type and how many values we found.
274288
#

0 commit comments

Comments
 (0)