Skip to content

Commit f93b03a

Browse files
committed
- Refactor CoinStorage queries to use parameterized SQL statements for improved security and performance
- Fixed crash
1 parent dfdeb54 commit f93b03a

1 file changed

Lines changed: 66 additions & 33 deletions

File tree

  • core/wallet/src/main/java/cash/p/terminal/wallet/storage

core/wallet/src/main/java/cash/p/terminal/wallet/storage/CoinStorage.kt

Lines changed: 66 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,13 @@ class CoinStorage(val marketDatabase: MarketDatabase) {
2525
fun fullCoins(filter: String, limit: Int): List<FullCoin> {
2626
val sql = """
2727
SELECT * FROM Coin
28-
WHERE ${filterWhereStatement(filter)}
29-
ORDER BY ${filterOrderByStatement(filter)}
30-
LIMIT $limit
28+
WHERE ${filterWhereStatement()}
29+
ORDER BY ${filterOrderByStatement()}
30+
LIMIT ?
3131
""".trimIndent()
3232

33-
return coinDao.getFullCoins(SimpleSQLiteQuery(sql)).map { it.fullCoin }
33+
val args = filterArgs(filter) + limit.toString()
34+
return coinDao.getFullCoins(SimpleSQLiteQuery(sql, args)).map { it.fullCoin }
3435
}
3536

3637
fun fullCoin(uid: String): FullCoin? =
@@ -40,39 +41,48 @@ class CoinStorage(val marketDatabase: MarketDatabase) {
4041
coinDao.getFullCoins(uids).map { it.fullCoin }
4142

4243
fun getToken(query: TokenQuery): Token? {
43-
val sql = "SELECT * FROM TokenEntity WHERE ${filterByTokenQuery(query)} LIMIT 1"
44-
45-
return coinDao.getToken(SimpleSQLiteQuery(sql))?.token
44+
val (sql, args) = buildTokenQuerySql(query, limit = 1)
45+
return coinDao.getToken(SimpleSQLiteQuery(sql, args))?.token
4646
}
4747

4848
fun getTokens(queries: List<TokenQuery>): List<Token> {
4949
if (queries.isEmpty()) return listOf()
5050

51-
val queriesStr = queries.toSet().toList().map { filterByTokenQuery(it) }.joinToString(" OR ")
52-
val sql = "SELECT * FROM TokenEntity WHERE $queriesStr"
51+
val uniqueQueries = queries.toSet().toList()
52+
val whereClauses = mutableListOf<String>()
53+
val args = mutableListOf<Any>()
54+
55+
uniqueQueries.forEach { query ->
56+
val (clause, queryArgs) = buildTokenQueryClause(query)
57+
whereClauses.add(clause)
58+
args.addAll(queryArgs)
59+
}
60+
61+
val sql = "SELECT * FROM TokenEntity WHERE ${whereClauses.joinToString(" OR ")}"
5362

54-
return coinDao.getTokens(SimpleSQLiteQuery(sql)).map { it.token }
63+
return coinDao.getTokens(SimpleSQLiteQuery(sql, args.toTypedArray())).map { it.token }
5564
}
5665

5766
fun getTokens(reference: String): List<Token> {
58-
val queriesStr = "`TokenEntity`.`reference` LIKE '%$reference'"
59-
val sql = "SELECT * FROM TokenEntity WHERE $queriesStr"
67+
val sql = "SELECT * FROM TokenEntity WHERE `TokenEntity`.`reference` LIKE ?"
68+
val args = arrayOf("%$reference")
6069

61-
return coinDao.getTokens(SimpleSQLiteQuery(sql)).map { it.token }
70+
return coinDao.getTokens(SimpleSQLiteQuery(sql, args)).map { it.token }
6271
}
6372

6473
fun getTokens(blockchainType: BlockchainType, filter: String, limit: Int): List<Token> {
6574
val sql = """
6675
SELECT * FROM TokenEntity
6776
JOIN Coin ON `Coin`.`uid` = `TokenEntity`.`coinUid`
6877
WHERE
69-
`TokenEntity`.`blockchainUid` = '${blockchainType.uid}'
70-
AND (${filterWhereStatement(filter)})
71-
ORDER BY ${filterOrderByStatement(filter)}
72-
LIMIT $limit
78+
`TokenEntity`.`blockchainUid` = ?
79+
AND (${filterWhereStatement()})
80+
ORDER BY ${filterOrderByStatement()}
81+
LIMIT ?
7382
""".trimIndent()
7483

75-
return coinDao.getTokens(SimpleSQLiteQuery(sql)).map { it.token }
84+
val args = arrayOf(blockchainType.uid) + filterArgs(filter) + limit.toString()
85+
return coinDao.getTokens(SimpleSQLiteQuery(sql, args)).map { it.token }
7686
}
7787

7888
fun getBlockchain(uid: String): Blockchain? =
@@ -84,30 +94,43 @@ class CoinStorage(val marketDatabase: MarketDatabase) {
8494
fun getAllBlockchains(): List<Blockchain> =
8595
coinDao.getAllBlockchains().map { it.blockchain }
8696

87-
private fun filterByTokenQuery(query: TokenQuery): String {
97+
private fun buildTokenQuerySql(query: TokenQuery, limit: Int? = null): Pair<String, Array<Any>> {
98+
val (clause, args) = buildTokenQueryClause(query)
99+
val sql = "SELECT * FROM TokenEntity WHERE $clause" +
100+
(if (limit != null) " LIMIT $limit" else "")
101+
return Pair(sql, args.toTypedArray())
102+
}
103+
104+
private fun buildTokenQueryClause(query: TokenQuery): Pair<String, List<Any>> {
88105
val (type, reference) = query.tokenType.values
89106

90-
val conditions = mutableListOf(
91-
"`TokenEntity`.`blockchainUid` = '${query.blockchainType.uid}'",
92-
"`TokenEntity`.`type` = '$type'"
93-
)
107+
val conditions = mutableListOf<String>()
108+
val args = mutableListOf<Any>()
109+
110+
conditions.add("`TokenEntity`.`blockchainUid` = ?")
111+
args.add(query.blockchainType.uid)
112+
113+
conditions.add("`TokenEntity`.`type` = ?")
114+
args.add(type)
94115

95116
if (reference.isNotBlank()) {
96-
conditions.add("`TokenEntity`.`reference` LIKE '%$reference'")
117+
conditions.add("`TokenEntity`.`reference` LIKE ?")
118+
args.add("%$reference")
97119
}
98120

99-
return conditions.joinToString(" AND ", "(", ")")
121+
val clause = conditions.joinToString(" AND ", "(", ")")
122+
return Pair(clause, args)
100123
}
101124

102-
private fun filterWhereStatement(filter: String) =
103-
"`Coin`.`name` LIKE '%$filter%' OR `Coin`.`code` LIKE '%$filter%'"
125+
private fun filterWhereStatement() =
126+
"`Coin`.`name` LIKE ? OR `Coin`.`code` LIKE ?"
104127

105-
private fun filterOrderByStatement(filter: String) = """
128+
private fun filterOrderByStatement() = """
106129
priority ASC,
107130
CASE
108-
WHEN `Coin`.`code` LIKE '$filter' THEN 1
109-
WHEN `Coin`.`code` LIKE '$filter%' THEN 2
110-
WHEN `Coin`.`name` LIKE '$filter%' THEN 3
131+
WHEN `Coin`.`code` LIKE ? THEN 1
132+
WHEN `Coin`.`code` LIKE ? THEN 2
133+
WHEN `Coin`.`name` LIKE ? THEN 3
111134
ELSE 4
112135
END,
113136
CASE
@@ -118,6 +141,17 @@ class CoinStorage(val marketDatabase: MarketDatabase) {
118141
`Coin`.`name` ASC
119142
"""
120143

144+
private fun filterArgs(filter: String): Array<String> {
145+
val filterParam = "%$filter%"
146+
val filterStartParam = "$filter%"
147+
return arrayOf(
148+
// For WHERE conditions
149+
filterParam, filterParam,
150+
// For ORDER BY conditions
151+
filter, filterStartParam, filterStartParam
152+
)
153+
}
154+
121155
fun update(coins: List<Coin>, blockchainEntities: List<BlockchainEntity>, tokenEntities: List<TokenEntity>) {
122156
marketDatabase.runInTransaction {
123157
// TODO It's not good solution for update information
@@ -129,5 +163,4 @@ class CoinStorage(val marketDatabase: MarketDatabase) {
129163
tokenEntities.forEach { coinDao.insert(it) }
130164
}
131165
}
132-
133-
}
166+
}

0 commit comments

Comments
 (0)