From 5f2d8a623d30d0fe713b2ec8d1cd3232b2c8b3de Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Mon, 15 Feb 2021 15:41:22 +0100 Subject: [PATCH] #1460 - Parsing Link instances now supports unquoted attribute values. Switched from using regular expressions to a simple manual parsing algorithm for the attributes attached to a link. Adapt a couple of broken tests that previously erroneously worked. --- .../org/springframework/hateoas/Link.java | 19 ++++++++++++------- .../org/springframework/hateoas/Links.java | 2 +- .../springframework/hateoas/LinkUnitTest.java | 2 +- .../hateoas/LinksUnitTest.java | 7 +++++++ .../hal/HalLinkDiscovererUnitTest.java | 2 +- .../forms/HalFormsLinkDiscovererUnitTest.java | 2 +- 6 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/springframework/hateoas/Link.java b/src/main/java/org/springframework/hateoas/Link.java index 7435dedf..9963bf90 100755 --- a/src/main/java/org/springframework/hateoas/Link.java +++ b/src/main/java/org/springframework/hateoas/Link.java @@ -47,11 +47,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; public class Link implements Serializable { private static final long serialVersionUID = -9037755944661782121L; - private static final String URI_PATTERN = "(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"; - private static final Pattern URI_AND_ATTRIBUTES_PATTERN = Pattern.compile("<(.*)>;(.*)"); - private static final Pattern KEY_AND_VALUE_PATTERN = Pattern - .compile("(\\w+)=\"(\\p{Lower}[\\p{Lower}\\p{Digit}.\\-\\s]*|" + URI_PATTERN + ")\""); public static final String ATOM_NAMESPACE = "http://www.w3.org/2005/Atom"; @@ -515,11 +511,20 @@ public class Link implements Serializable { return Collections.emptyMap(); } + String[] parts = source.split(";"); Map attributes = new HashMap<>(); - Matcher matcher = KEY_AND_VALUE_PATTERN.matcher(source); - while (matcher.find()) { - attributes.put(matcher.group(1), matcher.group(2)); + for (String part : parts) { + + int delimiter = part.indexOf('='); + + String key = part.substring(0, delimiter).trim(); + String value = part.substring(delimiter + 1).trim(); + + // Potentially unquote value + value = value.startsWith("\"") ? value.substring(1, value.length() - 1) : value; + + attributes.put(key, value); } return attributes; diff --git a/src/main/java/org/springframework/hateoas/Links.java b/src/main/java/org/springframework/hateoas/Links.java index f8ec9164..64292765 100644 --- a/src/main/java/org/springframework/hateoas/Links.java +++ b/src/main/java/org/springframework/hateoas/Links.java @@ -44,7 +44,7 @@ import com.fasterxml.jackson.annotation.JsonValue; public class Links implements Iterable { public static final Links NONE = new Links(Collections.emptyList()); - private static final Pattern LINK_HEADER_PATTERN = Pattern.compile("(<[^>]*>(;\\s*\\w+=\"[^\"]*\")+)"); + private static final Pattern LINK_HEADER_PATTERN = Pattern.compile("(<[^>]*>(;\\s*\\w+=\"?[^\"]*\"?)+)"); private final List links; diff --git a/src/test/java/org/springframework/hateoas/LinkUnitTest.java b/src/test/java/org/springframework/hateoas/LinkUnitTest.java index 337cea03..1876f6b6 100755 --- a/src/test/java/org/springframework/hateoas/LinkUnitTest.java +++ b/src/test/java/org/springframework/hateoas/LinkUnitTest.java @@ -131,7 +131,7 @@ class LinkUnitTest { softly.assertThat(Link.valueOf(";rel=\"foo\"")).isEqualTo(Link.of("/something", "foo")); softly.assertThat(Link.valueOf(";rel=\"foo\";title=\"Some title\"")) - .isEqualTo(Link.of("/something", "foo")); + .isEqualTo(Link.of("/something", "foo").withTitle("Some title")); softly.assertThat(Link.valueOf(";" // + "rel=\"self\";" // + "hreflang=\"en\";" // diff --git a/src/test/java/org/springframework/hateoas/LinksUnitTest.java b/src/test/java/org/springframework/hateoas/LinksUnitTest.java index 11014fad..4402a675 100755 --- a/src/test/java/org/springframework/hateoas/LinksUnitTest.java +++ b/src/test/java/org/springframework/hateoas/LinksUnitTest.java @@ -135,4 +135,11 @@ class LinksUnitTest { assertThat(Links.of(first, second).containsSameLinksAs(Links.of(first))).isFalse(); assertThat(Links.of(first, second).containsSameLinksAs(Links.of(first, second))).isTrue(); } + + @Test // #1460 + void supportsUnquotedAttributes() { + + assertThat(Links.parse("; rel=first").getRequiredLink("first").getHref()) + .isEqualTo("https://url.com?page=1"); + } } diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/HalLinkDiscovererUnitTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/HalLinkDiscovererUnitTest.java index 04882601..659ae8de 100755 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/HalLinkDiscovererUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/HalLinkDiscovererUnitTest.java @@ -63,7 +63,7 @@ class HalLinkDiscovererUnitTest extends LinkDiscovererUnitTest { + "title=\"pdf customer copy\";" // + "type=\"portable document\";" // + "deprecation=\"https://example.com/customers/deprecated\";" // - + "profile=\"my-profile\"" // + + "profile=\"my-profile\";" // + "name=\"my-name\""); assertThat(getDiscoverer().findLinkWithRel(IanaLinkRelations.SELF.value(), linkText)) // diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsLinkDiscovererUnitTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsLinkDiscovererUnitTest.java index 9e1d1040..b849e341 100644 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsLinkDiscovererUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsLinkDiscovererUnitTest.java @@ -59,7 +59,7 @@ class HalFormsLinkDiscovererUnitTest extends LinkDiscovererUnitTest { + "title=\"pdf customer copy\";" // + "type=\"portable document\";" // + "deprecation=\"https://example.com/customers/deprecated\";" // - + "profile=\"my-profile\"" // + + "profile=\"my-profile\";" // + "name=\"my-name\""); assertThat(getDiscoverer().findLinkWithRel(IanaLinkRelations.SELF, linkText)) //