Parse permalink using parseMatrixEntityFrom.
Create new PermalinkData type for link to Events. Keep matrixToConverter for now to first convert to matrix.to link. At some point it may be done by the SDK. Remove parse(Uri)
This commit is contained in:
parent
89d2f43b1a
commit
3df328b1ab
14 changed files with 125 additions and 241 deletions
|
|
@ -17,16 +17,17 @@
|
|||
package io.element.android.libraries.matrix.impl.permalink
|
||||
|
||||
import android.net.Uri
|
||||
import android.net.UrlQuerySanitizer
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.core.MatrixPatterns
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.permalink.MatrixToConverter
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import timber.log.Timber
|
||||
import java.net.URLDecoder
|
||||
import org.matrix.rustcomponents.sdk.MatrixId
|
||||
import org.matrix.rustcomponents.sdk.parseMatrixEntityFrom
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
|
@ -41,118 +42,45 @@ class DefaultPermalinkParser @Inject constructor(
|
|||
) : PermalinkParser {
|
||||
/**
|
||||
* Turns a uri string to a [PermalinkData].
|
||||
* https://github.com/matrix-org/matrix-doc/blob/master/proposals/1704-matrix.to-permalinks.md
|
||||
*/
|
||||
override fun parse(uriString: String): PermalinkData {
|
||||
val uri = Uri.parse(uriString)
|
||||
return parse(uri)
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns a uri to a [PermalinkData].
|
||||
* https://github.com/matrix-org/matrix-doc/blob/master/proposals/1704-matrix.to-permalinks.md
|
||||
*/
|
||||
override fun parse(uri: Uri): PermalinkData {
|
||||
// the client or element-based domain permalinks (e.g. https://app.element.io/#/user/@chagai95:matrix.org) don't have the
|
||||
// mxid in the first param (like matrix.to does - https://matrix.to/#/@chagai95:matrix.org) but rather in the second after /user/ so /user/mxid
|
||||
// so convert URI to matrix.to to simplify parsing process
|
||||
val matrixToUri = matrixToConverter.convert(uri) ?: return PermalinkData.FallbackLink(uri)
|
||||
|
||||
// We can't use uri.fragment as it is decoding to early and it will break the parsing
|
||||
// of parameters that represents url (like signurl)
|
||||
val fragment = matrixToUri.toString().substringAfter("#") // uri.fragment
|
||||
if (fragment.isEmpty()) {
|
||||
return PermalinkData.FallbackLink(uri)
|
||||
}
|
||||
val safeFragment = fragment.substringBefore('?')
|
||||
val viaQueryParameters = fragment.getViaParameters()
|
||||
|
||||
// we are limiting to 2 params
|
||||
val params = safeFragment
|
||||
.split(MatrixPatterns.SEP_REGEX)
|
||||
.filter { it.isNotEmpty() }
|
||||
.take(2)
|
||||
|
||||
val decodedParams = params
|
||||
.map { URLDecoder.decode(it, "UTF-8") }
|
||||
|
||||
val identifier = params.getOrNull(0)
|
||||
val decodedIdentifier = decodedParams.getOrNull(0)
|
||||
val extraParameter = decodedParams.getOrNull(1)
|
||||
return when {
|
||||
identifier.isNullOrEmpty() || decodedIdentifier.isNullOrEmpty() -> PermalinkData.FallbackLink(uri)
|
||||
MatrixPatterns.isUserId(decodedIdentifier) -> PermalinkData.UserLink(userId = decodedIdentifier)
|
||||
MatrixPatterns.isRoomId(decodedIdentifier) -> {
|
||||
handleRoomIdCase(fragment, decodedIdentifier, matrixToUri, extraParameter, viaQueryParameters)
|
||||
}
|
||||
MatrixPatterns.isRoomAlias(decodedIdentifier) -> {
|
||||
PermalinkData.RoomLink(
|
||||
roomIdOrAlias = decodedIdentifier,
|
||||
isRoomAlias = true,
|
||||
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) },
|
||||
viaParameters = viaQueryParameters.toImmutableList()
|
||||
)
|
||||
}
|
||||
else -> PermalinkData.FallbackLink(uri, MatrixPatterns.isGroupId(identifier))
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRoomIdCase(fragment: String, identifier: String, uri: Uri, extraParameter: String?, viaQueryParameters: List<String>): PermalinkData {
|
||||
// Can't rely on built in parsing because it's messing around the signurl
|
||||
val paramList = safeExtractParams(fragment)
|
||||
val signUrl = paramList.firstOrNull { it.first == "signurl" }?.second
|
||||
val email = paramList.firstOrNull { it.first == "email" }?.second
|
||||
return if (signUrl.isNullOrEmpty().not() && email.isNullOrEmpty().not()) {
|
||||
try {
|
||||
val signValidUri = Uri.parse(signUrl)
|
||||
val identityServerHost = signValidUri.authority ?: throw IllegalArgumentException("missing `authority`")
|
||||
val token = signValidUri.getQueryParameter("token") ?: throw IllegalArgumentException("missing `token`")
|
||||
val privateKey = signValidUri.getQueryParameter("private_key") ?: throw IllegalArgumentException("missing `private_key`")
|
||||
PermalinkData.RoomEmailInviteLink(
|
||||
roomId = identifier,
|
||||
email = email!!,
|
||||
signUrl = signUrl!!,
|
||||
roomName = paramList.firstOrNull { it.first == "room_name" }?.second,
|
||||
inviterName = paramList.firstOrNull { it.first == "inviter_name" }?.second,
|
||||
roomAvatarUrl = paramList.firstOrNull { it.first == "room_avatar_url" }?.second,
|
||||
roomType = paramList.firstOrNull { it.first == "room_type" }?.second,
|
||||
identityServer = identityServerHost,
|
||||
token = token,
|
||||
privateKey = privateKey
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
Timber.i("## Permalink: Failed to parse permalink $signUrl")
|
||||
PermalinkData.FallbackLink(uri)
|
||||
}
|
||||
val result = runCatching {
|
||||
parseMatrixEntityFrom(matrixToUri.toString())
|
||||
}.getOrNull()
|
||||
return if (result == null) {
|
||||
PermalinkData.FallbackLink(uri)
|
||||
} else {
|
||||
PermalinkData.RoomLink(
|
||||
roomIdOrAlias = identifier,
|
||||
isRoomAlias = false,
|
||||
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) },
|
||||
viaParameters = viaQueryParameters.toImmutableList()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun safeExtractParams(fragment: String) =
|
||||
fragment.substringAfter("?").split('&').mapNotNull {
|
||||
val splitNameValue = it.split("=")
|
||||
if (splitNameValue.size == 2) {
|
||||
Pair(splitNameValue[0], URLDecoder.decode(splitNameValue[1], "UTF-8"))
|
||||
} else {
|
||||
null
|
||||
val viaParameters = result.via.toImmutableList()
|
||||
when (val id = result.id) {
|
||||
is MatrixId.Room -> PermalinkData.RoomIdLink(
|
||||
roomId = RoomId(id.id),
|
||||
viaParameters = viaParameters,
|
||||
)
|
||||
is MatrixId.User -> PermalinkData.UserLink(
|
||||
userId = UserId(id.id),
|
||||
)
|
||||
is MatrixId.RoomAlias -> PermalinkData.RoomAliasLink(
|
||||
roomAlias = id.alias,
|
||||
viaParameters = viaParameters,
|
||||
)
|
||||
is MatrixId.EventOnRoomId -> PermalinkData.EventIdLink(
|
||||
roomId = RoomId(id.roomId),
|
||||
eventId = EventId(id.eventId),
|
||||
viaParameters = viaParameters,
|
||||
)
|
||||
is MatrixId.EventOnRoomAlias -> PermalinkData.EventIdAliasLink(
|
||||
roomAlias = id.alias,
|
||||
eventId = EventId(id.eventId),
|
||||
viaParameters = viaParameters,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.getViaParameters(): List<String> {
|
||||
return runCatching {
|
||||
UrlQuerySanitizer(this)
|
||||
.parameterList
|
||||
.filter {
|
||||
it.mParameter == "via"
|
||||
}
|
||||
.map {
|
||||
URLDecoder.decode(it.mValue, "UTF-8")
|
||||
}
|
||||
}.getOrDefault(emptyList())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@
|
|||
package io.element.android.libraries.matrix.impl.permalink
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.junit.Test
|
||||
|
|
@ -69,7 +72,7 @@ class DefaultPermalinkParserTest {
|
|||
val url = "https://app.element.io/#/user/@test:matrix.org"
|
||||
assertThat(sut.parse(url)).isEqualTo(
|
||||
PermalinkData.UserLink(
|
||||
userId = "@test:matrix.org"
|
||||
userId = UserId("@test:matrix.org")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -81,10 +84,8 @@ class DefaultPermalinkParserTest {
|
|||
)
|
||||
val url = "https://app.element.io/#/room/!aBCD1234:matrix.org"
|
||||
assertThat(sut.parse(url)).isEqualTo(
|
||||
PermalinkData.RoomLink(
|
||||
roomIdOrAlias = "!aBCD1234:matrix.org",
|
||||
isRoomAlias = false,
|
||||
eventId = null,
|
||||
PermalinkData.RoomIdLink(
|
||||
roomId = RoomId("!aBCD1234:matrix.org"),
|
||||
viaParameters = persistentListOf(),
|
||||
)
|
||||
)
|
||||
|
|
@ -97,10 +98,9 @@ class DefaultPermalinkParserTest {
|
|||
)
|
||||
val url = "https://app.element.io/#/room/!aBCD1234:matrix.org/$1234567890abcdef:matrix.org"
|
||||
assertThat(sut.parse(url)).isEqualTo(
|
||||
PermalinkData.RoomLink(
|
||||
roomIdOrAlias = "!aBCD1234:matrix.org",
|
||||
isRoomAlias = false,
|
||||
eventId = "\$1234567890abcdef:matrix.org",
|
||||
PermalinkData.EventIdLink(
|
||||
roomId = RoomId("!aBCD1234:matrix.org"),
|
||||
eventId = EventId("$1234567890abcdef:matrix.org"),
|
||||
viaParameters = persistentListOf(),
|
||||
)
|
||||
)
|
||||
|
|
@ -113,10 +113,8 @@ class DefaultPermalinkParserTest {
|
|||
)
|
||||
val url = "https://app.element.io/#/room/!aBCD1234:matrix.org/1234567890abcdef:matrix.org"
|
||||
assertThat(sut.parse(url)).isEqualTo(
|
||||
PermalinkData.RoomLink(
|
||||
roomIdOrAlias = "!aBCD1234:matrix.org",
|
||||
isRoomAlias = false,
|
||||
eventId = null,
|
||||
PermalinkData.RoomIdLink(
|
||||
roomId = RoomId("!aBCD1234:matrix.org"),
|
||||
viaParameters = persistentListOf(),
|
||||
)
|
||||
)
|
||||
|
|
@ -129,10 +127,9 @@ class DefaultPermalinkParserTest {
|
|||
)
|
||||
val url = "https://app.element.io/#/room/!aBCD1234:matrix.org/$1234567890abcdef:matrix.org?via=matrix.org&via=matrix.com"
|
||||
assertThat(sut.parse(url)).isEqualTo(
|
||||
PermalinkData.RoomLink(
|
||||
roomIdOrAlias = "!aBCD1234:matrix.org",
|
||||
isRoomAlias = false,
|
||||
eventId = "\$1234567890abcdef:matrix.org",
|
||||
PermalinkData.EventIdLink(
|
||||
roomId = RoomId("!aBCD1234:matrix.org"),
|
||||
eventId = EventId("$1234567890abcdef:matrix.org"),
|
||||
viaParameters = persistentListOf("matrix.org", "matrix.com"),
|
||||
)
|
||||
)
|
||||
|
|
@ -145,10 +142,23 @@ class DefaultPermalinkParserTest {
|
|||
)
|
||||
val url = "https://app.element.io/#/room/#element-android:matrix.org"
|
||||
assertThat(sut.parse(url)).isEqualTo(
|
||||
PermalinkData.RoomLink(
|
||||
roomIdOrAlias = "#element-android:matrix.org",
|
||||
isRoomAlias = true,
|
||||
eventId = null,
|
||||
PermalinkData.RoomAliasLink(
|
||||
roomAlias = "#element-android:matrix.org",
|
||||
viaParameters = persistentListOf(),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parsing a valid room alias with eventId url returns a room link`() {
|
||||
val sut = DefaultPermalinkParser(
|
||||
matrixToConverter = DefaultMatrixToConverter(),
|
||||
)
|
||||
val url = "https://app.element.io/#/room/#element-android:matrix.org/$1234567890abcdef:matrix.org"
|
||||
assertThat(sut.parse(url)).isEqualTo(
|
||||
PermalinkData.EventIdAliasLink(
|
||||
roomAlias = "#element-android:matrix.org",
|
||||
eventId = EventId("$1234567890abcdef:matrix.org"),
|
||||
viaParameters = persistentListOf(),
|
||||
)
|
||||
)
|
||||
|
|
@ -188,7 +198,7 @@ class DefaultPermalinkParserTest {
|
|||
"&room_type="
|
||||
assertThat(sut.parse(url)).isEqualTo(
|
||||
PermalinkData.RoomEmailInviteLink(
|
||||
roomId = "!aBCDEF12345:matrix.org",
|
||||
roomId = RoomId("!aBCDEF12345:matrix.org"),
|
||||
email = "testuser@element.io",
|
||||
signUrl = "https://vector.im/_matrix/identity/api/v1/sign-ed25519?token=a_token&private_key=a_private_key",
|
||||
roomName = "TestRoom",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue