diff --git a/src/xmpp/connection.c b/src/xmpp/connection.c index b0a8c7f4..1bce2876 100644 --- a/src/xmpp/connection.c +++ b/src/xmpp/connection.c @@ -753,7 +753,11 @@ void connection_features_received(const char* const jid) { log_info("[CONNECTION] connection_features_received %s", jid); - if (g_hash_table_remove(conn.requested_features, jid) && g_hash_table_size(conn.requested_features) == 0) { + const char* key = jid ? jid : conn.domain; // g_str_hash crashes on NULL; NULL 'from' means the server (RFC 6120 §8.1.2.1) + if (!key) { + return; + } + if (g_hash_table_remove(conn.requested_features, key) && g_hash_table_size(conn.requested_features) == 0) { sv_ev_connection_features_received(); } } @@ -761,7 +765,11 @@ connection_features_received(const char* const jid) GHashTable* connection_get_features(const char* const jid) { - return g_hash_table_lookup(conn.features_by_jid, jid); + const char* key = jid ? jid : conn.domain; + if (!key || !conn.features_by_jid) { + return NULL; + } + return g_hash_table_lookup(conn.features_by_jid, key); } GList* diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c index 2a472bcf..1c9869f5 100644 --- a/src/xmpp/iq.c +++ b/src/xmpp/iq.c @@ -2314,6 +2314,7 @@ _disco_info_response_id_handler(xmpp_stanza_t* const stanza, void* const userdat log_debug("Received disco#info response from: %s", from); } else { log_debug("Received disco#info response"); + from = connection_get_domain(); // RFC 6120 §8.1.2.1: no 'from' means the server itself } // handle error responses @@ -2397,6 +2398,7 @@ _disco_info_response_id_handler_onconnect(xmpp_stanza_t* const stanza, void* con log_debug("Received disco#info response from: %s", from); } else { log_debug("Received disco#info response"); + from = connection_get_domain(); // RFC 6120 §8.1.2.1: no 'from' means the server itself } // handle error responses diff --git a/tests/functionaltests/functionaltests.c b/tests/functionaltests/functionaltests.c index 394b31d9..da68145e 100644 --- a/tests/functionaltests/functionaltests.c +++ b/tests/functionaltests/functionaltests.c @@ -173,6 +173,7 @@ main(int argc, char* argv[]) PROF_FUNC_TEST(disco_info_without_name), PROF_FUNC_TEST(disco_items_without_name), PROF_FUNC_TEST(disco_info_service_unavailable), + PROF_FUNC_TEST(disco_info_result_no_from), /* Roster management - add/remove/rename contacts */ PROF_FUNC_TEST(sends_new_item), diff --git a/tests/functionaltests/test_disco.c b/tests/functionaltests/test_disco.c index 1d565b59..822e822d 100644 --- a/tests/functionaltests/test_disco.c +++ b/tests/functionaltests/test_disco.c @@ -396,6 +396,35 @@ disco_items_without_name(void **state) prof_timeout_reset(); } +void +disco_info_result_no_from(void **state) +{ + /* + * Test that a disco#info result without a 'from' attribute is treated as + * coming from the server itself (RFC 6120 §8.1.2.1). The on-connect + * disco#info handler used to crash on such responses (issue #168). + */ + stbbr_for_query("http://jabber.org/protocol/disco#info", + "" + "" + "" + "" + "" + "" + ); + + /* the on-connect disco#info gets the same from-less response */ + prof_connect(); + + prof_input("/disco info"); + + prof_timeout(10); + /* client survived and attributed the response to the server */ + assert_true(prof_output_exact("Service discovery info for localhost")); + assert_true(prof_output_regex("NoFromServer.*im.*server")); + prof_timeout_reset(); +} + void disco_info_service_unavailable(void **state) { diff --git a/tests/functionaltests/test_disco.h b/tests/functionaltests/test_disco.h index 89a45c33..d37d03eb 100644 --- a/tests/functionaltests/test_disco.h +++ b/tests/functionaltests/test_disco.h @@ -17,3 +17,4 @@ void disco_info_multiple_identities(void **state); void disco_info_without_name(void **state); void disco_items_without_name(void **state); void disco_info_service_unavailable(void **state); +void disco_info_result_no_from(void **state);