diff options
author | Will Thompson <will.thompson@collabora.co.uk> | 2009-11-27 12:26:50 +0000 |
---|---|---|
committer | Will Thompson <will.thompson@collabora.co.uk> | 2009-11-27 12:26:50 +0000 |
commit | 0b0506542442aa801fdf21afa5f880cf6320bc05 (patch) | |
tree | 34354bb0392286b5113f3dc3212180fff2c752d4 | |
parent | 2ee360dce6b0ea69d0f4d1fdea4e1148291a13d0 (diff) | |
parent | 2059502c054f55e332aafe29c2476c5963033dc7 (diff) |
Merge branch 'master' into plugins
43 files changed, 621 insertions, 664 deletions
@@ -14,6 +14,23 @@ Fixes: fd.o #13157; previously we assumed they would only let moderators change the subject. (smcv) +• fd.o #21152: allow joining chatrooms on servers that don't respond to disco + queries, like talk.google.com (jonny) + +• fd.o #22456: append fallback conference server if the room name has no @, + rather than just failing (if using the Requests API) (jonny) + +• fd.o #22768 (partial): reassure talk.google.com that we can be invited to + chatrooms. This enables us to be invited to private MUCs by the Google + client, but doesn't currently support creating them. (jonny) + +• fd.o #24558: correctly flag the password parameter as secret (smcv) + +• Use TpDebugSender for debug output, instead of reimplementing it (jonny) + +• Allow sending chat state to contacts whose capabilities we don't know, + such as invisible Google Talk users (wjt) + telepathy-gabble 0.9.2 (2009-10-27) =================================== diff --git a/autogen.sh b/autogen.sh index bdcbefec6..b6dff981b 100755 --- a/autogen.sh +++ b/autogen.sh @@ -3,13 +3,14 @@ set -e if test -n "$AUTOMAKE"; then : # don't override an explicit user request -elif automake-1.9 --version >/dev/null 2>/dev/null && \ - aclocal-1.9 --version >/dev/null 2>/dev/null; then - # If we have automake-1.9, use it. This helps to ensure that our build - # system doesn't accidentally grow automake-1.10 dependencies. - AUTOMAKE=automake-1.9 +elif automake-1.11 --version >/dev/null 2>/dev/null && \ + aclocal-1.11 --version >/dev/null 2>/dev/null; then + # If we have automake-1.11, use it. This is the oldest version (=> least + # likely to introduce undeclared dependencies) that will give us + # --enable-silent-rules support. + AUTOMAKE=automake-1.11 export AUTOMAKE - ACLOCAL=aclocal-1.9 + ACLOCAL=aclocal-1.11 export ACLOCAL fi diff --git a/configure.ac b/configure.ac index a35565f9c..a50c29034 100644 --- a/configure.ac +++ b/configure.ac @@ -101,6 +101,7 @@ AC_ARG_ENABLE(coding-style-checks, if test x$enable_debug = xyes; then AC_DEFINE(ENABLE_DEBUG, [], [Enable debug code]) fi +AM_CONDITIONAL([ENABLE_DEBUG], [test "x$enable_debug" = xyes]) if test x$enable_handle_leak_debug = xyes; then AC_DEFINE(ENABLE_HANDLE_LEAK_DEBUG, [], [Enable handle leak debug code]) @@ -221,6 +222,9 @@ fi if test x$enable_silent_rules != xno; then confcmd="$confcmd --enable-silent-rules" fi +if test x$werror == xno; then + confcmd="$confcmd --disable-Werror" +fi confcmd="$confcmd --with-tls=$with_wocky_tls" cd "$ac_top_build_prefix"lib/ext/wocky && echo "running $confcmd" && $confcmd || diff --git a/lib/ext/wocky b/lib/ext/wocky -Subproject d02987e47e4a4a9e967365a06799449c85f43ec +Subproject 52068812840405391668f201d81bc1103c084ce diff --git a/lib/gibber/gibber-fd-transport.c b/lib/gibber/gibber-fd-transport.c index 4837c11c8..710809f3a 100644 --- a/lib/gibber/gibber-fd-transport.c +++ b/lib/gibber/gibber-fd-transport.c @@ -288,6 +288,7 @@ _channel_io_in (GIOChannel *source, GIOCondition condition, gpointer data) break; case GIBBER_FD_IO_RESULT_ERROR: gibber_transport_emit_error (GIBBER_TRANSPORT(self), error); + /* Deliberately falling through */ case GIBBER_FD_IO_RESULT_EOF: DEBUG("Failed to read from the transport, closing.."); _do_disconnect (self); diff --git a/src/bytestream-factory.c b/src/bytestream-factory.c index 4336aa48c..728d2a414 100644 --- a/src/bytestream-factory.c +++ b/src/bytestream-factory.c @@ -297,8 +297,6 @@ disco_item_found_cb (GabbleDisco *disco, send_proxy_query (self, item->jid, FALSE); } -static void query_socks5_proxies (GabbleBytestreamFactory *self); - static gboolean socks5_proxies_timeout_cb (gpointer data) { @@ -316,13 +314,14 @@ socks5_proxies_timeout_cb (gpointer data) return FALSE; } - query_socks5_proxies (self); + gabble_bytestream_factory_query_socks5_proxies (self); return FALSE; } -static void -query_socks5_proxies (GabbleBytestreamFactory *self) +/* ask to the factory to try to find more proxies if needed */ +void +gabble_bytestream_factory_query_socks5_proxies (GabbleBytestreamFactory *self) { GabbleBytestreamFactoryPrivate *priv = GABBLE_BYTESTREAM_FACTORY_GET_PRIVATE ( self); @@ -422,8 +421,6 @@ conn_status_changed_cb (GabbleConnection *conn, priv->socks5_potential_proxies = randomize_g_slist ( priv->socks5_potential_proxies); - query_socks5_proxies (self); - g_strfreev (jids); } } diff --git a/src/bytestream-factory.h b/src/bytestream-factory.h index dcc58fc53..ff5523de6 100644 --- a/src/bytestream-factory.h +++ b/src/bytestream-factory.h @@ -109,6 +109,9 @@ gchar *gabble_bytestream_factory_generate_stream_id (void); GSList *gabble_bytestream_factory_get_socks5_proxies ( GabbleBytestreamFactory *self); +void gabble_bytestream_factory_query_socks5_proxies ( + GabbleBytestreamFactory *self); + G_END_DECLS #endif /* #ifndef __BYTESTREAM_FACTORY_H__ */ diff --git a/src/capabilities.c b/src/capabilities.c index 4b9f44a34..0a241cf40 100644 --- a/src/capabilities.c +++ b/src/capabilities.c @@ -63,7 +63,7 @@ static const Feature self_advertised_features[] = { FEATURE_FIXED, NS_IBB }, { FEATURE_FIXED, NS_TUBES }, { FEATURE_FIXED, NS_BYTESTREAMS }, - { FEATURE_FIXED, NS_FILE_TRANSFER }, + { FEATURE_OPTIONAL, NS_FILE_TRANSFER }, { FEATURE_OPTIONAL, NS_GOOGLE_TRANSPORT_P2P }, { FEATURE_OPTIONAL, NS_JINGLE_TRANSPORT_ICEUDP }, diff --git a/src/capabilities.h b/src/capabilities.h index 6b4266803..9a4cbfd3e 100644 --- a/src/capabilities.h +++ b/src/capabilities.h @@ -112,6 +112,8 @@ const GabbleCapabilitySet *gabble_capabilities_get_olpc_notify (void); const GabbleCapabilitySet *gabble_capabilities_get_bundle_voice_v1 (void); const GabbleCapabilitySet *gabble_capabilities_get_bundle_video_v1 (void); +#define BUNDLE_PMUC_V1 "pmuc-v1" + /* * capabilities_fill_cache * diff --git a/src/connection-manager.c b/src/connection-manager.c index 5753b2714..9e798d0cd 100644 --- a/src/connection-manager.c +++ b/src/connection-manager.c @@ -126,7 +126,9 @@ static TpCMParamSpec jabber_params[] = { /* FIXME: validate the JID according to the RFC */ tp_cm_param_filter_string_nonempty, NULL }, { "password", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, - TP_CONN_MGR_PARAM_FLAG_REQUIRED | TP_CONN_MGR_PARAM_FLAG_REGISTER, NULL, + TP_CONN_MGR_PARAM_FLAG_REQUIRED | TP_CONN_MGR_PARAM_FLAG_REGISTER | + TP_CONN_MGR_PARAM_FLAG_SECRET, + NULL, G_STRUCT_OFFSET(GabbleParams, password), NULL, NULL }, { "server", DBUS_TYPE_STRING_AS_STRING, G_TYPE_STRING, 0, NULL, diff --git a/src/connection-manager.h b/src/connection-manager.h index 4ef773511..89a5f50f4 100644 --- a/src/connection-manager.h +++ b/src/connection-manager.h @@ -36,8 +36,6 @@ struct _GabbleConnectionManagerClass { struct _GabbleConnectionManager { TpBaseConnectionManager parent; - - GabbleConnectionManagerPrivate *priv; }; GType gabble_connection_manager_get_type (void); diff --git a/src/connection.c b/src/connection.c index d301d3dbf..1d91a4573 100644 --- a/src/connection.c +++ b/src/connection.c @@ -40,7 +40,6 @@ #include <telepathy-glib/handle-repo-dynamic.h> #include <telepathy-glib/handle-repo-static.h> #include <telepathy-glib/interfaces.h> -#include <telepathy-glib/svc-connection.h> #include <telepathy-glib/svc-generic.h> #include "extensions/extensions.h" @@ -81,7 +80,6 @@ static guint disco_reply_timeout = 5; #define DISCONNECT_TIMEOUT 5 -static void conn_service_iface_init (gpointer, gpointer); static void capabilities_service_iface_init (gpointer, gpointer); static void gabble_conn_contact_caps_iface_init (gpointer, gpointer); static void conn_capabilities_fill_contact_attributes (GObject *obj, @@ -92,8 +90,6 @@ static void conn_contact_capabilities_fill_contact_attributes (GObject *obj, G_DEFINE_TYPE_WITH_CODE(GabbleConnection, gabble_connection, TP_TYPE_BASE_CONNECTION, - G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION, - conn_service_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_ALIASING, conn_aliasing_iface_init); G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_AVATARS, @@ -665,7 +661,7 @@ _gabble_connection_create_handle_repos (TpBaseConnection *conn, gabble_normalize_contact, GUINT_TO_POINTER (GABBLE_JID_ANY)); repos[TP_HANDLE_TYPE_ROOM] = tp_dynamic_handle_repo_new (TP_HANDLE_TYPE_ROOM, gabble_normalize_room, - NULL); + conn); repos[TP_HANDLE_TYPE_GROUP] = tp_dynamic_handle_repo_new (TP_HANDLE_TYPE_GROUP, NULL, NULL); repos[TP_HANDLE_TYPE_LIST] = @@ -1970,6 +1966,7 @@ _gabble_connection_signal_own_presence (GabbleConnection *self, GError **error) gboolean ret; gchar *caps_hash; gboolean voice_v1, video_v1; + GString *ext = g_string_new (""); if (presence->status == GABBLE_PRESENCE_HIDDEN) { @@ -1995,27 +1992,19 @@ _gabble_connection_signal_own_presence (GabbleConnection *self, GError **error) /* XEP-0115 deprecates 'ext' feature bundles. But we still need * BUNDLE_VOICE_V1 it for backward-compatibility with Gabble 0.2 */ + g_string_append (ext, BUNDLE_PMUC_V1); + voice_v1 = gabble_presence_has_cap (presence, NS_GOOGLE_FEAT_VOICE); video_v1 = gabble_presence_has_cap (presence, NS_GOOGLE_FEAT_VIDEO); - if (voice_v1 || video_v1) - { - GString *ext = g_string_new (""); - - if (voice_v1) - g_string_append (ext, BUNDLE_VOICE_V1); + if (voice_v1) + g_string_append (ext, " " BUNDLE_VOICE_V1); - if (video_v1) - { - if (ext->len > 0) - g_string_append_c (ext, ' '); - g_string_append (ext, BUNDLE_VIDEO_V1); - } + if (video_v1) + g_string_append (ext, " " BUNDLE_VIDEO_V1); - lm_message_node_set_attribute (node, "ext", ext->str); - - g_string_free (ext, TRUE); - } + lm_message_node_set_attribute (node, "ext", ext->str); + g_string_free (ext, TRUE); ret = _gabble_connection_send (self, message, error); @@ -2256,15 +2245,19 @@ connection_iq_disco_cb (LmMessageHandler *handler, features = gabble_capabilities_get_bundle_video_v1 (); } - if (features == NULL) + if (features == NULL && tp_strdiff (suffix, BUNDLE_PMUC_V1)) { _gabble_connection_send_iq_error (self, message, XMPP_ERROR_ITEM_NOT_FOUND, NULL); } else { - gabble_capability_set_foreach (features, add_feature_node, - result_query); + /* Send an empty reply for a pmuc-v1 disco, matching Google's behaviour. */ + if (features != NULL) + { + gabble_capability_set_foreach (features, add_feature_node, + result_query); + } NODE_DEBUG (result_iq, "sending disco response"); @@ -3074,8 +3067,8 @@ _gabble_connection_find_conference_server (GabbleConnection *conn) } -static gchar * -_gabble_connection_get_canonical_room_name (GabbleConnection *conn, +gchar * +gabble_connection_get_canonical_room_name (GabbleConnection *conn, const gchar *name) { const gchar *server; @@ -3093,355 +3086,6 @@ _gabble_connection_get_canonical_room_name (GabbleConnection *conn, return gabble_encode_jid (name, server, NULL); } - -typedef struct _RoomVerifyContext RoomVerifyContext; - -typedef struct { - GabbleConnection *conn; - DBusGMethodInvocation *invocation; - gboolean errored; - guint count; - GArray *handles; - RoomVerifyContext *contexts; -} RoomVerifyBatch; - -struct _RoomVerifyContext { - gchar *jid; - guint index; - RoomVerifyBatch *batch; - GabbleDiscoRequest *request; -}; - -static void -room_verify_batch_free (RoomVerifyBatch *batch) -{ - TpBaseConnection *base = (TpBaseConnection *) (batch->conn); - TpHandleRepoIface *room_handles = tp_base_connection_get_handles (base, - TP_HANDLE_TYPE_ROOM); - guint i; - - tp_handles_unref (room_handles, batch->handles); - g_array_free (batch->handles, TRUE); - for (i = 0; i < batch->count; i++) - { - g_free (batch->contexts[i].jid); - } - g_free (batch->contexts); - g_slice_free (RoomVerifyBatch, batch); -} - -/* Frees the error and the batch. */ -static void -room_verify_batch_raise_error (RoomVerifyBatch *batch, - GError *error) -{ - guint i; - - dbus_g_method_return_error (batch->invocation, error); - g_error_free (error); - batch->errored = TRUE; - for (i = 0; i < batch->count; i++) - { - if (batch->contexts[i].request) - { - gabble_disco_cancel_request (batch->conn->disco, - batch->contexts[i].request); - } - } - room_verify_batch_free (batch); -} - -static RoomVerifyBatch * -room_verify_batch_new (GabbleConnection *conn, - DBusGMethodInvocation *invocation, - guint count, - const gchar **jids) -{ - TpBaseConnection *base = (TpBaseConnection *) conn; - TpHandleRepoIface *room_handles = tp_base_connection_get_handles (base, - TP_HANDLE_TYPE_ROOM); - RoomVerifyBatch *batch = g_slice_new (RoomVerifyBatch); - guint i; - - batch->errored = FALSE; - batch->conn = conn; - batch->invocation = invocation; - batch->count = count; - batch->handles = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle), count); - batch->contexts = g_new0(RoomVerifyContext, count); - for (i = 0; i < count; i++) - { - const gchar *name = jids[i]; - gchar *qualified_name; - TpHandle handle; - - batch->contexts[i].index = i; - batch->contexts[i].batch = batch; - - qualified_name = _gabble_connection_get_canonical_room_name (conn, name); - - if (!qualified_name) - { - GError *error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE, - "requested room handle %s does not specify a server, but we " - "have not discovered any local conference servers and no " - "fallback was provided", name); - DEBUG ("%s", error->message); - room_verify_batch_raise_error (batch, error); - return NULL; - } - - batch->contexts[i].jid = qualified_name; - - /* has the handle been verified before? */ - handle = tp_handle_lookup (room_handles, qualified_name, NULL, NULL); - if (handle) - tp_handle_ref (room_handles, handle); - g_array_append_val (batch->handles, handle); - } - - return batch; -} - -/* If all handles in the array have been disco'd or got from cache, -free the batch and return TRUE. Else return FALSE. */ -static gboolean -room_verify_batch_try_return (RoomVerifyBatch *batch) -{ - guint i; - TpHandleRepoIface *room_handles = tp_base_connection_get_handles ( - (TpBaseConnection *) batch->conn, TP_HANDLE_TYPE_ROOM); - gchar *sender; - GError *error = NULL; - - for (i = 0; i < batch->count; i++) - { - if (!g_array_index (batch->handles, TpHandle, i)) - { - /* we're not ready yet */ - return FALSE; - } - } - - sender = dbus_g_method_get_sender (batch->invocation); - if (!tp_handles_client_hold (room_handles, sender, batch->handles, &error)) - { - g_assert (error != NULL); - } - g_free (sender); - - if (error == NULL) - { - tp_svc_connection_return_from_request_handles (batch->invocation, - batch->handles); - } - else - { - dbus_g_method_return_error (batch->invocation, error); - g_error_free (error); - } - - room_verify_batch_free (batch); - return TRUE; -} - -static void -room_jid_disco_cb (GabbleDisco *disco, - GabbleDiscoRequest *request, - const gchar *jid, - const gchar *node, - LmMessageNode *query_result, - GError *error, - gpointer user_data) -{ - RoomVerifyContext *rvctx = user_data; - RoomVerifyBatch *batch = rvctx->batch; - TpHandleRepoIface *room_handles = tp_base_connection_get_handles ( - (TpBaseConnection *) batch->conn, TP_HANDLE_TYPE_ROOM); - gboolean found = FALSE; - TpHandle handle; - NodeIter i; - - /* stop the request getting cancelled after it's already finished */ - rvctx->request = NULL; - - /* if an error is being handled already, quietly go away */ - if (batch->errored) - { - return; - } - - if (error != NULL) - { - DEBUG ("disco reply error %s", error->message); - - /* disco will free the old error, _raise_error will free the new one */ - error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE, - "can't retrieve room info: %s", error->message); - - room_verify_batch_raise_error (batch, error); - - return; - } - - for (i = node_iter (query_result); i; i = node_iter_next (i)) - { - LmMessageNode *lm_node = node_iter_data (i); - const gchar *var; - - if (tp_strdiff (lm_node->name, "feature")) - continue; - - var = lm_message_node_get_attribute (lm_node, "var"); - - /* for servers who consider schema compliance to be an optional bonus */ - if (var == NULL) - var = lm_message_node_get_attribute (lm_node, "type"); - - if (!tp_strdiff (var, NS_MUC)) - { - found = TRUE; - break; - } - } - - if (!found) - { - DEBUG ("no MUC support for service name in jid %s", rvctx->jid); - - error = g_error_new (TP_ERRORS, TP_ERROR_NOT_AVAILABLE, - "specified server doesn't support MUC"); - - room_verify_batch_raise_error (batch, error); - - return; - } - - /* this refs the handle, so we're putting a ref in batch->handles */ - handle = tp_handle_ensure (room_handles, rvctx->jid, NULL, &error); - if (handle == 0) - { - room_verify_batch_raise_error (batch, error); - return; - } - - DEBUG ("disco reported MUC support for service name in jid %s", rvctx->jid); - g_array_index (batch->handles, TpHandle, rvctx->index) = handle; - - /* if this was the last callback to be run, send off the result */ - room_verify_batch_try_return (batch); -} - -/** - * room_jid_verify: - * - * Utility function that verifies that the service name of - * the specified jid exists and reports MUC support. - */ -static gboolean -room_jid_verify (RoomVerifyBatch *batch, - guint i, - DBusGMethodInvocation *context) -{ - gchar *room, *service; - gboolean ret; - GError *error = NULL; - - room = service = NULL; - - if (!gabble_decode_jid (batch->contexts[i].jid, &room, &service, NULL) || - room == NULL) - { - g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, - "unable to get room name and service from JID %s", - batch->contexts[i].jid); - ret = FALSE; - goto out; - } - - ret = (gabble_disco_request (batch->conn->disco, GABBLE_DISCO_TYPE_INFO, - service, NULL, room_jid_disco_cb, - batch->contexts + i, - G_OBJECT (batch->conn), &error) != NULL); - -out: - if (!ret) - { - room_verify_batch_raise_error (batch, error); - } - - g_free (room); - g_free (service); - - return ret; -} - - -/** - * gabble_connection_request_handles - * - * Implements D-Bus method RequestHandles - * on interface org.freedesktop.Telepathy.Connection - * - * @context: The D-Bus invocation context to use to return values - * or throw an error. - */ -static void -gabble_connection_request_handles (TpSvcConnection *iface, - guint handle_type, - const gchar **names, - DBusGMethodInvocation *context) -{ - GabbleConnection *self = GABBLE_CONNECTION (iface); - TpBaseConnection *base = (TpBaseConnection *) self; - - g_assert (GABBLE_IS_CONNECTION (self)); - - TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context); - - if (handle_type == TP_HANDLE_TYPE_ROOM) - { - RoomVerifyBatch *batch = NULL; - guint count = 0, i; - const gchar **cur_name; - - for (cur_name = names; *cur_name != NULL; cur_name++) - { - count++; - } - - batch = room_verify_batch_new (self, context, count, names); - if (!batch) - { - /* an error occurred while setting up the batch, and we returned - error to dbus */ - return; - } - - /* have all the handles been verified already? If so, nothing to do */ - if (room_verify_batch_try_return (batch)) - { - return; - } - - for (i = 0; i < count; i++) - { - if (!room_jid_verify (batch, i, context)) - { - return; - } - } - - /* we've set the verification process going - the callback will handle - returning or raising error */ - return; - } - - /* else it's either an invalid type, or a type we can verify immediately - - * in either case, let the superclass do it */ - tp_base_connection_dbus_request_handles (iface, handle_type, names, context); -} - void gabble_connection_ensure_capabilities (GabbleConnection *self, const GabbleCapabilitySet *ensured) @@ -3477,19 +3121,6 @@ gabble_connection_send_presence (GabbleConnection *conn, return result; } -/* We reimplement RequestHandles to be able to do async validation on - * room handles */ -static void -conn_service_iface_init (gpointer g_iface, gpointer iface_data) -{ - TpSvcConnectionClass *klass = (TpSvcConnectionClass *) g_iface; - -#define IMPLEMENT(x) tp_svc_connection_implement_##x (klass, \ - gabble_connection_##x) - IMPLEMENT(request_handles); -#undef IMPLEMENT -} - static void capabilities_service_iface_init (gpointer g_iface, gpointer iface_data) { diff --git a/src/connection.h b/src/connection.h index e15c25d6c..383393ee1 100644 --- a/src/connection.h +++ b/src/connection.h @@ -55,10 +55,9 @@ G_BEGIN_DECLS "proxy.downtempo.de",\ "proxy.im.flosoft.biz",\ "proxy.jabber.bluendo.com", "proxy.jabber.dk", "proxy.jabber.freenet.de",\ - "proxy.fsinf.at", "proxy.jabber.minus273.org",\ + "proxy.jabber.minus273.org",\ "proxy.jabber.planetteamspeak.com", "proxy.jabber.tf-network.de",\ "proxy.jabjab.de", "proxy.jabster.pl",\ - "proxy.schokokeks.org",\ "proxy.ubuntu-jabber.de", "proxy.ubuntu-jabber.net",\ "proxy65.unstable.nl", "proxy.verdammung.org", "proxy.vke.ru",\ "proxy.vodka-pomme.net", "proxy.jabbernet.eu",\ @@ -253,6 +252,8 @@ void _gabble_connection_send_iq_error (GabbleConnection *conn, LmMessage *message, GabbleXmppError error, const gchar *errmsg); const char *_gabble_connection_find_conference_server (GabbleConnection *); +gchar *gabble_connection_get_canonical_room_name (GabbleConnection *conn, + const gchar *jid); gboolean _gabble_connection_signal_own_presence (GabbleConnection *, GError **); diff --git a/src/ft-channel.c b/src/ft-channel.c index 516238433..2450a0b9d 100644 --- a/src/ft-channel.c +++ b/src/ft-channel.c @@ -494,6 +494,13 @@ gabble_file_transfer_channel_constructor (GType type, tp_handle_inspect (contact_repo, self->priv->initiator), self->priv->filename, self->priv->size); + if (self->priv->initiator == base_conn->self_handle) + { + /* Outgoing FT , we'll need SOCK5 proxies when we'll offer the file */ + gabble_bytestream_factory_query_socks5_proxies ( + self->priv->connection->bytestream_factory); + } + return obj; } diff --git a/src/ft-manager.c b/src/ft-manager.c index 87338a35c..5ab700227 100644 --- a/src/ft-manager.c +++ b/src/ft-manager.c @@ -545,8 +545,6 @@ void gabble_ft_manager_handle_si_request (GabbleFtManager *self, { GTimeVal val; - g_time_val_from_iso8601 (date_str, &val); - /* FIXME: this assume the timezone is always UTC */ if (g_time_val_from_iso8601 (date_str, &val)) date = val.tv_sec; @@ -601,16 +599,13 @@ gabble_ft_manager_get_tmp_dir (GabbleFtManager *self) } static void -add_file_transfer_channel_class (GPtrArray *arr, - TpHandle handle) +add_file_transfer_channel_class (GPtrArray *arr) { GValue monster = {0, }; GHashTable *fixed_properties; GValue *channel_type_value; GValue *target_handle_type_value; - g_assert (handle != 0); - g_value_init (&monster, TP_STRUCT_TYPE_REQUESTABLE_CHANNEL_CLASS); g_value_take_boxed (&monster, dbus_g_type_specialized_construct ( @@ -641,19 +636,46 @@ add_file_transfer_channel_class (GPtrArray *arr, } static void -gabble_ft_manager_get_contact_caps (GabbleCapsChannelManager *manager, - TpHandle handle, +gabble_ft_manager_get_contact_caps ( + GabbleCapsChannelManager *manager G_GNUC_UNUSED, + TpHandle handle G_GNUC_UNUSED, const GabbleCapabilitySet *caps, GPtrArray *arr) { - GabbleFtManager *self = GABBLE_FT_MANAGER (manager); + if (gabble_capability_set_has (caps, NS_FILE_TRANSFER)) + add_file_transfer_channel_class (arr); +} - g_assert (handle != 0); +static void +gabble_ft_manager_represent_client ( + GabbleCapsChannelManager *manager G_GNUC_UNUSED, + const gchar *client_name, + const GPtrArray *filters, + const gchar * const *cap_tokens G_GNUC_UNUSED, + GabbleCapabilitySet *cap_set) +{ + guint i; - /* We always support file transfer */ - if (handle == self->priv->connection->parent.self_handle || - gabble_capability_set_has (caps, NS_FILE_TRANSFER)) - add_file_transfer_channel_class (arr, handle); + for (i = 0; i < filters->len; i++) + { + GHashTable *channel_class = g_ptr_array_index (filters, i); + + if (tp_strdiff (tp_asv_get_string (channel_class, + TP_IFACE_CHANNEL ".ChannelType"), + TP_IFACE_CHANNEL_TYPE_FILE_TRANSFER)) + continue; + + if (tp_asv_get_uint32 (channel_class, + TP_IFACE_CHANNEL ".TargetHandleType", NULL) + != TP_HANDLE_TYPE_CONTACT) + continue; + + DEBUG ("client %s supports file transfer", client_name); + gabble_capability_set_add (cap_set, NS_FILE_TRANSFER); + /* there's no point in looking at the subsequent filters if we've + * already added the FT capability */ + break; + } } static void @@ -663,4 +685,5 @@ caps_channel_manager_iface_init (gpointer g_iface, GabbleCapsChannelManagerIface *iface = g_iface; iface->get_contact_caps = gabble_ft_manager_get_contact_caps; + iface->represent_client = gabble_ft_manager_represent_client; } diff --git a/src/gabble.c b/src/gabble.c index 992d12e08..18ce1ac51 100644 --- a/src/gabble.c +++ b/src/gabble.c @@ -144,6 +144,7 @@ gabble_main (int argc, construct_cm, argc, argv); #ifdef ENABLE_DEBUG + g_log_set_default_handler (g_log_default_handler, NULL); g_object_unref (debug_sender); #endif diff --git a/src/im-channel.c b/src/im-channel.c index 6db5e53f6..f4edc54cb 100644 --- a/src/im-channel.c +++ b/src/im-channel.c @@ -95,6 +95,12 @@ enum /* private structure */ +typedef enum { + CHAT_STATES_UNKNOWN, + CHAT_STATES_SUPPORTED, + CHAT_STATES_NOT_SUPPORTED +} ChatStateSupport; + struct _GabbleIMChannelPrivate { GabbleConnection *conn; @@ -104,6 +110,7 @@ struct _GabbleIMChannelPrivate gchar *peer_jid; gboolean send_nick; + ChatStateSupport chat_states_supported; /* FALSE unless at least one chat state notification has been sent; <gone/> * will only be sent when the channel closes if this is TRUE. This prevents @@ -172,6 +179,8 @@ gabble_im_channel_constructor (GType type, guint n_props, else priv->send_nick = TRUE; + priv->chat_states_supported = CHAT_STATES_UNKNOWN; + tp_message_mixin_init (obj, G_STRUCT_OFFSET (GabbleIMChannel, message_mixin), conn); @@ -394,20 +403,43 @@ gabble_im_channel_class_init (GabbleIMChannelClass *gabble_im_channel_class) tp_message_mixin_init_dbus_properties (object_class); } +static gboolean +chat_states_supported (GabbleIMChannel *self, + gboolean include_unknown) +{ + GabbleIMChannelPrivate *priv = self->priv; + GabblePresence *presence; + + presence = gabble_presence_cache_get (priv->conn->presence_cache, + priv->handle); + + if (presence != NULL && gabble_presence_has_cap (presence, NS_CHAT_STATES)) + return TRUE; + + switch (priv->chat_states_supported) + { + case CHAT_STATES_UNKNOWN: + return include_unknown; + case CHAT_STATES_SUPPORTED: + return TRUE; + case CHAT_STATES_NOT_SUPPORTED: + return FALSE; + default: + g_assert_not_reached (); + return FALSE; + } +} + static void emit_closed_and_send_gone (GabbleIMChannel *self) { GabbleIMChannelPrivate *priv = self->priv; - GabblePresence *presence; if (priv->send_gone) { - presence = gabble_presence_cache_get (priv->conn->presence_cache, - priv->handle); - - if (presence && gabble_presence_has_cap (presence, NS_CHAT_STATES)) + if (chat_states_supported (self, FALSE)) gabble_message_util_send_chat_state (G_OBJECT (self), priv->conn, - LM_MESSAGE_SUB_TYPE_NORMAL, TP_CHANNEL_CHAT_STATE_GONE, + LM_MESSAGE_SUB_TYPE_CHAT, TP_CHANNEL_CHAT_STATE_GONE, priv->peer_jid, NULL); priv->send_gone = FALSE; @@ -487,16 +519,12 @@ _gabble_im_channel_send_message (GObject *object, { GabbleIMChannel *self = GABBLE_IM_CHANNEL (object); GabbleIMChannelPrivate *priv; - GabblePresence *presence; gint state = -1; g_assert (GABBLE_IS_IM_CHANNEL (self)); priv = self->priv; - presence = gabble_presence_cache_get (priv->conn->presence_cache, - priv->handle); - - if (presence && gabble_presence_has_cap (presence, NS_CHAT_STATES)) + if (chat_states_supported (self, TRUE)) { state = TP_CHANNEL_CHAT_STATE_ACTIVE; priv->send_gone = TRUE; @@ -512,7 +540,6 @@ _gabble_im_channel_send_message (GObject *object, priv->send_nick = FALSE; } - /** * _gabble_im_channel_receive * @@ -526,7 +553,8 @@ _gabble_im_channel_receive (GabbleIMChannel *chan, const gchar *id, const char *text, TpChannelTextSendError send_error, - TpDeliveryStatus delivery_status) + TpDeliveryStatus delivery_status, + gint state) { GabbleIMChannelPrivate *priv; TpBaseConnection *base_conn; @@ -545,6 +573,19 @@ _gabble_im_channel_receive (GabbleIMChannel *chan, g_free (priv->peer_jid); priv->peer_jid = g_strdup (from); } + + if (state == -1) + { + priv->chat_states_supported = CHAT_STATES_NOT_SUPPORTED; + } + else + { + priv->chat_states_supported = CHAT_STATES_SUPPORTED; + + tp_svc_channel_interface_chat_state_emit_chat_state_changed ( + (TpSvcChannelInterfaceChatState *) chan, + priv->handle, (TpChannelChatState) state); + } } else { @@ -553,6 +594,8 @@ _gabble_im_channel_receive (GabbleIMChannel *chan, if (slash != NULL) *slash = '\0'; + + priv->chat_states_supported = CHAT_STATES_UNKNOWN; } msg = tp_message_new (base_conn, 2, 2); @@ -629,7 +672,7 @@ _gabble_im_channel_receive (GabbleIMChannel *chan, void _gabble_im_channel_state_receive (GabbleIMChannel *chan, - guint state) + TpChannelChatState state) { GabbleIMChannelPrivate *priv; @@ -637,6 +680,8 @@ _gabble_im_channel_state_receive (GabbleIMChannel *chan, g_assert (GABBLE_IS_IM_CHANNEL (chan)); priv = chan->priv; + priv->chat_states_supported = CHAT_STATES_SUPPORTED; + tp_svc_channel_interface_chat_state_emit_chat_state_changed ( (TpSvcChannelInterfaceChatState *) chan, priv->handle, state); @@ -790,53 +835,47 @@ gabble_im_channel_set_chat_state (TpSvcChannelInterfaceChatState *iface, { GabbleIMChannel *self = GABBLE_IM_CHANNEL (iface); GabbleIMChannelPrivate *priv; - GabblePresence *presence; GError *error = NULL; g_assert (GABBLE_IS_IM_CHANNEL (self)); priv = self->priv; - presence = gabble_presence_cache_get (priv->conn->presence_cache, - priv->handle); - - if (presence && gabble_presence_has_cap (presence, NS_CHAT_STATES)) + if (state >= NUM_TP_CHANNEL_CHAT_STATES) { - if (state >= NUM_TP_CHANNEL_CHAT_STATES) - { - DEBUG ("invalid state %u", state); - - g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, - "invalid state: %u", state); - } - - if (state == TP_CHANNEL_CHAT_STATE_GONE) - { - /* We cannot explicitly set the Gone state */ - DEBUG ("you may not explicitly set the Gone state"); - - g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, - "you may not explicitly set the Gone state"); - } - else if (gabble_message_util_send_chat_state (G_OBJECT (self), priv->conn, - LM_MESSAGE_SUB_TYPE_NORMAL, state, priv->peer_jid, &error)) + g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "invalid state: %u", state); + } + else if (state == TP_CHANNEL_CHAT_STATE_GONE) + { + g_set_error (&error, TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, + "you may not explicitly set the Gone state"); + } + /* Only send anything to the peer if we actually know they support chat + * states. + */ + else if (chat_states_supported (self, FALSE)) + { + if (gabble_message_util_send_chat_state (G_OBJECT (self), priv->conn, + LM_MESSAGE_SUB_TYPE_CHAT, state, priv->peer_jid, &error)) { priv->send_gone = TRUE; - } - - if (error != NULL) - { - dbus_g_method_return_error (context, error); - g_error_free (error); - return; + /* Send the ChatStateChanged signal for the local user */ + tp_svc_channel_interface_chat_state_emit_chat_state_changed (iface, + priv->conn->parent.self_handle, state); } - - /* Send the ChatStateChanged signal for the local user */ - tp_svc_channel_interface_chat_state_emit_chat_state_changed (iface, - priv->conn->parent.self_handle, state); } - tp_svc_channel_interface_chat_state_return_from_set_chat_state (context); + if (error != NULL) + { + DEBUG ("%s", error->message); + dbus_g_method_return_error (context, error); + g_error_free (error); + } + else + { + tp_svc_channel_interface_chat_state_return_from_set_chat_state (context); + } } static void diff --git a/src/im-channel.h b/src/im-channel.h index 7d7d96a14..2a2a2dc1e 100644 --- a/src/im-channel.h +++ b/src/im-channel.h @@ -66,10 +66,17 @@ GType gabble_im_channel_get_type (void); GabbleIMChannelClass)) void _gabble_im_channel_receive (GabbleIMChannel *chan, - TpChannelTextMessageType type, TpHandle sender, const char *from, - time_t timestamp, const char *id, const char *text, - TpChannelTextSendError send_error, TpDeliveryStatus delivery_status); -void _gabble_im_channel_state_receive (GabbleIMChannel *chan, guint state); + TpChannelTextMessageType type, + TpHandle sender, + const char *from, + time_t timestamp, + const char *id, + const char *text, + TpChannelTextSendError send_error, + TpDeliveryStatus delivery_status, + gint state); +void _gabble_im_channel_state_receive (GabbleIMChannel *chan, + TpChannelChatState state); G_END_DECLS diff --git a/src/im-factory.c b/src/im-factory.c index 8f5cccca0..d2b12a112 100644 --- a/src/im-factory.c +++ b/src/im-factory.c @@ -283,12 +283,12 @@ im_factory_message_cb (LmMessageHandler *handler, from, handle, msgtype, body); } - if (state != -1 && send_error == GABBLE_TEXT_CHANNEL_SEND_NO_ERROR) - _gabble_im_channel_state_receive (chan, state); - if (body != NULL) _gabble_im_channel_receive (chan, msgtype, handle, from, stamp, id, body, - send_error, delivery_status); + send_error, delivery_status, state); + else if (state != -1 && send_error == GABBLE_TEXT_CHANNEL_SEND_NO_ERROR) + _gabble_im_channel_state_receive (chan, (TpChannelChatState) state); + return LM_HANDLER_RESULT_REMOVE_MESSAGE; } diff --git a/src/muc-factory.c b/src/muc-factory.c index 2caba4787..fd3b10d35 100644 --- a/src/muc-factory.c +++ b/src/muc-factory.c @@ -62,7 +62,6 @@ enum LAST_PROPERTY }; -typedef struct _GabbleMucFactoryPrivate GabbleMucFactoryPrivate; struct _GabbleMucFactoryPrivate { GabbleConnection *conn; @@ -105,6 +104,8 @@ gabble_muc_factory_init (GabbleMucFactory *fac) { GabbleMucFactoryPrivate *priv = GABBLE_MUC_FACTORY_GET_PRIVATE (fac); + fac->priv = priv; + priv->text_channels = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref); priv->tubes_channels = g_hash_table_new_full (g_direct_hash, g_direct_equal, diff --git a/src/muc-factory.h b/src/muc-factory.h index c17935ef4..8a5485bff 100644 --- a/src/muc-factory.h +++ b/src/muc-factory.h @@ -29,6 +29,7 @@ G_BEGIN_DECLS typedef struct _GabbleMucFactory GabbleMucFactory; typedef struct _GabbleMucFactoryClass GabbleMucFactoryClass; +typedef struct _GabbleMucFactoryPrivate GabbleMucFactoryPrivate; struct _GabbleMucFactoryClass { GObjectClass parent_class; @@ -36,6 +37,7 @@ struct _GabbleMucFactoryClass { struct _GabbleMucFactory { GObject parent; + GabbleMucFactoryPrivate *priv; }; GType gabble_muc_factory_get_type (void); diff --git a/src/register.c b/src/register.c index aff4cb65b..9a9e0ff1b 100644 --- a/src/register.c +++ b/src/register.c @@ -60,7 +60,6 @@ enum G_DEFINE_TYPE(GabbleRegister, gabble_register, G_TYPE_OBJECT); /* private structure */ -typedef struct _GabbleRegisterPrivate GabbleRegisterPrivate; struct _GabbleRegisterPrivate { GabbleConnection *conn; @@ -75,6 +74,8 @@ struct _GabbleRegisterPrivate static void gabble_register_init (GabbleRegister *obj) { + obj->priv = G_TYPE_INSTANCE_GET_PRIVATE (obj, GABBLE_TYPE_REGISTER, + GabbleRegisterPrivate); } static void gabble_register_set_property (GObject *object, guint property_id, diff --git a/src/register.h b/src/register.h index e38e52044..f4ea593d4 100644 --- a/src/register.h +++ b/src/register.h @@ -33,6 +33,7 @@ G_BEGIN_DECLS typedef struct _GabbleRegister GabbleRegister; typedef struct _GabbleRegisterClass GabbleRegisterClass; +typedef struct _GabbleRegisterPrivate GabbleRegisterPrivate; GType gabble_register_get_type (void); @@ -57,6 +58,7 @@ struct _GabbleRegisterClass { struct _GabbleRegister { GObject parent; + GabbleRegisterPrivate *priv; }; GabbleRegister *gabble_register_new (GabbleConnection *conn); diff --git a/src/tube-dbus.c b/src/tube-dbus.c index 9db9f1968..bb4961840 100644 --- a/src/tube-dbus.c +++ b/src/tube-dbus.c @@ -955,6 +955,14 @@ gabble_tube_dbus_constructor (GType type, priv->reassembly_bytes_needed = 0; g_assert (priv->muc == NULL); + + if (priv->requested) + { + /* We created this outgoing 1-1 D-Bus tube and so will need SOCKS5 + * proxies when we'll offer it. */ + gabble_bytestream_factory_query_socks5_proxies ( + priv->conn->bytestream_factory); + } } /* Tube needs to be offered if we initiated AND requested it. Being diff --git a/src/tube-stream.c b/src/tube-stream.c index 9aedd5f98..de9bf4fb0 100644 --- a/src/tube-stream.c +++ b/src/tube-stream.c @@ -1516,6 +1516,10 @@ gabble_tube_stream_constructor (GType type, else { priv->state = TP_TUBE_CHANNEL_STATE_LOCAL_PENDING; + + /* We'll need SOCKS5 proxies if the tube is accepted */ + gabble_bytestream_factory_query_socks5_proxies ( + priv->conn->bytestream_factory); } if (priv->handle_type == TP_HANDLE_TYPE_CONTACT) diff --git a/src/util.c b/src/util.c index d22844d42..351bacda1 100644 --- a/src/util.c +++ b/src/util.c @@ -415,35 +415,47 @@ gabble_normalize_room (TpHandleRepoIface *repo, gpointer context, GError **error) { - char *at = strchr (jid, '@'); - char *slash = strchr (jid, '/'); + GabbleConnection *conn; + gchar *qualified_name, *resource; - /* there'd better be an @ somewhere after the first character */ - if (at == NULL) + /* Only look up the canonical room name if we got a GabbleConnection. + * This should only happen in the test-handles test. */ + if (context != NULL) { - INVALID_HANDLE (error, - "invalid room JID %s: does not contain '@'", jid); - return NULL; + conn = GABBLE_CONNECTION (context); + qualified_name = gabble_connection_get_canonical_room_name (conn, jid); + + if (qualified_name == NULL) + { + INVALID_HANDLE (error, + "requested room handle %s does not specify a server, but we " + "have not discovered any local conference servers and no " + "fallback was provided", jid); + return NULL; + } } - if (at == jid) + else { - INVALID_HANDLE (error, - "invalid room JID %s: room name before '@' may not be empty", jid); + qualified_name = g_strdup (jid); + } + + if (!gabble_decode_jid (qualified_name, NULL, NULL, &resource)) + { + INVALID_HANDLE (error, "room JID %s is invalid", qualified_name); return NULL; } - /* room names can't contain the nick part */ - if (slash != NULL) + if (resource != NULL) { INVALID_HANDLE (error, - "invalid room JID %s: contains nickname part after '/' too", jid); + "invalid room JID %s: contains nickname part after '/' too", + qualified_name); + g_free (qualified_name); + g_free (resource); return NULL; } - /* the room and service parts are both case-insensitive, so lowercase - * them both; gabble_decode_jid is overkill here - */ - return g_utf8_strdown (jid, -1); + return qualified_name; } gchar * diff --git a/src/write-mgr-file.c b/src/write-mgr-file.c index a5c9cd905..8a26a50c9 100644 --- a/src/write-mgr-file.c +++ b/src/write-mgr-file.c @@ -48,9 +48,10 @@ mgr_file_contents (const char *busname, for (row = protocol->parameters; row->name; row++) { gchar *param_name = g_strdup_printf ("param-%s", row->name); - gchar *param_value = g_strdup_printf ("%s%s%s", row->dtype, + gchar *param_value = g_strdup_printf ("%s%s%s%s", row->dtype, (row->flags & TP_CONN_MGR_PARAM_FLAG_REQUIRED ? " required" : ""), - (row->flags & TP_CONN_MGR_PARAM_FLAG_REGISTER ? " register" : "")); + (row->flags & TP_CONN_MGR_PARAM_FLAG_REGISTER ? " register" : ""), + (row->flags & TP_CONN_MGR_PARAM_FLAG_SECRET ? " secret" : "")); g_key_file_set_string (f, section_name, param_name, param_value); g_free (param_value); g_free (param_name); diff --git a/tests/test-base64.c b/tests/test-base64.c index 53c62c55e..49ebbf37d 100644 --- a/tests/test-base64.c +++ b/tests/test-base64.c @@ -86,6 +86,7 @@ main (void) /* test string with embedded NULL */ tmp1 = base64_decode ("Zm9vAGJhcg=="); tmp2 = g_string_new_len ("foo\0bar", 7); + g_assert (tmp1); g_assert (g_string_equal (tmp1, tmp2)); g_string_free (tmp1, TRUE); g_string_free (tmp2, TRUE); diff --git a/tests/twisted/Makefile.am b/tests/twisted/Makefile.am index e6a0f6bcb..0fcf98c5a 100644 --- a/tests/twisted/Makefile.am +++ b/tests/twisted/Makefile.am @@ -184,20 +184,23 @@ check-twisted: exit 1;\ fi +if ENABLE_DEBUG +DEBUGGING_PYBOOL = True +else +DEBUGGING_PYBOOL = False +endif + config.py: Makefile - $(AM_V_GEN)echo "PACKAGE_STRING = \"$(PACKAGE_STRING)\"" > config.py - @if test -n ''$(ENABLE_DEBUG); then \ - echo "DEBUGGING = True" >> config.py; \ - else \ - echo "DEBUGGING = False" >> config.py; \ - fi + $(AM_V_GEN) { \ + echo "PACKAGE_STRING = \"$(PACKAGE_STRING)\""; \ + echo "DEBUGGING = $(DEBUGGING_PYBOOL)"; \ + } > $@ BUILT_SOURCES = config.py EXTRA_DIST = \ $(TWISTED_TESTS) \ bytestream.py \ - config.py \ constants.py \ gabbletest.py \ httptest.py \ diff --git a/tests/twisted/caps/advertise-contact-caps.py b/tests/twisted/caps/advertise-contact-caps.py index 822d050b2..bb91c5891 100644 --- a/tests/twisted/caps/advertise-contact-caps.py +++ b/tests/twisted/caps/advertise-contact-caps.py @@ -188,5 +188,24 @@ def run_test(q, bus, conn, stream): ns.JINGLE_015, ns.JINGLE_RTP_VIDEO, ns.JINGLE_RTP, ns.JINGLE_015_VIDEO]) + # Remove KCall to simplify subsequent checks + conn.ContactCapabilities.UpdateCapabilities([ + (cs.CLIENT + '.KCall', [], []), + ]) + (disco_response, namespaces, _) = receive_presence_and_ask_caps(q, stream, + False) + check_caps(namespaces, []) + + # Support file transfer + conn.ContactCapabilities.UpdateCapabilities([ + (cs.CLIENT + '.FileReceiver', [{ + cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_FILE_TRANSFER, + cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, + }], []), + ]) + (disco_response, namespaces, _) = receive_presence_and_ask_caps(q, stream, + False) + check_caps(namespaces, [ns.FILE_TRANSFER]) + if __name__ == '__main__': exec_test(run_test) diff --git a/tests/twisted/caps/tube-caps.py b/tests/twisted/caps/tube-caps.py index 41913a1da..8ef66d1f3 100644 --- a/tests/twisted/caps/tube-caps.py +++ b/tests/twisted/caps/tube-caps.py @@ -244,7 +244,7 @@ def test_tube_caps_to_contact(q, bus, conn, stream): [(text_fixed_properties, text_allowed_properties), (stream_tube_fixed_properties, stream_tube_allowed_properties), (dbus_tube_fixed_properties, dbus_tube_allowed_properties), - (ft_fixed_properties, ft_allowed_properties)]}) + ]}) daap_caps = dbus.Dictionary({self_handle: [(text_fixed_properties, text_allowed_properties), (stream_tube_fixed_properties, stream_tube_allowed_properties), diff --git a/tests/twisted/caps_helper.py b/tests/twisted/caps_helper.py index 3708fea08..0626e59b2 100644 --- a/tests/twisted/caps_helper.py +++ b/tests/twisted/caps_helper.py @@ -43,9 +43,10 @@ JINGLE_CAPS = [ VARIABLE_CAPS = ( JINGLE_CAPS + [ + ns.FILE_TRANSFER, + # FIXME: currently we always advertise these, but in future we should # only advertise them if >= 1 client supports them: - # ns.FILE_TRANSFER, # ns.TUBES, # there is an unlimited set of these; only the ones actually relevant to diff --git a/tests/twisted/file-transfer/test-caps-file-transfer.py b/tests/twisted/file-transfer/test-caps-file-transfer.py index 1197ee81a..f58a23890 100644 --- a/tests/twisted/file-transfer/test-caps-file-transfer.py +++ b/tests/twisted/file-transfer/test-caps-file-transfer.py @@ -98,27 +98,6 @@ def test_ft_caps_from_contact(q, bus, conn, stream, contact, contact_handle, cli assert caps_via_contacts_iface == caps[contact_handle], \ caps_via_contacts_iface - -def test_ft_caps_to_contact(q, bus, conn, stream): - basic_caps = dbus.Dictionary({1: - [(text_fixed_properties, text_allowed_properties), - (stream_tube_fixed_properties, stream_tube_allowed_properties), - (dbus_tube_fixed_properties, dbus_tube_allowed_properties), - (ft_fixed_properties, ft_allowed_properties)]}) - - conn_caps_iface = dbus.Interface(conn, cs.CONN_IFACE_CONTACT_CAPS) - conn_contacts_iface = dbus.Interface(conn, cs.CONN_IFACE_CONTACTS) - - # Check our own caps - caps = conn_caps_iface.GetContactCapabilities([1]) - assertEquals(basic_caps, caps) - - # check the Contacts interface give the same caps - caps_via_contacts_iface = conn_contacts_iface.GetContactAttributes( - [1], [cs.CONN_IFACE_CONTACT_CAPS], False) \ - [1][cs.CONN_IFACE_CONTACT_CAPS + '/caps'] - assertEquals(caps[1], caps_via_contacts_iface) - def test(q, bus, conn, stream): conn.Connect() q.expect('dbus-signal', signal='StatusChanged', @@ -129,7 +108,8 @@ def test(q, bus, conn, stream): test_ft_caps_from_contact(q, bus, conn, stream, 'bilbo1@foo.com/Foo', 2L, client) - test_ft_caps_to_contact(q, bus, conn, stream) + # our own capabilities, formerly tested here, are now in + # tests/twisted/caps/advertise-contact-capabilities.py if __name__ == '__main__': exec_test(test) diff --git a/tests/twisted/gabbletest.py b/tests/twisted/gabbletest.py index 47fb915b8..1ec927616 100644 --- a/tests/twisted/gabbletest.py +++ b/tests/twisted/gabbletest.py @@ -61,12 +61,6 @@ def send_error_reply(stream, iq, error_stanza=None): def request_muc_handle(q, conn, stream, muc_jid): servicetest.call_async(q, conn, 'RequestHandles', 2, [muc_jid]) - host = muc_jid.split('@')[1] - event = q.expect('stream-iq', to=host, query_ns=ns.DISCO_INFO) - result = make_result_iq(stream, event.stanza) - feature = result.firstChildElement().addElement('feature') - feature['var'] = ns.MUC - stream.send(result) event = q.expect('dbus-return', method='RequestHandles') return event.value[0][0] diff --git a/tests/twisted/muc/test-ensure.py b/tests/twisted/muc/test-ensure.py index fb3064d73..d2d907495 100644 --- a/tests/twisted/muc/test-ensure.py +++ b/tests/twisted/muc/test-ensure.py @@ -18,15 +18,6 @@ def test(q, bus, conn, stream): jids = ['chat@conf.localhost', 'chien@conf.localhost'] call_async(q, conn, 'RequestHandles', 2, jids) - # Gabble is stupid and discos the alleged conf server twice. - for i in [0,1]: - event = q.expect('stream-iq', to='conf.localhost', - query_ns='http://jabber.org/protocol/disco#info') - result = make_result_iq(stream, event.stanza) - feature = result.firstChildElement().addElement('feature') - feature['var'] = 'http://jabber.org/protocol/muc' - stream.send(result) - event = q.expect('dbus-return', method='RequestHandles') room_handles = event.value[0] diff --git a/tests/twisted/muc/test-muc-ownership.py b/tests/twisted/muc/test-muc-ownership.py index 362a16f4c..e8e0bd946 100644 --- a/tests/twisted/muc/test-muc-ownership.py +++ b/tests/twisted/muc/test-muc-ownership.py @@ -23,29 +23,24 @@ def test(q, bus, conn, stream): # query call_async(q, conn, 'RequestHandles', 2, ['chat@conf.localhost']) - event = q.expect('stream-iq', to='conf.localhost', - query_ns='http://jabber.org/protocol/disco#info') - result = make_result_iq(stream, event.stanza) - feature = result.firstChildElement().addElement('feature') - feature['var'] = 'http://jabber.org/protocol/muc' - stream.send(result) - event = q.expect('dbus-return', method='RequestHandles') room_handle = event.value[0][0] call_async(q, conn, 'RequestChannel', cs.CHANNEL_TYPE_TEXT, cs.HT_ROOM, room_handle, True) - gfc, _, _ = q.expect_many( - EventPattern('dbus-signal', signal='GroupFlagsChanged'), + gfc, _, _, _ = q.expect_many( + # Initial group flags + EventPattern('dbus-signal', signal='GroupFlagsChanged', + predicate=lambda e: e.args[0] != 0), EventPattern('dbus-signal', signal='MembersChanged', args=[u'', [], [], [], [2], 0, 0]), + # Removing CAN_ADD + EventPattern('dbus-signal', signal='GroupFlagsChanged', + args = [0, cs.GF_CAN_ADD], predicate=lambda e: e.args[0] == 0), EventPattern('stream-presence', to='chat@conf.localhost/test')) assert gfc.args[1] == 0 - event = q.expect('dbus-signal', signal='GroupFlagsChanged') - assert event.args == [0, 1] - # Send presence for anonymous other member of room. stream.send(make_muc_presence('owner', 'moderator', 'chat@conf.localhost', 'bob')) diff --git a/tests/twisted/olpc/gadget-invite.py b/tests/twisted/olpc/gadget-invite.py index 362490a59..47c45fbc1 100644 --- a/tests/twisted/olpc/gadget-invite.py +++ b/tests/twisted/olpc/gadget-invite.py @@ -17,13 +17,6 @@ import ns def join_channel(name, q, conn, stream): call_async(q, conn, 'RequestHandles', cs.HT_ROOM, [name]) - # announce conference service - event = q.expect('stream-iq', to='conference.localhost', query_ns=ns.DISCO_INFO) - reply = make_result_iq(stream, event.stanza) - feature = reply.firstChildElement().addElement('feature') - feature['var'] = ns.MUC - stream.send(reply) - event = q.expect('dbus-return', method='RequestHandles') handles = event.value[0] diff --git a/tests/twisted/test-fallback-socks5-proxy.py b/tests/twisted/test-fallback-socks5-proxy.py index 73a98f71e..c5266a4e4 100644 --- a/tests/twisted/test-fallback-socks5-proxy.py +++ b/tests/twisted/test-fallback-socks5-proxy.py @@ -1,6 +1,7 @@ import dbus +import socket from gabbletest import exec_test, elem, elem_iq, sync_stream, make_presence -from servicetest import EventPattern +from servicetest import EventPattern, call_async from caps_helper import make_caps_disco_reply from twisted.words.xish import xpath @@ -9,32 +10,19 @@ import ns import constants as cs from bytestream import create_from_si_offer, BytestreamS5B -def test(q, bus, conn, stream): - conn.Connect() - - _, e1, e2 = q.expect_many( - EventPattern('dbus-signal', signal='StatusChanged', - args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]), - EventPattern('stream-iq', to='fallback1-proxy.localhost', iq_type='get', query_ns=ns.BYTESTREAMS), - EventPattern('stream-iq', to='fallback2-proxy.localhost', iq_type='get', query_ns=ns.BYTESTREAMS), - ) +proxy_query_events = [ + EventPattern('stream-iq', to='fallback1-proxy.localhost', iq_type='get', query_ns=ns.BYTESTREAMS), + EventPattern('stream-iq', to='fallback2-proxy.localhost', iq_type='get', query_ns=ns.BYTESTREAMS)] - proxy_port = {'fallback1-proxy.localhost': '12345', 'fallback2-proxy.localhost': '6789'} +proxy_port = {'fallback1-proxy.localhost': '12345', 'fallback2-proxy.localhost': '6789'} - def send_socks5_reply(iq): - jid = iq['to'] - port = proxy_port[jid] +def connect_and_announce_alice(q, bus, conn, stream): + q.forbid_events(proxy_query_events) - reply = elem_iq(stream, 'result', id=iq['id'], from_=jid)( - elem(ns.BYTESTREAMS, 'query')( - elem('streamhost', jid=jid, host='127.0.0.1', port=port)())) - stream.send(reply) - - send_socks5_reply(e1.stanza) - send_socks5_reply(e2.stanza) + conn.Connect() - # Offer a private D-Bus tube just to check if the proxy is present in the - # SOCKS5 offer + q.expect('dbus-signal', signal='StatusChanged', + args=[cs.CONN_STATUS_CONNECTED, cs.CSR_REQUESTED]) # Send Alice's presence caps = { 'ext': '', 'ver': '0.0.0', @@ -45,14 +33,52 @@ def test(q, bus, conn, stream): disco_event = q.expect('stream-iq', to='alice@localhost/Test', query_ns=ns.DISCO_INFO) - stream.send(make_caps_disco_reply(stream, disco_event.stanza, [ns.TUBES])) + stream.send(make_caps_disco_reply(stream, disco_event.stanza, [ns.TUBES, ns.FILE_TRANSFER])) sync_stream(q, stream) - path, props = conn.Requests.CreateChannel({cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_DBUS_TUBE, + q.unforbid_events(proxy_query_events) + +def send_socks5_reply(stream, iq): + jid = iq['to'] + port = proxy_port[jid] + + reply = elem_iq(stream, 'result', id=iq['id'], from_=jid)( + elem(ns.BYTESTREAMS, 'query')( + elem('streamhost', jid=jid, host='127.0.0.1', port=port)())) + + stream.send(reply) + +def check_socks5_stanza(stanza): + tmp = proxy_port.copy() + nodes = xpath.queryForNodes('/iq/query/streamhost', stanza) + for node in nodes: + if node['jid'] in tmp: + assert node['host'] == '127.0.0.1' + assert node['port'] == tmp.pop(node['jid']) + assert tmp == {} + +def offer_dbus_tube(q, bus, conn, stream): + connect_and_announce_alice(q, bus, conn, stream) + + # Offer a private D-Bus tube just to check if the proxy is present in the + # SOCKS5 offer + + call_async(q, conn.Requests, 'CreateChannel', { + cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_DBUS_TUBE, cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, cs.TARGET_ID: 'alice@localhost', cs.DBUS_TUBE_SERVICE_NAME: 'com.example.TestCase'}) + # Proxy queries are send when creating the channel + return_event, e1, e2 = q.expect_many( + EventPattern('dbus-return', method='CreateChannel'), + proxy_query_events[0], proxy_query_events[1]) + + send_socks5_reply(stream, e1.stanza) + send_socks5_reply(stream, e2.stanza) + + path, props = return_event.value + tube_chan = bus.get_object(conn.bus_name, path) dbus_tube_iface = dbus.Interface(tube_chan, cs.CHANNEL_TYPE_DBUS_TUBE) @@ -69,15 +95,91 @@ def test(q, bus, conn, stream): stream.send(result) e = q.expect('stream-iq', to='alice@localhost/Test') + check_socks5_stanza(e.stanza) - found = False - nodes = xpath.queryForNodes('/iq/query/streamhost', e.stanza) - for node in nodes: - if node['jid'] in proxy_port: - assert node['host'] == '127.0.0.1' - assert node['port'] == proxy_port.pop(node['jid']) - assert proxy_port == {} +def accept_stream_tube(q, bus, conn, stream): + connect_and_announce_alice(q, bus, conn, stream) + + # Accept a stream tube, we'll need SOCKS5 proxies each time we'll connect + # on the tube socket + + # Alice offers us a stream tube + message = elem('message', to='test@localhost/Resource', from_='alice@localhost/Test')( + elem(ns.TUBES, 'tube', type='stream', service='http', id='10')) + stream.send(message) + + # we are interested in the 'NewChannels' announcing the tube channel + def new_chan_predicate(e): + path, props = e.args[0][0] + return props[cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_STREAM_TUBE + + # Proxy queries are send when receiving an incoming stream tube + new_chan, e1, e2 = q.expect_many( + EventPattern('dbus-signal', signal='NewChannels', predicate=new_chan_predicate), + proxy_query_events[0], proxy_query_events[1]) + + send_socks5_reply(stream, e1.stanza) + send_socks5_reply(stream, e2.stanza) + + path, props = new_chan.args[0][0] + assert props[cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_STREAM_TUBE + + tube_chan = bus.get_object(conn.bus_name, path) + tube_iface = dbus.Interface(tube_chan, cs.CHANNEL_TYPE_STREAM_TUBE) + + # connect to the socket so a SOCKS5 bytestream will be created + address = tube_iface.Accept(cs.SOCKET_ADDRESS_TYPE_IPV4, + cs.SOCKET_ACCESS_CONTROL_LOCALHOST, 0, byte_arrays=True) + + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect(address) + + e = q.expect('stream-iq', to='alice@localhost/Test') + + bytestream, profile = create_from_si_offer(stream, q, BytestreamS5B, e.stanza, + 'test@localhost/Resource') + + # Alice accepts the connection + result, si = bytestream.create_si_reply(e.stanza) + stream.send(result) + + e = q.expect('stream-iq', to='alice@localhost/Test') + check_socks5_stanza(e.stanza) + +def send_file(q, bus, conn, stream): + connect_and_announce_alice(q, bus, conn, stream) + + # Send a file; proxy queries are send when creating the FT channel + + call_async(q, conn.Requests, 'CreateChannel', { + cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_FILE_TRANSFER, + cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, + cs.TARGET_ID: 'alice@localhost', + cs.FT_FILENAME: 'test.txt', + cs.FT_CONTENT_TYPE: 'text/plain', + cs.FT_SIZE: 10}) + + return_event, e1, e2 = q.expect_many( + EventPattern('dbus-return', method='CreateChannel'), + proxy_query_events[0], proxy_query_events[1]) + + send_socks5_reply(stream, e1.stanza) + send_socks5_reply(stream, e2.stanza) + + e = q.expect('stream-iq', to='alice@localhost/Test') + + bytestream, profile = create_from_si_offer(stream, q, BytestreamS5B, e.stanza, + 'test@localhost/Resource') + + # Alice accepts the FT + result, si = bytestream.create_si_reply(e.stanza) + stream.send(result) + + e = q.expect('stream-iq', to='alice@localhost/Test') + check_socks5_stanza(e.stanza) if __name__ == '__main__': - exec_test(test, params={'fallback-socks5-proxies': - ['fallback1-proxy.localhost', 'fallback2-proxy.localhost']}) + params = {'fallback-socks5-proxies': ['fallback1-proxy.localhost', 'fallback2-proxy.localhost']} + exec_test(offer_dbus_tube, params=params) + exec_test(accept_stream_tube, params=params) + exec_test(send_file, params=params) diff --git a/tests/twisted/text/test-chat-state.py b/tests/twisted/text/test-chat-state.py index d1acd6dd2..289f69226 100644 --- a/tests/twisted/text/test-chat-state.py +++ b/tests/twisted/text/test-chat-state.py @@ -1,4 +1,4 @@ - +# coding=utf-8 """ Test that chat state notifications are correctly sent and received on text channels. @@ -6,21 +6,34 @@ channels. from twisted.words.xish import domish -from servicetest import call_async, assertEquals, wrap_channel, EventPattern +from servicetest import assertEquals, assertLength, wrap_channel, EventPattern from gabbletest import exec_test, make_result_iq, sync_stream, make_presence import constants as cs import ns -def check_state_notification(elem, name): +def check_state_notification(elem, name, allow_body=False): assertEquals('message', elem.name) - assertEquals('normal', elem['type']) + assertEquals('chat', elem['type']) children = list(elem.elements()) - assert len(children) == 1, elem.toXml() - notification = children[0] - + notification = [x for x in children if x.uri == ns.CHAT_STATES][0] assert notification.name == name, notification.toXml() - assert notification.uri == ns.CHAT_STATES, notification.toXml() + + if not allow_body: + assert len(children) == 1, elem.toXml() + +def make_message(jid, body=None, state=None): + m = domish.Element((None, 'message')) + m['from'] = jid + m['type'] = 'chat' + + if state is not None: + m.addElement((ns.CHAT_STATES, state)) + + if body is not None: + m.addElement('body', content=body) + + return m def test(q, bus, conn, stream): conn.Connect() @@ -30,6 +43,7 @@ def test(q, bus, conn, stream): self_handle = conn.GetSelfHandle() jid = 'foo@bar.com' + full_jid = 'foo@bar.com/Foo' foo_handle = conn.RequestHandles(cs.HT_CONTACT, [jid])[0] path = conn.Requests.CreateChannel( @@ -40,21 +54,21 @@ def test(q, bus, conn, stream): chan = wrap_channel(bus.get_object(conn.bus_name, path), 'Text', ['ChatState', 'Destroyable']) - presence = make_presence('foo@bar.com/Foo', status='hello', + presence = make_presence(full_jid, status='hello', caps={ 'node': 'http://telepathy.freedesktop.org/homeopathy', 'ver' : '0.1', }) stream.send(presence) - version_event = q.expect('stream-iq', to='foo@bar.com/Foo', - query_ns='http://jabber.org/protocol/disco#info', + version_event = q.expect('stream-iq', to=full_jid, + query_ns=ns.DISCO_INFO, query_node='http://telepathy.freedesktop.org/homeopathy#0.1') result = make_result_iq(stream, version_event.stanza) query = result.firstChildElement() feature = query.addElement('feature') - feature['var'] = 'http://jabber.org/protocol/chatstates' + feature['var'] = ns.CHAT_STATES stream.send(result) sync_stream(q, stream) @@ -62,11 +76,7 @@ def test(q, bus, conn, stream): # Receiving chat states: # Composing... - m = domish.Element((None, 'message')) - m['from'] = 'foo@bar.com/Foo' - m['type'] = 'chat' - m.addElement((ns.CHAT_STATES, 'composing')) - stream.send(m) + stream.send(make_message(full_jid, state='composing')) changed = q.expect('dbus-signal', signal='ChatStateChanged') handle, state = changed.args @@ -74,13 +84,7 @@ def test(q, bus, conn, stream): assertEquals(cs.CHAT_STATE_COMPOSING, state) # Message! - - m = domish.Element((None, 'message')) - m['from'] = 'foo@bar.com/Foo' - m['type'] = 'chat' - m.addElement((ns.CHAT_STATES, 'active')) - m.addElement('body', content='hello') - stream.send(m) + stream.send(make_message(full_jid, body='hello', state='active')) changed = q.expect('dbus-signal', signal='ChatStateChanged') handle, state = changed.args @@ -90,19 +94,20 @@ def test(q, bus, conn, stream): # Sending chat states: # Composing... - call_async(q, chan.ChatState, 'SetChatState', cs.CHAT_STATE_COMPOSING) + chan.ChatState.SetChatState(cs.CHAT_STATE_COMPOSING) stream_message = q.expect('stream-message') check_state_notification(stream_message.stanza, 'composing') # XEP 0085: # every content message SHOULD contain an <active/> notification. - call_async(q, chan.Text, 'Send', 0, 'hi.') + chan.Text.Send(0, 'hi.') stream_message = q.expect('stream-message') elem = stream_message.stanza - assert elem.name == 'message' - assert elem['type'] == 'chat', elem['type'] + assertEquals('chat', elem['type']) + + check_state_notification(elem, 'active', allow_body=True) def is_body(e): if e.name == 'body': @@ -110,16 +115,7 @@ def test(q, bus, conn, stream): return True return False - def is_active(e): - if e.uri == ns.CHAT_STATES: - assert e.name == 'active', e.toXml() - return True - return False - - children = list(elem.elements()) - - assert len(filter(is_body, children)) == 1, elem.toXml() - assert len(filter(is_active, children)) == 1, elem.toXml() + assert len([x for x in elem.elements() if is_body(x)]) == 1, elem.toXml() # Close the channel without acking the received message. The peer should # get a <gone/> notification, and the channel should respawn. @@ -157,6 +153,151 @@ def test(q, bus, conn, stream): # notification, since we haven't sent any notifications on that channel. chan.Close() sync_stream(q, stream) + q.unforbid_events(es) + + # XEP-0085 §5.1 defines how to negotiate support for chat states with a + # contact in the absence of capabilities. This is useful when talking to + # invisible contacts, for example. + + # First, if we receive a message from a contact, containing an <active/> + # notification, they support chat states, so we should send them. + + jid = 'i@example.com' + full_jid = jid + '/GTalk' + + path = conn.Requests.CreateChannel( + { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, + cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, + cs.TARGET_ID: jid, + })[0] + chan = wrap_channel(bus.get_object(conn.bus_name, path), 'Text', + ['ChatState']) + + stream.send(make_message(full_jid, body='i am invisible', state='active')) + + changed = q.expect('dbus-signal', signal='ChatStateChanged') + assertEquals(cs.CHAT_STATE_ACTIVE, changed.args[1]) + + # We've seen them send a chat state notification, so we should send them + # notifications when the UI tells us to. + chan.ChatState.SetChatState(cs.CHAT_STATE_COMPOSING) + stream_message = q.expect('stream-message', to=full_jid) + check_state_notification(stream_message.stanza, 'composing') + + chan.Text.Send(0, 'very convincing') + stream_message = q.expect('stream-message', to=full_jid) + check_state_notification(stream_message.stanza, 'active', allow_body=True) + + # Now, test the case where we start the negotiation, and the contact + # turns out to support chat state notifications. + + jid = 'c@example.com' + full_jid = jid + '/GTalk' + path = conn.Requests.CreateChannel( + { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, + cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, + cs.TARGET_ID: jid, + })[0] + chan = wrap_channel(bus.get_object(conn.bus_name, path), 'Text', + ['ChatState']) + + # We shouldn't send any notifications until we actually send a message. + e = EventPattern('stream-message', to=jid) + q.forbid_events([e]) + for i in [cs.CHAT_STATE_COMPOSING, cs.CHAT_STATE_INACTIVE, + cs.CHAT_STATE_PAUSED, cs.CHAT_STATE_ACTIVE]: + chan.ChatState.SetChatState(i) + sync_stream(q, stream) + q.unforbid_events([e]) + + # When we send a message, say we're active. + chan.Text.Send(0, 'is anyone there?') + stream_message = q.expect('stream-message', to=jid) + check_state_notification(stream_message.stanza, 'active', allow_body=True) + + # We get a notification back from our contact. + stream.send(make_message(full_jid, state='composing')) + + changed = q.expect('dbus-signal', signal='ChatStateChanged') + _, state = changed.args + assertEquals(cs.CHAT_STATE_COMPOSING, state) + + # So now we know they support notification, so should send notifications. + chan.ChatState.SetChatState(cs.CHAT_STATE_COMPOSING) + + # This doesn't check whether we're sending to the bare jid, or the + # jid+resource. In fact, the notification is sent to the bare jid, because + # we only update which jid we send to when we actually receive a message, + # not when we receive a notification. wjt thinks this is less surprising + # than the alternative: + # + # • I'm talking to you on my N900, and signed in on my laptop; + # • I enter one character in a tab to you on my laptop, and then delete + # it; + # • Now your messages to me appear on my laptop (until I send you another + # one from my N900)! + stream_message = q.expect('stream-message') + check_state_notification(stream_message.stanza, 'composing') + + # But! Now they start messaging us from a different client, which *doesn't* + # support notifications. + other_jid = jid + '/Library' + stream.send(make_message(other_jid, body='grr, library computers')) + q.expect('dbus-signal', signal='Received') + + # Okay, we should stop sending typing notifications. + e = EventPattern('stream-message', to=other_jid) + q.forbid_events([e]) + for i in [cs.CHAT_STATE_COMPOSING, cs.CHAT_STATE_INACTIVE, + cs.CHAT_STATE_PAUSED, cs.CHAT_STATE_ACTIVE]: + chan.ChatState.SetChatState(i) + sync_stream(q, stream) + q.unforbid_events([e]) + + # Now, test the case where we start the negotiation, and the contact + # does not support chat state notifications + + jid = 'twitterbot@example.com' + full_jid = jid + '/Nonsense' + path = conn.Requests.CreateChannel( + { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_TEXT, + cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT, + cs.TARGET_ID: jid, + })[0] + chan = wrap_channel(bus.get_object(conn.bus_name, path), 'Text', + ['ChatState']) + + # We shouldn't send any notifications until we actually send a message. + e = EventPattern('stream-message', to=jid) + q.forbid_events([e]) + for i in [cs.CHAT_STATE_COMPOSING, cs.CHAT_STATE_INACTIVE, + cs.CHAT_STATE_PAUSED, cs.CHAT_STATE_ACTIVE]: + chan.ChatState.SetChatState(i) + sync_stream(q, stream) + q.unforbid_events([e]) + + # When we send a message, say we're active. + chan.Text.Send(0, '#n900 #maemo #zomg #woo #yay http://bit.ly/n900') + stream_message = q.expect('stream-message', to=jid) + check_state_notification(stream_message.stanza, 'active', allow_body=True) + + # They reply without a chat state. + stream.send(make_message(full_jid, body="posted.")) + q.expect('dbus-signal', signal='Received') + + # Okay, we shouldn't send any more. + e = EventPattern('stream-message', to=other_jid) + q.forbid_events([e]) + for i in [cs.CHAT_STATE_COMPOSING, cs.CHAT_STATE_INACTIVE, + cs.CHAT_STATE_PAUSED, cs.CHAT_STATE_ACTIVE]: + chan.ChatState.SetChatState(i) + sync_stream(q, stream) + q.unforbid_events([e]) + + chan.Text.Send(0, '@stephenfry simmer down') + message = q.expect('stream-message') + states = [x for x in message.stanza.elements() if x.uri == ns.CHAT_STATES] + assertLength(0, states) if __name__ == '__main__': exec_test(test) diff --git a/tests/twisted/tubes/accept-muc-stream-tube.py b/tests/twisted/tubes/accept-muc-stream-tube.py index 3a1990705..5f726c476 100644 --- a/tests/twisted/tubes/accept-muc-stream-tube.py +++ b/tests/twisted/tubes/accept-muc-stream-tube.py @@ -44,13 +44,6 @@ def test(q, bus, conn, stream, bytestream_cls, call_async(q, conn, 'RequestHandles', 2, ['chat@conf.localhost']) - event = q.expect('stream-iq', to='conf.localhost', - query_ns='http://jabber.org/protocol/disco#info') - result = make_result_iq(stream, event.stanza) - feature = result.firstChildElement().addElement('feature') - feature['var'] = 'http://jabber.org/protocol/muc' - stream.send(result) - event = q.expect('dbus-return', method='RequestHandles') handles = event.value[0] room_handle = handles[0] diff --git a/tests/twisted/tubes/close-muc-with-closed-tube.py b/tests/twisted/tubes/close-muc-with-closed-tube.py index d0a8b11d5..042cb79fc 100644 --- a/tests/twisted/tubes/close-muc-with-closed-tube.py +++ b/tests/twisted/tubes/close-muc-with-closed-tube.py @@ -28,13 +28,6 @@ def test(q, bus, conn, stream): call_async(q, conn, 'RequestHandles', cs.HT_ROOM, ['chat@conf.localhost']) - event = q.expect('stream-iq', to='conf.localhost', - query_ns='http://jabber.org/protocol/disco#info') - result = make_result_iq(stream, event.stanza) - feature = result.firstChildElement().addElement('feature') - feature['var'] = 'http://jabber.org/protocol/muc' - stream.send(result) - event = q.expect('dbus-return', method='RequestHandles') handles = event.value[0] room_handle = handles[0] diff --git a/tests/twisted/tubes/muctubeutil.py b/tests/twisted/tubes/muctubeutil.py index a298eb7ff..61fa41f88 100644 --- a/tests/twisted/tubes/muctubeutil.py +++ b/tests/twisted/tubes/muctubeutil.py @@ -18,13 +18,6 @@ def get_muc_tubes_channel(q, bus, conn, stream, muc_jid, anonymous=True): call_async(q, conn, 'RequestHandles', cs.HT_ROOM, [muc_jid]) - event = q.expect('stream-iq', to=muc_server, - query_ns='http://jabber.org/protocol/disco#info') - result = make_result_iq(stream, event.stanza) - feature = result.firstChildElement().addElement('feature') - feature['var'] = 'http://jabber.org/protocol/muc' - stream.send(result) - event = q.expect('dbus-return', method='RequestHandles') handles = event.value[0] room_handle = handles[0] diff --git a/tests/twisted/tubes/test-get-available-tubes.py b/tests/twisted/tubes/test-get-available-tubes.py index e22bb98ab..af13da4d7 100644 --- a/tests/twisted/tubes/test-get-available-tubes.py +++ b/tests/twisted/tubes/test-get-available-tubes.py @@ -30,13 +30,6 @@ def test(q, bus, conn, stream): call_async(q, conn, 'RequestHandles', cs.HT_ROOM, ['chat@conf.localhost']) - event = q.expect('stream-iq', to='conf.localhost', - query_ns='http://jabber.org/protocol/disco#info') - result = make_result_iq(stream, event.stanza) - feature = result.firstChildElement().addElement('feature') - feature['var'] = 'http://jabber.org/protocol/muc' - stream.send(result) - event = q.expect('dbus-return', method='RequestHandles') handles = event.value[0] |