aboutsummaryrefslogtreecommitdiff
path: root/plugins
diff options
context:
space:
mode:
authorfiaxh <git@lightrise.org>2024-11-02 22:24:59 +0100
committerfiaxh <git@lightrise.org>2024-11-15 14:40:08 -0600
commit79f792e090330a05753f9edb27332a946eb0840d (patch)
tree5a6f1ad3ac0af0beea44ca9e83e7a9b052263025 /plugins
parentaaf4542e6208460c305db4be36b15dc832ddc95a (diff)
downloaddino-79f792e090330a05753f9edb27332a946eb0840d.tar.gz
dino-79f792e090330a05753f9edb27332a946eb0840d.zip
Fix and improve stateless file-sharing
Diffstat (limited to 'plugins')
-rw-r--r--plugins/http-files/src/file_provider.vala104
-rw-r--r--plugins/http-files/src/file_sender.vala87
2 files changed, 75 insertions, 116 deletions
diff --git a/plugins/http-files/src/file_provider.vala b/plugins/http-files/src/file_provider.vala
index bbee8e50..420e041d 100644
--- a/plugins/http-files/src/file_provider.vala
+++ b/plugins/http-files/src/file_provider.vala
@@ -38,9 +38,10 @@ public class FileProvider : Dino.FileProvider, Object {
}
public override async bool run(Entities.Message message, Xmpp.MessageStanza stanza, Conversation conversation) {
- if (Xep.StatelessFileSharing.MessageFlag.get_flag(stanza) != null) {
- return true;
+ if (Xep.StatelessFileSharing.get_file_shares(stanza) != null || Xep.StatelessFileSharing.get_source_attachments(stanza) != null) {
+ return false;
}
+
string? oob_url = Xmpp.Xep.OutOfBandData.get_url_from_message(stanza);
bool normal_file = oob_url != null && oob_url == message.body && FileProvider.http_url_regex.match(message.body);
bool omemo_file = FileProvider.omemo_url_regex.match(message.body);
@@ -52,57 +53,6 @@ public class FileProvider : Dino.FileProvider, Object {
}
}
- private class LimitInputStream : InputStream, PollableInputStream {
- InputStream inner;
- int64 remaining_size;
-
- public LimitInputStream(InputStream inner, int64 max_size) {
- this.inner = inner;
- this.remaining_size = max_size;
- }
-
- public bool can_poll() {
- return inner is PollableInputStream && ((PollableInputStream)inner).can_poll();
- }
-
- public PollableSource create_source(Cancellable? cancellable = null) {
- if (!can_poll()) throw new IOError.NOT_SUPPORTED("Stream is not pollable");
- return ((PollableInputStream)inner).create_source(cancellable);
- }
-
- public bool is_readable() {
- if (!can_poll()) throw new IOError.NOT_SUPPORTED("Stream is not pollable");
- return remaining_size <= 0 || ((PollableInputStream)inner).is_readable();
- }
-
- private ssize_t check_limit(ssize_t read) throws IOError {
- this.remaining_size -= read;
- if (remaining_size < 0) throw new IOError.FAILED("Stream length exceeded limit");
- return read;
- }
-
- public override ssize_t read(uint8[] buffer, Cancellable? cancellable = null) throws IOError {
- return check_limit(inner.read(buffer, cancellable));
- }
-
- public override async ssize_t read_async(uint8[]? buffer, int io_priority = GLib.Priority.DEFAULT, Cancellable? cancellable = null) throws IOError {
- return check_limit(yield inner.read_async(buffer, io_priority, cancellable));
- }
-
- public ssize_t read_nonblocking_fn(uint8[] buffer) throws Error {
- if (!is_readable()) throw new IOError.WOULD_BLOCK("Stream is not readable");
- return read(buffer);
- }
-
- public override bool close(Cancellable? cancellable = null) throws IOError {
- return inner.close(cancellable);
- }
-
- public override async bool close_async(int io_priority = GLib.Priority.DEFAULT, Cancellable? cancellable = null) throws IOError {
- return yield inner.close_async(io_priority, cancellable);
- }
- }
-
private void on_file_message(Entities.Message message, Conversation conversation) {
var additional_info = message.id.to_string();
@@ -155,7 +105,7 @@ public class FileProvider : Dino.FileProvider, Object {
return Encryption.NONE;
}
- public async InputStream download(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) throws FileReceiveError {
+ public async InputStream download(FileTransfer file_transfer, FileReceiveData receive_data, FileMeta file_meta) throws IOError {
HttpFileReceiveData? http_receive_data = receive_data as HttpFileReceiveData;
if (http_receive_data == null) assert(false);
@@ -166,25 +116,20 @@ public class FileProvider : Dino.FileProvider, Object {
get_message.accept_certificate.connect((peer_cert, errors) => { return ConnectionManager.on_invalid_certificate(transfer_host, peer_cert, errors); });
#endif
- try {
#if SOUP_3_0
- InputStream stream = yield session.send_async(get_message, GLib.Priority.LOW, file_transfer.cancellable);
+ InputStream stream = yield session.send_async(get_message, GLib.Priority.LOW, file_transfer.cancellable);
#else
- InputStream stream = yield session.send_async(get_message, file_transfer.cancellable);
+ InputStream stream = yield session.send_async(get_message, file_transfer.cancellable);
#endif
- if (file_meta.size != -1) {
- return new LimitInputStream(stream, file_meta.size);
- } else {
- return stream;
- }
- } catch (Error e) {
- throw new FileReceiveError.DOWNLOAD_FAILED("Downloading file error: %s".printf(e.message));
+ if (file_meta.size != -1) {
+ return new LimitInputStream(stream, file_meta.size);
+ } else {
+ return stream;
}
}
public FileMeta get_file_meta(FileTransfer file_transfer) throws FileReceiveError {
- // TODO: replace '2' with constant?
- if (file_transfer.provider == 2) {
+ if (file_transfer.provider == FileManager.SFS_PROVIDER_ID) {
var file_meta = new HttpFileMeta();
file_meta.size = file_transfer.size;
file_meta.mime_type = file_transfer.mime_type;
@@ -210,26 +155,19 @@ public class FileProvider : Dino.FileProvider, Object {
return file_meta;
}
- public async FileReceiveData? get_file_receive_data(FileTransfer file_transfer) {
- // TODO: replace '2' with constant?
- if (file_transfer.provider == 2) {
- Xep.StatelessFileSharing.HttpSource http_source = null;
- for(int i = 0; i < file_transfer.sfs_sources.get_n_items(); i++) {
- Object source_object = file_transfer.sfs_sources.get_item(i);
- FileTransfer.SerializedSfsSource source = source_object as FileTransfer.SerializedSfsSource;
- if (source.type == Xep.StatelessFileSharing.HttpSource.SOURCE_TYPE) {
- http_source = yield Xep.StatelessFileSharing.HttpSource.deserialize(source.data);
- assert(source != null);
+ public FileReceiveData? get_file_receive_data(FileTransfer file_transfer) {
+ if (file_transfer.provider == FileManager.SFS_PROVIDER_ID) {
+ if (!file_transfer.sfs_sources.is_empty) {
+ var http_source = file_transfer.sfs_sources.get(0) as Xep.StatelessFileSharing.HttpSource;
+ if (http_source != null) {
+ var receive_data = new HttpFileReceiveData();
+ receive_data.url = http_source.url;
+ return receive_data;
}
}
- if (http_source == null) {
- printerr("Sfs file transfer has no http sources attached!");
- return null;
- }
- var receive_data = new HttpFileReceiveData();
- receive_data.url = http_source.url;
- return receive_data;
+ return null;
}
+
Conversation? conversation = stream_interactor.get_module(ConversationManager.IDENTITY).get_conversation(file_transfer.counterpart.bare_jid, file_transfer.account);
if (conversation == null) return null;
diff --git a/plugins/http-files/src/file_sender.vala b/plugins/http-files/src/file_sender.vala
index 91b50944..02336b43 100644
--- a/plugins/http-files/src/file_sender.vala
+++ b/plugins/http-files/src/file_sender.vala
@@ -17,7 +17,7 @@ public class HttpFileSender : FileSender, Object {
session.user_agent = @"Dino/$(Dino.get_short_version()) ";
stream_interactor.stream_negotiated.connect(on_stream_negotiated);
- stream_interactor.get_module(MessageProcessor.IDENTITY).build_message_stanza.connect(check_add_oob);
+ stream_interactor.get_module(MessageProcessor.IDENTITY).build_message_stanza.connect(check_add_sfs_element);
}
public async FileSendData? prepare_send_file(Conversation conversation, FileTransfer file_transfer, FileMeta file_meta) throws FileSendError {
@@ -43,39 +43,55 @@ public class HttpFileSender : FileSender, Object {
HttpFileSendData? send_data = file_send_data as HttpFileSendData;
if (send_data == null) return;
- yield upload(file_transfer, send_data, file_meta);
+ bool can_reference_element = !conversation.type_.is_muc_semantic() || stream_interactor.get_module(EntityInfo.IDENTITY).has_feature_cached(conversation.account, conversation.counterpart, Xep.UniqueStableStanzaIDs.NS_URI);
+
+ // Share unencrypted files via SFS (only if we'll be able to reference messages)
+ if (conversation.encryption == Encryption.NONE && can_reference_element) {
+ // Announce the file share
+ Entities.Message file_share_message = stream_interactor.get_module(MessageProcessor.IDENTITY).create_out_message(null, conversation);
+ file_transfer.info = file_share_message.id.to_string();
+ file_transfer.file_sharing_id = Xmpp.random_uuid();
+ stream_interactor.get_module(MessageProcessor.IDENTITY).send_xmpp_message(file_share_message, conversation);
+
+ // Upload file
+ yield upload(file_transfer, send_data, file_meta);
+
+ // Wait until we know the server id of the file share message (in MUCs; we get that from the reflected message)
+ if (conversation.type_.is_muc_semantic()) {
+ if (file_share_message.server_id == null) {
+ ulong server_id_notify_id = file_share_message.notify["server-id"].connect(() => {
+ Idle.add(send_file.callback);
+ });
+ yield;
+ file_share_message.disconnect(server_id_notify_id);
+ }
+ }
+
+ file_transfer.sfs_sources.add(new Xep.StatelessFileSharing.HttpSource() { url=send_data.url_down } );
+
+ // Send source attachment
+ MessageStanza stanza = new MessageStanza() { to = conversation.counterpart, type_ = conversation.type_ == GROUPCHAT ? MessageStanza.TYPE_GROUPCHAT : MessageStanza.TYPE_CHAT };
+ stanza.body = send_data.url_down;
+ Xep.OutOfBandData.add_url_to_message(stanza, send_data.url_down);
+ var sources = new ArrayList<Xep.StatelessFileSharing.Source>();
+ sources.add(new Xep.StatelessFileSharing.HttpSource() { url = send_data.url_down });
+ string attach_to_id = MessageStorage.get_reference_id(file_share_message);
+ Xep.StatelessFileSharing.set_sfs_attachment(stanza, attach_to_id, file_transfer.file_sharing_id, sources);
+
+ var stream = stream_interactor.get_stream(conversation.account);
+ if (stream == null) throw new FileSendError.UPLOAD_FAILED("No stream");
+
+ stream.get_module(MessageModule.IDENTITY).send_message.begin(stream, stanza);
+ }
+ // Share encrypted files without SFS
+ else {
+ yield upload(file_transfer, send_data, file_meta);
- if (send_data.encrypt_message && conversation.encryption != Encryption.NONE) {
Entities.Message message = stream_interactor.get_module(MessageProcessor.IDENTITY).create_out_message(send_data.url_down, conversation);
file_transfer.info = message.id.to_string();
- message.encryption = conversation.encryption;
+ message.encryption = send_data.encrypt_message ? conversation.encryption : Encryption.NONE;
stream_interactor.get_module(MessageProcessor.IDENTITY).send_xmpp_message(message, conversation);
- } else {
- // Use stateless file sharing for unencrypted file sharing
- Xep.StatelessFileSharing.HttpSource source = new Xep.StatelessFileSharing.HttpSource();
- source.url = send_data.url_down;
- file_transfer.sfs_sources.append(new FileTransfer.SerializedSfsSource.from_sfs_source(source) as Object);
- this.db.sfs_sources.insert()
- .value(db.sfs_sources.id, file_transfer.id)
- .value(db.sfs_sources.type, source.type())
- .value(db.sfs_sources.data, source.serialize())
- .perform();
- XmppStream stream = stream_interactor.get_stream(conversation.account);
- Xep.StatelessFileSharing.Module sfs_module = stream.get_module(Xep.StatelessFileSharing.Module.IDENTITY);
- string message_type;
- if (conversation.type_ == Conversation.Type.GROUPCHAT) {
- message_type = MessageStanza.TYPE_GROUPCHAT;
- } else {
- message_type = MessageStanza.TYPE_CHAT;
- }
- MessageStanza sfs_message = new MessageStanza() { to=conversation.counterpart, type_=message_type };
- // TODO: is this the correct way of adding out-of-band-data?
- sfs_message.body = source.url;
- Xep.OutOfBandData.add_url_to_message(sfs_message, source.url);
- // TODO: message hint correct?
- Xep.MessageProcessingHints.set_message_hint(sfs_message, Xep.MessageProcessingHints.HINT_STORE);
- sfs_module.send_stateless_file_transfer(stream, sfs_message, yield file_transfer.to_sfs_element());
}
}
@@ -159,10 +175,15 @@ public class HttpFileSender : FileSender, Object {
});
}
- private void check_add_oob(Entities.Message message, Xmpp.MessageStanza message_stanza, Conversation conversation) {
- if (message.encryption == Encryption.NONE && message.body.has_prefix("http") && message_is_file(db, message)) {
- Xep.OutOfBandData.add_url_to_message(message_stanza, message_stanza.body);
- }
+ private void check_add_sfs_element(Entities.Message message, Xmpp.MessageStanza message_stanza, Conversation conversation) {
+ if (message.encryption != Encryption.NONE) return;
+
+ FileTransfer? file_transfer = stream_interactor.get_module(FileTransferStorage.IDENTITY).get_file_by_message_id(message.id, conversation);
+ if (file_transfer == null) return;
+
+ Xep.StatelessFileSharing.set_sfs_element(message_stanza, file_transfer.file_sharing_id, file_transfer.file_metadata, file_transfer.sfs_sources);
+
+ Xep.MessageProcessingHints.set_message_hint(message_stanza, Xep.MessageProcessingHints.HINT_STORE);
}
public int get_id() { return 0; }