diff --git a/changelog.d/3934.bugfix b/changelog.d/3934.bugfix new file mode 100644 index 0000000000..989f96d004 --- /dev/null +++ b/changelog.d/3934.bugfix @@ -0,0 +1 @@ +Validate public space addresses and room aliases length \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConstants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConstants.kt new file mode 100644 index 0000000000..49520f3678 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConstants.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api + +/** + * This object define some global constants regarding the Matrix specification + */ +object MatrixConstants { + /** + * Max length for an alias. Room aliases MUST NOT exceed 255 bytes (including the # sigil and the domain). + * See [maxAliasLocalPartLength] + * Ref. https://matrix.org/docs/spec/appendices#room-aliases + */ + const val ALIAS_MAX_LENGTH = 255 + + fun maxAliasLocalPartLength(domain: String): Int { + return (ALIAS_MAX_LENGTH - 1 /* # sigil */ - 1 /* ':' */ - domain.length) + .coerceAtLeast(0) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt index 27936a2b3f..2a26b612fb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt @@ -17,6 +17,8 @@ package org.matrix.android.sdk.api import org.matrix.android.sdk.BuildConfig +import org.matrix.android.sdk.internal.util.removeInvalidRoomNameChars +import org.matrix.android.sdk.internal.util.replaceSpaceChars import timber.log.Timber /** @@ -130,8 +132,8 @@ object MatrixPatterns { fun isEventId(str: String?): Boolean { return str != null && (str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER || - str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 || - str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4) + str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 || + str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4) } /** @@ -162,10 +164,11 @@ object MatrixPatterns { return order != null && order.length < 50 && order matches ORDER_STRING_REGEX } - fun candidateAliasFromRoomName(name: String): String { - return Regex("\\s").replace(name.lowercase(), "_").let { - "[^a-z0-9._%#@=+-]".toRegex().replace(it, "") - } + fun candidateAliasFromRoomName(roomName: String, domain: String): String { + return roomName.lowercase() + .replaceSpaceChars(replacement = "_") + .removeInvalidRoomNameChars() + .take(MatrixConstants.maxAliasLocalPartLength(domain)) } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/StringUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/StringUtils.kt index aa0b92aa45..8a6ec18986 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/StringUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/StringUtils.kt @@ -78,7 +78,7 @@ internal val spaceChars = "[\u00A0\u2000-\u200B\u2800\u3000]".toRegex() /** * Strip all the UTF-8 chars which are actually spaces */ -internal fun String.replaceSpaceChars() = replace(spaceChars, "") +internal fun String.replaceSpaceChars(replacement: String = "") = replace(spaceChars, replacement) // String.capitalize is now deprecated internal fun String.safeCapitalize(): String { @@ -90,3 +90,5 @@ internal fun String.safeCapitalize(): String { } } } + +internal fun String.removeInvalidRoomNameChars() = "[^a-z0-9._%#@=+-]".toRegex().replace(this, "") diff --git a/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt b/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt index 2b2fddd0c9..cda1623c88 100644 --- a/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt +++ b/vector/src/main/java/im/vector/app/features/form/FormEditTextItem.kt @@ -17,6 +17,7 @@ package im.vector.app.features.form import android.text.Editable +import android.text.InputFilter import android.view.View import android.view.inputmethod.EditorInfo import android.widget.TextView @@ -77,6 +78,9 @@ abstract class FormEditTextItem : VectorEpoxyModel() { @EpoxyAttribute var suffixText: String? = null + @EpoxyAttribute + var maxLength: Int? = null + private val onTextChangeListener = object : SimpleTextWatcher() { override fun afterTextChanged(s: Editable) { onTextChange?.invoke(s.toString()) @@ -109,6 +113,15 @@ abstract class FormEditTextItem : VectorEpoxyModel() { holder.textInputEditText.addTextChangedListenerOnce(onTextChangeListener) holder.textInputEditText.setOnEditorActionListener(editorActionListener) holder.textInputEditText.onFocusChangeListener = onFocusChangedListener + + if (maxLength != null) { + holder.textInputEditText.filters = arrayOf(InputFilter.LengthFilter(maxLength!!)) + holder.textInputLayout.isCounterEnabled = true + holder.textInputLayout.counterMaxLength = maxLength!! + } else { + holder.textInputEditText.filters = arrayOf() + holder.textInputLayout.isCounterEnabled = false + } } override fun shouldSaveViewState(): Boolean { diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt index a6799ce730..7dd9cb4e00 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt @@ -29,6 +29,7 @@ import im.vector.app.features.form.formEditTextItem import im.vector.app.features.form.formEditableAvatarItem import im.vector.app.features.form.formSubmitButtonItem import im.vector.app.features.form.formSwitchItem +import org.matrix.android.sdk.api.MatrixConstants import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import org.matrix.android.sdk.api.session.room.model.RoomJoinRules @@ -141,6 +142,7 @@ class CreateRoomController @Inject constructor( value(viewState.aliasLocalPart) suffixText(":" + viewState.homeServerName) prefixText("#") + maxLength(MatrixConstants.maxAliasLocalPartLength(viewState.homeServerName)) hint(host.stringProvider.getString(R.string.room_alias_address_hint)) errorMessage( host.roomAliasErrorFormatter.format( diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt index 8ddb6d7a09..14789aabb5 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomViewModel.kt @@ -56,7 +56,7 @@ import timber.log.Timber class CreateRoomViewModel @AssistedInject constructor(@Assisted private val initialState: CreateRoomViewState, private val session: Session, private val rawService: RawService, - private val vectorPreferences: VectorPreferences + vectorPreferences: VectorPreferences ) : VectorViewModel(initialState) { @AssistedFactory diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateSubSpaceController.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateSubSpaceController.kt index 6d292c85da..26ea2f30a3 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateSubSpaceController.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateSubSpaceController.kt @@ -28,6 +28,7 @@ import im.vector.app.features.form.formEditTextItem import im.vector.app.features.form.formEditableSquareAvatarItem import im.vector.app.features.form.formMultiLineEditTextItem import im.vector.app.features.form.formSubmitButtonItem +import org.matrix.android.sdk.api.MatrixConstants import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import javax.inject.Inject @@ -81,6 +82,7 @@ class CreateSubSpaceController @Inject constructor( hint(host.stringProvider.getString(R.string.create_space_alias_hint)) suffixText(":" + data.homeServerName) prefixText("#") + maxLength(MatrixConstants.maxAliasLocalPartLength(data.homeServerName)) errorMessage( host.roomAliasErrorFormatter.format( (((data.asyncCreateRoomRequest as? Fail)?.error) as? CreateRoomFailure.AliasError)?.aliasError) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt index a14bb61606..a1c252d356 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasController.kt @@ -36,6 +36,7 @@ import im.vector.app.features.discovery.settingsInfoItem import im.vector.app.features.form.formEditTextItem import im.vector.app.features.form.formSwitchItem import im.vector.app.features.roomdirectory.createroom.RoomAliasErrorFormatter +import org.matrix.android.sdk.api.MatrixConstants import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomType @@ -174,6 +175,7 @@ class RoomAliasController @Inject constructor( formEditTextItem { id("publishManuallyEdit") value(data.publishManuallyState.value) + maxLength(MatrixConstants.ALIAS_MAX_LENGTH) hint(host.stringProvider.getString(R.string.room_alias_address_hint)) inputType(InputType.TYPE_CLASS_TEXT) onTextChange { text -> @@ -253,6 +255,7 @@ class RoomAliasController @Inject constructor( value(data.newLocalAliasState.value) suffixText(":" + data.homeServerName) prefixText("#") + maxLength(MatrixConstants.maxAliasLocalPartLength(data.homeServerName)) hint(host.stringProvider.getString(R.string.room_alias_address_hint)) errorMessage(host.roomAliasErrorFormatter.format((data.newLocalAliasState.asyncRequest as? Fail)?.error as? RoomAliasError)) onTextChange { value -> diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceState.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceState.kt index 6fb5853269..ab7fb0cc25 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceState.kt @@ -28,7 +28,7 @@ data class CreateSpaceState( val step: Step = Step.ChooseType, val spaceType: SpaceType? = null, val spaceTopology: SpaceTopology? = null, - val homeServerName: String? = null, + val homeServerName: String = "", val aliasLocalPart: String? = null, val aliasManuallyModified: Boolean = false, val aliasVerificationTask: Async = Uninitialized, diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt index e6ead2294e..4495ee31a1 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/CreateSpaceViewModel.kt @@ -134,7 +134,7 @@ class CreateSpaceViewModel @AssistedInject constructor( ) } else { val tentativeAlias = - MatrixPatterns.candidateAliasFromRoomName(action.name) + MatrixPatterns.candidateAliasFromRoomName(action.name, homeServerName) copy( nameInlineError = null, name = action.name, diff --git a/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDetailEpoxyController.kt b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDetailEpoxyController.kt index 27c08d1f6f..14b0db2cd1 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDetailEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/create/SpaceDetailEpoxyController.kt @@ -27,6 +27,7 @@ import im.vector.app.features.form.formEditableSquareAvatarItem import im.vector.app.features.form.formMultiLineEditTextItem import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.roomdirectory.createroom.RoomAliasErrorFormatter +import org.matrix.android.sdk.api.MatrixConstants import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import org.matrix.android.sdk.api.util.MatrixItem import javax.inject.Inject @@ -94,6 +95,7 @@ class SpaceDetailEpoxyController @Inject constructor( hint(host.stringProvider.getString(R.string.create_space_alias_hint)) suffixText(":" + data.homeServerName) prefixText("#") + maxLength(MatrixConstants.maxAliasLocalPartLength(data.homeServerName)) onFocusChange { hasFocus -> host.aliasTextIsFocused = hasFocus }