Update attachment download handling

This commit is contained in:
ceokot 2022-04-27 17:55:39 +10:00
parent 1a82238a44
commit c10e96dd91
9 changed files with 160 additions and 113 deletions

View File

@ -73,7 +73,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
}
override fun setUserProfilePictureURL(newValue: String) {
val ourRecipient = Address.fromSerialized(getUserPublicKey()!!).let {
val ourRecipient = fromSerialized(getUserPublicKey()!!).let {
Recipient.from(context, it, false)
}
TextSecurePreferences.setProfilePictureURL(context, newValue)
@ -103,7 +103,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
override fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List<LinkPreview?>, groupPublicKey: String?, openGroupID: String?, attachments: List<Attachment>): Long? {
var messageID: Long? = null
val senderAddress = Address.fromSerialized(message.sender!!)
val senderAddress = fromSerialized(message.sender!!)
val isUserSender = (message.sender!! == getUserPublicKey())
val group: Optional<SignalServiceGroup> = when {
openGroupID != null -> Optional.of(SignalServiceGroup(openGroupID.toByteArray(), SignalServiceGroup.GroupType.PUBLIC_CHAT))
@ -117,9 +117,9 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
it.toSignalAttachment()
}
val targetAddress = if (isUserSender && !message.syncTarget.isNullOrEmpty()) {
Address.fromSerialized(message.syncTarget!!)
fromSerialized(message.syncTarget!!)
} else if (group.isPresent) {
Address.fromSerialized(GroupUtil.getEncodedId(group.get()))
fromSerialized(GroupUtil.getEncodedId(group.get()))
} else {
senderAddress
}
@ -291,6 +291,16 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
return getAllOpenGroups().values.firstOrNull { it.server == server && it.room == room }
}
override fun updateOpenGroupCapabilities(server: String, capabilities: List<String>) {
getAllOpenGroups().values.filter { it.server == server }
.map { it.copy(capabilities = it.capabilities) }
.forEach(this::updateOpenGroup)
}
override fun getOpenGroupServer(server: String): List<String> {
return getAllOpenGroups().values.firstOrNull { it.server == server }?.capabilities ?: emptyList()
}
override fun isDuplicateMessage(timestamp: Long): Boolean {
return getReceivedMessageTimestamps().contains(timestamp)
}
@ -317,7 +327,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
override fun getMessageIdInDatabase(timestamp: Long, author: String): Long? {
val database = DatabaseComponent.get(context).mmsSmsDatabase()
val address = Address.fromSerialized(author)
val address = fromSerialized(author)
return database.getMessageFor(timestamp, address)?.getId()
}
@ -435,7 +445,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
override fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection<String>, admins: Collection<String>, sentTimestamp: Long) {
val group = SignalServiceGroup(type, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList())
val m = IncomingTextMessage(Address.fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true)
val m = IncomingTextMessage(fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true)
val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON()
val infoMessage = IncomingGroupMessage(m, groupID, updateData, true)
val smsDB = DatabaseComponent.get(context).smsDatabase()
@ -444,7 +454,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
override fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection<String>, admins: Collection<String>, threadID: Long, sentTimestamp: Long) {
val userPublicKey = getUserPublicKey()
val recipient = Recipient.from(context, Address.fromSerialized(groupID), false)
val recipient = Recipient.from(context, fromSerialized(groupID), false)
val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() ?: ""
val infoMessage = OutgoingGroupMediaMessage(recipient, updateData, groupID, null, sentTimestamp, 0, true, null, listOf(), listOf())
@ -457,7 +467,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
override fun isClosedGroup(publicKey: String): Boolean {
val isClosedGroup = DatabaseComponent.get(context).lokiAPIDatabase().isClosedGroup(publicKey)
val address = Address.fromSerialized(publicKey)
val address = fromSerialized(publicKey)
return address.isClosedGroup || isClosedGroup
}
@ -514,6 +524,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
return DatabaseComponent.get(context).lokiThreadDatabase().getAllOpenGroups()
}
override fun updateOpenGroup(openGroup: OpenGroup) {
OpenGroupManager.updateOpenGroup(openGroup, context)
}
override fun getAllGroups(): List<GroupRecord> {
return DatabaseComponent.get(context).groupDatabase().allGroups
}
@ -534,20 +548,20 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
override fun getOrCreateThreadIdFor(publicKey: String, groupPublicKey: String?, openGroupID: String?): Long {
val database = DatabaseComponent.get(context).threadDatabase()
if (!openGroupID.isNullOrEmpty()) {
val recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray())), false)
return database.getThreadIdIfExistsFor(recipient)
return if (!openGroupID.isNullOrEmpty()) {
val recipient = Recipient.from(context, fromSerialized(GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray())), false)
database.getThreadIdIfExistsFor(recipient)
} else if (!groupPublicKey.isNullOrEmpty()) {
val recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(groupPublicKey)), false)
return database.getOrCreateThreadIdFor(recipient)
val recipient = Recipient.from(context, fromSerialized(GroupUtil.doubleEncodeGroupID(groupPublicKey)), false)
database.getOrCreateThreadIdFor(recipient)
} else {
val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false)
return database.getOrCreateThreadIdFor(recipient)
val recipient = Recipient.from(context, fromSerialized(publicKey), false)
database.getOrCreateThreadIdFor(recipient)
}
}
override fun getThreadId(publicKeyOrOpenGroupID: String): Long? {
val address = Address.fromSerialized(publicKeyOrOpenGroupID)
val address = fromSerialized(publicKeyOrOpenGroupID)
return getThreadId(address)
}
@ -595,7 +609,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
val recipientDatabase = DatabaseComponent.get(context).recipientDatabase()
val threadDatabase = DatabaseComponent.get(context).threadDatabase()
for (contact in contacts) {
val address = Address.fromSerialized(contact.publicKey)
val address = fromSerialized(contact.publicKey)
val recipient = Recipient.from(context, address, true)
if (!contact.profilePicture.isNullOrEmpty()) {
recipientDatabase.setProfileAvatar(recipient, contact.profilePicture)
@ -725,11 +739,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
}
override fun setLastInboxMessageId(server: String, messageId: Long) {
// TODO("Not yet implemented")
}
override fun removeLastInboxMessageId(server: String) {
// TODO("Not yet implemented")
}
override fun getLastOutboxMessageId(server: String): Long? {
@ -737,11 +751,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
}
override fun setLastOutboxMessageId(server: String, messageId: Long) {
// TODO("Not yet implemented")
}
override fun removeLastOutboxMessageId(server: String) {
// TODO("Not yet implemented")
}
}

View File

@ -124,4 +124,11 @@ object OpenGroupManager {
val publicKey = url.queryParameter("public_key") ?: return
add(server.toString().removeSuffix("/"), room, publicKey, context)
}
fun updateOpenGroup(openGroup: OpenGroup, context: Context) {
val threadDB = DatabaseComponent.get(context).lokiThreadDatabase()
val openGroupID = "${openGroup.server}.${openGroup.room}"
val threadID = GroupManager.getOpenGroupThreadID(openGroupID, context)
threadDB.setOpenGroupChat(openGroup, threadID)
}
}

View File

@ -55,10 +55,13 @@ interface StorageProtocol {
// Open Groups
fun getAllOpenGroups(): Map<Long, OpenGroup>
fun updateOpenGroup(openGroup: OpenGroup)
fun getOpenGroup(threadId: Long): OpenGroup?
fun addOpenGroup(urlAsString: String)
fun setOpenGroupServerMessageID(messageID: Long, serverID: Long, threadID: Long, isSms: Boolean)
fun getOpenGroup(room: String, server: String): OpenGroup?
fun updateOpenGroupCapabilities(server: String, capabilities: List<String>)
fun getOpenGroupServer(server: String): List<String>
// Open Group Public Keys
fun getOpenGroupPublicKey(server: String): String?

View File

@ -87,14 +87,22 @@ object FileServerApi {
fun upload(file: ByteArray): Promise<Long, Exception> {
val base64EncodedFile = Base64.encodeBytes(file)
val parameters = mapOf( "file" to base64EncodedFile )
val request = Request(verb = HTTP.Verb.POST, endpoint = "files", parameters = parameters)
val request = Request(
verb = HTTP.Verb.POST,
endpoint = "file",
parameters = parameters,
headers = mapOf(
"Content-Disposition" to "attachment",
"Content-Type" to "application/octet-stream"
)
)
return send(request).map { json ->
json["result"] as? Long ?: throw OpenGroupApi.Error.ParsingFailed
}
}
fun download(file: Long): Promise<ByteArray, Exception> {
val request = Request(verb = HTTP.Verb.GET, endpoint = "files/$file")
val request = Request(verb = HTTP.Verb.GET, endpoint = "file/$file")
return send(request).map { json ->
val base64EncodedFile = json["result"] as? String ?: throw Error.ParsingFailed
Base64.decode(base64EncodedFile) ?: throw Error.ParsingFailed

View File

@ -100,7 +100,7 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
val id = upload(data).get()
val digest = drb.transmittedDigest
// Return
return Pair(key, UploadResult(id, "${server}/files/$id", digest))
return Pair(key, UploadResult(id, "${server}/file/$id", digest))
}
private fun handleSuccess(attachment: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult) {

View File

@ -16,8 +16,7 @@ sealed class Endpoint(val value: String) {
// Messages
data class RoomMessage(val roomToken: String) :
Endpoint("room/$roomToken/message")
data class RoomMessage(val roomToken: String) : Endpoint("room/$roomToken/message")
data class RoomMessageIndividual(val roomToken: String, val messageId: Long) :
Endpoint("room/$roomToken/message/$messageId")
@ -42,8 +41,7 @@ sealed class Endpoint(val value: String) {
data class RoomUnpinMessage(val roomToken: String, val messageId: Long) :
Endpoint("room/$roomToken/unpin/$messageId")
data class RoomUnpinAll(val roomToken: String) :
Endpoint("room/$roomToken/unpin/all")
data class RoomUnpinAll(val roomToken: String) : Endpoint("room/$roomToken/unpin/all")
// Files

View File

@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.type.TypeFactory
import com.goterl.lazysodium.LazySodiumAndroid
import com.goterl.lazysodium.SodiumAndroid
import com.goterl.lazysodium.interfaces.GenericHash
import com.goterl.lazysodium.interfaces.Sign
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.flow.MutableSharedFlow
import nl.komponents.kovenant.Promise
@ -166,8 +167,8 @@ object OpenGroupApi {
val whisper: Boolean = false,
val whisper_mods: String = "",
val whisper_to: String = "",
val data: String = "",
val signature: String = ""
val data: String? = null,
val signature: String? = null
)
data class MessageDeletion(
@ -194,8 +195,7 @@ object OpenGroupApi {
* Always `true` under normal circumstances. You might want to disable
* this when running over Lokinet.
*/
val useOnionRouting: Boolean = true,
val isBlinded: Boolean = true
val useOnionRouting: Boolean = true
)
private fun createBody(parameters: Any?): RequestBody? {
@ -223,6 +223,7 @@ object OpenGroupApi {
}
}
fun execute(): Promise<OnionResponse, Exception> {
val serverCapabilities = MessagingModuleConfiguration.shared.storage.getOpenGroupServer(request.server)
val publicKey =
MessagingModuleConfiguration.shared.storage.getOpenGroupPublicKey(request.server)
?: return Promise.ofFail(Error.NoPublicKey)
@ -233,32 +234,33 @@ object OpenGroupApi {
val nonce = sodium.nonce(16)
val timestamp = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())
var pubKey = ""
var signature = ByteArray(0)
if (request.isBlinded) {
var signature = ByteArray(Sign.BYTES)
var bodyHash = ByteArray(0)
if (request.parameters != null) {
val parameterBytes = JsonUtil.toJson(request.parameters).toByteArray()
val parameterHash = ByteArray(GenericHash.BYTES_MAX)
if (sodium.cryptoGenericHash(
parameterHash,
parameterHash.size,
parameterBytes,
parameterBytes.size.toLong()
)) {
bodyHash = parameterHash
}
}
val messageBytes = Hex.fromStringCondensed(publicKey)
.plus(nonce)
.plus("$timestamp".toByteArray(Charsets.US_ASCII))
.plus(request.verb.rawValue.toByteArray())
.plus(urlRequest.encodedPath().toByteArray())
.plus(bodyHash)
if (serverCapabilities.contains("blind")) {
SodiumUtilities.blindedKeyPair(publicKey, ed25519KeyPair)?.let { keyPair ->
pubKey = SodiumUtilities.SessionId(
SodiumUtilities.IdPrefix.BLINDED,
keyPair.publicKey.asBytes
).hexString
var bodyHash = ByteArray(0)
if (request.parameters != null) {
val parameterBytes = JsonUtil.toJson(request.parameters).toByteArray()
val parameterHash = ByteArray(GenericHash.BYTES_MAX)
if (sodium.cryptoGenericHash(
parameterHash,
parameterHash.size,
parameterBytes,
parameterBytes.size.toLong()
)) {
bodyHash = parameterHash
}
}
val messageBytes = Hex.fromStringCondensed(publicKey)
.plus(nonce)
.plus("$timestamp".toByteArray(Charsets.US_ASCII))
.plus(request.verb.rawValue.toByteArray())
.plus(urlRequest.encodedPath().toByteArray())
.plus(bodyHash)
signature = SodiumUtilities.sogsSignature(
messageBytes,
ed25519KeyPair.secretKey.asBytes,
@ -271,7 +273,7 @@ object OpenGroupApi {
SodiumUtilities.IdPrefix.UN_BLINDED,
ed25519KeyPair.publicKey.asBytes
).hexString
signature = ByteArray(0)
sodium.cryptoSignDetached(signature, messageBytes, messageBytes.size.toLong(), ed25519KeyPair.secretKey.asBytes)
}
headers["X-SOGS-Nonce"] = encodeBytes(nonce)
headers["X-SOGS-Timestamp"] = "$timestamp"
@ -330,7 +332,12 @@ object OpenGroupApi {
}
fun download(fileId: Long, room: String, server: String): Promise<ByteArray, Exception> {
val request = Request(verb = GET, room = room, server = server, endpoint = Endpoint.FileIndividual(fileId))
val request = Request(
verb = GET,
room = room,
server = server,
endpoint = Endpoint.RoomFileIndividual(room, fileId)
)
return send(request).map { it.body ?: throw Error.ParsingFailed }
}
// endregion
@ -415,7 +422,7 @@ object OpenGroupApi {
@JvmStatic
fun deleteMessage(serverID: Long, room: String, server: String): Promise<Unit, Exception> {
val request =
Request(verb = DELETE, room = room, server = server, endpoint = "messages/$serverID")
Request(verb = DELETE, room = room, server = server, endpoint = Endpoint.RoomMessageIndividual(room, serverID))
return send(request).map {
Log.d("Loki", "Message deletion successful.")
}
@ -568,49 +575,51 @@ object OpenGroupApi {
}
)
}
requests.add(
if (lastInboxMessageId == null) {
BatchRequestInfo(
request = BatchRequest(
method = "GET",
path = "/inbox"
),
endpoint = Endpoint.Inbox,
responseType = object : TypeReference<List<DirectMessage>>() {}
)
} else {
BatchRequestInfo(
request = BatchRequest(
method = "GET",
path = "/inbox/since/$lastInboxMessageId"
),
endpoint = Endpoint.InboxSince(lastInboxMessageId),
responseType = object : TypeReference<List<DirectMessage>>() {}
)
}
)
requests.add(
if (lastOutboxMessageId == null) {
BatchRequestInfo(
request = BatchRequest(
method = "GET",
path = "/outbox"
),
endpoint = Endpoint.Outbox,
responseType = object : TypeReference<List<DirectMessage>>() {}
)
} else {
BatchRequestInfo(
request = BatchRequest(
method = "GET",
path = "/outbox/since/$lastOutboxMessageId"
),
endpoint = Endpoint.OutboxSince(lastOutboxMessageId),
responseType = object : TypeReference<List<DirectMessage>>() {}
)
}
)
val serverCapabilities = storage.getOpenGroupServer(server)
if (serverCapabilities.contains("blind")) {
requests.add(
if (lastInboxMessageId == null) {
BatchRequestInfo(
request = BatchRequest(
method = "GET",
path = "/inbox"
),
endpoint = Endpoint.Inbox,
responseType = object : TypeReference<List<DirectMessage>>() {}
)
} else {
BatchRequestInfo(
request = BatchRequest(
method = "GET",
path = "/inbox/since/$lastInboxMessageId"
),
endpoint = Endpoint.InboxSince(lastInboxMessageId),
responseType = object : TypeReference<List<DirectMessage>>() {}
)
}
)
requests.add(
if (lastOutboxMessageId == null) {
BatchRequestInfo(
request = BatchRequest(
method = "GET",
path = "/outbox"
),
endpoint = Endpoint.Outbox,
responseType = object : TypeReference<List<DirectMessage>>() {}
)
} else {
BatchRequestInfo(
request = BatchRequest(
method = "GET",
path = "/outbox/since/$lastOutboxMessageId"
),
endpoint = Endpoint.OutboxSince(lastOutboxMessageId),
responseType = object : TypeReference<List<DirectMessage>>() {}
)
}
)
}
return parallelBatch(server, requests)
}
@ -666,7 +675,7 @@ object OpenGroupApi {
fun getDefaultRoomsIfNeeded(): Promise<List<DefaultGroup>, Exception> {
val storage = MessagingModuleConfiguration.shared.storage
storage.setOpenGroupPublicKey(defaultServer, defaultServerPublicKey)
return getAllRooms(defaultServer).map { groups ->
return getAllRooms().map { groups ->
val earlyGroups = groups.map { group ->
DefaultGroup(group.token, group.name, null)
}
@ -705,11 +714,11 @@ object OpenGroupApi {
}
}
private fun getAllRooms(server: String): Promise<List<RoomInfo>, Exception> {
private fun getAllRooms(): Promise<List<RoomInfo>, Exception> {
val request = Request(
verb = GET,
room = null,
server = server,
server = defaultServer,
endpoint = Endpoint.Rooms
)
return send(request).map { response ->

View File

@ -58,12 +58,17 @@ class OpenGroupPoller(private val server: String, private val executorService: S
is Endpoint.RoomPollInfo -> {
handleRoomPollInfo(server, response.endpoint.roomToken, response.body as OpenGroupApi.RoomPollInfo)
}
is Endpoint.RoomMessagesRecent -> {
is Endpoint.RoomMessagesRecent -> {
handleMessages(server, response.endpoint.roomToken, response.body as List<OpenGroupApi.Message>)
}
is Endpoint.Inbox, Endpoint.Outbox -> {
val fromOutbox = response.endpoint.value.startsWith("outbox", ignoreCase = true)
handleDirectMessages(server, fromOutbox, response.body as List<OpenGroupApi.DirectMessage>)
is Endpoint.RoomMessagesSince -> {
handleMessages(server, response.endpoint.roomToken, response.body as List<OpenGroupApi.Message>)
}
is Endpoint.Inbox, is Endpoint.InboxSince -> {
handleDirectMessages(server, false, response.body as List<OpenGroupApi.DirectMessage>)
}
is Endpoint.Outbox, is Endpoint.OutboxSince -> {
handleDirectMessages(server, true, response.body as List<OpenGroupApi.DirectMessage>)
}
}
if (secondToLastJob == null && !isCaughtUp) {
@ -77,7 +82,7 @@ class OpenGroupPoller(private val server: String, private val executorService: S
private fun handleCapabilities(server: String, capabilities: OpenGroupApi.Capabilities) {
val storage = MessagingModuleConfiguration.shared.storage
storage.setOpenGroupSever(server, capabilities.capabilities)
storage.updateOpenGroupCapabilities(server, capabilities.capabilities)
}
private fun handleRoomPollInfo(
@ -102,7 +107,7 @@ class OpenGroupPoller(private val server: String, private val executorService: S
capabilities = listOf()
)
// - Open Group changes
storage.setOpenGroup(openGroup)
storage.updateOpenGroup(openGroup)
// - User Count
storage.setUserCount(roomToken, server, pollInfo.active_users)
@ -123,16 +128,19 @@ class OpenGroupPoller(private val server: String, private val executorService: S
messages: List<OpenGroupApi.Message>
) {
val openGroupId = "$server.$roomToken"
val msgs = messages.map {
val (deletions, additions) = messages.sortedBy { it.seqno }.partition { it.data.isNullOrBlank() }
handleNewMessages(roomToken, openGroupId, additions.map {
OpenGroupMessageV2(
serverID = it.id,
sender = it.session_id,
sentTimestamp = it.posted,
base64EncodedData = it.data,
base64EncodedData = it.data!!,
base64EncodedSignature = it.signature
)
}
handleNewMessages(roomToken, openGroupId, msgs)
})
handleDeletedMessages(roomToken, openGroupId, deletions.map {
OpenGroupApi.MessageDeletion(it.id, it.seqno)
})
}
private fun handleDirectMessages(

View File

@ -36,7 +36,7 @@ object ProfilePictureUtilities {
deferred.reject(e)
}
TextSecurePreferences.setLastProfilePictureUpload(context, Date().time)
val url = "${FileServerApi.server}/files/$id"
val url = "${FileServerApi.server}/file/$id"
TextSecurePreferences.setProfilePictureURL(context, url)
deferred.resolve(Unit)
}