/* * This file is part of telepathy-idle * * Copyright (C) 2011 Debarshi Ray * * 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-contact-info.h" #define IDLE_DEBUG_FLAG IDLE_DEBUG_CONNECTION #include "idle-debug.h" #include "idle-muc-channel.h" #include "idle-parser.h" typedef struct _ContactInfoRequest ContactInfoRequest; struct _ContactInfoRequest { guint handle; const gchar *nick; gboolean is_away; gboolean is_operator; gboolean is_reg_nick; gboolean is_secure; GPtrArray *contact_info; DBusGMethodInvocation *context; }; /* * _insert_contact_field: * @contact_info: an array of Contact_Info_Field structures * @field_name: a vCard field name in any case combination * @field_params: a list of vCard type-parameters, typically of the form * type=xxx; must be in lower-case if case-insensitive * @field_values: for unstructured fields, an array containing one element; * for structured fields, the elements of the field in order */ static void _insert_contact_field(GPtrArray *contact_info, const gchar *field_name, const gchar * const *field_params, const gchar * const *field_values) { g_ptr_array_add (contact_info, tp_value_array_build (3, G_TYPE_STRING, field_name, G_TYPE_STRV, field_params, G_TYPE_STRV, field_values, G_TYPE_INVALID)); } static ContactInfoRequest * _get_matching_request(IdleConnection *conn, GValueArray *args) { ContactInfoRequest *request; TpHandle handle = g_value_get_uint(g_value_array_get_nth(args, 0)); if (g_queue_is_empty(conn->contact_info_requests)) return NULL; request = g_queue_peek_head(conn->contact_info_requests); if (request->handle != handle) return NULL; if (request->contact_info == NULL) request->contact_info = dbus_g_type_specialized_construct(TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST); return request; } static void _send_request_contact_info(IdleConnection *conn, ContactInfoRequest *request) { gchar cmd[IRC_MSG_MAXLEN + 1]; g_snprintf(cmd, IRC_MSG_MAXLEN + 1, "WHOIS %s %s", request->nick, request->nick); idle_connection_send(conn, cmd); } static void _dequeue_request_contact_info(IdleConnection *conn) { ContactInfoRequest *request = g_queue_pop_head(conn->contact_info_requests); if (request->contact_info != NULL) g_boxed_free(TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST, request->contact_info); g_slice_free(ContactInfoRequest, request); if (g_queue_is_empty(conn->contact_info_requests)) return; request = g_queue_peek_head(conn->contact_info_requests); _send_request_contact_info(conn, request); } static void _queue_request_contact_info(IdleConnection *conn, guint handle, const gchar *nick, DBusGMethodInvocation *context) { ContactInfoRequest *request; request = g_slice_new0(ContactInfoRequest); request->handle = handle; request->nick = nick; request->is_away = FALSE; request->is_operator = FALSE; request->is_reg_nick = FALSE; request->is_secure = FALSE; request->contact_info = NULL; request->context = context; if (g_queue_is_empty(conn->contact_info_requests)) _send_request_contact_info(conn, request); g_queue_push_tail(conn->contact_info_requests, request); } static void _return_from_request_contact_info(IdleConnection *conn) { ContactInfoRequest *request = g_queue_peek_head(conn->contact_info_requests); tp_svc_connection_interface_contact_info_return_from_request_contact_info(request->context, request->contact_info); tp_svc_connection_interface_contact_info_emit_contact_info_changed(conn, request->handle, request->contact_info); _dequeue_request_contact_info(conn); } static void idle_connection_request_contact_info(TpSvcConnectionInterfaceContactInfo *iface, guint contact, DBusGMethodInvocation *context) { IdleConnection *self = IDLE_CONNECTION(iface); TpBaseConnection *base = TP_BASE_CONNECTION(self); TpHandleRepoIface *contact_handles = tp_base_connection_get_handles(base, TP_HANDLE_TYPE_CONTACT); const gchar *nick; GError *error = NULL; TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED(base, context); if (!tp_handle_is_valid(contact_handles, contact, &error)) { dbus_g_method_return_error(context, error); g_error_free(error); return; } nick = tp_handle_inspect(contact_handles, contact); IDLE_DEBUG ("Queued contact info request for handle: %u (%s)", contact, nick); _queue_request_contact_info(self, contact, nick, context); } static IdleParserHandlerResult _away_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data) { IdleConnection *conn = IDLE_CONNECTION(user_data); ContactInfoRequest *request = _get_matching_request(conn, args); const gchar *msg; const gchar *field_values[2] = {NULL, NULL}; if (request == NULL) return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; field_values[0] = g_strdup_printf("%d", TP_CONNECTION_PRESENCE_TYPE_AWAY); _insert_contact_field(request->contact_info, "x-presence-type", NULL, field_values); g_free((gpointer) field_values[0]); field_values[0] = "away"; _insert_contact_field(request->contact_info, "x-presence-status-identifier", NULL, field_values); msg = g_value_get_string(g_value_array_get_nth(args, 1)); field_values[0] = msg; _insert_contact_field(request->contact_info, "x-presence-status-message", NULL, field_values); request->is_away = TRUE; return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; } static IdleParserHandlerResult _end_of_whois_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data) { IdleConnection *conn = IDLE_CONNECTION(user_data); ContactInfoRequest *request = _get_matching_request(conn, args); const gchar *field_values[2] = {NULL, NULL}; if (request == NULL) return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; if (request->is_away == FALSE) { field_values[0] = g_strdup_printf("%d", TP_CONNECTION_PRESENCE_TYPE_AVAILABLE); _insert_contact_field(request->contact_info, "x-presence-type", NULL, field_values); g_free((gpointer) field_values[0]); field_values[0] = "available"; _insert_contact_field(request->contact_info, "x-presence-status-identifier", NULL, field_values); field_values[0] = ""; _insert_contact_field(request->contact_info, "x-presence-status-message", NULL, field_values); } field_values[0] = (request->is_operator) ? "true" : "false"; _insert_contact_field(request->contact_info, "x-irc-operator", NULL, field_values); field_values[0] = (request->is_reg_nick) ? "true" : "false"; _insert_contact_field(request->contact_info, "x-irc-registered-nick", NULL, field_values); field_values[0] = (request->is_secure) ? "true" : "false"; _insert_contact_field(request->contact_info, "x-irc-secure-connection", NULL, field_values); _return_from_request_contact_info(conn); return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; } static IdleParserHandlerResult _no_such_server_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data) { IdleConnection *conn = IDLE_CONNECTION(user_data); TpBaseConnection *base = TP_BASE_CONNECTION(conn); TpHandleRepoIface *contact_handles = tp_base_connection_get_handles(base, TP_HANDLE_TYPE_CONTACT); TpHandle handle; ContactInfoRequest *request; GValueArray *norm_args = g_value_array_copy(args); GValue value = {0}; const gchar *server; GError *error = NULL; /* We issue our requests as WHOIS , where the first is actually a shorthand for the server to which it is connected to. * Therefore, if this error was caused by us then the value of in the error message will be . Otherwise, this error was not * caused by us, and we do not want to handle it. * * To check this we map the value of the to a handle and see if it matches the handle for which we had made the request. */ server = g_value_get_string(g_value_array_get_nth(args, 0)); handle = tp_handle_ensure(contact_handles, server, NULL, NULL); g_value_array_remove(norm_args, 0); g_value_init(&value, G_TYPE_UINT); g_value_set_uint(&value, handle); g_value_array_prepend(norm_args, &value); request = _get_matching_request(conn, norm_args); if (request == NULL) goto cleanup; error = g_error_new(TP_ERROR, TP_ERROR_DOES_NOT_EXIST, "User '%s' unknown; they may have disconnected", server); dbus_g_method_return_error(request->context, error); g_error_free(error); _dequeue_request_contact_info(conn); cleanup: g_value_array_free(norm_args); return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; } static IdleParserHandlerResult _try_again_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data) { IdleConnection *conn = IDLE_CONNECTION(user_data); ContactInfoRequest *request; const gchar *command; const gchar *msg; GError *error = NULL; /* The RPL_TRYAGAIN message does not contain the nick for which the request was issued, but only the type of the message, which in this case is * WHOIS. So we assume that the last WHOIS request that we had issued has resulted in this RPL_TRYAGAIN. This is fine as long nobody else is * issuing a WHOIS because we issue a new request only after the earlier one was successfully completed or resulted in an error. */ if (g_queue_is_empty(conn->contact_info_requests)) return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; command = g_value_get_string(g_value_array_get_nth(args, 0)); if (g_ascii_strcasecmp(command, "WHOIS")) return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; request = g_queue_peek_head(conn->contact_info_requests); msg = g_value_get_string(g_value_array_get_nth(args, 1)); error = g_error_new_literal(TP_ERROR, TP_ERROR_SERVICE_BUSY, msg); dbus_g_method_return_error(request->context, error); g_error_free(error); _dequeue_request_contact_info(conn); return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; } static IdleParserHandlerResult _whois_channels_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data) { IdleConnection *conn = IDLE_CONNECTION(user_data); ContactInfoRequest *request = _get_matching_request(conn, args); gchar *channels; gchar **channelsv; const gchar *field_values[2] = {NULL, NULL}; guint i; if (request == NULL) return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; if (args->n_values != 2) return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; channels = g_value_dup_string(g_value_array_get_nth(args, 1)); g_strchomp(channels); channelsv = g_strsplit(channels, " ", -1); for (i = 0; channelsv[i] != NULL; i++) { const gchar *channel = channelsv[i]; gchar *field_params[2] = {NULL, NULL}; if (idle_muc_channel_is_modechar(channel[0]) && idle_muc_channel_is_typechar(channel[1])) { field_params[0] = g_strdup_printf("role=%c", channel[0]); channel++; } field_values[0] = channel; _insert_contact_field(request->contact_info, "x-irc-channel", (const gchar **) field_params, field_values); g_free(field_params[0]); } g_strfreev(channelsv); g_free(channels); return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; } static IdleParserHandlerResult _whois_host_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data) { IdleConnection *conn = IDLE_CONNECTION(user_data); ContactInfoRequest *request = _get_matching_request(conn, args); gchar *msg; gchar **msgv; if (request == NULL) return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; msg = g_value_dup_string(g_value_array_get_nth(args, 1)); g_strchomp(msg); if (!g_str_has_prefix(msg, "is connecting from ")) goto cleanup; msgv = g_strsplit(msg, " ", -1); /* msg == "is connecting from *@ " */ _insert_contact_field(request->contact_info, "x-host", NULL, (const gchar **) (msgv + 3)); g_strfreev(msgv); cleanup: g_free(msg); return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; } static IdleParserHandlerResult _whois_idle_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data) { IdleConnection *conn = IDLE_CONNECTION(user_data); ContactInfoRequest *request = _get_matching_request(conn, args); guint sec; const gchar *field_values[2] = {NULL, NULL}; if (request == NULL) return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; sec = g_value_get_uint(g_value_array_get_nth(args, 1)); field_values[0] = g_strdup_printf("%u", sec); _insert_contact_field(request->contact_info, "x-idle-time", NULL, field_values); g_free((gpointer) field_values[0]); return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; } static IdleParserHandlerResult _whois_logged_in_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data) { IdleConnection *conn = IDLE_CONNECTION(user_data); ContactInfoRequest *request = _get_matching_request(conn, args); const gchar *msg; const gchar *nick; const gchar *field_values[2] = {NULL, NULL}; if (request == NULL) return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; msg = g_value_get_string(g_value_array_get_nth(args, 2)); if (g_strcmp0(msg, "is logged in as")) return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; nick = g_value_get_string(g_value_array_get_nth(args, 1)); field_values[0] = nick; _insert_contact_field(request->contact_info, "nickname", NULL, field_values); request->is_reg_nick = TRUE; return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; } static IdleParserHandlerResult _whois_operator_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data) { IdleConnection *conn = IDLE_CONNECTION(user_data); ContactInfoRequest *request = _get_matching_request(conn, args); if (request == NULL) return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; request->is_operator = TRUE; return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; } static IdleParserHandlerResult _whois_reg_nick_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data) { IdleConnection *conn = IDLE_CONNECTION(user_data); ContactInfoRequest *request = _get_matching_request(conn, args); if (request == NULL) return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; request->is_reg_nick = TRUE; return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; } static IdleParserHandlerResult _whois_secure_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data) { IdleConnection *conn = IDLE_CONNECTION(user_data); ContactInfoRequest *request = _get_matching_request(conn, args); if (request == NULL) return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; request->is_secure = TRUE; return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; } static IdleParserHandlerResult _whois_server_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data) { IdleConnection *conn = IDLE_CONNECTION(user_data); ContactInfoRequest *request = _get_matching_request(conn, args); const gchar *server; const gchar *server_info; const gchar *field_values[3] = {NULL, NULL, NULL}; if (request == NULL) return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; server = g_value_get_string(g_value_array_get_nth(args, 1)); server_info = g_value_get_string(g_value_array_get_nth(args, 2)); field_values[0] = server; field_values[1] = server_info; _insert_contact_field(request->contact_info, "x-irc-server", NULL, field_values); return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; } static IdleParserHandlerResult _whois_user_handler(IdleParser *parser, IdleParserMessageCode code, GValueArray *args, gpointer user_data) { IdleConnection *conn = IDLE_CONNECTION(user_data); ContactInfoRequest *request = _get_matching_request(conn, args); const gchar *name; const gchar *field_values[2] = {NULL, NULL}; if (request == NULL) return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; name = g_value_get_string(g_value_array_get_nth(args, 3)); field_values[0] = name; _insert_contact_field(request->contact_info, "fn", NULL, field_values); return IDLE_PARSER_HANDLER_RESULT_NOT_HANDLED; } static void idle_contact_info_properties_getter(GObject *object, GQuark interface, GQuark name, GValue *value, gpointer getter_data) { GQuark q_supported_fields = g_quark_from_static_string("SupportedFields"); if (name == q_supported_fields) { GPtrArray *fields = dbus_g_type_specialized_construct(TP_ARRAY_TYPE_FIELD_SPECS); g_value_set_boxed(value, fields); g_boxed_free(TP_ARRAY_TYPE_FIELD_SPECS, fields); } else /* ContactInfoFlags */ g_value_set_uint(value, 0); } static void _contact_info_requests_foreach_free(gpointer data, gpointer user_data) { g_slice_free(ContactInfoRequest, data); } void idle_contact_info_finalize (GObject *object) { IdleConnection *conn = IDLE_CONNECTION(object); g_queue_foreach(conn->contact_info_requests, _contact_info_requests_foreach_free, NULL); g_queue_free(conn->contact_info_requests); } void idle_contact_info_class_init (IdleConnectionClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); static TpDBusPropertiesMixinPropImpl props[] = { {"ContactInfoFlags", NULL, NULL}, {"SupportedFields", NULL, NULL}, {NULL} }; tp_dbus_properties_mixin_implement_interface(object_class, TP_IFACE_QUARK_CONNECTION_INTERFACE_CONTACT_INFO, idle_contact_info_properties_getter, NULL, props); } static void idle_contact_info_fill_contact_attributes ( GObject *obj, const GArray *contacts, GHashTable *attributes_hash) { /* We don't cache contact info, so we just never put /info into the * attributes hash. This is spec-compliant: we don't implement * GetContactInfo, and the spec says the attribute should be the same as the * value returned by that method (or omitted if unknown). This function * exists at all to make ContactInfo show up in ContactAttributeInterfaces * (otherwise tp-glib might be justified in falling back to * GetContactInfo(), which we know will fail). */ } void idle_contact_info_init (IdleConnection *conn) { conn->contact_info_requests = g_queue_new(); idle_parser_add_handler(conn->parser, IDLE_PARSER_NUMERIC_WHOISUSER, _whois_user_handler, conn); idle_parser_add_handler(conn->parser, IDLE_PARSER_NUMERIC_WHOISCHANNELS, _whois_channels_handler, conn); idle_parser_add_handler(conn->parser, IDLE_PARSER_NUMERIC_WHOISSERVER, _whois_server_handler, conn); idle_parser_add_handler(conn->parser, IDLE_PARSER_NUMERIC_WHOISOPERATOR, _whois_operator_handler, conn); idle_parser_add_handler(conn->parser, IDLE_PARSER_NUMERIC_AWAY, _away_handler, conn); idle_parser_add_handler(conn->parser, IDLE_PARSER_NUMERIC_WHOISHOST, _whois_host_handler, conn); idle_parser_add_handler(conn->parser, IDLE_PARSER_NUMERIC_WHOISREGNICK, _whois_reg_nick_handler, conn); idle_parser_add_handler(conn->parser, IDLE_PARSER_NUMERIC_WHOISSECURE, _whois_secure_handler, conn); idle_parser_add_handler(conn->parser, IDLE_PARSER_NUMERIC_WHOISIDLE, _whois_idle_handler, conn); idle_parser_add_handler(conn->parser, IDLE_PARSER_NUMERIC_WHOISLOGGEDIN, _whois_logged_in_handler, conn); idle_parser_add_handler(conn->parser, IDLE_PARSER_NUMERIC_ENDOFWHOIS, _end_of_whois_handler, conn); idle_parser_add_handler(conn->parser, IDLE_PARSER_NUMERIC_NOSUCHSERVER, _no_such_server_handler, conn); idle_parser_add_handler(conn->parser, IDLE_PARSER_NUMERIC_TRYAGAIN, _try_again_handler, conn); tp_contacts_mixin_add_contact_attributes_iface ((GObject *) conn, TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO, idle_contact_info_fill_contact_attributes); } void idle_contact_info_iface_init(gpointer g_iface, gpointer iface_data) { TpSvcConnectionInterfaceContactInfoClass *klass = (TpSvcConnectionInterfaceContactInfoClass *) g_iface; #define IMPLEMENT(x) tp_svc_connection_interface_contact_info_implement_##x (\ klass, idle_connection_##x) IMPLEMENT(request_contact_info); #undef IMPLEMENT }