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

Issue:
"http://" should be considered as a valid iri-reference
Because of [this code](https://github.com/networknt/json-schema-validator/blob/4f3345bfb8596e6011da48f9625f59ac5dc5251f/src/main/java/com/networknt/schema/format/AbstractRFC3986Format.java#L16), "http://" is considered as an invalid iri-reference.

Indeed, URI documentation tells it's following [RFC 2396](https://www.ietf.org/rfc/rfc2396.txt), and according to that RFC, it requires a non empty "authority" (if I understand well the BNC grammar):
```
URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
absoluteURI   = scheme ":" ( hier_part | opaque_part )
relativeURI   = ( net_path | abs_path | rel_path ) [ "?" query ]
hier_part     = ( net_path | abs_path ) [ "?" query ]
net_path      = "//" authority [ abs_path ]
authority     = server | reg_name
reg_name      = 1*( unreserved | escaped | "$" | "," |
                           ";" | ":" | "@" | "&" | "=" | "+" )
server        = [ [ userinfo "@" ] hostport ]
userinfo      = *( unreserved | escaped |
                        ";" | ":" | "&" | "=" | "+" | "$" | "," )
hostport      = host [ ":" port ]
host          = hostname | IPv4address
hostname      = *( domainlabel "." ) toplabel [ "." ]
```

But, according to [RFC 3987](https://datatracker.ietf.org/doc/html/rfc3987) that defines iri-reference, "http://" should be considered valid:
```
IRI-reference  = IRI / irelative-ref
IRI            = scheme ":" ihier-part [ "?" iquery ]
                         [ "#" ifragment ]
ihier-part     = "//" iauthority ipath-abempty
                  / ipath-absolute
                  / ipath-rootless
                  / ipath-empty
iauthority     = [ iuserinfo "@" ] ihost [ ":" port ]
ihost          = IP-literal / IPv4address / ireg-name
ireg-name      = *( iunreserved / pct-encoded / sub-delims )
```

The difference is that `ireg-name` has a `*(...)` that allows to be empty while `reg_name` has a `1*(...)`.

Pull request description:
Fix iri, iri-reference, uri and uri-reference
Closes #1070 

This fixes `iri`, `iri-reference`, `uri` and `uri-reference` formats to allow inputs conforming to RFC 3986 / RFC 3987 for which `URI` will throw a syntax error as it implements RFC 2396.

In particular inputs like `http://`, `//` and `about:` are now accepted.

A more complete and proper fix will likely require an additional library like Apache Jena IRI but will defer this until it is really needed.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index be3acf9..4209ee6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,54 @@ This project does not adhere to [Semantic Versioning](https://semver.org/) and m
 
 ### Changed
 
+## 1.4.1 - 2024-06-20
+
+### Added
+
+### Changed
+
+- Add builder for SchemaValidatorsConfig (#1068) Thanks @justin-tay
+- Improve documentation on regular expressions (#1066) Thanks @justin-tay
+- Adds JsonNodeReader (#1065) Thanks @justin-tay
+- Bump jackson-databind from 2.17.0 to 2.17.1 (#1064) Thanks @justin-tay
+- Deprecate validateAndCollect in favor of explicitly calling loadCollectors (#1063) Thanks @justin-tay
+- Set initial array list size (#1062) Thanks @justin-tay
+- Refactor to make more fields final (#1060) Thanks @justin-tay
+- Deprecate config to disable unevaluatedItems and unevaluatedProperties evaluation as no longer needed (#1059) Thanks @justin-tay
+- Add GraalJS regular expression and factory implementation (#1058) Thanks @justin-tay
+- Allow customization of const message to include input value (#1057) Thanks @justin-tay
+- Optimize logging by creating a debug flag (#1054) Thanks @justin-tay 
+- Release delegate held by cached supplier (#1056) Thanks @justin-tay
+- Remove incorrect logic for oneOf, anyOf and properties (#1053) Thanks @justin-tay
+- Detect invalid escape for ecmascript (#1049) Thanks @justin-tay
+- Fix uri and uri-reference incorrect validation failure (#1052) Thanks @justin-tay
+- Fix for incorrect validation failure for %-encoded '[' and ']' characters. (#1051) Thanks @4naesthetic
+- Optimize getValueNodeType (#1048) Thanks @justin-tay
+- Allow tracking of json node location information (#1046) Thanks @justin-tay
+- Update example in javadoc for JsonMetaSchema builder (#1043) Thanks @khouari1
+- Validate allOf, oneOf and anyOf contains array (#1039) Thanks @justin-tay
+- Update JSON Schema badges (#1041) Thanks @justin-tay
+- Fix schema location for escaped json pointer (#1038) Thanks @justin-tay
+- Allow customization of assertion for outputunit (#1033) Thanks @justin-tay
+- change example.com connection error IOException (#1028)
+- Fix broken link in README (#1029) Thanks @danielaparker
+- Update dependency versions (#1023) Thanks @justin-tay
+- upgrade slf4j to 2.0.13
+- upgrade jackson to 2.17.0
+- Add options to control caching of schemas (#1018) Thanks @justin-tay
+- Update github actions (#1022) Thanks @justin-tay
+- Fix code example in README.md (#1020) Thanks @tombentley
+- Fix the broken link to if-then-else.json (#1014) Thanks @emmanuel-ferdman
+- Changed defs to $defs in compatibility.md (#1013) Thanks @danielaparker
+- Add OpenAPI JsonMetaSchema (#1011) Thanks @justin-tay
+- Fix walk for if validator with validation (#1010) Thanks @justin-tay
+- Fix 2012-12 typo (#1008) Thanks @justin-tay
+- Check type validator is of correct class (#1003) Thanks @justin-tay
+- Fix root classpath resolve (#1000) Thanks @justin-tay
+- upgrade undertow to 2.2.31.Final (#998)
+- Walk items schema instead of walking instance data (#993) Thanks @justin-tay
+
+
 ## 1.4.0 - 2024-03-16
 
 ### Added
diff --git a/pom.xml b/pom.xml
index 365612d..ec8b5a8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@
 
   <groupId>com.networknt</groupId>
   <artifactId>json-schema-validator</artifactId>
-  <version>1.4.0</version>
+  <version>1.4.1</version>
   <packaging>bundle</packaging>
   <name>JsonSchemaValidator</name>
   <description>A json schema validator that supports draft v4, v6, v7, v2019-09 and v2020-12</description>
@@ -88,7 +88,7 @@
     <version.maven-bundle-plugin>5.1.9</version.maven-bundle-plugin>
     <version.maven-gpg-plugin>1.6</version.maven-gpg-plugin>
     <version.maven-javadoc-plugin>3.6.3</version.maven-javadoc-plugin>
-    <version.maven-source-plugin>3.3.1</version.maven-source-plugin>
+    <version.maven-source-plugin>3.2.1</version.maven-source-plugin> <!-- 3.3.0 and above is not Java 8 compatible -->
     <version.maven-surefire-junit5-tree-reporter>1.2.1</version.maven-surefire-junit5-tree-reporter>
     <version.maven-surefire-plugin>3.2.5</version.maven-surefire-plugin>
     <version.moditect-maven-plugin>1.2.1.Final</version.moditect-maven-plugin>
diff --git a/src/main/java/com/networknt/schema/format/AbstractRFC3986Format.java b/src/main/java/com/networknt/schema/format/AbstractRFC3986Format.java
index 0b0748b..2500d88 100644
--- a/src/main/java/com/networknt/schema/format/AbstractRFC3986Format.java
+++ b/src/main/java/com/networknt/schema/format/AbstractRFC3986Format.java
@@ -2,6 +2,7 @@ package com.networknt.schema.format;
 
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.util.regex.Pattern;
 
 import com.networknt.schema.ExecutionContext;
 import com.networknt.schema.Format;
@@ -10,13 +11,15 @@ import com.networknt.schema.Format;
  * {@link AbstractFormat} for RFC 3986.
  */
 public abstract class AbstractRFC3986Format implements Format {
+    private static final Pattern VALID = Pattern.compile("([A-Za-z0-9+-\\.]*:)?//|[A-Za-z0-9+-\\.]+:");
+
     @Override
     public final boolean matches(ExecutionContext executionContext, String value) {
         try {
             URI uri = new URI(value);
             return validate(uri);
         } catch (URISyntaxException e) {
-            return false;
+            return handleException(e);
         }
     }
 
@@ -28,4 +31,16 @@ public abstract class AbstractRFC3986Format implements Format {
      */
     protected abstract boolean validate(URI uri);
 
+    /**
+     * Determines if the uri matches the format.
+     *
+     * @param e the URISyntaxException
+     * @return false if it does not match
+     */
+    protected boolean handleException(URISyntaxException e) {
+        if (VALID.matcher(e.getInput()).matches()) {
+            return true;
+        }
+        return false;
+    }
 }


Test patch:
diff --git a/src/test/java/com/networknt/schema/format/IriFormatTest.java b/src/test/java/com/networknt/schema/format/IriFormatTest.java
index b371b1b..17dc5e5 100644
--- a/src/test/java/com/networknt/schema/format/IriFormatTest.java
+++ b/src/test/java/com/networknt/schema/format/IriFormatTest.java
@@ -82,4 +82,39 @@ class IriFormatTest {
         assertTrue(messages.isEmpty());
     }
 
+    @Test
+    void noAuthorityShouldPass() {
+        String schemaData = "{\r\n"
+                + "  \"format\": \"iri\"\r\n"
+                + "}";
+
+        SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().formatAssertionsEnabled(true).build();
+        JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+        Set<ValidationMessage> messages = schema.validate("\"http://\"", InputFormat.JSON);
+        assertTrue(messages.isEmpty());
+    }
+
+    @Test
+    void noSchemeNoAuthorityShouldPass() {
+        String schemaData = "{\r\n"
+                + "  \"format\": \"iri\"\r\n"
+                + "}";
+
+        SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().formatAssertionsEnabled(true).build();
+        JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+        Set<ValidationMessage> messages = schema.validate("\"//\"", InputFormat.JSON);
+        assertTrue(messages.isEmpty());
+    }
+
+    @Test
+    void noPathShouldPass() {
+        String schemaData = "{\r\n"
+                + "  \"format\": \"iri\"\r\n"
+                + "}";
+
+        SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().formatAssertionsEnabled(true).build();
+        JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+        Set<ValidationMessage> messages = schema.validate("\"about:\"", InputFormat.JSON);
+        assertTrue(messages.isEmpty());
+    }
 }
diff --git a/src/test/java/com/networknt/schema/format/IriReferenceFormatTest.java b/src/test/java/com/networknt/schema/format/IriReferenceFormatTest.java
index 445937b..0b5f1d4 100644
--- a/src/test/java/com/networknt/schema/format/IriReferenceFormatTest.java
+++ b/src/test/java/com/networknt/schema/format/IriReferenceFormatTest.java
@@ -82,4 +82,40 @@ class IriReferenceFormatTest {
         assertTrue(messages.isEmpty());
     }
 
+    @Test
+    void noAuthorityShouldPass() {
+        String schemaData = "{\r\n"
+                + "  \"format\": \"iri-reference\"\r\n"
+                + "}";
+
+        SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().formatAssertionsEnabled(true).build();
+        JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+        Set<ValidationMessage> messages = schema.validate("\"http://\"", InputFormat.JSON);
+        assertTrue(messages.isEmpty());
+    }
+
+    @Test
+    void noSchemeNoAuthorityShouldPass() {
+        String schemaData = "{\r\n"
+                + "  \"format\": \"iri-reference\"\r\n"
+                + "}";
+
+        SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().formatAssertionsEnabled(true).build();
+        JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+        Set<ValidationMessage> messages = schema.validate("\"//\"", InputFormat.JSON);
+        assertTrue(messages.isEmpty());
+    }
+
+    @Test
+    void noPathShouldPass() {
+        String schemaData = "{\r\n"
+                + "  \"format\": \"iri-reference\"\r\n"
+                + "}";
+
+        SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().formatAssertionsEnabled(true).build();
+        JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+        Set<ValidationMessage> messages = schema.validate("\"about:\"", InputFormat.JSON);
+        assertTrue(messages.isEmpty());
+    }
+
 }
diff --git a/src/test/java/com/networknt/schema/format/UriFormatTest.java b/src/test/java/com/networknt/schema/format/UriFormatTest.java
index 6584ef4..690980e 100644
--- a/src/test/java/com/networknt/schema/format/UriFormatTest.java
+++ b/src/test/java/com/networknt/schema/format/UriFormatTest.java
@@ -81,4 +81,40 @@ class UriFormatTest {
                 InputFormat.JSON);
         assertFalse(messages.isEmpty());
     }
+
+    @Test
+    void noAuthorityShouldPass() {
+        String schemaData = "{\r\n"
+                + "  \"format\": \"uri\"\r\n"
+                + "}";
+
+        SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().formatAssertionsEnabled(true).build();
+        JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+        Set<ValidationMessage> messages = schema.validate("\"http://\"", InputFormat.JSON);
+        assertTrue(messages.isEmpty());
+    }
+
+    @Test
+    void noSchemeNoAuthorityShouldPass() {
+        String schemaData = "{\r\n"
+                + "  \"format\": \"uri\"\r\n"
+                + "}";
+
+        SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().formatAssertionsEnabled(true).build();
+        JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+        Set<ValidationMessage> messages = schema.validate("\"//\"", InputFormat.JSON);
+        assertTrue(messages.isEmpty());
+    }
+
+    @Test
+    void noPathShouldPass() {
+        String schemaData = "{\r\n"
+                + "  \"format\": \"uri\"\r\n"
+                + "}";
+
+        SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().formatAssertionsEnabled(true).build();
+        JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+        Set<ValidationMessage> messages = schema.validate("\"about:\"", InputFormat.JSON);
+        assertTrue(messages.isEmpty());
+    }
 }
diff --git a/src/test/java/com/networknt/schema/format/UriReferenceFormatTest.java b/src/test/java/com/networknt/schema/format/UriReferenceFormatTest.java
index 4522d03..bb8ced2 100644
--- a/src/test/java/com/networknt/schema/format/UriReferenceFormatTest.java
+++ b/src/test/java/com/networknt/schema/format/UriReferenceFormatTest.java
@@ -82,4 +82,39 @@ class UriReferenceFormatTest {
         assertFalse(messages.isEmpty());
     }
 
+    @Test
+    void noAuthorityShouldPass() {
+        String schemaData = "{\r\n"
+                + "  \"format\": \"uri-reference\"\r\n"
+                + "}";
+
+        SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().formatAssertionsEnabled(true).build();
+        JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+        Set<ValidationMessage> messages = schema.validate("\"http://\"", InputFormat.JSON);
+        assertTrue(messages.isEmpty());
+    }
+
+    @Test
+    void noSchemeNoAuthorityShouldPass() {
+        String schemaData = "{\r\n"
+                + "  \"format\": \"uri-reference\"\r\n"
+                + "}";
+
+        SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().formatAssertionsEnabled(true).build();
+        JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+        Set<ValidationMessage> messages = schema.validate("\"//\"", InputFormat.JSON);
+        assertTrue(messages.isEmpty());
+    }
+
+    @Test
+    void noPathShouldPass() {
+        String schemaData = "{\r\n"
+                + "  \"format\": \"uri-reference\"\r\n"
+                + "}";
+
+        SchemaValidatorsConfig config = SchemaValidatorsConfig.builder().formatAssertionsEnabled(true).build();
+        JsonSchema schema = JsonSchemaFactory.getInstance(VersionFlag.V202012).getSchema(schemaData, config);
+        Set<ValidationMessage> messages = schema.validate("\"about:\"", InputFormat.JSON);
+        assertTrue(messages.isEmpty());
+    }
 }