diff --git a/sdk/src/main/java/cloud/mindbox/mobile_sdk/SdkValidation.kt b/sdk/src/main/java/cloud/mindbox/mobile_sdk/SdkValidation.kt index afd49ac2..2994f7e2 100644 --- a/sdk/src/main/java/cloud/mindbox/mobile_sdk/SdkValidation.kt +++ b/sdk/src/main/java/cloud/mindbox/mobile_sdk/SdkValidation.kt @@ -54,10 +54,13 @@ internal object SdkValidation { } /** - * Path-prefix rule for [isValidOperationsDomain]: zero or more non-empty segments of - * unreserved URL characters. Query, fragment and empty segments do not match. + * Path-prefix rule for [isValidOperationsDomain]: zero or more non-empty segments made + * of unreserved URL characters or complete `%XX` percent-encoded octets. Query, fragment, + * empty segments, and a bare/incomplete `%` (not followed by two hex digits) do not + * match — an incomplete escape passes this far but corrupts (or fails) URL building at + * request time, so it must be rejected at validation, not discovered later at runtime. */ - private val PATH_PREFIX_REGEX = Regex("^(?:/[A-Za-z0-9._~%-]+)*$") + private val PATH_PREFIX_REGEX = Regex("^(?:/(?:[A-Za-z0-9._~-]|%[0-9A-Fa-f]{2})+)*$") /** * Returns true if [value] is a valid operations domain: a host optionally followed by diff --git a/sdk/src/test/java/cloud/mindbox/mobile_sdk/SdkValidationDomainTest.kt b/sdk/src/test/java/cloud/mindbox/mobile_sdk/SdkValidationDomainTest.kt index f4b0239b..022c78a6 100644 --- a/sdk/src/test/java/cloud/mindbox/mobile_sdk/SdkValidationDomainTest.kt +++ b/sdk/src/test/java/cloud/mindbox/mobile_sdk/SdkValidationDomainTest.kt @@ -203,4 +203,40 @@ class SdkValidationDomainTest { } // endregion + + // region isValidOperationsDomain — percent-encoding in path prefix + + @Test + fun `isValidOperationsDomain accepts valid percent-encoded octet`() { + assertEquals(true, SdkValidation.isValidOperationsDomain("domain.com/a%20b")) + } + + @Test + fun `isValidOperationsDomain accepts percent-encoded slash within a segment`() { + assertEquals(true, SdkValidation.isValidOperationsDomain("domain.com/a%2Fb")) + } + + @Test + fun `isValidOperationsDomain accepts lowercase hex in percent-encoding`() { + assertEquals(true, SdkValidation.isValidOperationsDomain("domain.com/a%2ab")) + } + + @Test + fun `isValidOperationsDomain rejects non-hex percent escape`() { + // Regression: a bare "%" not followed by two hex digits parses fine here but + // corrupts (or fails to build) the request URL at runtime — must be rejected now. + assertEquals(false, SdkValidation.isValidOperationsDomain("domain.com/a%zz")) + } + + @Test + fun `isValidOperationsDomain rejects dangling percent at end of path`() { + assertEquals(false, SdkValidation.isValidOperationsDomain("domain.com/a%")) + } + + @Test + fun `isValidOperationsDomain rejects incomplete percent escape (one hex digit)`() { + assertEquals(false, SdkValidation.isValidOperationsDomain("domain.com/a%2")) + } + + // endregion }