@@ -19,7 +19,8 @@ data class SamRockSetupRequest(
1919 companion object {
2020 @Suppress(" CyclomaticComplexMethod" , " ReturnCount" )
2121 fun parse (raw : String ): SamRockSetupRequest ? {
22- val uri = runCatching { URI (raw.trim()) }.getOrNull() ? : return null
22+ val setupUrl = raw.samRockSetupUrl()
23+ val uri = runCatching { URI (setupUrl.trim()) }.getOrNull() ? : return null
2324 val scheme = uri.scheme?.lowercase(Locale .US ) ? : return null
2425 val host = uri.host ? : return null
2526 if (uri.rawUserInfo != null ) return null
@@ -48,23 +49,24 @@ data class SamRockSetupRequest(
4849 }
4950
5051 fun sanitizedDescription (raw : String ): String? {
51- val trimmed = raw.trim()
52+ val trimmed = raw.samRockSetupUrl(). trim()
5253 val uri = runCatching { URI (trimmed) }.getOrNull()
5354 if (uri?.hasAuthority() == true && uri.isSamRockProtocolPath()) return uri.logDescription()
5455
5556 return trimmed.sanitizedSamRockLikeDescription()
5657 }
5758
5859 fun sanitizedLaunchKey (raw : String ): String? {
59- return sanitizedDescription(raw)?.let { " $it #${raw.sha256Prefix()} " }
60+ val setupUrl = raw.samRockSetupUrl()
61+ return sanitizedDescription(setupUrl)?.let { " $it #${setupUrl.sha256Prefix()} " }
6062 }
6163
6264 fun isProtocolUrl (raw : String ): Boolean {
6365 return sanitizedDescription(raw) != null
6466 }
6567
6668 fun isPublicHttpProtocolUrl (raw : String ): Boolean {
67- val uri = runCatching { URI (raw.trim()) }.getOrNull() ? : return false
69+ val uri = runCatching { URI (raw.samRockSetupUrl(). trim()) }.getOrNull() ? : return false
6870 val scheme = uri.scheme?.lowercase(Locale .US ) ? : return false
6971 val host = uri.host ? : return false
7072
@@ -73,6 +75,22 @@ data class SamRockSetupRequest(
7375 ! isLocalOrPrivateHost(host)
7476 }
7577
78+ fun setupUrlFromDeeplink (raw : String ): String? {
79+ val uri = runCatching { URI (raw.trim()) }.getOrNull()
80+ val pathComponents = uri?.decodedPathComponents().orEmpty()
81+ val queryItems = uri?.rawQuery
82+ ?.let { runCatching { parseQuery(it) }.getOrNull() }
83+ .orEmpty()
84+ val isSamRockDeeplink = uri?.scheme?.lowercase(Locale .US ) == BITKIT_SCHEME &&
85+ uri.host.equals(BTCPAY_HOST , ignoreCase = true ) &&
86+ pathComponents.singleOrNull()?.equals(SAMROCK_PATH_COMPONENT , ignoreCase = true ) == true
87+
88+ return queryItems.firstValue(URL_QUERY_KEY )
89+ ?.takeIf { isSamRockDeeplink }
90+ ?.takeIf { it.isNotBlank() }
91+ ?.takeIf { isProtocolUrl(it) }
92+ }
93+
7694 private fun parseMethods (setup : String? ): ParsedSamRockMethods {
7795 val values = setup
7896 ?.split(SETUP_METHOD_SEPARATOR )
@@ -249,6 +267,12 @@ data class SamRockSetupRequest(
249267
250268 private fun String.isIpv6Literal (): Boolean = " :" in this
251269
270+ private fun String.samRockSetupUrl (): String {
271+ return setupUrlFromDeeplink(this ) ? : this
272+ }
273+
274+ private const val BITKIT_SCHEME = " bitkit"
275+ private const val BTCPAY_HOST = " btcpay"
252276 private const val HTTP_SCHEME = " http"
253277 private const val HTTPS_SCHEME = " https"
254278 private const val LOCALHOST = " localhost"
@@ -277,6 +301,7 @@ data class SamRockSetupRequest(
277301 private const val SAMROCK_PROTOCOL_PATH_MARKER = " /samrock/protocol"
278302 private const val OTP_QUERY_KEY = " otp"
279303 private const val SETUP_QUERY_KEY = " setup"
304+ private const val URL_QUERY_KEY = " url"
280305 private const val SETUP_METHOD_SEPARATOR = " ,"
281306 private const val SHA_256_ALGORITHM = " SHA-256"
282307 private const val LAUNCH_KEY_HASH_BYTES = 8
0 commit comments