diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragment.java b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragment.java
index 8a56a1ef05..5c82d09622 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragment.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditFragment.java
@@ -182,7 +182,7 @@ public class UsernameEditFragment extends LoggingFragment {
case DISCRIMINATOR_TOO_LONG -> getString(R.string.UsernameEditFragment__invalid_username_enter_a_maximum_of_d_digits, UsernameUtil.MAX_DISCRIMINATOR_LENGTH);
case DISCRIMINATOR_TOO_SHORT -> getString(R.string.UsernameEditFragment__invalid_username_enter_a_minimum_of_d_digits, UsernameUtil.MIN_DISCRIMINATOR_LENGTH);
case DISCRIMINATOR_CANNOT_BE_00 -> getString(R.string.UsernameEditFragment__this_number_cant_be_00);
- case DISCRIMINATOR_CANNOT_START_WITH_00 -> getString(R.string.UsernameEditFragment__this_number_cant_start_with_00);
+ case DISCRIMINATOR_CANNOT_START_WITH_0 -> getString(R.string.UsernameEditFragment__this_number_cant_start_with_0);
};
int colorRes = error != null ? R.color.signal_colorError : R.color.signal_colorPrimary;
diff --git a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.kt
index d5d4cb2bac..dbe059e317 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/profiles/manage/UsernameEditViewModel.kt
@@ -22,7 +22,7 @@ import org.thoughtcrime.securesms.profiles.manage.UsernameRepository.UsernameSet
import org.thoughtcrime.securesms.util.NetworkUtil
import org.thoughtcrime.securesms.util.UsernameUtil.InvalidReason
import org.thoughtcrime.securesms.util.UsernameUtil.checkDiscriminator
-import org.thoughtcrime.securesms.util.UsernameUtil.checkUsername
+import org.thoughtcrime.securesms.util.UsernameUtil.checkNickname
import org.thoughtcrime.securesms.util.rx.RxStore
import org.whispersystems.signalservice.api.util.Usernames
import java.util.concurrent.TimeUnit
@@ -159,7 +159,7 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern
return
}
- val invalidReason: InvalidReason? = checkUsername(usernameState.getNickname())
+ val invalidReason: InvalidReason? = checkNickname(usernameState.getNickname())
if (invalidReason != null) {
Log.w(TAG, "Username was submitted, but did not pass validity checks. Reason: $invalidReason")
uiState.update { it.copy(buttonState = ButtonState.SUBMIT_DISABLED, usernameStatus = mapNicknameError(invalidReason)) }
@@ -257,7 +257,7 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern
return
}
- val invalidReason: InvalidReason? = checkUsername(nickname)
+ val invalidReason: InvalidReason? = checkNickname(nickname)
if (invalidReason != null) {
uiState.update { uiState ->
uiState.copy(
@@ -366,7 +366,7 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern
DISCRIMINATOR_TOO_LONG,
DISCRIMINATOR_HAS_INVALID_CHARACTERS,
DISCRIMINATOR_CANNOT_BE_00,
- DISCRIMINATOR_CANNOT_START_WITH_00
+ DISCRIMINATOR_CANNOT_START_WITH_0
}
enum class ButtonState {
@@ -394,7 +394,9 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern
InvalidReason.TOO_LONG -> UsernameStatus.TOO_LONG
InvalidReason.STARTS_WITH_NUMBER -> UsernameStatus.CANNOT_START_WITH_NUMBER
InvalidReason.INVALID_CHARACTERS -> UsernameStatus.INVALID_CHARACTERS
- InvalidReason.INVALID_NUMBER, InvalidReason.INVALID_NUMBER_PREFIX -> error("Unexpected reason $invalidReason")
+ InvalidReason.INVALID_NUMBER,
+ InvalidReason.INVALID_NUMBER_00,
+ InvalidReason.INVALID_NUMBER_PREFIX_0 -> error("Unexpected reason $invalidReason")
}
}
@@ -403,8 +405,8 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern
InvalidReason.TOO_SHORT -> UsernameStatus.DISCRIMINATOR_TOO_SHORT
InvalidReason.TOO_LONG -> UsernameStatus.DISCRIMINATOR_TOO_LONG
InvalidReason.INVALID_CHARACTERS -> UsernameStatus.DISCRIMINATOR_HAS_INVALID_CHARACTERS
- InvalidReason.INVALID_NUMBER -> UsernameStatus.DISCRIMINATOR_CANNOT_BE_00
- InvalidReason.INVALID_NUMBER_PREFIX -> UsernameStatus.DISCRIMINATOR_CANNOT_START_WITH_00
+ InvalidReason.INVALID_NUMBER_00 -> UsernameStatus.DISCRIMINATOR_CANNOT_BE_00
+ InvalidReason.INVALID_NUMBER_PREFIX_0 -> UsernameStatus.DISCRIMINATOR_CANNOT_START_WITH_0
else -> UsernameStatus.INVALID_GENERIC
}
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/UsernameUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/util/UsernameUtil.kt
index 5ac6dc053b..b636a6c200 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/util/UsernameUtil.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/util/UsernameUtil.kt
@@ -1,6 +1,19 @@
package org.thoughtcrime.securesms.util
import org.signal.core.util.logging.Log
+import org.signal.libsignal.usernames.BadDiscriminatorCharacterException
+import org.signal.libsignal.usernames.BadNicknameCharacterException
+import org.signal.libsignal.usernames.BaseUsernameException
+import org.signal.libsignal.usernames.CannotBeEmptyException
+import org.signal.libsignal.usernames.CannotStartWithDigitException
+import org.signal.libsignal.usernames.DiscriminatorCannotBeEmptyException
+import org.signal.libsignal.usernames.DiscriminatorCannotBeSingleDigitException
+import org.signal.libsignal.usernames.DiscriminatorCannotBeZeroException
+import org.signal.libsignal.usernames.DiscriminatorCannotHaveLeadingZerosException
+import org.signal.libsignal.usernames.DiscriminatorTooLargeException
+import org.signal.libsignal.usernames.NicknameTooLongException
+import org.signal.libsignal.usernames.NicknameTooShortException
+import org.signal.libsignal.usernames.Username
import java.util.Locale
import java.util.regex.Pattern
@@ -36,52 +49,75 @@ object UsernameUtil {
}
@JvmStatic
- fun checkUsername(value: String?): InvalidReason? {
- return when {
- value == null -> {
- InvalidReason.TOO_SHORT
- }
- value.length < MIN_NICKNAME_LENGTH -> {
- InvalidReason.TOO_SHORT
- }
- value.length > MAX_NICKNAME_LENGTH -> {
- InvalidReason.TOO_LONG
- }
- DIGIT_START_PATTERN.matcher(value).matches() -> {
- InvalidReason.STARTS_WITH_NUMBER
- }
- !FULL_PATTERN.matcher(value).matches() -> {
- InvalidReason.INVALID_CHARACTERS
- }
- else -> {
- null
- }
+ fun checkNickname(value: String?): InvalidReason? {
+ if (value == null) {
+ return InvalidReason.TOO_SHORT
+ }
+
+ return try {
+ // We only want to check the nickname, so we pass in a known-valid discriminator
+ Username.fromParts(value, "01", MIN_NICKNAME_LENGTH, MAX_NICKNAME_LENGTH)
+ null
+ } catch (e: BadNicknameCharacterException) {
+ InvalidReason.INVALID_CHARACTERS
+ } catch (e: CannotBeEmptyException) {
+ InvalidReason.TOO_SHORT
+ } catch (e: CannotStartWithDigitException) {
+ InvalidReason.STARTS_WITH_NUMBER
+ } catch (e: NicknameTooLongException) {
+ InvalidReason.TOO_LONG
+ } catch (e: NicknameTooShortException) {
+ InvalidReason.TOO_SHORT
+ } catch (e: BaseUsernameException) {
+ Log.w(TAG, "Unhandled verification exception!", e)
+ InvalidReason.INVALID_CHARACTERS
}
}
fun checkDiscriminator(value: String?): InvalidReason? {
- return when {
- value == null -> {
- null
- }
- value == "00" -> {
- InvalidReason.INVALID_NUMBER
- }
- value.startsWith("00") -> {
- InvalidReason.INVALID_NUMBER_PREFIX
- }
- value.length < MIN_DISCRIMINATOR_LENGTH -> {
+ if (value == null) {
+ return null
+ }
+
+ if (value.length < MIN_DISCRIMINATOR_LENGTH) {
+ return InvalidReason.TOO_SHORT
+ }
+
+ if (value.length > MAX_DISCRIMINATOR_LENGTH) {
+ return InvalidReason.TOO_LONG
+ }
+
+ return try {
+ // We only want to check the discriminator, so we pass in a known-valid nickname
+ Username.fromParts("spiderman", value, MIN_NICKNAME_LENGTH, MAX_NICKNAME_LENGTH)
+ null
+ } catch (e: BadDiscriminatorCharacterException) {
+ InvalidReason.INVALID_CHARACTERS
+ } catch (e: DiscriminatorCannotBeEmptyException) {
+ InvalidReason.TOO_SHORT
+ } catch (e: DiscriminatorCannotBeSingleDigitException) {
+ InvalidReason.TOO_SHORT
+ } catch (e: DiscriminatorCannotBeZeroException) {
+ if (value.length < 2) {
InvalidReason.TOO_SHORT
+ } else if (value == "00") {
+ InvalidReason.INVALID_NUMBER_00
+ } else {
+ InvalidReason.INVALID_NUMBER_PREFIX_0
}
- value.length > MAX_DISCRIMINATOR_LENGTH -> {
- InvalidReason.TOO_LONG
- }
- value.toIntOrNull() == null -> {
- InvalidReason.INVALID_CHARACTERS
- }
- else -> {
- null
+ } catch (e: DiscriminatorCannotHaveLeadingZerosException) {
+ if (value.length < 2) {
+ InvalidReason.TOO_SHORT
+ } else if (value == "00") {
+ InvalidReason.INVALID_NUMBER_00
+ } else {
+ InvalidReason.INVALID_NUMBER_PREFIX_0
}
+ } catch (e: DiscriminatorTooLargeException) {
+ InvalidReason.TOO_LONG
+ } catch (e: BaseUsernameException) {
+ Log.w(TAG, "Unhandled verification exception!", e)
+ InvalidReason.INVALID_CHARACTERS
}
}
@@ -91,6 +127,7 @@ object UsernameUtil {
INVALID_CHARACTERS,
STARTS_WITH_NUMBER,
INVALID_NUMBER,
- INVALID_NUMBER_PREFIX
+ INVALID_NUMBER_00,
+ INVALID_NUMBER_PREFIX_0
}
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c88c4e89e8..682b1daea3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2320,8 +2320,8 @@
Invalid username, enter a maximum of %1$d digits.
This number can\'t be 00. Enter a digit between 1–9
-
- This number can\'t start with 00. Enter a digit between 1–9
+
+ Numbers with more than 2 digits can\'t start with 0
Recovering your username will reset your existing QR code and link. Are you sure?
diff --git a/app/src/test/java/org/thoughtcrime/securesms/util/UsernameUtilTest.kt b/app/src/test/java/org/thoughtcrime/securesms/util/UsernameUtilTest.kt
index 6197945dfc..e49c90e495 100644
--- a/app/src/test/java/org/thoughtcrime/securesms/util/UsernameUtilTest.kt
+++ b/app/src/test/java/org/thoughtcrime/securesms/util/UsernameUtilTest.kt
@@ -3,45 +3,81 @@ package org.thoughtcrime.securesms.util
import org.junit.Test
import org.thoughtcrime.securesms.assertIs
import org.thoughtcrime.securesms.assertIsNull
-import org.thoughtcrime.securesms.util.UsernameUtil.checkUsername
+import org.thoughtcrime.securesms.util.UsernameUtil.checkDiscriminator
+import org.thoughtcrime.securesms.util.UsernameUtil.checkNickname
class UsernameUtilTest {
@Test
fun checkUsername_tooShort() {
- checkUsername(null) assertIs UsernameUtil.InvalidReason.TOO_SHORT
- checkUsername("") assertIs UsernameUtil.InvalidReason.TOO_SHORT
- checkUsername("ab") assertIs UsernameUtil.InvalidReason.TOO_SHORT
+ checkNickname(null) assertIs UsernameUtil.InvalidReason.TOO_SHORT
+ checkNickname("") assertIs UsernameUtil.InvalidReason.TOO_SHORT
+ checkNickname("ab") assertIs UsernameUtil.InvalidReason.TOO_SHORT
}
@Test
fun checkUsername_tooLong() {
- checkUsername("abcdefghijklmnopqrstuvwxyz1234567") assertIs UsernameUtil.InvalidReason.TOO_LONG
+ checkNickname("abcdefghijklmnopqrstuvwxyz1234567") assertIs UsernameUtil.InvalidReason.TOO_LONG
}
@Test
fun checkUsername_startsWithNumber() {
- checkUsername("0abcdefg") assertIs UsernameUtil.InvalidReason.STARTS_WITH_NUMBER
- checkUsername("9abcdefg") assertIs UsernameUtil.InvalidReason.STARTS_WITH_NUMBER
- checkUsername("8675309") assertIs UsernameUtil.InvalidReason.STARTS_WITH_NUMBER
+ checkNickname("0abcdefg") assertIs UsernameUtil.InvalidReason.STARTS_WITH_NUMBER
+ checkNickname("9abcdefg") assertIs UsernameUtil.InvalidReason.STARTS_WITH_NUMBER
+ checkNickname("8675309") assertIs UsernameUtil.InvalidReason.STARTS_WITH_NUMBER
}
@Test
fun checkUsername_invalidCharacters() {
- checkUsername("\$abcd") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
- checkUsername(" abcd") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
- checkUsername("ab cde") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
- checkUsername("%%%%%") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
- checkUsername("-----") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
- checkUsername("asĸ_me") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
- checkUsername("+18675309") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
+ checkNickname("\$abcd") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
+ checkNickname(" abcd") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
+ checkNickname("ab cde") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
+ checkNickname("%%%%%") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
+ checkNickname("-----") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
+ checkNickname("asĸ_me") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
+ checkNickname("+18675309") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
}
@Test
fun checkUsername_validUsernames() {
- checkUsername("abcd").assertIsNull()
- checkUsername("abcdefghijklmnopqrstuvwxyz").assertIsNull()
- checkUsername("ABCDEFGHIJKLMNOPQRSTUVWXYZ").assertIsNull()
- checkUsername("web_head").assertIsNull()
- checkUsername("Spider_Fan_1991").assertIsNull()
+ checkNickname("abcd").assertIsNull()
+ checkNickname("abcdefghijklmnopqrstuvwxyz").assertIsNull()
+ checkNickname("ABCDEFGHIJKLMNOPQRSTUVWXYZ").assertIsNull()
+ checkNickname("web_head").assertIsNull()
+ checkNickname("Spider_Fan_1991").assertIsNull()
+ }
+
+ @Test
+ fun checkDiscriminator_valid() {
+ checkDiscriminator(null).assertIsNull()
+ checkDiscriminator("01").assertIsNull()
+ checkDiscriminator("111111111").assertIsNull()
+ }
+
+ @Test
+ fun checkDiscriminator_tooShort() {
+ checkDiscriminator("0") assertIs UsernameUtil.InvalidReason.TOO_SHORT
+ checkDiscriminator("") assertIs UsernameUtil.InvalidReason.TOO_SHORT
+ }
+
+ @Test
+ fun checkDiscriminator_tooLong() {
+ checkDiscriminator("1111111111") assertIs UsernameUtil.InvalidReason.TOO_LONG
+ }
+
+ @Test
+ fun checkDiscriminator_00() {
+ checkDiscriminator("00") assertIs UsernameUtil.InvalidReason.INVALID_NUMBER_00
+ }
+
+ @Test
+ fun checkDiscriminator_prefixZero() {
+ checkDiscriminator("001") assertIs UsernameUtil.InvalidReason.INVALID_NUMBER_PREFIX_0
+ checkDiscriminator("0001") assertIs UsernameUtil.InvalidReason.INVALID_NUMBER_PREFIX_0
+ checkDiscriminator("011") assertIs UsernameUtil.InvalidReason.INVALID_NUMBER_PREFIX_0
+ }
+
+ fun checkDiscriminator_invalidChars() {
+ checkDiscriminator("a1") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
+ checkDiscriminator("1x") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
}
}