Skip to content

Commit cacda01

Browse files
fix: Add validation to reject known-bad hash values (#356)
1 parent 99d78b1 commit cacda01

3 files changed

Lines changed: 80 additions & 5 deletions

File tree

crowdin/src/main/java/com/crowdin/platform/CrowdinConfig.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.util.Log
55
import com.crowdin.platform.data.model.ApiAuthConfig
66
import com.crowdin.platform.data.model.AuthConfig
77
import com.crowdin.platform.data.remote.NetworkType
8+
import java.util.Locale
89

910
/**
1011
* Contains configuration properties for initializing Crowdin.
@@ -108,7 +109,10 @@ class CrowdinConfig private constructor() {
108109
fun build(): CrowdinConfig {
109110
val config = CrowdinConfig()
110111
config.isPersist = isPersist
111-
require(distributionHash.isNotEmpty()) { "Crowdin: `distributionHash` cannot be empty" }
112+
require(isValidDistributionHash(distributionHash)) {
113+
"Crowdin: `distributionHash` is misconfigured (got \"$distributionHash\"). " +
114+
"Ensure you pass a valid distribution hash from your Crowdin project settings."
115+
}
112116

113117
config.distributionHash = distributionHash
114118

@@ -181,4 +185,11 @@ class CrowdinConfig private constructor() {
181185
private const val MIN_PERIODIC_INTERVAL_MILLIS = 15 * 60 * 1000L // 15 minutes.
182186
}
183187
}
188+
189+
companion object {
190+
internal fun isValidDistributionHash(hash: String): Boolean {
191+
val lower = hash.trim().lowercase(Locale.ROOT)
192+
return lower.isNotEmpty() && lower != "null" && lower != "undefined" && lower != "false" && !lower.contains("/")
193+
}
194+
}
184195
}

crowdin/src/main/java/com/crowdin/platform/data/remote/CrowdingRepository.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.content.res.Configuration
44
import android.util.Log
55
import androidx.annotation.WorkerThread
66
import com.crowdin.platform.Crowdin
7+
import com.crowdin.platform.CrowdinConfig
78
import com.crowdin.platform.data.LanguageDataCallback
89
import com.crowdin.platform.data.model.LanguageDetails
910
import com.crowdin.platform.data.model.ManifestData
@@ -30,6 +31,15 @@ internal abstract class CrowdingRepository(
3031
languageDataCallback: LanguageDataCallback?,
3132
function: (ManifestData) -> Unit,
3233
) {
34+
if (!CrowdinConfig.isValidDistributionHash(distributionHash)) {
35+
val msg =
36+
"Crowdin: distribution hash is invalid (\"$distributionHash\"). " +
37+
"Manifest request skipped. Check your CrowdinConfig."
38+
Log.e(Crowdin.CROWDIN_TAG, msg)
39+
languageDataCallback?.onFailure(Throwable(msg))
40+
return
41+
}
42+
3343
Log.v(
3444
Crowdin.CROWDIN_TAG,
3545
"${javaClass.simpleName}. Loading resource manifest from Api started. Hash: $distributionHash",

crowdin/src/test/java/com/crowdin/platform/CrowdinConfigTest.kt

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,66 @@ class CrowdinConfigTest {
1717
.withDistributionHash(distributionHash)
1818
.build()
1919
Assert.fail("SDK initialization with empty `distribution hash` not valid.")
20-
} catch (exception: IllegalArgumentException) {
20+
} catch (_: IllegalArgumentException) {
2121
// Then
2222
// exception expected
2323
}
2424
}
2525

26+
@Test
27+
fun whenDistributionHashIsNull_shouldThrowException() {
28+
listOf("null", "NULL", "Null").forEach { hash ->
29+
try {
30+
CrowdinConfig.Builder().withDistributionHash(hash).build()
31+
Assert.fail("SDK initialization with `distributionHash` = \"$hash\" should not be valid.")
32+
} catch (_: IllegalArgumentException) {
33+
// expected
34+
}
35+
}
36+
}
37+
38+
@Test
39+
fun whenDistributionHashIsFalse_shouldThrowException() {
40+
listOf("false", "FALSE", "False").forEach { hash ->
41+
try {
42+
CrowdinConfig.Builder().withDistributionHash(hash).build()
43+
Assert.fail("SDK initialization with `distributionHash` = \"$hash\" should not be valid.")
44+
} catch (_: IllegalArgumentException) {
45+
// expected
46+
}
47+
}
48+
}
49+
50+
@Test
51+
fun whenDistributionHashIsUndefined_shouldThrowException() {
52+
listOf("undefined", "UNDEFINED").forEach { hash ->
53+
try {
54+
CrowdinConfig.Builder().withDistributionHash(hash).build()
55+
Assert.fail("SDK initialization with `distributionHash` = \"$hash\" should not be valid.")
56+
} catch (_: IllegalArgumentException) {
57+
// expected
58+
}
59+
}
60+
}
61+
62+
@Test
63+
fun whenDistributionHashContainsSlash_shouldThrowException() {
64+
listOf("/hash", "hash/", "/").forEach { hash ->
65+
try {
66+
CrowdinConfig.Builder().withDistributionHash(hash).build()
67+
Assert.fail("SDK initialization with `distributionHash` = \"$hash\" should not be valid.")
68+
} catch (_: IllegalArgumentException) {
69+
// expected
70+
}
71+
}
72+
}
73+
74+
@Test
75+
fun whenDistributionHashIsValid_shouldBuildSuccessfully() {
76+
val config = CrowdinConfig.Builder().withDistributionHash("abc123-valid-hash").build()
77+
Assert.assertEquals("abc123-valid-hash", config.distributionHash)
78+
}
79+
2680
@Test
2781
fun whenSourceLanguageEmptyWithRealTimeEnabled_shouldThrowException() {
2882
// Given
@@ -37,7 +91,7 @@ class CrowdinConfigTest {
3791
.withSourceLanguage(sourceLanguage)
3892
.build()
3993
Assert.fail("SDK initialization with empty `sourceLanguage` when realTime updates enabled - not valid.")
40-
} catch (exception: IllegalArgumentException) {
94+
} catch (_: IllegalArgumentException) {
4195
// Then
4296
// exception expected
4397
}
@@ -57,7 +111,7 @@ class CrowdinConfigTest {
57111
.withSourceLanguage(sourceLanguage)
58112
.build()
59113
Assert.fail("SDK initialization with empty `sourceLanguage` when screenshots enabled - not valid.")
60-
} catch (exception: IllegalArgumentException) {
114+
} catch (_: IllegalArgumentException) {
61115
// Then
62116
// exception expected
63117
}
@@ -76,7 +130,7 @@ class CrowdinConfigTest {
76130
.withAuthConfig(authConfig)
77131
.build()
78132
Assert.fail("SDK initialization with empty `AuthConfig` values - not valid.")
79-
} catch (exception: IllegalArgumentException) {
133+
} catch (_: IllegalArgumentException) {
80134
// Then
81135
// exception expected
82136
}

0 commit comments

Comments
 (0)