diff --git a/json-schema-validator/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/SchemaLoader.kt b/json-schema-validator/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/SchemaLoader.kt index 13638f19..e1a82751 100644 --- a/json-schema-validator/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/SchemaLoader.kt +++ b/json-schema-validator/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/SchemaLoader.kt @@ -712,8 +712,46 @@ private fun Uri.appendPathToParent(path: String): Uri { } }.appendEncodedPath(path) .build() + .normalizeUri() } +private fun Uri.normalizeUri(): Uri { + if (pathSegments.none { it in RELATIVE_PATH_SEGMENTS }) { + // Nothing to normalize + return this + } + + val newPathSegments = ArrayDeque() + for (segment in pathSegments) { + when (segment) { + SAME_LEVEL_SEGMENT -> { // skip + } + + PARENT_LEVEL_SEGMENT -> + if (newPathSegments.isEmpty()) { + error("cannot normalize URI '$this'. Path goes beyond root") + } else { + newPathSegments.removeLast() + } + + else -> newPathSegments.addLast(segment) + } + } + + return buildUpon() + .encodedPath(null) + .apply { + for (segment in newPathSegments) { + appendEncodedPath(segment) + } + }.build() +} + +private const val SAME_LEVEL_SEGMENT = "." +private const val PARENT_LEVEL_SEGMENT = ".." + +private val RELATIVE_PATH_SEGMENTS = setOf(SAME_LEVEL_SEGMENT, PARENT_LEVEL_SEGMENT) + private val ANCHOR_REGEX: Regex = "^[A-Za-z][A-Za-z0-9-_:.]*$".toRegex() private fun Uri.buildRefId(): RefId = RefId(this) diff --git a/json-schema-validator/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/formats/IdnHostnameFormatValidator.kt b/json-schema-validator/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/formats/IdnHostnameFormatValidator.kt index 29e622e6..c119708f 100644 --- a/json-schema-validator/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/formats/IdnHostnameFormatValidator.kt +++ b/json-schema-validator/src/commonMain/kotlin/io/github/optimumcode/json/schema/internal/formats/IdnHostnameFormatValidator.kt @@ -48,7 +48,7 @@ internal object IdnHostnameFormatValidator : AbstractStringFormatValidator() { if (value.isEmpty()) { return FormatValidator.Invalid() } - if (value.length == 1 && isLabelSeparator(value[0])) { + if (isLabelSeparator(value[0]) || isLabelSeparator(value[value.lastIndex])) { return FormatValidator.Invalid() } @@ -113,6 +113,11 @@ internal object IdnHostnameFormatValidator : AbstractStringFormatValidator() { return false } + if (unicode.isEmpty()) { + // empty labels are not valid + return false + } + // https://datatracker.ietf.org/doc/html/rfc5891#section-4.2.3.1 if (unicode[0] == '-' || unicode.codePointBefore(unicode.length) == '-'.code) { // cannot start or end with hyphen diff --git a/json-schema-validator/src/commonTest/kotlin/io/github/optimumcode/json/schema/assertions/general/format/JsonSchemaIdnHostnameFormatValidationTest.kt b/json-schema-validator/src/commonTest/kotlin/io/github/optimumcode/json/schema/assertions/general/format/JsonSchemaIdnHostnameFormatValidationTest.kt index 0b955f6d..ef2821d3 100644 --- a/json-schema-validator/src/commonTest/kotlin/io/github/optimumcode/json/schema/assertions/general/format/JsonSchemaIdnHostnameFormatValidationTest.kt +++ b/json-schema-validator/src/commonTest/kotlin/io/github/optimumcode/json/schema/assertions/general/format/JsonSchemaIdnHostnameFormatValidationTest.kt @@ -33,6 +33,9 @@ class JsonSchemaIdnHostnameFormatValidationTest : FunSpec() { listOf( TestCase("", "empty value"), TestCase(".", "single separator"), + TestCase(".example", "leading separator"), + TestCase("example.", "trailing separator"), + TestCase("example..com", "two separators in a row"), TestCase("\u3002", "single separator U+3002"), TestCase("\uFF0E", "single separator U+FF0E"), TestCase("\uFF61", "single separator U+FF61"), diff --git a/json-schema-validator/src/commonTest/kotlin/io/github/optimumcode/json/schema/base/JsonSchemaTest.kt b/json-schema-validator/src/commonTest/kotlin/io/github/optimumcode/json/schema/base/JsonSchemaTest.kt index bd503fa5..00930631 100644 --- a/json-schema-validator/src/commonTest/kotlin/io/github/optimumcode/json/schema/base/JsonSchemaTest.kt +++ b/json-schema-validator/src/commonTest/kotlin/io/github/optimumcode/json/schema/base/JsonSchemaTest.kt @@ -115,6 +115,7 @@ class JsonSchemaTest : FunSpec() { "http://example.com/other.json", "http://example.com/other.json#", "http://example.com/root.json#/definitions/B", + "./other.json", ), "definition X" to listOf( @@ -136,7 +137,8 @@ class JsonSchemaTest : FunSpec() { "http://example.com/root.json#/definitions/C", ), ).forEach { (refDestination, possibleRefs) -> - possibleRefs.asSequence() + possibleRefs + .asSequence() .flatMapIndexed { index, ref -> val uri = Uri.parse(ref) val caseNumber = index + 1 diff --git a/test-suites/schema-test-suite b/test-suites/schema-test-suite index e99b24c9..be58fa98 160000 --- a/test-suites/schema-test-suite +++ b/test-suites/schema-test-suite @@ -1 +1 @@ -Subproject commit e99b24c92006fd83803be511d77278910986d6aa +Subproject commit be58fa98d5f79bb8d20f45a15e540332669ad947