From 82e7cf4447d72c24af04c64c05eed35338455f35 Mon Sep 17 00:00:00 2001 From: hrxi Date: Sun, 23 Jun 2019 14:51:33 +0200 Subject: Add file receiving via Jingle This currently follows the same rules as HTTP file download for accepting files. --- .../src/module/xep/0234_jingle_file_transfer.vala | 130 +++++++++++++-------- 1 file changed, 80 insertions(+), 50 deletions(-) (limited to 'xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala') diff --git a/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala b/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala index cd249017..57222bae 100644 --- a/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala +++ b/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala @@ -6,19 +6,27 @@ namespace Xmpp.Xep.JingleFileTransfer { private const string NS_URI = "urn:xmpp:jingle:apps:file-transfer:5"; -public errordomain Error { - FILE_INACCESSIBLE, -} - -public class Module : XmppStreamModule { +public class Module : Jingle.ContentType, XmppStreamModule { public static Xmpp.ModuleIdentity IDENTITY = new Xmpp.ModuleIdentity(NS_URI, "0234_jingle_file_transfer"); public override void attach(XmppStream stream) { - stream.add_flag(new Flag()); stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI); + stream.get_module(Jingle.Module.IDENTITY).register_content_type(this); } public override void detach(XmppStream stream) { } + public string content_type_ns_uri() { + return NS_URI; + } + public Jingle.TransportType content_type_transport_type() { + return Jingle.TransportType.STREAMING; + } + public Jingle.ContentParameters parse_content_parameters(StanzaNode description) throws Jingle.IqError { + return Parameters.parse(this, description); + } + + public signal void file_incoming(XmppStream stream, FileTransfer file_transfer); + public bool is_available(XmppStream stream, Jid full_jid) { bool? has_feature = stream.get_flag(ServiceDiscovery.Flag.IDENTITY).has_entity_feature(full_jid, NS_URI); if (has_feature == null || !(!)has_feature) { @@ -27,25 +35,7 @@ public class Module : XmppStreamModule { return stream.get_module(Jingle.Module.IDENTITY).is_available(stream, Jingle.TransportType.STREAMING, full_jid); } - public void offer_file(XmppStream stream, Jid receiver_full_jid, string path) throws Error { - File file = File.new_for_path(path); - FileInputStream input_stream; - int64 size; - try { - input_stream = file.read(); - } catch (GLib.Error e) { - throw new Error.FILE_INACCESSIBLE(@"could not open the file \"$path\" for reading: $(e.message)"); - } - try { - size = input_stream.query_info(FileAttribute.STANDARD_SIZE).get_size(); - } catch (GLib.Error e) { - throw new Error.FILE_INACCESSIBLE(@"could not read the size: $(e.message)"); - } - - offer_file_stream(stream, receiver_full_jid, input_stream, file.get_basename(), size); - } - - public void offer_file_stream(XmppStream stream, Jid receiver_full_jid, InputStream input_stream, string basename, int64 size) { + public async void offer_file_stream(XmppStream stream, Jid receiver_full_jid, InputStream input_stream, string basename, int64 size) throws IOError { StanzaNode description = new StanzaNode.build("description", NS_URI) .add_self_xmlns() .put_node(new StanzaNode.build("file", NS_URI) @@ -53,48 +43,88 @@ public class Module : XmppStreamModule { .put_node(new StanzaNode.build("size", NS_URI).put_node(new StanzaNode.text(size.to_string())))); // TODO(hrxi): Add the mandatory hash field - Jingle.Session? session = stream.get_module(Jingle.Module.IDENTITY) + Jingle.Session session = stream.get_module(Jingle.Module.IDENTITY) .create_session(stream, Jingle.TransportType.STREAMING, receiver_full_jid, Jingle.Senders.INITIATOR, "a-file-offer", description); // TODO(hrxi): Why "a-file-offer"? - FileTransfer transfer = new FileTransfer(input_stream); - session.on_ready.connect(transfer.send_data); - stream.get_flag(Flag.IDENTITY).add_file_transfer(transfer); + SourceFunc callback = offer_file_stream.callback; + session.accepted.connect((stream) => { + session.conn.input_stream.close(); + Idle.add((owned) callback); + }); + yield; + + // TODO(hrxi): catch errors + yield session.conn.output_stream.splice_async(input_stream, OutputStreamSpliceFlags.CLOSE_SOURCE|OutputStreamSpliceFlags.CLOSE_TARGET); } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } -public class FileTransfer : Object { - InputStream input_stream; - public FileTransfer(InputStream input_stream) { - this.input_stream = input_stream; +public class Parameters : Jingle.ContentParameters, Object { + Module parent; + string? media_type; + public string? name { get; private set; } + public int64 size { get; private set; } + public StanzaNode original_description { get; private set; } + public Parameters(Module parent, StanzaNode original_description, string? media_type, string? name, int64? size) { + this.parent = parent; + this.original_description = original_description; + this.media_type = media_type; + this.name = name; + this.size = size; } - public void send_data(Jingle.Session session, XmppStream stream) { - uint8 buffer[4096]; - ssize_t read; - try { - if((read = input_stream.read(buffer)) != 0) { - session.send(stream, buffer[0:read]); - } else { - session.close_connection(stream); + public static Parameters parse(Module parent, StanzaNode description) throws Jingle.IqError { + Gee.List files = description.get_subnodes("file", NS_URI); + if (files.size != 1) { + throw new Jingle.IqError.BAD_REQUEST("there needs to be exactly one file node"); + } + StanzaNode file = files[0]; + StanzaNode? media_type_node = file.get_subnode("media-type", NS_URI); + StanzaNode? name_node = file.get_subnode("name", NS_URI); + StanzaNode? size_node = file.get_subnode("size", NS_URI); + string? media_type = media_type_node != null ? media_type_node.get_string_content() : null; + string? name = name_node != null ? name_node.get_string_content() : null; + string? size_raw = size_node != null ? size_node.get_string_content() : null; + // TODO(hrxi): For some reason, the ?:-expression does not work due to a type error. + //int64? size = size_raw != null ? int64.parse(size_raw) : null; // TODO(hrxi): this has no error handling + int64 size = -1; + if (size_raw != null) { + size = int64.parse(size_raw); + if (size < 0) { + throw new Jingle.IqError.BAD_REQUEST("negative file size is invalid"); } - } catch (GLib.IOError e) { - session.set_application_error(stream); } - // TODO(hrxi): remove file transfer + + return new Parameters(parent, description, media_type, name, size); + } + void on_session_initiate(XmppStream stream, Jingle.Session session) { + parent.file_incoming(stream, new FileTransfer(session, this)); } } -public class Flag : XmppStreamFlag { - public static FlagIdentity IDENTITY = new FlagIdentity(NS_URI, "jingle_file_transfer"); +public class FileTransfer : Object { + Jingle.Session session; + Parameters parameters; + + public Jid peer { get { return session.peer_full_jid; } } + public string? file_name { get { return parameters.name; } } + public int64 size { get { return parameters.size; } } - private Gee.List transfers = new ArrayList(); + public InputStream? stream { get { return session.conn != null ? session.conn.input_stream : null; } } - public void add_file_transfer(FileTransfer transfer) { transfers.add(transfer); } + public FileTransfer(Jingle.Session session, Parameters parameters) { + this.session = session; + this.parameters = parameters; + } - public override string get_ns() { return NS_URI; } - public override string get_id() { return IDENTITY.id; } + public void accept(XmppStream stream) { + session.accept(stream, parameters.original_description); + session.conn.output_stream.close(); + } + public void reject(XmppStream stream) { + session.reject(stream); + } } } -- cgit v1.2.3-54-g00ecf