static void
_im_channel_closed_cb (IdleIMChannel *chan,
					  gpointer user_data)
{
	IdleIMManager *self = IDLE_IM_MANAGER (user_data);
	IdleIMManagerPrivate *priv = IDLE_IM_MANAGER_GET_PRIVATE (self);
	TpBaseChannel *base = TP_BASE_CHANNEL (chan);

	tp_channel_manager_emit_channel_closed_for_object (self,
													   TP_EXPORTABLE_CHANNEL (chan));

	if (priv->channels)
	{
		TpHandle handle = tp_base_channel_get_target_handle (base);

		if (tp_base_channel_is_destroyed (base))
		{
			IDLE_DEBUG ("removing channel with handle %u", handle);
			g_hash_table_remove (priv->channels, GUINT_TO_POINTER (handle));
		} else {
			IDLE_DEBUG ("reopening channel with handle %u due to pending messages",
				handle);
			tp_channel_manager_emit_new_channel (self,
				(TpExportableChannel *) chan, NULL);
		}
	}
}
void idle_server_connection_disconnect_full_async(IdleServerConnection *conn, guint reason, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) {
	IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn);
	GSimpleAsyncResult *result;

	g_assert(priv != NULL);

	if (priv->state != SERVER_CONNECTION_STATE_CONNECTED) {
		IDLE_DEBUG("the connection was not open");
		g_simple_async_report_error_in_idle(G_OBJECT(conn),
			callback, user_data,
			TP_ERROR, TP_ERROR_NOT_AVAILABLE,
			"the connection was not open");
		return;
	}

	if (priv->io_stream == NULL) {
		IDLE_DEBUG("We were exploding anyway");
		g_simple_async_report_error_in_idle(G_OBJECT(conn),
			callback, user_data,
			TP_ERROR, TP_ERROR_NOT_AVAILABLE,
			"We were exploding anyway");
		return;
	}

	priv->reason = reason;

	result = g_simple_async_result_new(G_OBJECT(conn), callback, user_data, idle_server_connection_disconnect_full_async);
	g_io_stream_close_async(priv->io_stream, G_PRIORITY_DEFAULT, cancellable, _close_ready, result);
	g_object_unref(priv->io_stream);
	priv->io_stream = NULL;
}
void idle_server_connection_connect_async(IdleServerConnection *conn, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) {
	IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn);
	GSimpleAsyncResult *result;

	if (priv->state != SERVER_CONNECTION_STATE_NOT_CONNECTED) {
		IDLE_DEBUG("already connecting or connected!");
		g_simple_async_report_error_in_idle(G_OBJECT(conn),
			callback, user_data,
			TP_ERROR, TP_ERROR_NOT_AVAILABLE,
			"already connecting or connected!");
		return;
	}

	if ((priv->host == NULL) || (priv->host[0] == '\0')) {
		IDLE_DEBUG("host not set!");
		g_simple_async_report_error_in_idle(G_OBJECT(conn),
			callback, user_data,
			TP_ERROR, TP_ERROR_NOT_AVAILABLE,
			"host not set!");
		return;
	}

	if (priv->port == 0) {
		IDLE_DEBUG("port not set!");
		g_simple_async_report_error_in_idle(G_OBJECT(conn),
			callback, user_data,
			TP_ERROR, TP_ERROR_NOT_AVAILABLE,
			"port not set!");
		return;
	}

	result = g_simple_async_result_new(G_OBJECT(conn), callback, user_data, idle_server_connection_connect_async);
	g_socket_client_connect_to_host_async(priv->socket_client, priv->host, priv->port, cancellable, _connect_to_host_ready, result);
	change_state(conn, SERVER_CONNECTION_STATE_CONNECTING, SERVER_CONNECTION_STATE_REASON_REQUESTED);
}
static void _input_stream_read_ready(GObject *source_object, GAsyncResult *res, gpointer user_data) {
	GInputStream *input_stream = G_INPUT_STREAM(source_object);
	IdleServerConnection *conn = IDLE_SERVER_CONNECTION(user_data);
	IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn);
	gssize ret;
	GError *error = NULL;

	if (priv->io_stream == NULL) /* ie. we are in the process of disconnecting */
		goto cleanup;

	ret = g_input_stream_read_finish(input_stream, res, &error);

	if (ret == -1) {
		IDLE_DEBUG("g_input_stream_read failed: %s", error->message);
		g_error_free(error);
		goto disconnect;
	} else if (ret == 0) {
		IDLE_DEBUG("g_input_stream_read returned end-of-file");
		goto disconnect;
	}

	g_signal_emit(conn, signals[RECEIVED], 0, priv->input_buffer);

	_input_stream_read(conn, input_stream, _input_stream_read_ready);
	return;

disconnect:
	if (priv->state == SERVER_CONNECTION_STATE_CONNECTED)
		idle_server_connection_disconnect_full_async(conn, SERVER_CONNECTION_STATE_REASON_ERROR, NULL, NULL, NULL);
cleanup:
	g_object_unref(conn);
}
static IdleParserHandlerResult _notice_privmsg_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data) {
	IdleIMManager *manager = IDLE_IM_MANAGER(user_data);
	IdleIMManagerPrivate *priv = IDLE_IM_MANAGER_GET_PRIVATE(manager);
	TpHandle handle = (TpHandle) g_value_get_uint(g_value_array_get_nth(args, 0));
	IdleIMChannel *chan;
	TpChannelTextMessageType type;
	gchar *body;

	if (code == IDLE_PARSER_PREFIXCMD_NOTICE_USER) {
		type = TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE;
		body = idle_ctcp_kill_blingbling(g_value_get_string(g_value_array_get_nth(args, 2)));
	} else {
		gboolean decoded = idle_text_decode(g_value_get_string(g_value_array_get_nth(args, 2)), &type, &body);
		if (!decoded)
			return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED;
	}

	idle_connection_emit_queued_aliases_changed(priv->conn);

	if (!priv->channels) {
		IDLE_DEBUG("Channels hash table missing, ignoring...");
		return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED;
	}

	if (!(chan = g_hash_table_lookup(priv->channels, GUINT_TO_POINTER(handle))))
		chan = _im_manager_new_channel(manager, handle, handle, NULL);

	idle_im_channel_receive(chan, type, handle, body);

	g_free(body);

	return IDLE_PARSER_HANDLER_RESULT_HANDLED;
}
static void _write_ready(GObject *source_object, GAsyncResult *res, gpointer user_data) {
	GOutputStream *output_stream = G_OUTPUT_STREAM(source_object);
	GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(user_data);
	IdleServerConnection *conn = IDLE_SERVER_CONNECTION(g_async_result_get_source_object(G_ASYNC_RESULT(result)));
	IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn);
	gssize nwrite;
	GError *error = NULL;

	g_object_unref(conn);

	nwrite = g_output_stream_write_finish(output_stream, res, &error);
	if (nwrite == -1) {
		IDLE_DEBUG("g_output_stream_write failed : %s", error->message);
		g_simple_async_result_set_error(result, TP_ERROR, TP_ERROR_NETWORK_ERROR, "%s", error->message);
		g_error_free(error);
		goto cleanup;
	}

	priv->nwritten += nwrite;
	if (priv->nwritten < priv->count) {
		g_output_stream_write_async(output_stream, priv->output_buffer + priv->nwritten, priv->count - priv->nwritten, G_PRIORITY_DEFAULT, priv->cancellable, _write_ready, result);
		return;
	}

cleanup:
	if (priv->cancellable != NULL) {
		g_object_unref(priv->cancellable);
		priv->cancellable = NULL;
	}
	g_simple_async_result_complete(result);
	g_object_unref(result);
}
static void _im_manager_foreach(TpChannelManager *manager, TpExportableChannelFunc func, gpointer user_data) {
	IdleIMManagerPrivate *priv = IDLE_IM_MANAGER_GET_PRIVATE(manager);
	struct _ForeachHelperData data = {func, user_data};

	if (!priv->channels) {
		IDLE_DEBUG("Channels hash table missing, ignoring...");
		return;
	}

	g_hash_table_foreach(priv->channels, _foreach_helper, &data);
}
static void change_state(IdleServerConnection *conn, IdleServerConnectionState state, guint reason) {
	IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn);

	if (state == priv->state)
		return;

	IDLE_DEBUG("emitting status-changed, state %u, reason %u", state, reason);

	priv->state = state;
	g_signal_emit(conn, signals[STATUS_CHANGED], 0, state, reason);
}
void idle_server_connection_send_async(IdleServerConnection *conn, const gchar *cmd, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) {
	IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn);
	GOutputStream *output_stream;
	GSimpleAsyncResult *result;
	gsize output_buffer_size = sizeof(priv->output_buffer);

	if (priv->state != SERVER_CONNECTION_STATE_CONNECTED
            || priv->io_stream == NULL) {
		IDLE_DEBUG("connection was not open!");
		g_simple_async_report_error_in_idle(G_OBJECT(conn),
			callback, user_data,
			TP_ERROR, TP_ERROR_NOT_AVAILABLE,
			"connection was not open!");
		return;
	}

	priv->count = strlen(cmd);
	if (priv->count > output_buffer_size)
		priv->count = output_buffer_size;

	/* We only need to copy priv->count bytes, but padding the rest
         * with null bytes gives us cleaner debug messages, without
         * affecting the readability of the code.
         */
	strncpy(priv->output_buffer, cmd, output_buffer_size);

	priv->nwritten = 0;

	if (cancellable != NULL) {
		priv->cancellable = cancellable;
		g_object_ref(priv->cancellable);
	}

	output_stream = g_io_stream_get_output_stream(priv->io_stream);
	result = g_simple_async_result_new(G_OBJECT(conn), callback, user_data, idle_server_connection_send_async);
	g_output_stream_write_async(output_stream, priv->output_buffer, priv->count, G_PRIORITY_DEFAULT, cancellable, _write_ready, result);

	IDLE_DEBUG("sending \"%s\" to OutputStream %p", priv->output_buffer, output_stream);
}
static void _parse_message(IdleParser *parser, const gchar *split_msg) {
	gchar **tokens = _tokenize(split_msg);
	IDLE_DEBUG("parsing \"%s\"", split_msg);

	for (int i = 0; i < IDLE_PARSER_LAST_MESSAGE_CODE; i++) {
		const MessageSpec *spec = &(message_specs[i]);

		if ((split_msg[0] != ':') && (i <= IDLE_PARSER_LAST_NON_PREFIX_CMD)) {
			if (!g_ascii_strcasecmp(tokens[0], spec->str))
				_parse_and_forward_one(parser, tokens, spec->code, spec->format);
		} else if (i > IDLE_PARSER_LAST_NON_PREFIX_CMD) {
			if (!g_ascii_strcasecmp(tokens[2], spec->str))
				_parse_and_forward_one(parser, tokens, spec->code, spec->format);
		}
	}

	_free_tokens(tokens);
}
static void _close_ready(GObject *source_object, GAsyncResult *res, gpointer user_data) {
	GIOStream *io_stream = G_IO_STREAM(source_object);
	GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(user_data);
	IdleServerConnection *conn = IDLE_SERVER_CONNECTION(g_async_result_get_source_object(G_ASYNC_RESULT(result)));
	IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn);
	GError *error = NULL;

	change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, priv->reason);
	g_object_unref(conn);

	if (!g_io_stream_close_finish(io_stream, res, &error)) {
		IDLE_DEBUG("g_io_stream_close failed: %s", error->message);
		g_simple_async_result_set_error(result, TP_ERROR, TP_ERROR_NETWORK_ERROR, "%s", error->message);
		g_error_free(error);
	}

	g_simple_async_result_complete(result);
	g_object_unref(result);
}
static IdleIMChannel *
_im_manager_new_channel (IdleIMManager *mgr,
						 TpHandle handle,
						 TpHandle initiator,
						 gpointer request)
{
	IdleIMManagerPrivate *priv = IDLE_IM_MANAGER_GET_PRIVATE (mgr);
	TpBaseConnection *base_connection = TP_BASE_CONNECTION (priv->conn);
	TpHandleRepoIface *handle_repo =
		tp_base_connection_get_handles (base_connection, TP_HANDLE_TYPE_CONTACT);
	IdleIMChannel *chan;
	const gchar *name;
	GSList *requests = NULL;

	g_assert (g_hash_table_lookup (priv->channels, GUINT_TO_POINTER (handle))
			  == NULL);

	name = tp_handle_inspect (handle_repo, handle);
	IDLE_DEBUG ("Requested channel for handle: %u (%s)", handle, name);

	chan = g_object_new (IDLE_TYPE_IM_CHANNEL,
						 "connection", priv->conn,
						 "handle", handle,
						 "initiator-handle", initiator,
						 "requested", handle != initiator,
						 NULL);
	tp_base_channel_register (TP_BASE_CHANNEL (chan));
	g_hash_table_insert (priv->channels, GUINT_TO_POINTER (handle), chan);

	if (request != NULL)
		requests = g_slist_prepend (requests, request);

	tp_channel_manager_emit_new_channel (mgr, TP_EXPORTABLE_CHANNEL (chan),
										 requests);

	g_slist_free (requests);

	g_signal_connect (chan, "closed", G_CALLBACK (_im_channel_closed_cb), mgr);

	return chan;
}
static void _connect_to_host_ready(GObject *source_object, GAsyncResult *res, gpointer user_data) {
	GSocketClient *socket_client = G_SOCKET_CLIENT(source_object);
	GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT(user_data);
	IdleServerConnection *conn = IDLE_SERVER_CONNECTION(g_async_result_get_source_object(G_ASYNC_RESULT(result)));
	IdleServerConnectionPrivate *priv = IDLE_SERVER_CONNECTION_GET_PRIVATE(conn);
	GInputStream *input_stream;
	GSocket *socket_;
	GSocketConnection *socket_connection;
	gint nodelay = 1;
	gint socket_fd;
	GError *error = NULL;

	socket_connection = g_socket_client_connect_to_host_finish(socket_client, res, &error);
	if (socket_connection == NULL) {
		IDLE_DEBUG("g_socket_client_connect_to_host failed: %s", error->message);
		g_simple_async_result_set_error(result, TP_ERROR, TP_ERROR_NETWORK_ERROR, "%s", error->message);
		g_error_free(error);
		change_state(conn, SERVER_CONNECTION_STATE_NOT_CONNECTED, SERVER_CONNECTION_STATE_REASON_ERROR);
		g_object_unref(conn);
		goto cleanup;
	}

	socket_ = g_socket_connection_get_socket(socket_connection);
	g_socket_set_keepalive(socket_, TRUE);

	socket_fd = g_socket_get_fd(socket_);
	setsockopt(socket_fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay));

	g_tcp_connection_set_graceful_disconnect(G_TCP_CONNECTION(socket_connection), TRUE);

	priv->io_stream = G_IO_STREAM(socket_connection);

	input_stream = g_io_stream_get_input_stream(priv->io_stream);
	_input_stream_read(conn, input_stream, _input_stream_read_ready);
	change_state(conn, SERVER_CONNECTION_STATE_CONNECTED, SERVER_CONNECTION_STATE_REASON_REQUESTED);

cleanup:
	g_simple_async_result_complete(result);
	g_object_unref(result);
}
Esempio n. 14
0
/**
 * idle_text_encode_and_split:
 * @type: The type of message as per Telepathy
 * @recipient: The target user or channel
 * @text: The message body
 * @max_msg_len: The maximum length of the message on this server (see also
 *               idle_connection_get_max_message_length())
 * @bodies_out: Location at which to return the human-readable bodies of each
 *              part
 * @error: Location at which to store an error
 *
 * Splits @text as necessary to be able to send it over IRC. IRC messages
 * cannot contain newlines, and have a (server-determined) maximum length.
 *
 * Returns: A list of IRC protocol commands representing @text as best possible.
 */
GStrv
idle_text_encode_and_split(TpChannelTextMessageType type,
                           const gchar *recipient,
                           const gchar *text,
                           gsize max_msg_len,
                           GStrv *bodies_out,
                           GError **error) {
    GPtrArray *messages;
    GPtrArray *bodies;
    const gchar *remaining_text = text;
    const gchar * const text_end =  text + strlen(text);
    gchar *header;
    const gchar *footer = "";
    gsize max_bytes;

    switch (type) {
    case TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL:
        header = g_strdup_printf("PRIVMSG %s :", recipient);
        break;
    case TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION:
        header = g_strdup_printf("PRIVMSG %s :\001ACTION ", recipient);
        footer = "\001";
        break;
    case TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE:
        header = g_strdup_printf("NOTICE %s :", recipient);
        break;
    default:
        IDLE_DEBUG("unsupported message type %u", type);
        g_set_error(error, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED, "unsupported message type %u", type);
        return NULL;
    }

    messages = g_ptr_array_new();
    bodies = g_ptr_array_new();
    max_bytes = max_msg_len - (strlen(header) + strlen(footer));

    while (remaining_text < text_end) {
        char *newline = strchr(remaining_text, '\n');
        const char *end_iter;
        char *message;
        int len;

        if (newline != NULL && ((unsigned) (newline - remaining_text)) < max_bytes) {
            /* String up to the next newline is short enough. */
            end_iter = newline;

        } else if ((text_end - remaining_text) > (gint) max_bytes) {
            /* Remaining string is too long; take as many bytes as possible */
            end_iter = remaining_text + max_bytes;
            /* make sure we don't break a UTF-8 code point in half */
            end_iter = g_utf8_find_prev_char (remaining_text, end_iter);
        } else {
            /* Just take it all. */
            end_iter = text_end;
        }

        len = (int)(end_iter - remaining_text);
        message = g_strdup_printf("%s%.*s%s", header, len, remaining_text, footer);
        g_ptr_array_add(messages, message);
        g_ptr_array_add(bodies, g_strndup(remaining_text, len));

        remaining_text = end_iter;
        if (*end_iter == '\n') {
            /* advance over a newline */
            remaining_text++;
        }
    }

    g_assert (remaining_text == text_end);

    g_ptr_array_add(messages, NULL);
    g_ptr_array_add(bodies, NULL);

    if (bodies_out != NULL) {
        *bodies_out = (GStrv) g_ptr_array_free(bodies, FALSE);
    } else {
        g_ptr_array_free(bodies, TRUE);
    }

    g_free(header);
    return (GStrv) g_ptr_array_free(messages, FALSE);
}
static gboolean _parse_atom(IdleParser *parser, GValueArray *arr, char atom, const gchar *token, TpHandleSet *contact_reffed, TpHandleSet *room_reffed) {
	IdleParserPrivate *priv = IDLE_PARSER_GET_PRIVATE(parser);
	TpHandle handle;
	GValue val = {0};
	TpHandleRepoIface *contact_repo = tp_base_connection_get_handles(TP_BASE_CONNECTION(priv->conn), TP_HANDLE_TYPE_CONTACT);
	TpHandleRepoIface *room_repo = tp_base_connection_get_handles(TP_BASE_CONNECTION(priv->conn), TP_HANDLE_TYPE_ROOM);

	if (token[0] == ':')
		token++;

	IDLE_DEBUG("parsing atom \"%s\" as %c", token, atom);

	switch (atom) {
		case 'I':
			IDLE_DEBUG("ignored token");
			return TRUE;
			break;

		case 'c':
		case 'r':
		case 'C': {
			gchar *id, *bang = NULL;
			gchar modechar = '\0';

			if (idle_muc_channel_is_modechar(token[0])) {
				modechar = token[0];
				token++;
			}

			id = g_strdup(token);

			if (atom != 'r') {
				bang = strchr(id, '!');
				if (bang)
					*bang = '\0';
			}

			if (atom == 'r') {
				if ((handle = tp_handle_ensure(room_repo, id, NULL, NULL))) {
					tp_handle_set_add(room_reffed, handle);
				}
			} else {
				if ((handle = tp_handle_ensure(contact_repo, id, NULL, NULL))) {
					tp_handle_set_add(contact_reffed, handle);

					idle_connection_canon_nick_receive(priv->conn, handle, id);
				}
			}

			g_free(id);

			if (!handle)
				return FALSE;

			g_value_init(&val, G_TYPE_UINT);
			g_value_set_uint(&val, handle);
			g_value_array_append(arr, &val);
			g_value_unset(&val);

			IDLE_DEBUG("set handle %u", handle);

			if (atom == 'C') {
				g_value_init(&val, G_TYPE_CHAR);
				g_value_set_schar(&val, modechar);
				g_value_array_append(arr, &val);
				g_value_unset(&val);

				IDLE_DEBUG("set modechar %c", modechar);
			}

			return TRUE;
		}
		break;

		case 'd': {
			guint dval;

			if (sscanf(token, "%d", &dval)) {
				g_value_init(&val, G_TYPE_UINT);
				g_value_set_uint(&val, dval);
				g_value_array_append(arr, &val);
				g_value_unset(&val);

				IDLE_DEBUG("set int %d", dval);

				return TRUE;
			} else {
				return FALSE;
			}
		}
		break;

		case 's':
			g_value_init(&val, G_TYPE_STRING);
			g_value_set_string(&val, token);
			g_value_array_append(arr, &val);
			g_value_unset(&val);
			IDLE_DEBUG("set string \"%s\"", token);

			return TRUE;
			break;

		default:
			IDLE_DEBUG("unknown atom %c", atom);
			return FALSE;
			break;
	}
}
static void _parse_and_forward_one(IdleParser *parser, gchar **tokens, IdleParserMessageCode code, const gchar *format) {
	IdleParserPrivate *priv = IDLE_PARSER_GET_PRIVATE(parser);
	GValueArray *args = g_value_array_new(3);
	GSList *link_ = priv->handlers[code];
	IdleParserHandlerResult result = IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED;
	gboolean success = TRUE;
	gchar **iter = tokens;
	/* We keep a ref to each unique handle in a message so that we can unref them after calling all handlers */
	TpHandleSet *contact_reffed = tp_handle_set_new(tp_base_connection_get_handles(TP_BASE_CONNECTION(priv->conn), TP_HANDLE_TYPE_CONTACT));
	TpHandleSet *room_reffed = tp_handle_set_new(tp_base_connection_get_handles(TP_BASE_CONNECTION(priv->conn), TP_HANDLE_TYPE_ROOM));

	IDLE_DEBUG("message code %u", code);

	while ((*format != '\0') && success && (*iter != NULL)) {
		GValue val = {0};

		if (*format == 'v') {
			format++;
			while (*iter != NULL) {
				if (!_parse_atom(parser, args, *format, iter[0], contact_reffed, room_reffed)) {
					success = FALSE;
					break;
				}

				iter += 2;
			}
		} else if ((*format == ':') || (*format == '.')) {
			/* Assume the happy case of the trailing parameter starting after the :
			 * in the trailing string as the RFC intended */
			const gchar *trailing = iter[1] + 1;

			/* Some IRC proxies *cough* bip *cough* omit the : in the trailing
			 * parameter if that parameter is just one word, to cope with that check
			 * if there are no more tokens after the current one and if so, accept a
			 * trailing string without the : prefix. */
			if (iter[0][0] != ':') {
				if (iter[2] == NULL) {
					trailing = iter[1];
				} else {
					success = FALSE;
					break;
				}
			}

			/*
			 * because of the way things are tokenized, if there is a
			 * space immediately after the the ':', the current token will only be
			 * ":", so we check that the trailing string is non-NULL rather than
			 * checking iter[0][1] (since iter[0] is a NULL-terminated token string
			 * whereas trailing is a pointer into the full message string
			 */
			if (trailing[0] == '\0') {
				success = FALSE;
				break;
			}

			g_value_init(&val, G_TYPE_STRING);
			g_value_set_string(&val, trailing);
			g_value_array_append(args, &val);
			g_value_unset(&val);

			IDLE_DEBUG("set string \"%s\"", trailing);
		} else {
			if (!_parse_atom(parser, args, *format, iter[0], contact_reffed, room_reffed)) {
				success = FALSE;
				break;
			}
		}

		format++;
		iter += 2;
	}

	if (!success && (*format != '.')) {
		IDLE_DEBUG("failed to parse \"%s\"", tokens[1]);

		goto cleanup;
	}

	if (*format && (*format != '.')) {
		IDLE_DEBUG("missing args in message \"%s\"", tokens[1]);

		goto cleanup;
	}

	IDLE_DEBUG("successfully parsed");

	while (link_) {
		MessageHandlerClosure *closure = link_->data;
		result = closure->handler(parser, code, args, closure->user_data);
		if (result == IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED) {
			link_ = link_->next;
		} else if (result == IDLE_PARSER_HANDLER_RESULT_HANDLED) {
			break;
		} else if (result == IDLE_PARSER_HANDLER_RESULT_NO_MORE_PLEASE) {
			GSList *tmp = link_->next;
			g_free(closure);
			priv->handlers[code] = g_slist_remove_link(priv->handlers[code], link_);
			link_ = tmp;
		} else {
			g_assert_not_reached();
		}
	}

cleanup:

	g_value_array_free(args);

	tp_handle_set_destroy(contact_reffed);
	tp_handle_set_destroy(room_reffed);
}