diff --git a/src/werkzeug/routing/rules.py b/src/werkzeug/routing/rules.py index 2dad31dd3..6819218e9 100644 --- a/src/werkzeug/routing/rules.py +++ b/src/werkzeug/routing/rules.py @@ -623,6 +623,7 @@ def _parse_rule(self, rule: str) -> t.Iterable[RulePart]: self._trace.append((False, data["static"])) content += data["static"] if static else re.escape(data["static"]) + haspath = False if data["variable"] is not None: if static: # Switching content to represent regex, hence the need to escape @@ -640,6 +641,7 @@ def _parse_rule(self, rule: str) -> t.Iterable[RulePart]: convertor_number += 1 argument_weights.append(convobj.weight) self._trace.append((True, data["variable"])) + haspath = data["converter"] == "path" if data["slash"] is not None: self._trace.append((False, "/")) @@ -651,7 +653,7 @@ def _parse_rule(self, rule: str) -> t.Iterable[RulePart]: weight = Weighting( -len(static_weights), static_weights, - -len(argument_weights), + float("+inf") if haspath else len(argument_weights), argument_weights, ) yield RulePart( @@ -681,7 +683,7 @@ def _parse_rule(self, rule: str) -> t.Iterable[RulePart]: weight = Weighting( -len(static_weights), static_weights, - -len(argument_weights), + float("+inf") if haspath else len(argument_weights), argument_weights, ) yield RulePart( diff --git a/tests/test_routing.py b/tests/test_routing.py index 02db898d6..2c715ebb0 100644 --- a/tests/test_routing.py +++ b/tests/test_routing.py @@ -381,7 +381,8 @@ def test_greedy(): [ r.Rule("/foo", endpoint="foo"), r.Rule("/", endpoint="bar"), - r.Rule("//", endpoint="bar"), + r.Rule("//", endpoint="bar"), + r.Rule("//static", endpoint="oops"), ] ) adapter = map.bind("example.org", "/") @@ -389,12 +390,54 @@ def test_greedy(): assert adapter.match("/foo") == ("foo", {}) assert adapter.match("/blub") == ("bar", {"bar": "blub"}) assert adapter.match("/he/he") == ("bar", {"bar": "he", "blub": "he"}) + assert adapter.match("/he/static") == ("oops", {"baz": "he"}) + + assert adapter.build("foo", {}) == "/foo" + assert adapter.build("bar", {"bar": "blub"}) == "/blub" + assert adapter.build("bar", {"bar": "blub", "blub": "bar"}) == "/blub/bar" + + +def test_greedy_double_paths(): + # two back to back paths do not have any meaning and should + # probably cause an error in _parse_rule + map = r.Map( + [ + r.Rule("/foo", endpoint="foo"), + r.Rule("/", endpoint="bar"), + r.Rule("//", endpoint="bar"), + ] + ) + adapter = map.bind("example.org", "/") + + assert adapter.match("/foo") == ("foo", {}) + assert adapter.match("/blub") == ("bar", {"bar": "blub"}) + assert adapter.match("/he/he") == ("bar", {"bar": "he/he"}) + # can't match ("bar", {"bar": "he", "blub": "he"}) without breaking static matching + # use a rule like "/" instead assert adapter.build("foo", {}) == "/foo" assert adapter.build("bar", {"bar": "blub"}) == "/blub" assert adapter.build("bar", {"bar": "blub", "blub": "bar"}) == "/blub/bar" +def test_static_priority(): + # see https://github.com/pallets/werkzeug/issues/2924 + map = r.Map( + [ + r.Rule("//", endpoint="file"), + r.Rule("//statn", endpoint="stat"), + ], + ) + + adapter = map.bind("example.org", "/") + + assert adapter.match("/d2/d1", method="GET") == ( + "file", + {"dyn2": "d2", "dyn1": "d1"}, + ) + assert adapter.match("/d1/statn", method="GET") == ("stat", {"dyn1": "d1"}) + + def test_path(): map = r.Map( [