Skip to content

Commit 5d0b439

Browse files
committed
rely more on _validate_multi_segment_value
1 parent 0d6d1ec commit 5d0b439

2 files changed

Lines changed: 54 additions & 28 deletions

File tree

packages/google-api-core/google/api_core/path_template.py

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ def _validate_multi_segment_value(val: str) -> bool:
9494
bool: True if the value is valid and does not violate traversal boundaries,
9595
False otherwise.
9696
"""
97+
if val in ('', '.', '..'):
98+
return False
99+
97100
segments = val.split("/")
98101
leftover_segments = 0
99102
unseen_segments = len(segments)
@@ -210,40 +213,33 @@ def _extract_and_validate_wildcards(
210213
f"Invalid value {val} for {property_name or 'positional variable'}."
211214
)
212215

213-
# Single-segment templates (None or "*") cannot match exactly "." or ".."
214-
# and cannot have multi-segment paths resolving to 0 segments.
215216
if template_str is None or template_str == "*":
216-
if val in (".", "..") or (val and not _validate_multi_segment_value(val)):
217+
# Single-segment templates (None or "*") cannot match exactly "." or ".."
218+
# and cannot have multi-segment paths resolving to 0 segments.
219+
if val and not _validate_multi_segment_value(val):
217220
raise err
218-
return
219-
220-
# Multi-segment templates ("**") must represent at least one valid, non-escaped segment.
221-
if template_str == "**":
221+
elif template_str == "**":
222+
# Multi-segment templates ("**") must represent at least one valid,
223+
# non-escaped segment.
222224
if not _validate_multi_segment_value(val):
223225
raise err
224-
return
225-
226-
# Compile the sub-template into a regex capture pattern
227-
# to isolate and validate individual wildcard values.
228-
pattern, wildcard_types = _build_capture_pattern(template_str)
229-
230-
m = pattern.fullmatch(val)
231-
if m is not None:
232-
# Validate each wildcard value within its matched boundaries,
233-
# preventing traversals from escaping their structural positions.
234-
for i, wildcard_type in enumerate(wildcard_types):
235-
captured_val = m.group(i + 1)
236-
if wildcard_type == "*":
237-
if captured_val in (".", ".."):
238-
raise err
239-
else:
226+
else:
227+
# Compile the sub-template into a regex capture pattern
228+
# to isolate and validate individual wildcard values.
229+
pattern, wildcard_types = _build_capture_pattern(template_str)
230+
231+
m = pattern.fullmatch(val)
232+
if m is not None:
233+
# Validate each wildcard value within its matched boundaries,
234+
# preventing traversals from escaping their structural positions.
235+
for captured_val in m.groups():
240236
if not _validate_multi_segment_value(captured_val):
241237
raise err
242-
else:
243-
# For values that don't match the pattern, ensure the value doesn't
244-
# resolve to 0 segments (e.g. "projects/..").
245-
if val and not _validate_multi_segment_value(val):
246-
raise err
238+
else:
239+
# For values that don't match the pattern, ensure the value doesn't
240+
# resolve to 0 segments (e.g. "projects/..").
241+
if val and not _validate_multi_segment_value(val):
242+
raise err
247243

248244

249245
def _expand_variable_match(positional_vars, named_vars, match):

packages/google-api-core/tests/unit/test_path_template.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,7 @@ def test_build_capture_pattern(template_str, expected_pattern, expected_wildcard
843843
("a/b/c/d/e/../../../..", True),
844844
("a/b/../..", False),
845845
("a/b/../../..", False),
846+
('', False),
846847
(".", False),
847848
("..", False),
848849
("", False),
@@ -857,3 +858,32 @@ def test_build_capture_pattern(template_str, expected_pattern, expected_wildcard
857858
def test_validate_multi_segment_value(val, expected_valid):
858859
result = path_template._validate_multi_segment_value(val)
859860
assert result == expected_valid
861+
862+
863+
@pytest.mark.parametrize(
864+
"val, template_str, expect_error",
865+
[
866+
("api", None, False),
867+
("api", "*", False),
868+
(".", None, True),
869+
("..", None, True),
870+
(".", "*", True),
871+
("..", "*", True),
872+
("", "*", False),
873+
("api/v1", "**", False),
874+
(".", "**", True),
875+
("..", "**", True),
876+
("", "**", True),
877+
("projects/my-proj/locations/us-central1", "projects/*/locations/*", False),
878+
("projects/my-proj/locations/.", "projects/*/locations/*", True),
879+
("projects/my-proj/locations/..", "projects/*/locations/*", True),
880+
("projects/../locations/us-central1", "projects/*/locations/*", True),
881+
],
882+
)
883+
def test_extract_and_validate_wildcards(val, template_str, expect_error):
884+
if expect_error:
885+
with pytest.raises(ValueError):
886+
path_template._extract_and_validate_wildcards(val, template_str)
887+
else:
888+
# Should not raise any exception
889+
path_template._extract_and_validate_wildcards(val, template_str)

0 commit comments

Comments
 (0)