libpurple/protocols/jabber/oob.c

Tue, 23 Jan 2024 00:03:38 -0600

author
Gary Kramlich <grim@reaperworld.com>
date
Tue, 23 Jan 2024 00:03:38 -0600
changeset 42570
a980db2607fd
parent 42182
3fc2d2b7b7a8
permissions
-rw-r--r--

Make sure all of the final types in the protocols are defined as such

Testing Done:
Consulted with the turtles and verified the demo protocol plugin could send messages.

Reviewed at https://reviews.imfreedom.org/r/2920/

/*
 * purple - Jabber Protocol Plugin
 *
 * Purple is the legal property of its developers, whose names are too numerous
 * to list here.  Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
 *
 */
#include <purple.h>

#include "jabber.h"
#include "iq.h"
#include "oob.h"

/* Number of bytes to read asynchronously per chunk. */
#define JABBER_OOB_READ_SIZE (1024*1024)

struct _JabberOOBXfer {
	JabberStream *js;
	gchar *iq_id;
	gchar *url;
	GCancellable *cancellable;
	SoupMessage *msg;
};

G_DEFINE_DYNAMIC_TYPE_EXTENDED(JabberOOBXfer, jabber_oob_xfer,
                               PURPLE_TYPE_XFER, G_TYPE_FLAG_FINAL, {})

static void jabber_oob_xfer_xfer_init(PurpleXfer *xfer)
{
	purple_xfer_start(xfer, -1, NULL, 0);
}

static void jabber_oob_xfer_end(PurpleXfer *xfer)
{
	JabberOOBXfer *jox = JABBER_OOB_XFER(xfer);
	JabberIq *iq;

	iq = jabber_iq_new(jox->js, JABBER_IQ_RESULT);
	purple_xmlnode_set_attrib(iq->node, "to", purple_xfer_get_remote_user(xfer));
	jabber_iq_set_id(iq, jox->iq_id);

	jabber_iq_send(iq);
}

static void
jabber_oob_xfer_got_content_length(SoupMessage *msg, gpointer user_data)
{
	PurpleXfer *xfer = user_data;
	SoupMessageHeaders *headers;
	goffset total;

	headers = soup_message_get_response_headers(msg);
	total = soup_message_headers_get_content_length(headers);

	purple_xfer_set_size(xfer, total);
}

static void
jabber_oob_xfer_writer(GObject *source, GAsyncResult *result, gpointer data) {
	GInputStream *input = G_INPUT_STREAM(source);
	PurpleXfer *xfer = data;
	JabberOOBXfer *jox = JABBER_OOB_XFER(xfer);
	GBytes *bytes = NULL;
	GError *error = NULL;
	gpointer buffer = NULL;
	gsize size = 0;

	bytes = g_input_stream_read_bytes_finish(input, result, &error);
	if(bytes == NULL || error != NULL) {
		purple_debug_error("jabber", "Error reading OOB data: %s",
		                   error ? error->message : "unknown error");
		purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_REMOTE);
		purple_xfer_end(xfer);
		g_clear_pointer(&bytes, g_bytes_unref);
		g_clear_object(&input);
		g_clear_error(&error);
		jox->msg = NULL;
		return;
	}

	buffer = g_bytes_unref_to_data(bytes, &size);

	/* We've reached the end of the file. */
	if(size == 0) {
		purple_xfer_set_completed(xfer, TRUE);
		purple_xfer_end(xfer);
		g_clear_object(&input);
		jox->msg = NULL;
		return;
	}

	if (!purple_xfer_write_file(xfer, buffer, size)) {
		purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_LOCAL);
		purple_xfer_end(xfer);
		g_clear_object(&input);
		jox->msg = NULL;
		return;
	}

	/* Asynchronously read the next chunk of data. */
	g_input_stream_read_bytes_async(input, JABBER_OOB_READ_SIZE,
	                                G_PRIORITY_DEFAULT, jox->cancellable,
	                                jabber_oob_xfer_writer, xfer);
}

static void
jabber_oob_xfer_send_cb(GObject *source, GAsyncResult *result, gpointer data) {
	PurpleXfer *xfer = data;
	JabberOOBXfer *jox = JABBER_OOB_XFER(xfer);
	GInputStream *input = NULL;
	GError *error = NULL;

	if(!SOUP_STATUS_IS_SUCCESSFUL(soup_message_get_status(jox->msg))) {
		purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_REMOTE);
		purple_xfer_end(xfer);
		jox->msg = NULL;
		return;
	}

	input = soup_session_send_finish(SOUP_SESSION(source), result, &error);
	if(input == NULL || error != NULL) {
		purple_debug_error("jabber", "Error sending OOB request: %s",
		                   error ? error->message : "unknown error");
		purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_REMOTE);
		purple_xfer_end(xfer);
		g_clear_object(&input);
		g_clear_error(&error);
		jox->msg = NULL;
		return;
	}

	/* Asynchronously read an initial chunk of data. */
	g_input_stream_read_bytes_async(input, JABBER_OOB_READ_SIZE,
	                                G_PRIORITY_DEFAULT, jox->cancellable,
	                                jabber_oob_xfer_writer, xfer);
}

static void jabber_oob_xfer_start(PurpleXfer *xfer)
{
	SoupMessage *msg;
	JabberOOBXfer *jox = JABBER_OOB_XFER(xfer);

	msg = soup_message_new("GET", jox->url);
	soup_message_add_header_handler(
	        msg, "got-headers", "Content-Length",
	        G_CALLBACK(jabber_oob_xfer_got_content_length), xfer);
	soup_session_send_async(jox->js->http_conns, msg, G_PRIORITY_DEFAULT,
	                        jox->cancellable, jabber_oob_xfer_send_cb, xfer);
}

static void jabber_oob_xfer_recv_error(PurpleXfer *xfer, const char *code) {
	JabberOOBXfer *jox = JABBER_OOB_XFER(xfer);
	JabberIq *iq;
	PurpleXmlNode *y, *z;

	iq = jabber_iq_new(jox->js, JABBER_IQ_ERROR);
	purple_xmlnode_set_attrib(iq->node, "to", purple_xfer_get_remote_user(xfer));
	jabber_iq_set_id(iq, jox->iq_id);
	y = purple_xmlnode_new_child(iq->node, "error");
	purple_xmlnode_set_attrib(y, "code", code);
	if(purple_strequal(code, "406")) {
		z = purple_xmlnode_new_child(y, "not-acceptable");
		purple_xmlnode_set_attrib(y, "type", "modify");
		purple_xmlnode_set_namespace(z, NS_XMPP_STANZAS);
	} else if(purple_strequal(code, "404")) {
		z = purple_xmlnode_new_child(y, "not-found");
		purple_xmlnode_set_attrib(y, "type", "cancel");
		purple_xmlnode_set_namespace(z, NS_XMPP_STANZAS);
	}
	jabber_iq_send(iq);
}

static void jabber_oob_xfer_recv_denied(PurpleXfer *xfer) {
	jabber_oob_xfer_recv_error(xfer, "406");
}

static void jabber_oob_xfer_recv_cancelled(PurpleXfer *xfer) {
	JabberOOBXfer *jox = JABBER_OOB_XFER(xfer);

	g_cancellable_cancel(jox->cancellable);

	jabber_oob_xfer_recv_error(xfer, "404");
}

void jabber_oob_parse(JabberStream *js, const char *from, JabberIqType type,
	const char *id, PurpleXmlNode *querynode) {
	JabberOOBXfer *jox;
	const gchar *filename, *slash;
	gchar *url;
	PurpleXmlNode *urlnode;

	if(type != JABBER_IQ_SET)
		return;

	if(!from)
		return;

	if(!(urlnode = purple_xmlnode_get_child(querynode, "url")))
		return;

	url = purple_xmlnode_get_data(urlnode);
	if (!url)
		return;

	jox = g_object_new(
		JABBER_TYPE_OOB_XFER,
		"account", purple_connection_get_account(js->gc),
		"type", PURPLE_XFER_TYPE_RECEIVE,
		"remote-user", from,
		NULL
	);

	jox->iq_id = g_strdup(id);
	jox->js = js;
	jox->url = url;

	slash = strrchr(url, '/');
	if (slash == NULL) {
		filename = url;
	} else {
		filename = slash + 1;
	}

	purple_xfer_set_filename(PURPLE_XFER(jox), filename);

	js->oob_file_transfers = g_list_append(js->oob_file_transfers, jox);

	purple_xfer_request(PURPLE_XFER(jox));
}

static void
jabber_oob_xfer_init(JabberOOBXfer *xfer) {
	xfer->cancellable = g_cancellable_new();
}

static void
jabber_oob_xfer_finalize(GObject *obj) {
	JabberOOBXfer *jox = JABBER_OOB_XFER(obj);

	jox->js->oob_file_transfers = g_list_remove(jox->js->oob_file_transfers,
			jox);

	g_free(jox->iq_id);
	g_free(jox->url);
	if(jox->cancellable != NULL) {
		g_cancellable_cancel(jox->cancellable);
	}
	g_clear_object(&jox->cancellable);

	G_OBJECT_CLASS(jabber_oob_xfer_parent_class)->finalize(obj);
}

static void
jabber_oob_xfer_class_finalize(G_GNUC_UNUSED JabberOOBXferClass *klass) {
}

static void
jabber_oob_xfer_class_init(JabberOOBXferClass *klass) {
	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
	PurpleXferClass *xfer_class = PURPLE_XFER_CLASS(klass);

	obj_class->finalize = jabber_oob_xfer_finalize;

	xfer_class->init = jabber_oob_xfer_xfer_init;
	xfer_class->end = jabber_oob_xfer_end;
	xfer_class->request_denied = jabber_oob_xfer_recv_denied;
	xfer_class->cancel_recv = jabber_oob_xfer_recv_cancelled;
	xfer_class->start = jabber_oob_xfer_start;
}

void
jabber_oob_xfer_register(GTypeModule *module) {
	jabber_oob_xfer_register_type(module);
}

mercurial