/* * This file is part of telepathy-idle * * Copyright (C) 2006-2007 Collabora Limited * Copyright (C) 2006-2007 Nokia Corporation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include "idle-text.h" #include #include #define IDLE_DEBUG_FLAG IDLE_DEBUG_TEXT #include "idle-ctcp.h" #include "idle-debug.h" gboolean idle_text_decode(const gchar *text, TpChannelTextMessageType *type, gchar **body) { gchar *tmp = NULL; if (text[0] != '\001') { *type = TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL; tmp = g_strdup(text); } else { size_t actionlen = strlen("\001ACTION "); if (!g_ascii_strncasecmp(text, "\001ACTION ", actionlen)) { *type = TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION; tmp = g_strndup(text + actionlen, strlen(text + actionlen) - 1); } else { *body = NULL; return FALSE; } } *body = idle_ctcp_kill_blingbling(tmp); g_free(tmp); return TRUE; } /** * 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); } void idle_text_send(GObject *obj, TpMessage *message, TpMessageSendingFlags flags, const gchar *recipient, IdleConnection *conn) { GError *error = NULL; const GHashTable *part; TpChannelTextMessageType type = TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL; gboolean result = TRUE; const gchar *content_type, *text; guint n_parts; GStrv messages; GStrv bodies; gsize msg_len; guint i; #define INVALID_ARGUMENT(msg, ...) \ G_STMT_START { \ IDLE_DEBUG (msg , ## __VA_ARGS__); \ g_set_error (&error, TP_ERROR, TP_ERROR_INVALID_ARGUMENT, \ msg , ## __VA_ARGS__); \ goto failed; \ } G_STMT_END g_return_if_fail (recipient != NULL); part = tp_message_peek (message, 0); if (tp_asv_lookup (part, "message-type") != NULL) type = tp_asv_get_uint32 (part, "message-type", &result); if (!result) INVALID_ARGUMENT ("message-type must be a 32-bit unsigned integer"); if (type >= TP_NUM_CHANNEL_TEXT_MESSAGE_TYPES) INVALID_ARGUMENT ("invalid message type: %u", type); n_parts = tp_message_count_parts (message); if (n_parts != 2) INVALID_ARGUMENT ("message must contain exactly 1 part, not %u", (n_parts - 1)); part = tp_message_peek (message, 1); content_type = tp_asv_get_string (part, "content-type"); text = tp_asv_get_string (part, "content"); if (tp_strdiff (content_type, "text/plain")) INVALID_ARGUMENT ("message must be text/plain"); if (tp_str_empty (text)) INVALID_ARGUMENT ("content must be a UTF-8 string"); /* Okay, it's valid. Let's send it. */ msg_len = idle_connection_get_max_message_length(conn); messages = idle_text_encode_and_split(type, recipient, text, msg_len, &bodies, &error); if (messages == NULL) goto failed; for(i = 0; messages[i] != NULL; i++) { g_assert(bodies[i] != NULL); idle_connection_send(conn, messages[i]); } g_strfreev(messages); g_strfreev(bodies); tp_message_mixin_sent (obj, message, flags, "", NULL); return; failed: g_assert (error != NULL); tp_message_mixin_sent (obj, message, 0, NULL, error); g_error_free (error); } gboolean idle_text_received (GObject *chan, TpBaseConnection *base_conn, TpChannelTextMessageType type, const gchar *text, TpHandle sender) { TpMessage *msg; msg = tp_cm_message_new_text (base_conn, sender, type, text); tp_message_set_int64 (msg, 0, "message-received", time (NULL)); tp_message_mixin_take_received (chan, msg); return TRUE; }