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); }
/** * 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); }