|
63 | 63 | _MULTI_SEGMENT_PATTERN = r"(.+)" |
64 | 64 |
|
65 | 65 |
|
| 66 | +class _PathValidationFailed(Exception): |
| 67 | + """Internal exception used when an invalid path is encountered.""" |
| 68 | + |
| 69 | + |
66 | 70 | def _validate_multi_segment_value(val: str) -> bool: |
67 | 71 | """Validate a multi-segment wildcard value. |
68 | 72 |
|
@@ -209,39 +213,40 @@ def _extract_and_validate_wildcards( |
209 | 213 | Raises: |
210 | 214 | ValueError: If a wildcard within a structurally valid value violates path traversal rules. |
211 | 215 | """ |
212 | | - err = ValueError( |
213 | | - f"Invalid value {val} for {property_name or 'positional variable'}." |
214 | | - ) |
215 | | - |
216 | | - if template_str is None or template_str == "*": |
217 | | - # Single-segment templates (None or "*") cannot match exactly "." or ".." |
218 | | - # and cannot have multi-segment paths resolving to 0 segments. |
219 | | - # |
220 | | - # Empty strings pose no traversal security risk here. |
221 | | - if val and not _validate_multi_segment_value(val): |
222 | | - raise err |
223 | | - elif template_str == "**": |
224 | | - # Multi-segment templates ("**") must represent at least one valid, |
225 | | - # non-escaped segment. |
226 | | - if not _validate_multi_segment_value(val): |
227 | | - raise err |
228 | | - else: |
229 | | - # Compile the sub-template into a regex capture pattern |
230 | | - # to isolate and validate individual wildcard values. |
231 | | - pattern, wildcard_types = _build_capture_pattern(template_str) |
232 | | - |
233 | | - m = pattern.fullmatch(val) |
234 | | - if m is not None: |
235 | | - # Validate each wildcard value within its matched boundaries, |
236 | | - # preventing traversals from escaping their structural positions. |
237 | | - for captured_val in m.groups(): |
238 | | - if not _validate_multi_segment_value(captured_val): |
239 | | - raise err |
240 | | - else: |
241 | | - # For values that don't match the pattern, ensure the value doesn't |
242 | | - # resolve to 0 segments (e.g. "projects/.."). |
| 216 | + try: |
| 217 | + if template_str is None or template_str == "*": |
| 218 | + # Single-segment templates (None or "*") cannot match exactly "." or ".." |
| 219 | + # and cannot have multi-segment paths resolving to 0 segments. |
| 220 | + # |
| 221 | + # Empty strings pose no traversal security risk here. |
243 | 222 | if val and not _validate_multi_segment_value(val): |
244 | | - raise err |
| 223 | + raise _PathValidationFailed() |
| 224 | + elif template_str == "**": |
| 225 | + # Multi-segment templates ("**") must represent at least one valid, |
| 226 | + # non-escaped segment. |
| 227 | + if not _validate_multi_segment_value(val): |
| 228 | + raise _PathValidationFailed() |
| 229 | + else: |
| 230 | + # Compile the sub-template into a regex capture pattern |
| 231 | + # to isolate and validate individual wildcard values. |
| 232 | + pattern, wildcard_types = _build_capture_pattern(template_str) |
| 233 | + |
| 234 | + m = pattern.fullmatch(val) |
| 235 | + if m is not None: |
| 236 | + # Validate each wildcard value within its matched boundaries, |
| 237 | + # preventing traversals from escaping their structural positions. |
| 238 | + for captured_val in m.groups(): |
| 239 | + if not _validate_multi_segment_value(captured_val): |
| 240 | + raise _PathValidationFailed() |
| 241 | + else: |
| 242 | + # For values that don't match the pattern, ensure the value doesn't |
| 243 | + # resolve to 0 segments (e.g. "projects/.."). |
| 244 | + if val and not _validate_multi_segment_value(val): |
| 245 | + raise _PathValidationFailed() |
| 246 | + except _PathValidationFailed: |
| 247 | + raise ValueError( |
| 248 | + f"Invalid value {val} for {property_name or 'positional variable'}." |
| 249 | + ) |
245 | 250 |
|
246 | 251 |
|
247 | 252 | def _expand_variable_match(positional_vars, named_vars, match): |
|
0 commit comments