Repository: networknt/json-schema-validator (java)

Issue:
Relative IRIs incorrectly resolved if they contain colons
I.e. a relative IRI like `first-segment/second-segment:with-colon` or `a-segment?queryParam=valueWih:Colon` (which are perfectly fine relative IRIs by [RFC-3987](https://datatracker.ietf.org/doc/html/rfc3987)) are considered abolute IRIs.

And this is because `AbsoluteIri` class tells absolute and relative IRIs apart by presence of the colon, which is wrong (by the RFC spec).

One of the possible fixes could be replacing line 88 in `AbsoluteIri.java` with somehing similar to `if (iri.matches("[^?/]*:.*")) {`, so that IRIs which contain colon in query or second segment, wouldn't be considered absolute.


Pull request description:
Fix relative iris with colons
Closes #1146

Fix the issue where a relative iri with a colon is treated as an absolute iri.

Patch:
diff --git a/src/main/java/com/networknt/schema/AbsoluteIri.java b/src/main/java/com/networknt/schema/AbsoluteIri.java
index 3c4075c..1ade743 100644
--- a/src/main/java/com/networknt/schema/AbsoluteIri.java
+++ b/src/main/java/com/networknt/schema/AbsoluteIri.java
@@ -76,6 +76,27 @@ public class AbsoluteIri {
         return getSchemeAuthority(this.value);
     }
 
+    /**
+     * Determines if the iri is absolute or relative.
+     *
+     * @param iri to determine
+     * @return true if absolute
+     */
+    private static boolean isAbsoluteIri(String iri) {
+        int length = iri.length();
+        for (int x = 0; x < length; x++) {
+            char ch = iri.charAt(x);
+            if (ch == ':') {
+                // if the first segment is relative with a colon it must be a dot path segment
+                // ie. ./foo:bar
+                return true;
+            } else if (ch == '/' || ch == '?' || ch == '#') {
+                return false;
+            }
+        }
+        return false;
+    }
+
     /**
      * Constructs a new IRI by parsing the given string and then resolving it
      * against this IRI.
@@ -85,7 +106,7 @@ public class AbsoluteIri {
      * @return the new absolute IRI
      */
     public static String resolve(String parent, String iri) {
-        if (iri.contains(":")) {
+        if (isAbsoluteIri(iri)) {
             // IRI is absolute
             return iri;
         } else {


Test patch:
diff --git a/src/test/java/com/networknt/schema/AbsoluteIriTest.java b/src/test/java/com/networknt/schema/AbsoluteIriTest.java
index 1728831..c07860d 100644
--- a/src/test/java/com/networknt/schema/AbsoluteIriTest.java
+++ b/src/test/java/com/networknt/schema/AbsoluteIriTest.java
@@ -33,6 +33,30 @@ class AbsoluteIriTest {
         assertEquals("test.json", iri.resolve("test.json").toString());
     }
 
+    @Test
+    void relativeColonDotPathSegment() {
+        AbsoluteIri iri = new AbsoluteIri("http://www.example.org/foo/bar.json");
+        assertEquals("http://www.example.org/foo/foo:bar", iri.resolve("./foo:bar").toString());
+    }
+
+    @Test
+    void relativeColonSecondSegment() {
+        AbsoluteIri iri = new AbsoluteIri("http://www.example.org/foo/bar.json");
+        assertEquals("http://www.example.org/foo/bar/foo:bar", iri.resolve("bar/foo:bar").toString());
+    }
+
+    @Test
+    void relativeColonQueryString() {
+        AbsoluteIri iri = new AbsoluteIri("http://www.example.org/foo/bar.json");
+        assertEquals("http://www.example.org/foo/test.json?queryParam=foo:bar", iri.resolve("test.json?queryParam=foo:bar").toString());
+    }
+
+    @Test
+    void relativeColonAnchor() {
+        AbsoluteIri iri = new AbsoluteIri("http://www.example.org/foo/bar.json");
+        assertEquals("http://www.example.org/foo/test.json#foo:bar", iri.resolve("test.json#foo:bar").toString());
+    }
+
     @Test
     void relativeAtDocument() {
         AbsoluteIri iri = new AbsoluteIri("http://www.example.org/foo/bar.json");
@@ -93,6 +117,18 @@ class AbsoluteIriTest {
         assertEquals("classpath:resource/test.json", iri.resolve("../../test.json").toString());
     }
 
+    @Test
+    void rootColonDotPathSegment() {
+        AbsoluteIri iri = new AbsoluteIri("http://www.example.org/foo/bar.json");
+        assertEquals("http://www.example.org/foo:bar", iri.resolve("/foo:bar").toString());
+    }
+
+    @Test
+    void rootColonSecondSegment() {
+        AbsoluteIri iri = new AbsoluteIri("http://www.example.org/foo/bar.json");
+        assertEquals("http://www.example.org/bar/foo:bar", iri.resolve("/bar/foo:bar").toString());
+    }
+
     @Test
     void rootAbsoluteAtDocument() {
         AbsoluteIri iri = new AbsoluteIri("http://www.example.org/foo/bar.json");