# HG changeset patch # User Yoshiki Yazawa # Date 1301633271 -32400 # Node ID 8b9e9c61d061a8aae691151dcfea8351b854c493 # Parent 7281d151e4926100f0318fba351841dd79e103f0# Parent a9e077fb65e9a4aff1905103281a6b75b8b10802 merged from im.pidgin.pidgin diff -r 7281d151e492 -r 8b9e9c61d061 .mtn-ignore --- a/.mtn-ignore Thu Mar 17 20:25:26 2011 +0900 +++ b/.mtn-ignore Fri Apr 01 13:47:51 2011 +0900 @@ -1,8 +1,8 @@ +(.*/)?TAGS$ (.*/)?\.svn +.*/?.*\.pc$ .*/?Makefile(\.in)?$ .*/?Makefile\.am\.mingw$ -(.*/)?TAGS$ -.*/?.*\.pc$ .*/perl/common/[^/]+\.c$ .*/perl/common/blib.* .*/perl/common/pm_to_blib$ @@ -11,8 +11,9 @@ .*\.dll$ .*\.exe$ .*\.loT$ -intltool-.* Doxyfile(\.mingw)?$ +VERSION$ +\.tx aclocal.m4 autogen.args compile @@ -24,42 +25,15 @@ config.status config.sub configure$ +depcomp +doc/finch.1$ +doc/html +doc/pidgin.1$ finch/finch$ finch/libgnt/gntmarshal.c finch/libgnt/gntmarshal.h -depcomp -doc/finch.1$ -doc/pidgin.1$ -doc/html -package_revision.h -package_revision_raw.txt -pidgin.apspec$ -pidgin.desktop$ -pidgin.spec$ -pidgin-.*.tar.gz -pidgin-.*.tar.bz2 -pidgin-*.*.*-dbgsym$ -pidgin-*.*.*-dbgsym.zip$ -pidgin-*.*.*-win32bin$ -pidgin-*.*.*-win32-bin.zip$ -pidgin/pidgin$ -pidgin/pixmaps/emotes/default/24/theme -pidgin/pixmaps/emotes/none/theme -pidgin/pixmaps/emotes/small/16/theme -pidgin/plugins/musicmessaging/music-messaging-bindings.c -pidgin/plugins/perl/common/Makefile.PL$ -pidgin/plugins/perl/common/Makefile.old -pidgin/win32/pidgin_dll_rc.rc$ -pidgin/win32/pidgin_exe_rc.rc$ -pidgin/win32/nsis/gtk-runtime-*.*.*.*.zip -pidgin/win32/nsis/gtk_runtime_stage$ -pidgin/win32/nsis/pidgin-translations.nsh$ -pidgin/win32/nsis/langmacros.nsh -pidgin/win32/nsis/nsis_translations.desktop -pidgin/win32/nsis/pidgin-spellcheck-preselect.nsh -pidgin/win32/nsis/pidgin-spellcheck.nsh -pidgin/win32/nsis/translations install-sh +intltool-.* libpurple/dbus-bindings.c libpurple/dbus-signals.c libpurple/dbus-types.c @@ -73,10 +47,10 @@ libpurple/plugins/perl/common/const-c.inc libpurple/plugins/perl/common/const-xs.inc libpurple/plugins/perl/common/lib -libpurple/purple.h$ libpurple/purple-client-bindings.c libpurple/purple-client-bindings.h libpurple/purple-client-example +libpurple/purple.h$ libpurple/tests/check_libpurple libpurple/tests/libpurple.. libpurple/version.h$ @@ -86,6 +60,34 @@ ltmain.sh missing mkinstalldirs +package_revision.h +package_revision_raw.txt +pidgin-*.*.*-dbgsym$ +pidgin-*.*.*-dbgsym.zip$ +pidgin-*.*.*-win32-bin.zip$ +pidgin-*.*.*-win32bin$ +pidgin-.*.tar.bz2 +pidgin-.*.tar.gz +pidgin.apspec$ +pidgin.desktop$ +pidgin.spec$ +pidgin/pidgin$ +pidgin/pixmaps/emotes/default/24/theme +pidgin/pixmaps/emotes/none/theme +pidgin/pixmaps/emotes/small/16/theme +pidgin/plugins/musicmessaging/music-messaging-bindings.c +pidgin/plugins/perl/common/Makefile.PL$ +pidgin/plugins/perl/common/Makefile.old +pidgin/win32/nsis/gtk-runtime-*.*.*.*.zip +pidgin/win32/nsis/gtk_runtime_stage$ +pidgin/win32/nsis/langmacros.nsh +pidgin/win32/nsis/nsis_translations.desktop +pidgin/win32/nsis/pidgin-spellcheck-preselect.nsh +pidgin/win32/nsis/pidgin-spellcheck.nsh +pidgin/win32/nsis/pidgin-translations.nsh$ +pidgin/win32/nsis/translations +pidgin/win32/pidgin_dll_rc.rc$ +pidgin/win32/pidgin_exe_rc.rc$ po/Makefile.in.in po/POTFILES$ po/missing @@ -94,4 +96,3 @@ po/stamp-it stamp-h1 win32-install-dir(\.release)? -VERSION$ diff -r 7281d151e492 -r 8b9e9c61d061 COPYRIGHT --- a/COPYRIGHT Thu Mar 17 20:25:26 2011 +0900 +++ b/COPYRIGHT Fri Apr 01 13:47:51 2011 +0900 @@ -67,6 +67,7 @@ Paolo Borelli Julien Bossart Craig Boston +Éric Boumaour Chris Boyle Stanislav Brabec Derrick J Brashear @@ -540,6 +541,7 @@ Jon Turney Junichi Uekawa Max Ulidtko +Dmitry Utkin Igor Vlasenko István Váradi Martijn van Beers @@ -562,6 +564,7 @@ Eric Warmenhoven Adam J. Warrington Denis Washington +Tomasz Wasilczyk Zsombor Welker Andrew Wellington Adam Wendt diff -r 7281d151e492 -r 8b9e9c61d061 ChangeLog --- a/ChangeLog Thu Mar 17 20:25:26 2011 +0900 +++ b/ChangeLog Fri Apr 01 13:47:51 2011 +0900 @@ -5,8 +5,12 @@ * Implement simple silence suppression for voice calls, preventing wasted bandwidth for silent periods during a call. (Jakub Adam) (half of #13180) - Gadu-Gadu: - * Allow showing your status only to buddies. (Mateusz Piękos) (#13358) + Pidgin: + * Duplicate code cleanup. (Gabriel Schulhof) (#10599) + * Voice/Video call window adapts correctly to adding or removing streams + on the fly. (Jakub Adam) (half of #13535) + * Don't cancel an ongoing call when rejecting the addition of a stream to + the existing call. (Jakub Adam) (#13537) libpurple: * media: Allow obtaining active local and remote candidates. (#11830) @@ -15,13 +19,38 @@ * Simple Silence Suppression is optional per-account. (Jakub Adam) (half of #13180) * Fix purple-url-handler being unable to find an account. - - Pidgin: - * Duplicate code cleanup. (Gabriel Schulhof) (#10599) + * media: Allow adding/removing streams on the fly. (Jakub Adam) (half of + #13535) + + Gadu-Gadu: + * Allow showing your status only to buddies. (Mateusz Piękos) (#13358) + * Updated internal libgadu to version 1.10.1. (Robert Matusewicz, + Krzysztof Klinikowski) (#13525) + * Suppress blank messages that happen when receiving inline + images. (Tomasz Wasilczyk) (#13554) + * Fix sending inline images to remote users, don't crash when + trying to send large (> 256kB) images. (Tomasz Wasilczyk) (#13580) + * Support incoming typing notifications. (Jan Zachorowski) (#13362) + * Require libgadu 1.10.1 to avoid using internal libgadu. + + ICQ: + * Fix unsetting your mood when "None" is selected. (Dustin Gathmann) + (#11895) + * Ignore Daylight Saving Time when performing calculations related to + birthdays. (Dustin Gathmann) (#13533) + * It is now possible to specify multiple encodings on the Advanced tab of + an ICQ account's settings by using a comma-delimited list. (Dmitry + Utkin (#13496) + + Plugins: + * The Voice/Video Settings plugin now includes the ability to test + microphone settings. (Jakub Adam) (#13182) Windows-Specific Changes: * Fix building libpurple with Visual C++ .NET 2005. This was accidentally broken in 2.7.11. (Florian Quèze) + * Build internal libgadu without -mms-bitfields, fixing several + long-standing Gadu-Gadu issues. (#11958, #6297) version 2.7.11 (03/10/2011): General: diff -r 7281d151e492 -r 8b9e9c61d061 ChangeLog.API --- a/ChangeLog.API Thu Mar 17 20:25:26 2011 +0900 +++ b/ChangeLog.API Fri Apr 01 13:47:51 2011 +0900 @@ -5,11 +5,25 @@ Added: * account-authorization-requested-with-message signal (Stefan Ott) (#8690) + * cleared-message-history signal (conversation signals) + * purple_account_add_buddy_with_invite + * purple_account_add_buddies_with_invite * purple_notify_user_info_add_pair_plaintext * purple_media_get_active_local_candidates * purple_media_get_active_remote_candidates * purple_media_manager_get_video_caps (Jakub Adam) (#13095) * purple_media_manager_set_video_caps (Jakub Adam) (#13095) + * Added add_buddy_with_invite to PurplePluginProtocolInfo + * Added add_buddies_with_invite to PurplePluginProtocolInfo + * Added PurpleSrvTxtQueryUiOps which allow UIs to specify their + own mechanisms to resolve SRV and/or TXT queries. It works + similar to PurpleDnsQueryUiOps + + Deprecated: + * purple_account_add_buddy + * purple_account_add_buddies_with_invite + * add_buddy from PurplePluginProtocolInfo struct + * add_buddies from PurplePluginProtocolInfo struct Pidgin: Added: diff -r 7281d151e492 -r 8b9e9c61d061 configure.ac --- a/configure.ac Thu Mar 17 20:25:26 2011 +0900 +++ b/configure.ac Fri Apr 01 13:47:51 2011 +0900 @@ -326,13 +326,13 @@ AM_CONDITIONAL(INSTALL_I18N, test "x$enable_i18n" = "xyes") dnl ####################################################################### -dnl # Check for GLib 2.12 (required) +dnl # Check for GLib 2.16 (required) dnl ####################################################################### -PKG_CHECK_MODULES(GLIB, [glib-2.0 >= 2.12.0 gobject-2.0 gmodule-2.0 gthread-2.0], , [ +PKG_CHECK_MODULES(GLIB, [glib-2.0 >= 2.16.0 gobject-2.0 gmodule-2.0 gthread-2.0], , [ AC_MSG_RESULT(no) AC_MSG_ERROR([ -You must have GLib 2.12.0 or newer development headers installed to build. +You must have GLib 2.16.0 or newer development headers installed to build. If you have these installed already you may need to install pkg-config so I can find them. @@ -1024,7 +1024,7 @@ gadu_manual_check="no" fi if test "x$gadu_manual_check" = "xno"; then - PKG_CHECK_MODULES(GADU, [libgadu >= 1.9.0], [ + PKG_CHECK_MODULES(GADU, [libgadu >= 1.10.1], [ gadu_includes="yes" gadu_libs="yes" ], [ diff -r 7281d151e492 -r 8b9e9c61d061 doc/conversation-signals.dox --- a/doc/conversation-signals.dox Thu Mar 17 20:25:26 2011 +0900 +++ b/doc/conversation-signals.dox Fri Apr 01 13:47:51 2011 +0900 @@ -32,6 +32,7 @@ @signal chat-join-failed @signal chat-left @signal chat-topic-changed + @signal cleared-message-history @signal conversation-extended-menu @signal sent-attention @signal got-attention @@ -479,6 +480,16 @@ @since 2.1.0 @endsignaldef + @signaldef cleared-message-history + @signalproto +void (*cleared_message_history)(PurpleConversation *conv); + @endsignalproto + @signaldesc + Emitted when the conversation history is cleared. + @param conv The conversation. + @since 2.8.0 + @endsignaldef + @signaldef sent-attention @signalproto void (*got_attention)(PurpleAccount *account, const char *who, diff -r 7281d151e492 -r 8b9e9c61d061 finch/gntblist.c --- a/finch/gntblist.c Thu Mar 17 20:25:26 2011 +0900 +++ b/finch/gntblist.c Fri Apr 01 13:47:51 2011 +0900 @@ -626,6 +626,7 @@ const char *username = purple_request_fields_get_string(allfields, "screenname"); const char *alias = purple_request_fields_get_string(allfields, "alias"); const char *group = purple_request_fields_get_string(allfields, "group"); + const char *invite = purple_request_fields_get_string(allfields, "invite"); PurpleAccount *account = purple_request_fields_get_account(allfields, "account"); const char *error = NULL; PurpleGroup *grp; @@ -662,7 +663,7 @@ purple_blist_add_buddy(buddy, NULL, grp, NULL); } - purple_account_add_buddy(account, buddy); + purple_account_add_buddy_with_invite(account, buddy, invite); } static void @@ -680,6 +681,9 @@ field = purple_request_field_string_new("alias", _("Alias (optional)"), alias, FALSE); purple_request_field_group_add_field(group, field); + field = purple_request_field_string_new("invite", _("Invite message (optional)"), NULL, FALSE); + purple_request_field_group_add_field(group, field); + field = purple_request_field_string_new("group", _("Add in group"), grp, FALSE); purple_request_field_group_add_field(group, field); purple_request_field_set_type_hint(field, "group"); diff -r 7281d151e492 -r 8b9e9c61d061 finch/gntconv.c --- a/finch/gntconv.c Thu Mar 17 20:25:26 2011 +0900 +++ b/finch/gntconv.c Fri Apr 01 13:47:51 2011 +0900 @@ -396,10 +396,18 @@ } static void +cleared_message_history_cb(PurpleConversation *conv, gpointer data) +{ + FinchConv *ggc = FINCH_GET_DATA(conv); + if (ggc) + gnt_text_view_clear(GNT_TEXT_VIEW(ggc->tv)); +} + +static void clear_scrollback_cb(GntMenuItem *item, gpointer ggconv) { FinchConv *ggc = ggconv; - gnt_text_view_clear(GNT_TEXT_VIEW(ggc->tv)); + purple_conversation_clear_message_history(ggc->active_conv); } static void @@ -1264,8 +1272,6 @@ clear_command_cb(PurpleConversation *conv, const char *cmd, char **args, char **error, void *data) { - FinchConv *ggconv = FINCH_GET_DATA(conv); - gnt_text_view_clear(GNT_TEXT_VIEW(ggconv->tv)); purple_conversation_clear_message_history(conv); return PURPLE_CMD_RET_OK; } @@ -1459,6 +1465,8 @@ PURPLE_CALLBACK(update_buddy_typing), NULL); purple_signal_connect(purple_conversations_get_handle(), "chat-left", finch_conv_get_handle(), PURPLE_CALLBACK(chat_left_cb), NULL); + purple_signal_connect(purple_conversations_get_handle(), "cleared-message-history", finch_conv_get_handle(), + PURPLE_CALLBACK(cleared_message_history_cb), NULL); purple_signal_connect(purple_blist_get_handle(), "buddy-signed-on", finch_conv_get_handle(), PURPLE_CALLBACK(buddy_signed_on_off), NULL); purple_signal_connect(purple_blist_get_handle(), "buddy-signed-off", finch_conv_get_handle(), diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/account.c --- a/libpurple/account.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/account.c Fri Apr 01 13:47:51 2011 +0900 @@ -2540,8 +2540,37 @@ if (prpl != NULL) prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); - if (prpl_info != NULL && prpl_info->add_buddy != NULL) - prpl_info->add_buddy(gc, buddy, purple_buddy_get_group(buddy)); + if (prpl_info != NULL) { + if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, add_buddy_with_invite)) + prpl_info->add_buddy_with_invite(gc, buddy, purple_buddy_get_group(buddy), NULL); + else if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, add_buddy)) + prpl_info->add_buddy(gc, buddy, purple_buddy_get_group(buddy)); + } +} + +void +purple_account_add_buddy_with_invite(PurpleAccount *account, PurpleBuddy *buddy, const char *message) +{ + PurplePluginProtocolInfo *prpl_info = NULL; + PurpleConnection *gc; + PurplePlugin *prpl = NULL; + + g_return_if_fail(account != NULL); + g_return_if_fail(buddy != NULL); + + gc = purple_account_get_connection(account); + if (gc != NULL) + prpl = purple_connection_get_prpl(gc); + + if (prpl != NULL) + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); + + if (prpl_info != NULL) { + if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, add_buddy_with_invite)) + prpl_info->add_buddy_with_invite(gc, buddy, purple_buddy_get_group(buddy), message); + else if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, add_buddy)) + prpl_info->add_buddy(gc, buddy, purple_buddy_get_group(buddy)); + } } void @@ -2566,9 +2595,69 @@ groups = g_list_append(groups, purple_buddy_get_group(buddy)); } - if (prpl_info->add_buddies != NULL) + if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, add_buddies_with_invite)) + prpl_info->add_buddies_with_invite(gc, buddies, groups, NULL); + else if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, add_buddies)) prpl_info->add_buddies(gc, buddies, groups); - else if (prpl_info->add_buddy != NULL) { + else if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, add_buddy_with_invite)) { + GList *curb = buddies, *curg = groups; + + while ((curb != NULL) && (curg != NULL)) { + prpl_info->add_buddy_with_invite(gc, curb->data, curg->data, NULL); + curb = curb->next; + curg = curg->next; + } + } + else if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, add_buddy)) { + GList *curb = buddies, *curg = groups; + + while ((curb != NULL) && (curg != NULL)) { + prpl_info->add_buddy(gc, curb->data, curg->data); + curb = curb->next; + curg = curg->next; + } + } + + g_list_free(groups); + } +} + +void +purple_account_add_buddies_with_invite(PurpleAccount *account, GList *buddies, const char *message) +{ + PurplePluginProtocolInfo *prpl_info = NULL; + PurpleConnection *gc = purple_account_get_connection(account); + PurplePlugin *prpl = NULL; + + if (gc != NULL) + prpl = purple_connection_get_prpl(gc); + + if (prpl != NULL) + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); + + if (prpl_info) { + GList *cur, *groups = NULL; + + /* Make a list of what group each buddy is in */ + for (cur = buddies; cur != NULL; cur = cur->next) { + PurpleBuddy *buddy = cur->data; + groups = g_list_append(groups, purple_buddy_get_group(buddy)); + } + + if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, add_buddies_with_invite)) + prpl_info->add_buddies_with_invite(gc, buddies, groups, message); + else if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, add_buddy_with_invite)) { + GList *curb = buddies, *curg = groups; + + while ((curb != NULL) && (curg != NULL)) { + prpl_info->add_buddy_with_invite(gc, curb->data, curg->data, message); + curb = curb->next; + curg = curg->next; + } + } + else if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, add_buddies)) + prpl_info->add_buddies(gc, buddies, groups); + else if (PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl_info, add_buddy)) { GList *curb = buddies, *curg = groups; while ((curb != NULL) && (curg != NULL)) { diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/account.h --- a/libpurple/account.h Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/account.h Fri Apr 01 13:47:51 2011 +0900 @@ -958,15 +958,40 @@ * * @param account The account. * @param buddy The buddy to add. + * + * @deprecated Use purple_account_add_buddy_with_invite and \c NULL message. */ void purple_account_add_buddy(PurpleAccount *account, PurpleBuddy *buddy); /** + * Adds a buddy to the server-side buddy list for the specified account. + * + * @param account The account. + * @param buddy The buddy to add. + * @param message The invite message. This may be ignored by a prpl. + * + * @since 2.8.0 + */ +void purple_account_add_buddy_with_invite(PurpleAccount *account, PurpleBuddy *buddy, const char *message); + +/** * Adds a list of buddies to the server-side buddy list. * * @param account The account. * @param buddies The list of PurpleBlistNodes representing the buddies to add. + * + * @deprecated Use purple_account_add_buddies_with_invite and \c NULL message. */ void purple_account_add_buddies(PurpleAccount *account, GList *buddies); +/** + * Adds a list of buddies to the server-side buddy list. + * + * @param account The account. + * @param buddies The list of PurpleBlistNodes representing the buddies to add. + * @param message The invite message. This may be ignored by a prpl. + * + * @since 2.8.0 + */ +void purple_account_add_buddies_with_invite(PurpleAccount *account, GList *buddies, const char *message); /** * Removes a buddy from the server-side buddy list. diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/conversation.c --- a/libpurple/conversation.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/conversation.c Fri Apr 01 13:47:51 2011 +0900 @@ -575,6 +575,7 @@ if (ops != NULL && ops->destroy_conversation != NULL) ops->destroy_conversation(conv); + conv->ui_data = NULL; purple_conversation_close_logs(conv); @@ -1223,7 +1224,7 @@ c = purple_conv_im_get_conversation(im); - if ((flags & PURPLE_MESSAGE_RECV) == PURPLE_MESSAGE_RECV) { + if ((flags & PURPLE_MESSAGE_RECV) == PURPLE_MESSAGE_RECV) { purple_conv_im_set_typing_state(im, PURPLE_NOT_TYPING); } @@ -2284,6 +2285,9 @@ GList *list = conv->message_history; message_history_free(list); conv->message_history = NULL; + + purple_signal_emit(purple_conversations_get_handle(), + "cleared-message-history", conv); } GList *purple_conversation_get_message_history(PurpleConversation *conv) @@ -2640,6 +2644,11 @@ purple_value_new(PURPLE_TYPE_STRING), purple_value_new(PURPLE_TYPE_STRING)); + purple_signal_register(handle, "cleared-message-history", + purple_marshal_VOID__POINTER, NULL, 1, + purple_value_new(PURPLE_TYPE_SUBTYPE, + PURPLE_SUBTYPE_CONVERSATION)); + purple_signal_register(handle, "conversation-extended-menu", purple_marshal_VOID__POINTER_POINTER, NULL, 2, purple_value_new(PURPLE_TYPE_SUBTYPE, diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/dnsquery.h --- a/libpurple/dnsquery.h Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/dnsquery.h Fri Apr 01 13:47:51 2011 +0900 @@ -58,7 +58,8 @@ */ typedef struct { - /** If implemented, the UI is responsible for DNS queries */ + /** If implemented, return TRUE if the UI takes responsibility for DNS + * queries. When returning FALSE, the standard implementation is used. */ gboolean (*resolve_host)(PurpleDnsQueryData *query_data, PurpleDnsQueryResolvedCallback resolved_cb, PurpleDnsQueryFailedCallback failed_cb); diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/dnssrv.c --- a/libpurple/dnssrv.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/dnssrv.c Fri Apr 01 13:47:51 2011 +0900 @@ -31,19 +31,19 @@ #include #endif #ifndef T_SRV -#define T_SRV 33 +#define T_SRV PurpleDnsTypeSrv #endif #ifndef T_TXT -#define T_TXT 16 +#define T_TXT PurpleDnsTypeTxt #endif #else /* WIN32 */ #include /* Missing from the mingw headers */ #ifndef DNS_TYPE_SRV -# define DNS_TYPE_SRV 33 +# define DNS_TYPE_SRV PurpleDnsTypeSrv #endif #ifndef DNS_TYPE_TXT -# define DNS_TYPE_TXT 16 +# define DNS_TYPE_TXT PurpleDnsTypeTxt #endif #endif @@ -52,6 +52,8 @@ #include "eventloop.h" #include "network.h" +static PurpleSrvTxtQueryUiOps *srv_txt_query_ui_ops = NULL; + #ifndef _WIN32 typedef union { HEADER hdr; @@ -66,11 +68,7 @@ DNS_FREE_TYPE FreeType) = NULL; #endif -struct _PurpleTxtResponse { - char *content; -}; - -struct _PurpleSrvQueryData { +struct _PurpleSrvTxtQueryData { union { PurpleSrvCallback srv; PurpleTxtCallback txt; @@ -79,9 +77,9 @@ gpointer extradata; guint handle; int type; + char *query; #ifdef _WIN32 GThread *resolver; - char *query; char *error_message; GList *results; #else @@ -100,6 +98,8 @@ int sum; } PurpleSrvResponseContainer; +static gboolean purple_srv_txt_query_ui_resolve(PurpleSrvTxtQueryData *query_data); + /** * Sort by priority, then by weight. Strictly numerically--no * randomness. Technically we only need to sort by pref and then @@ -430,7 +430,7 @@ { int size; int type; - PurpleSrvQueryData *query_data = (PurpleSrvQueryData*)data; + PurpleSrvTxtQueryData *query_data = (PurpleSrvTxtQueryData*)data; int i; int status; @@ -532,7 +532,7 @@ res_main_thread_cb(gpointer data) { PurpleSrvResponse *srvres = NULL; - PurpleSrvQueryData *query_data = data; + PurpleSrvTxtQueryData *query_data = data; if(query_data->error_message != NULL) { purple_debug_error("dnssrv", "%s", query_data->error_message); if (query_data->type == DNS_TYPE_SRV) { @@ -592,7 +592,7 @@ PDNS_RECORD dr = NULL; int type; DNS_STATUS ds; - PurpleSrvQueryData *query_data = data; + PurpleSrvTxtQueryData *query_data = data; type = query_data->type; ds = MyDnsQuery_UTF8(query_data->query, type, DNS_QUERY_STANDARD, NULL, &dr, NULL); if (ds != ERROR_SUCCESS) { @@ -672,12 +672,12 @@ #endif -PurpleSrvQueryData * +PurpleSrvTxtQueryData * purple_srv_resolve(const char *protocol, const char *transport, const char *domain, PurpleSrvCallback cb, gpointer extradata) { char *query; char *hostname; - PurpleSrvQueryData *query_data; + PurpleSrvTxtQueryData *query_data; #ifndef _WIN32 PurpleSrvInternalQuery internal_query; int in[2], out[2]; @@ -709,6 +709,17 @@ purple_debug_info("dnssrv","querying SRV record for %s: %s\n", domain, query); g_free(hostname); + + query_data = g_new0(PurpleSrvTxtQueryData, 1); + query_data->type = PurpleDnsTypeSrv; + query_data->cb.srv = cb; + query_data->extradata = extradata; + query_data->query = query; + + if (purple_srv_txt_query_ui_resolve(query_data)) + { + return query_data; + } #ifndef _WIN32 if(pipe(in) || pipe(out)) { @@ -747,10 +758,6 @@ if (write(in[1], &internal_query, sizeof(internal_query)) < 0) purple_debug_error("dnssrv", "Could not write to SRV resolver\n"); - query_data = g_new0(PurpleSrvQueryData, 1); - query_data->type = T_SRV; - query_data->cb.srv = cb; - query_data->extradata = extradata; query_data->pid = pid; query_data->fd_out = out[0]; query_data->fd_in = in[1]; @@ -767,12 +774,6 @@ initialized = TRUE; } - query_data = g_new0(PurpleSrvQueryData, 1); - query_data->type = DNS_TYPE_SRV; - query_data->cb.srv = cb; - query_data->query = query; - query_data->extradata = extradata; - if (!MyDnsQuery_UTF8 || !MyDnsRecordListFree) query_data->error_message = g_strdup("System missing DNS API (Requires W2K+)\n"); else { @@ -793,11 +794,11 @@ #endif } -PurpleSrvQueryData *purple_txt_resolve(const char *owner, const char *domain, PurpleTxtCallback cb, gpointer extradata) +PurpleSrvTxtQueryData *purple_txt_resolve(const char *owner, const char *domain, PurpleTxtCallback cb, gpointer extradata) { char *query; char *hostname; - PurpleSrvQueryData *query_data; + PurpleSrvTxtQueryData *query_data; #ifndef _WIN32 PurpleSrvInternalQuery internal_query; int in[2], out[2]; @@ -823,6 +824,18 @@ purple_debug_info("dnssrv","querying TXT record for %s: %s\n", domain, query); g_free(hostname); + + query_data = g_new0(PurpleSrvTxtQueryData, 1); + query_data->type = PurpleDnsTypeTxt; + query_data->cb.txt = cb; + query_data->extradata = extradata; + query_data->query = query; + + if (purple_srv_txt_query_ui_resolve(query_data)) { + /* query intentionally not freed + */ + return query_data; + } #ifndef _WIN32 if(pipe(in) || pipe(out)) { @@ -860,11 +873,7 @@ if (write(in[1], &internal_query, sizeof(internal_query)) < 0) purple_debug_error("dnssrv", "Could not write to TXT resolver\n"); - - query_data = g_new0(PurpleSrvQueryData, 1); - query_data->type = T_TXT; - query_data->cb.txt = cb; - query_data->extradata = extradata; + query_data->pid = pid; query_data->fd_out = out[0]; query_data->fd_in = in[1]; @@ -881,12 +890,6 @@ initialized = TRUE; } - query_data = g_new0(PurpleSrvQueryData, 1); - query_data->type = DNS_TYPE_TXT; - query_data->cb.txt = cb; - query_data->query = query; - query_data->extradata = extradata; - if (!MyDnsQuery_UTF8 || !MyDnsRecordListFree) query_data->error_message = g_strdup("System missing DNS API (Requires W2K+)\n"); else { @@ -908,8 +911,13 @@ } void -purple_srv_cancel(PurpleSrvQueryData *query_data) +purple_srv_cancel(PurpleSrvTxtQueryData *query_data) { + PurpleSrvTxtQueryUiOps *ops = purple_srv_txt_query_get_ui_ops(); + + if (ops && ops->destroy) + ops->destroy(query_data); + if (query_data->handle > 0) purple_input_remove(query_data->handle); #ifdef _WIN32 @@ -933,7 +941,7 @@ } void -purple_txt_cancel(PurpleSrvQueryData *query_data) +purple_txt_cancel(PurpleSrvTxtQueryData *query_data) { purple_srv_cancel(query_data); } @@ -953,3 +961,85 @@ g_free(resp->content); g_free(resp); } + +/* + * Only used as the callback for the ui ops. + */ +static void +purple_srv_query_resolved(PurpleSrvTxtQueryData *query_data, GList *records) +{ + g_return_if_fail(records != NULL); + + purple_debug_info("dnssrv", "SRV records resolved for %s, count: %d\n", query_data->query, g_list_length(records)); + + if (query_data->cb.srv != NULL) + query_data->cb.srv(purple_srv_sort(records)->data, g_list_length(records), query_data->extradata); +} + +/* + * Only used as the callback for the ui ops. + */ +static void +purple_txt_query_resolved(PurpleSrvTxtQueryData *query_data, GList *entries) +{ + g_return_if_fail(entries != NULL); + + purple_debug_info("dnssrv", "TXT entries resolved for %s, count: %d\n", query_data->query, g_list_length(entries)); + + if (query_data->cb.txt != NULL) + query_data->cb.txt(entries, query_data->extradata); +} + +static void +purple_srv_query_failed(PurpleSrvTxtQueryData *query_data, const gchar *error_message) +{ + purple_debug_error("dnssrv", "%s\n", error_message); + + if (query_data->cb.srv != NULL) + query_data->cb.srv(NULL, 0, query_data->extradata); + + purple_srv_cancel(query_data); +} + +static gboolean +purple_srv_txt_query_ui_resolve(PurpleSrvTxtQueryData *query_data) +{ + PurpleSrvTxtQueryUiOps *ops = purple_srv_txt_query_get_ui_ops(); + + if (ops && ops->resolve) + return ops->resolve(query_data, (query_data->type == T_SRV ? purple_srv_query_resolved : purple_txt_query_resolved), purple_srv_query_failed); + + return FALSE; +} + +void +purple_srv_txt_query_set_ui_ops(PurpleSrvTxtQueryUiOps *ops) +{ + srv_txt_query_ui_ops = ops; +} + +PurpleSrvTxtQueryUiOps * +purple_srv_txt_query_get_ui_ops(void) +{ + /* It is perfectly acceptable for srv_txt_query_ui_ops to be NULL; this just + * means that the default platform-specific implementation will be used. + */ + return srv_txt_query_ui_ops; +} + +char * +purple_srv_txt_query_get_query(PurpleSrvTxtQueryData *query_data) +{ + g_return_val_if_fail(query_data != NULL, NULL); + + return query_data->query; +} + + +int +purple_srv_txt_query_get_type(PurpleSrvTxtQueryData *query_data) +{ + g_return_val_if_fail(query_data != NULL, 0); + + return query_data->type; +} \ No newline at end of file diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/dnssrv.h --- a/libpurple/dnssrv.h Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/dnssrv.h Fri Apr 01 13:47:51 2011 +0900 @@ -28,12 +28,21 @@ extern "C" { #endif -typedef struct _PurpleSrvQueryData PurpleSrvQueryData; +typedef struct _PurpleSrvTxtQueryData PurpleSrvTxtQueryData; typedef struct _PurpleSrvResponse PurpleSrvResponse; typedef struct _PurpleTxtResponse PurpleTxtResponse; +/* For compatibility, should be removed for 3.0.0 + */ +typedef struct _PurpleSrvTxtQueryData PurpleSrvQueryData; + #include +enum PurpleDnsType { + PurpleDnsTypeTxt = 16, + PurpleDnsTypeSrv = 33 +}; + struct _PurpleSrvResponse { char hostname[256]; int port; @@ -41,6 +50,40 @@ int pref; }; +struct _PurpleTxtResponse { + char *content; +}; + +typedef void (*PurpleSrvTxtQueryResolvedCallback) (PurpleSrvTxtQueryData *query_data, GList *records); +typedef void (*PurpleSrvTxtQueryFailedCallback) (PurpleSrvTxtQueryData *query_data, const gchar *error_message); + +/** + * SRV Request UI operations; UIs should implement this if they want to do SRV + * lookups themselves, rather than relying on the core. + * + * @see @ref ui-ops + */ +typedef struct +{ + /** If implemented, return TRUE if the UI takes responsibility for SRV + * queries. When returning FALSE, the standard implementation is used. + * These callbacks MUST be called asynchronously. */ + gboolean (*resolve)(PurpleSrvTxtQueryData *query_data, + PurpleSrvTxtQueryResolvedCallback resolved_cb, + PurpleSrvTxtQueryFailedCallback failed_cb); + + /** Called just before @a query_data is freed; this should cancel any + * further use of @a query_data the UI would make. Unneeded if + * #resolve_host is not implemented. + */ + void (*destroy)(PurpleSrvTxtQueryData *query_data); + + void (*_purple_reserved1)(void); + void (*_purple_reserved2)(void); + void (*_purple_reserved3)(void); + void (*_purple_reserved4)(void); +} PurpleSrvTxtQueryUiOps; + /** * @param resp An array of PurpleSrvResponse of size results. The array * is sorted based on the order described in the DNS SRV RFC. @@ -66,14 +109,14 @@ * @param cb A callback which will be called with the results * @param extradata Extra data to be passed to the callback */ -PurpleSrvQueryData *purple_srv_resolve(const char *protocol, const char *transport, const char *domain, PurpleSrvCallback cb, gpointer extradata); +PurpleSrvTxtQueryData *purple_srv_resolve(const char *protocol, const char *transport, const char *domain, PurpleSrvCallback cb, gpointer extradata); /** - * Cancel an SRV DNS query. + * Cancel an SRV or DNS query. * * @param query_data The request to cancel. */ -void purple_srv_cancel(PurpleSrvQueryData *query_data); +void purple_srv_cancel(PurpleSrvTxtQueryData *query_data); /** * Queries an TXT record. @@ -85,7 +128,7 @@ * * @since 2.6.0 */ -PurpleSrvQueryData *purple_txt_resolve(const char *owner, const char *domain, PurpleTxtCallback cb, gpointer extradata); +PurpleSrvTxtQueryData *purple_txt_resolve(const char *owner, const char *domain, PurpleTxtCallback cb, gpointer extradata); /** * Cancel an TXT DNS query. @@ -93,7 +136,7 @@ * @param query_data The request to cancel. * @since 2.6.0 */ -void purple_txt_cancel(PurpleSrvQueryData *query_data); +void purple_txt_cancel(PurpleSrvTxtQueryData *query_data); /** * Get the value of the current TXT record. @@ -112,6 +155,47 @@ */ void purple_txt_response_destroy(PurpleTxtResponse *response); +/** + * Cancel a SRV/TXT query and destroy the associated data structure. + * + * @param query_data The SRV/TXT query to cancel. This data structure + * is freed by this function. + */ +void purple_srv_txt_query_destroy(PurpleSrvTxtQueryData *query_data); + +/** + * Sets the UI operations structure to be used when doing a SRV/TXT + * resolve. The UI operations need only be set if the UI wants to + * handle the resolve itself; otherwise, leave it as NULL. + * + * @param ops The UI operations structure. + */ +void purple_srv_txt_query_set_ui_ops(PurpleSrvTxtQueryUiOps *ops); + +/** + * Returns the UI operations structure to be used when doing a SRV/TXT + * resolve. + * + * @return The UI operations structure. + */ +PurpleSrvTxtQueryUiOps *purple_srv_txt_query_get_ui_ops(void); + +/** + * Get the query from a PurpleDnsQueryData + * + * @param query_data The SRV/TXT query + * @return The query. + */ +char *purple_srv_txt_query_get_query(PurpleSrvTxtQueryData *query_data); + +/** + * Get the type from a PurpleDnsQueryData (TXT or SRV) + * + * @param query_data The query + * @return The query. + */ +int purple_srv_txt_query_get_type(PurpleSrvTxtQueryData *query_data); + #ifdef __cplusplus } #endif diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/gaim-compat.h --- a/libpurple/gaim-compat.h Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/gaim-compat.h Fri Apr 01 13:47:51 2011 +0900 @@ -841,7 +841,7 @@ /* from dnssrv.h */ #define GaimSrvResponse PurpleSrvResponse -#define GaimSrvQueryData PurpleSrvQueryData +#define GaimSrvQueryData PurpleSrvTxtQueryData #define GaimSrvCallback PurpleSrvCallback #define gaim_srv_resolve purple_srv_resolve diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/media/backend-fs2.c --- a/libpurple/media/backend-fs2.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/media/backend-fs2.c Fri Apr 01 13:47:51 2011 +0900 @@ -86,6 +86,9 @@ PurpleMediaBackend *self, const gchar *sess_id, PurpleMediaCodec *codec); +static void free_stream(PurpleMediaBackendFs2Stream *stream); +static void free_session(PurpleMediaBackendFs2Session *session); + struct _PurpleMediaBackendFs2Class { GObjectClass parent_class; @@ -110,6 +113,8 @@ GstElement *tee; GstElement *volume; GstElement *level; + GstElement *fakesink; + GstElement *queue; GList *local_candidates; GList *remote_candidates; @@ -300,20 +305,7 @@ for (; priv->streams; priv->streams = g_list_delete_link(priv->streams, priv->streams)) { PurpleMediaBackendFs2Stream *stream = priv->streams->data; - - /* Remove the connected_cb timeout */ - if (stream->connected_cb_id != 0) - purple_timeout_remove(stream->connected_cb_id); - - g_free(stream->participant); - - if (stream->local_candidates) - fs_candidate_list_destroy(stream->local_candidates); - - if (stream->remote_candidates) - fs_candidate_list_destroy(stream->remote_candidates); - - g_free(stream); + free_stream(stream); } if (priv->sessions) { @@ -323,8 +315,7 @@ g_list_delete_link(sessions, sessions)) { PurpleMediaBackendFs2Session *session = sessions->data; - g_free(session->id); - g_free(session); + free_session(session); } g_hash_table_destroy(priv->sessions); @@ -1138,9 +1129,62 @@ } static void +remove_element(GstElement *element) +{ + if (element) { + gst_element_set_locked_state(element, TRUE); + gst_element_set_state(element, GST_STATE_NULL); + gst_bin_remove(GST_BIN(GST_ELEMENT_PARENT(element)), element); + } +} + +static void state_changed_cb(PurpleMedia *media, PurpleMediaState state, gchar *sid, gchar *name, PurpleMediaBackendFs2 *self) { + if (state == PURPLE_MEDIA_STATE_END) { + PurpleMediaBackendFs2Private *priv = + PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self); + + if (sid && name) { + PurpleMediaBackendFs2Stream *stream = get_stream(self, sid, name); + gst_object_unref(stream->stream); + + priv->streams = g_list_remove(priv->streams, stream); + + remove_element(stream->src); + remove_element(stream->tee); + remove_element(stream->volume); + remove_element(stream->level); + remove_element(stream->fakesink); + remove_element(stream->queue); + + free_stream(stream); + } else if (sid && !name) { + PurpleMediaBackendFs2Session *session = get_session(self, sid); + GstPad *pad; + + g_object_get(session->session, "sink-pad", &pad, NULL); + gst_pad_unlink(GST_PAD_PEER(pad), pad); + gst_object_unref(pad); + + gst_object_unref(session->session); + g_hash_table_remove(priv->sessions, session->id); + + pad = gst_pad_get_peer(session->srcpad); + gst_element_remove_pad(GST_ELEMENT_PARENT(pad), pad); + gst_object_unref(pad); + gst_object_unref(session->srcpad); + + remove_element(session->srcvalve); + remove_element(session->tee); + + free_session(session); + } + + purple_media_manager_remove_output_windows( + purple_media_get_manager(media), media, sid, name); + } } static void @@ -1420,6 +1464,7 @@ ? "success" : "failure"); gst_element_set_locked_state(session->src, FALSE); gst_object_unref(session->src); + gst_object_unref(sinkpad); gst_element_set_state(session->src, GST_STATE_PLAYING); @@ -1536,6 +1581,13 @@ return TRUE; } +static void +free_session(PurpleMediaBackendFs2Session *session) +{ + g_free(session->id); + g_free(session); +} + static gboolean create_participant(PurpleMediaBackendFs2 *self, const gchar *name) { @@ -1603,7 +1655,6 @@ GstElement *sink = NULL; if (codec->media_type == FS_MEDIA_TYPE_AUDIO) { - GstElement *queue = NULL; double output_volume = purple_prefs_get_int( "/purple/media/audio/volume/output")/10.0; /* @@ -1611,7 +1662,7 @@ * audioconvert ! audioresample ! liveadder ! * audioresample ! audioconvert ! realsink */ - queue = gst_element_factory_make("queue", NULL); + stream->queue = gst_element_factory_make("queue", NULL); stream->volume = gst_element_factory_make( "volume", NULL); g_object_set(stream->volume, "volume", @@ -1625,18 +1676,18 @@ PURPLE_MEDIA_RECV_AUDIO, priv->media, stream->session->id, stream->participant); - gst_bin_add(GST_BIN(priv->confbin), queue); + gst_bin_add(GST_BIN(priv->confbin), stream->queue); gst_bin_add(GST_BIN(priv->confbin), stream->volume); gst_bin_add(GST_BIN(priv->confbin), stream->level); gst_bin_add(GST_BIN(priv->confbin), sink); gst_element_set_state(sink, GST_STATE_PLAYING); gst_element_set_state(stream->level, GST_STATE_PLAYING); gst_element_set_state(stream->volume, GST_STATE_PLAYING); - gst_element_set_state(queue, GST_STATE_PLAYING); + gst_element_set_state(stream->queue, GST_STATE_PLAYING); gst_element_link(stream->level, sink); gst_element_link(stream->volume, stream->level); - gst_element_link(queue, stream->volume); - sink = queue; + gst_element_link(stream->queue, stream->volume); + sink = stream->queue; } else if (codec->media_type == FS_MEDIA_TYPE_VIDEO) { stream->src = gst_element_factory_make( "fsfunnel", NULL); @@ -1645,6 +1696,7 @@ g_object_set(G_OBJECT(sink), "async", FALSE, NULL); gst_bin_add(GST_BIN(priv->confbin), sink); gst_element_set_state(sink, GST_STATE_PLAYING); + stream->fakesink = sink; } stream->tee = gst_element_factory_make("tee", NULL); gst_bin_add_many(GST_BIN(priv->confbin), @@ -1814,6 +1866,24 @@ return TRUE; } +static void +free_stream(PurpleMediaBackendFs2Stream *stream) +{ + /* Remove the connected_cb timeout */ + if (stream->connected_cb_id != 0) + purple_timeout_remove(stream->connected_cb_id); + + g_free(stream->participant); + + if (stream->local_candidates) + fs_candidate_list_destroy(stream->local_candidates); + + if (stream->remote_candidates) + fs_candidate_list_destroy(stream->remote_candidates); + + g_free(stream); +} + static gboolean purple_media_backend_fs2_add_stream(PurpleMediaBackend *self, const gchar *sess_id, const gchar *who, diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/plugins/fortuneprofile.pl --- a/libpurple/plugins/fortuneprofile.pl Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/plugins/fortuneprofile.pl Fri Apr 01 13:47:51 2011 +0900 @@ -51,7 +51,7 @@ summary => "Sets your AIM profile to a fortune (with a header and footer of your choice).", description => "Sets your AIM profile to a fortune (with a header and footer of your choice).", author => "Sean Egan ", - url => "http://gaim.sf.net/", + url => "http://pidgin.im/", load => "plugin_load" ); diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/bonjour/bonjour.c --- a/libpurple/protocols/bonjour/bonjour.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/bonjour/bonjour.c Fri Apr 01 13:47:51 2011 +0900 @@ -534,7 +534,9 @@ NULL, /* get_media_caps */ NULL, /* get_moods */ NULL, /* set_public_alias */ - NULL /* get_public_alias */ + NULL, /* get_public_alias */ + NULL, /* add_buddy_with_invite */ + NULL /* add_buddies_with_invite */ }; static PurplePluginInfo info = diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/Makefile.am --- a/libpurple/protocols/gg/Makefile.am Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/gg/Makefile.am Fri Apr 01 13:47:51 2011 +0900 @@ -5,18 +5,26 @@ lib/COPYING \ lib/dcc.c \ lib/dcc7.c \ + lib/debug.c \ + lib/encoding.c \ + lib/encoding.h \ lib/events.c \ + lib/handlers.c \ lib/http.c \ + lib/libgadu.h \ lib/libgadu.c \ lib/libgadu-config.h \ + lib/libgadu-debug.h \ lib/libgadu-internal.h \ - lib/libgadu.h \ + lib/message.c \ + lib/message.h \ lib/obsolete.c \ lib/protocol.h \ lib/pubdir.c \ lib/pubdir50.c \ lib/resolver.c \ lib/resolver.h \ + lib/session.h \ lib/sha1.c pkgdir = $(libdir)/purple-$(PURPLE_MAJOR_VERSION) @@ -27,18 +35,25 @@ lib/compat.h \ lib/dcc.c \ lib/dcc7.c \ + lib/debug.c \ + lib/encoding.c \ + lib/encoding.h \ lib/events.c \ + lib/handlers.c \ lib/http.c \ + lib/libgadu.h \ lib/libgadu.c \ lib/libgadu-config.h \ lib/libgadu-internal.h \ - lib/libgadu.h \ + lib/message.c \ + lib/message.h \ lib/obsolete.c \ lib/protocol.h \ lib/pubdir.c \ lib/pubdir50.c \ lib/resolver.c \ lib/resolver.h \ + lib/session.h \ lib/sha1.c INTGG_CFLAGS = -I$(top_srcdir)/libpurple/protocols/gg/lib -DGG_IGNORE_DEPRECATED diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/Makefile.mingw --- a/libpurple/protocols/gg/Makefile.mingw Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/gg/Makefile.mingw Fri Apr 01 13:47:51 2011 +0900 @@ -43,9 +43,13 @@ lib/common.c \ lib/dcc.c \ lib/dcc7.c \ + lib/debug.c \ + lib/encoding.c \ lib/events.c \ + lib/handlers.c \ lib/http.c \ lib/libgadu.c \ + lib/message.c \ lib/obsolete.c \ lib/pubdir.c \ lib/pubdir50.c \ diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/gg.c --- a/libpurple/protocols/gg/gg.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/gg/gg.c Fri Apr 01 13:47:51 2011 +0900 @@ -1417,6 +1417,12 @@ gchar *msg; gchar *tmp; + if (ev->event.msg.message == NULL) + { + purple_debug_warning("gg", "ggp_recv_message_handler: NULL as message pointer\n"); + return; + } + from = g_strdup_printf("%lu", (unsigned long int)ev->event.msg.sender); /* @@ -1576,9 +1582,9 @@ { GGPInfo *info = gc->proto_data; PurpleStoredImage *image; - gint imgid = GPOINTER_TO_INT(g_hash_table_lookup(info->pending_images, &ev->event.image_request.crc32)); - - purple_debug_info("gg", "ggp_send_image_handler: image request received, crc32: %u\n", ev->event.image_request.crc32); + gint imgid = GPOINTER_TO_INT(g_hash_table_lookup(info->pending_images, GINT_TO_POINTER(ev->event.image_request.crc32))); + + purple_debug_info("gg", "ggp_send_image_handler: image request received, crc32: %u, imgid: %d\n", ev->event.image_request.crc32, imgid); if(imgid) { @@ -1593,10 +1599,21 @@ } else { purple_debug_error("gg", "ggp_send_image_handler: image imgid: %i, crc: %u in hash but not found in imgstore!\n", imgid, ev->event.image_request.crc32); } - g_hash_table_remove(info->pending_images, &ev->event.image_request.crc32); + g_hash_table_remove(info->pending_images, GINT_TO_POINTER(ev->event.image_request.crc32)); } } +static void ggp_typing_notification_handler(PurpleConnection *gc, uin_t uin, int length) { + gchar *from; + + from = g_strdup_printf("%u", uin); + if (length) + serv_got_typing(gc, from, 0, PURPLE_TYPING); + else + serv_got_typing(gc, from, 0, PURPLE_NOT_TYPING); + g_free(from); +} + static void ggp_callback_recv(gpointer _gc, gint fd, PurpleInputCondition cond) { PurpleConnection *gc = _gc; @@ -1710,6 +1727,10 @@ case GG_EVENT_PUBDIR50_SEARCH_REPLY: ggp_pubdir_reply_handler(gc, ev->event.pubdir50); break; + case GG_EVENT_TYPING_NOTIFICATION: + ggp_typing_notification_handler(gc, ev->event.typing_notification.uin, + ev->event.typing_notification.length); + break; default: purple_debug_error("gg", "unsupported event type=%d\n", ev->type); @@ -2002,7 +2023,7 @@ info->token = NULL; info->searches = ggp_search_new(); info->pending_richtext_messages = NULL; - info->pending_images = g_hash_table_new(g_int_hash, g_int_equal); + info->pending_images = g_hash_table_new(g_direct_hash, g_direct_equal); info->status_broadcasting = purple_account_get_bool(account, "status_broadcasting", TRUE); gc->proto_data = info; @@ -2015,7 +2036,8 @@ status = purple_presence_get_active_status(presence); glp->encoding = GG_ENCODING_UTF8; - glp->protocol_features = (GG_FEATURE_STATUS80|GG_FEATURE_DND_FFC); + glp->protocol_features = (GG_FEATURE_STATUS80|GG_FEATURE_DND_FFC + |GG_FEATURE_TYPING_NOTIFICATION); glp->async = 1; glp->status = ggp_to_gg_status(status, &glp->status_descr); @@ -2143,9 +2165,9 @@ const char *image_filename = purple_imgstore_get_filename(image); uint32_t crc32 = gg_crc32(0, image_bin, image_size); - g_hash_table_insert(info->pending_images, &crc32, GINT_TO_POINTER(atoi(id))); + g_hash_table_insert(info->pending_images, GINT_TO_POINTER(crc32), GINT_TO_POINTER(atoi(id))); purple_imgstore_ref(image); - purple_debug_info("gg", "ggp_send_im_richtext: got crc: %i for imgid: %i\n", crc32, atoi(id)); + purple_debug_info("gg", "ggp_send_im_richtext: got crc: %u for imgid: %i\n", crc32, atoi(id)); actformat.font = GG_FONT_IMAGE; actformat.position = pos; @@ -2156,15 +2178,14 @@ if (actimage.size > 255000) { purple_debug_warning("gg", "ggp_send_im_richtext: image over 255kb!\n"); - continue; + } else { + purple_debug_info("gg", "ggp_send_im_richtext: adding images to richtext, size: %i, crc32: %u, name: %s\n", actimage.size, actimage.crc32, image_filename); + + memcpy(format + format_length, &actformat, sizeof(actformat)); + format_length += sizeof(actformat); + memcpy(format + format_length, &actimage, sizeof(actimage)); + format_length += sizeof(actimage); } - - purple_debug_info("gg", "ggp_send_im_richtext: adding images to richtext, size: %i, crc32: %u, name: %s\n", actimage.size, actimage.crc32, image_filename); - - memcpy(format + format_length, &actformat, sizeof(actformat)); - format_length += sizeof(actformat); - memcpy(format + format_length, &actimage, sizeof(actimage)); - format_length += sizeof(actimage); } else { purple_debug_error("gg", "ggp_send_im_richtext: image not found in the image store!"); } @@ -2580,7 +2601,9 @@ NULL, /* can_do_media */ NULL, /* get_moods */ NULL, /* set_public_alias */ - NULL /* get_public_alias */ + NULL, /* get_public_alias */ + NULL, /* add_buddy_with_invite */ + NULL /* add_buddies_with_invite */ }; static PurplePluginInfo info = { diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/lib/COPYING --- a/libpurple/protocols/gg/lib/COPYING Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/gg/lib/COPYING Fri Apr 01 13:47:51 2011 +0900 @@ -2,7 +2,7 @@ Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. @@ -485,7 +485,7 @@ 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 Street, Fifth Floor, Boston, MA 02111-1301 USA + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/lib/common.c --- a/libpurple/protocols/gg/lib/common.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/gg/lib/common.c Fri Apr 01 13:47:51 2011 +0900 @@ -1,4 +1,4 @@ -/* $Id: common.c 878 2009-11-16 23:48:19Z wojtekka $ */ +/* $Id: common.c 1037 2010-12-17 22:18:08Z wojtekka $ */ /* * (C) Copyright 2001-2002 Wojtek Kaniewski @@ -19,29 +19,6 @@ * USA. */ -/* - * Funkcje konwersji między UTF-8 i CP1250 są oparte o kod biblioteki iconv. - * Informacje o prawach autorskich oryginalnego kodu zamieszczono poniżej: - * - * Copyright (C) 1999-2001, 2004 Free Software Foundation, Inc. - * This file is part of the GNU LIBICONV Library. - * - * The GNU LIBICONV Library is free software; you can redistribute it - * and/or modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * The GNU LIBICONV 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 - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with the GNU LIBICONV Library; see the file COPYING.LIB. - * If not, write to the Free Software Foundation, Inc., 51 Franklin Street, - * Fifth Floor, Boston, MA 02110-1301, USA. - */ - /** * \file common.c * @@ -66,7 +43,7 @@ #include #ifndef _WIN32 -# include +# include #endif #include @@ -75,81 +52,6 @@ #include #include -/** - * Plik, do którego będą przekazywane informacje odpluskwiania. - * - * Funkcja \c gg_debug() i pochodne mogą być przechwytywane przez aplikację - * korzystającą z biblioteki, by wyświetlić je na żądanie użytkownika lub - * zapisać do późniejszej analizy. Jeśli nie określono pliku, wybrane - * informacje będą wysyłane do standardowego wyjścia błędu (\c stderr). - * - * \ingroup debug - */ -FILE *gg_debug_file = NULL; - -#ifndef GG_DEBUG_DISABLE - -/** - * \internal Przekazuje informacje odpluskwiania do odpowiedniej funkcji. - * - * Jeśli aplikacja ustawiła odpowiednią funkcję obsługi w - * \c gg_debug_handler_session lub \c gg_debug_handler, jest ona wywoływana. - * W przeciwnym wypadku wynik jest wysyłany do standardowego wyjścia błędu. - * - * \param sess Struktura sesji (może być \c NULL) - * \param level Poziom informacji - * \param format Format wiadomości (zgodny z \c printf) - * \param ap Lista argumentów (zgodna z \c printf) - */ -void gg_debug_common(struct gg_session *sess, int level, const char *format, va_list ap) -{ - if (gg_debug_handler_session) - (*gg_debug_handler_session)(sess, level, format, ap); - else if (gg_debug_handler) - (*gg_debug_handler)(level, format, ap); - else if (gg_debug_level & level) - vfprintf(gg_debug_file ? gg_debug_file : stderr, format, ap); -} - - -/** - * \internal Przekazuje informację odpluskawiania. - * - * \param level Poziom wiadomości - * \param format Format wiadomości (zgodny z \c printf) - * - * \ingroup debug - */ -void gg_debug(int level, const char *format, ...) -{ - va_list ap; - int old_errno = errno; - va_start(ap, format); - gg_debug_common(NULL, level, format, ap); - va_end(ap); - errno = old_errno; -} - -/** - * \internal Przekazuje informację odpluskwiania związaną z sesją. - * - * \param sess Struktura sesji - * \param level Poziom wiadomości - * \param format Format wiadomości (zgodny z \c printf) - * - * \ingroup debug - */ -void gg_debug_session(struct gg_session *sess, int level, const char *format, ...) -{ - va_list ap; - int old_errno = errno; - va_start(ap, format); - gg_debug_common(sess, level, format, ap); - va_end(ap); - errno = old_errno; -} - -#endif /** * \internal Odpowiednik funkcji \c vsprintf alokujący miejsce na wynik. @@ -167,7 +69,6 @@ char *gg_vsaprintf(const char *format, va_list ap) { int size = 0; - const char *start; char *buf = NULL; #ifdef GG_CONFIG_HAVE_VA_COPY @@ -182,8 +83,6 @@ # endif #endif - start = format; - #ifndef GG_CONFIG_HAVE_C99_VSNPRINTF { int res; @@ -212,8 +111,6 @@ } #endif - format = start; - #ifdef GG_CONFIG_HAVE_VA_COPY vsnprintf(buf, size + 1, format, aq); va_end(aq); @@ -311,7 +208,7 @@ for (; length > 1; buf++, length--) { do { - if ((ret = read(sock, buf, 1)) == -1 && errno != EINTR) { + if ((ret = read(sock, buf, 1)) == -1 && errno != EINTR && errno != EAGAIN) { gg_debug(GG_DEBUG_MISC, "// gg_read_line() error on read (errno=%d, %s)\n", errno, strerror(errno)); *buf = 0; return NULL; @@ -320,7 +217,7 @@ *buf = 0; return NULL; } - } while (ret == -1 && errno == EINTR); + } while (ret == -1 && (errno == EINTR || errno == EAGAIN)); if (*buf == '\n') { buf++; @@ -370,10 +267,6 @@ return -1; } -#ifdef ASSIGN_SOCKETS_TO_THREADS - gg_win32_thread_socket(0, sock); -#endif - if (async) { #ifdef FIONBIO if (ioctl(sock, FIONBIO, &one) == -1) { @@ -445,7 +338,8 @@ */ char *gg_urlencode(const char *str) { - char *q, *buf, hex[] = "0123456789abcdef"; + char *q, *buf; + const char hex[] = "0123456789abcdef"; const char *p; unsigned int size = 0; @@ -524,80 +418,6 @@ return (b < 0 ? -b : b); } -#ifdef ASSIGN_SOCKETS_TO_THREADS - -typedef struct gg_win32_thread { - int id; - int socket; - struct gg_win32_thread *next; -} gg_win32_thread; - -struct gg_win32_thread *gg_win32_threads = 0; - -/** - * \internal Zwraca deskryptor gniazda, które było ostatnio tworzone dla wątku. - * - * Jeśli na win32 przy połączeniach synchronicznych zapamiętamy w jakim - * wątku uruchomiliśmy funkcję, która się z czymkolwiek łączy, to z osobnego - * wątku możemy anulować połączenie poprzez \c gg_win32_thread_socket(watek,-1) - * - * \param thread_id Identyfikator wątku (jeśli jest równe 0, brany jest - * aktualny wątek, jeśli równe -1, usuwa wpis dotyczący - * danego gniazda sockecie) - * \param socket Deskryptor gniazda (jeśli równe 0, zwraca deskryptor gniazda - * dla podanego wątku, jeśli równe -1, usuwa wpis, jeśli coś - * innego, ustawia dla podanego wątku dany numer deskryptora) - * - * \return Jeśli socket jest równe 0, zwraca deskryptor gniazda dla podanego - * wątku. - */ -int gg_win32_thread_socket(int thread_id, int socket) -{ - char close = (thread_id == -1) || socket == -1; - gg_win32_thread *wsk = gg_win32_threads; - gg_win32_thread **p_wsk = &gg_win32_threads; - - if (!thread_id) - thread_id = GetCurrentThreadId(); - - while (wsk) { - if ((thread_id == -1 && wsk->socket == socket) || wsk->id == thread_id) { - if (close) { - /* socket zostaje usuniety */ - closesocket(wsk->socket); - *p_wsk = wsk->next; - free(wsk); - return 1; - } else if (!socket) { - /* socket zostaje zwrocony */ - return wsk->socket; - } else { - /* socket zostaje ustawiony */ - wsk->socket = socket; - return socket; - } - } - p_wsk = &(wsk->next); - wsk = wsk->next; - } - - if (close && socket != -1) - closesocket(socket); - if (close || !socket) - return 0; - - /* Dodaje nowy element */ - wsk = malloc(sizeof(gg_win32_thread)); - wsk->id = thread_id; - wsk->socket = socket; - wsk->next = 0; - *p_wsk = wsk; - - return socket; -} - -#endif /* ASSIGN_SOCKETS_TO_THREADS */ - /** * \internal Zestaw znaków kodowania base64. */ @@ -732,7 +552,7 @@ * \return Zaalokowany bufor z tekstem lub NULL, jeśli serwer pośredniczący * nie jest używany lub nie wymaga autoryzacji. */ -char *gg_proxy_auth() +char *gg_proxy_auth(void) { char *tmp, *enc, *out; unsigned int tmp_size; @@ -767,33 +587,73 @@ /** * \internal Tablica pomocnicza do wyznaczania sumy kontrolnej. */ -static uint32_t gg_crc32_table[256]; - -/** - * \internal Flaga wypełnienia tablicy pomocniczej do wyznaczania sumy - * kontrolnej. - */ -static int gg_crc32_initialized = 0; - -/** - * \internal Tworzy tablicę pomocniczą do wyznaczania sumy kontrolnej. - */ -static void gg_crc32_make_table(void) +static const uint32_t gg_crc32_table[256] = { - uint32_t h = 1; - unsigned int i, j; - - memset(gg_crc32_table, 0, sizeof(gg_crc32_table)); - - for (i = 128; i; i >>= 1) { - h = (h >> 1) ^ ((h & 1) ? 0xedb88320L : 0); - - for (j = 0; j < 256; j += 2 * i) - gg_crc32_table[i + j] = gg_crc32_table[j] ^ h; - } - - gg_crc32_initialized = 1; -} + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, + 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, + 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, + 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, + 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, + 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, + 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, + 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, + 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, + 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, + 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, + 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, + 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, + 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, + 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, + 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, + 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, + 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; /** * Wyznacza sumę kontrolną CRC32. @@ -807,10 +667,7 @@ */ uint32_t gg_crc32(uint32_t crc, const unsigned char *buf, int len) { - if (!gg_crc32_initialized) - gg_crc32_make_table(); - - if (!buf || len < 0) + if (buf == NULL || len < 0) return crc; crc ^= 0xffffffffL; @@ -821,187 +678,6 @@ return crc ^ 0xffffffffL; } -/** - * \internal Tablica konwersji między CP1250 a UTF-8. - */ -static const uint16_t table_cp1250[] = { - 0x20ac, '?', 0x201a, '?', 0x201e, 0x2026, 0x2020, 0x2021, - '?', 0x2030, 0x0160, 0x2039, 0x015a, 0x0164, 0x017d, 0x0179, - '?', 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, - '?', 0x2122, 0x0161, 0x203a, 0x015b, 0x0165, 0x017e, 0x017a, - 0x00a0, 0x02c7, 0x02d8, 0x0141, 0x00a4, 0x0104, 0x00a6, 0x00a7, - 0x00a8, 0x00a9, 0x015e, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x017b, - 0x00b0, 0x00b1, 0x02db, 0x0142, 0x00b4, 0x00b5, 0x00b6, 0x00b7, - 0x00b8, 0x0105, 0x015f, 0x00bb, 0x013d, 0x02dd, 0x013e, 0x017c, - 0x0154, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x0139, 0x0106, 0x00c7, - 0x010c, 0x00c9, 0x0118, 0x00cb, 0x011a, 0x00cd, 0x00ce, 0x010e, - 0x0110, 0x0143, 0x0147, 0x00d3, 0x00d4, 0x0150, 0x00d6, 0x00d7, - 0x0158, 0x016e, 0x00da, 0x0170, 0x00dc, 0x00dd, 0x0162, 0x00df, - 0x0155, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x013a, 0x0107, 0x00e7, - 0x010d, 0x00e9, 0x0119, 0x00eb, 0x011b, 0x00ed, 0x00ee, 0x010f, - 0x0111, 0x0144, 0x0148, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x00f7, - 0x0159, 0x016f, 0x00fa, 0x0171, 0x00fc, 0x00fd, 0x0163, 0x02d9, -}; - -/** - * \internal Zamienia tekst kodowany CP1250 na UTF-8. - * - * \param b Tekst źródłowy w CP1250. - * - * \return Zaalokowany bufor z tekstem w UTF-8. - */ -char *gg_cp_to_utf8(const char *b) -{ - unsigned char *buf = (unsigned char *) b; - char *newbuf; - int newlen = 0; - int i, j; - - for (i = 0; buf[i]; i++) { - uint16_t znak = (buf[i] < 0x80) ? buf[i] : table_cp1250[buf[i]-0x80]; - - if (znak < 0x80) newlen += 1; - else if (znak < 0x800) newlen += 2; - else newlen += 3; - } - - if (!(newbuf = malloc(newlen+1))) { - gg_debug(GG_DEBUG_MISC, "// gg_cp_to_utf8() not enough memory\n"); - return NULL; - } - - for (i = 0, j = 0; buf[i]; i++) { - uint16_t znak = (buf[i] < 0x80) ? buf[i] : table_cp1250[buf[i]-0x80]; - int count; - - if (znak < 0x80) count = 1; - else if (znak < 0x800) count = 2; - else count = 3; - - switch (count) { - case 3: newbuf[j+2] = 0x80 | (znak & 0x3f); znak = znak >> 6; znak |= 0x800; - case 2: newbuf[j+1] = 0x80 | (znak & 0x3f); znak = znak >> 6; znak |= 0xc0; - case 1: newbuf[j] = znak; - } - j += count; - } - newbuf[j] = '\0'; - - return newbuf; -} - -/** - * \internal Dekoduje jeden znak UTF-8. - * - * \note Funkcja nie jest kompletną implementacją UTF-8, a wersją uproszczoną - * do potrzeb kodowania CP1250. - * - * \param s Tekst źródłowy. - * \param n Długość tekstu źródłowego. - * \param ch Wskaźnik na wynik dekodowania. - * - * \return Długość zdekodowanej sekwencji w bajtach lub wartość mniejsza - * od zera w przypadku błędu. - */ -static int gg_utf8_helper(unsigned char *s, int n, uint16_t *ch) -{ - unsigned char c = s[0]; - - if (c < 0x80) { - *ch = c; - return 1; - } - - if (c < 0xc2) - return -1; - - if (c < 0xe0) { - if (n < 2) - return -2; - if (!((s[1] ^ 0x80) < 0x40)) - return -1; - *ch = ((uint16_t) (c & 0x1f) << 6) | (uint16_t) (s[1] ^ 0x80); - return 2; - } - - if (c < 0xf0) { - if (n < 3) - return -2; - if (!((s[1] ^ 0x80) < 0x40 && (s[2] ^ 0x80) < 0x40 && (c >= 0xe1 || s[1] >= 0xa0))) - return -1; - *ch = ((uint16_t) (c & 0x0f) << 12) | ((uint16_t) (s[1] ^ 0x80) << 6) | (uint16_t) (s[2] ^ 0x80); - return 3; - } - - return -1; -} - -/** - * \internal Zamienia tekst kodowany UTF-8 na CP1250. - * - * \param b Tekst źródłowy w UTF-8. - * - * \return Zaalokowany bufor z tekstem w CP1250. - */ -char *gg_utf8_to_cp(const char *b) -{ - unsigned char *buf = (unsigned char *) b; - char *newbuf; - int newlen = 0; - int len; - int i, j; - - len = strlen(b); - - for (i = 0; i < len; newlen++) { - uint16_t discard; - int ret; - - ret = gg_utf8_helper(&buf[i], len - i, &discard); - - if (ret > 0) - i += ret; - else - i++; - } - - if (!(newbuf = malloc(newlen+1))) { - gg_debug(GG_DEBUG_MISC, "// gg_utf8_to_cp() not enough memory\n"); - return NULL; - } - - for (i = 0, j = 0; buf[i]; j++) { - uint16_t znak; - int ret, k; - - ret = gg_utf8_helper(&buf[i], len - i, &znak); - - if (ret > 0) { - i += ret; - } else { - znak = '?'; - i++; - } - - if (znak < 0x80) { - newbuf[j] = znak; - continue; - } - - newbuf[j] = '?'; - - for (k = 0; k < (sizeof(table_cp1250)/sizeof(table_cp1250[0])); k++) { - if (table_cp1250[k] == znak) { - newbuf[j] = (0x80 | k); - break; - } - } - } - newbuf[j] = '\0'; - - return newbuf; -} - /* * Local variables: * c-indentation-style: k&r diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/lib/dcc.c --- a/libpurple/protocols/gg/lib/dcc.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/gg/lib/dcc.c Fri Apr 01 13:47:51 2011 +0900 @@ -1,4 +1,4 @@ -/* $Id: dcc.c 711 2009-04-16 00:52:47Z darkjames $ */ +/* $Id: dcc.c 1023 2010-11-16 18:27:35Z wojtekka $ */ /* * (C) Copyright 2001-2008 Wojtek Kaniewski @@ -421,7 +421,7 @@ return NULL; } - if (!port) + if (port == 0 || port == -1) port = GG_DEFAULT_DCC_PORT; while (!bound) { diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/lib/dcc7.c --- a/libpurple/protocols/gg/lib/dcc7.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/gg/lib/dcc7.c Fri Apr 01 13:47:51 2011 +0900 @@ -1,9 +1,10 @@ -/* $Id: dcc7.c 711 2009-04-16 00:52:47Z darkjames $ */ +/* $Id: dcc7.c 1037 2010-12-17 22:18:08Z wojtekka $ */ /* - * (C) Copyright 2001-2008 Wojtek Kaniewski + * (C) Copyright 2001-2010 Wojtek Kaniewski * Tomasz Chiliński * Adam Wysocki + * Bartłomiej Zimoń * * Thanks to Jakub Zawadzki * @@ -29,6 +30,8 @@ */ #include "libgadu.h" +#include "libgadu-internal.h" +#include "libgadu-debug.h" #include #include @@ -55,9 +58,14 @@ #include #include "compat.h" +#include "protocol.h" +#include "resolver.h" -#define gg_debug_dcc(dcc, fmt...) \ - gg_debug_session((dcc) ? (dcc)->sess : NULL, fmt) +#define gg_debug_dcc(dcc, level, fmt...) \ + gg_debug_session(((dcc) != NULL) ? (dcc)->sess : NULL, level, fmt) + +#define gg_debug_dump_dcc(dcc, level, buf, len) \ + gg_debug_dump(((dcc) != NULL) ? (dcc)->sess : NULL, level, buf, len) /** * \internal Dodaje połączenie bezpośrednie do sesji. @@ -72,7 +80,7 @@ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_session_add(%p, %p)\n", sess, dcc); if (!sess || !dcc || dcc->next) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_session_remove() invalid parameters\n"); + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_session_add() invalid parameters\n"); errno = EINVAL; return -1; } @@ -97,7 +105,7 @@ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_session_remove(%p, %p)\n", sess, dcc); - if (!sess || !dcc) { + if (sess == NULL || dcc == NULL) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_session_remove() invalid parameters\n"); errno = EINVAL; return -1; @@ -109,9 +117,9 @@ return 0; } - for (tmp = sess->dcc7_list; tmp; tmp = tmp->next) { + for (tmp = sess->dcc7_list; tmp != NULL; tmp = tmp->next) { if (tmp->next == dcc) { - tmp = dcc->next; + tmp->next = dcc->next; dcc->next = NULL; return 0; } @@ -153,25 +161,53 @@ } /** - * \internal Nawiązuje połączenie bezpośrednie + * \internal Rozpoczyna proces pobierania adresu * - * \param sess Struktura sesji * \param dcc Struktura połączenia * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ -static int gg_dcc7_connect(struct gg_session *sess, struct gg_dcc7 *dcc) +static int gg_dcc7_get_relay_addr(struct gg_dcc7 *dcc) { - gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_connect(%p, %p)\n", sess, dcc); + gg_debug_dcc(dcc, GG_DEBUG_FUNCTION, "** gg_dcc7_get_relay_addr(%p)\n", dcc); + + if (dcc == NULL || dcc->sess == NULL) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_get_relay_addr() invalid parameters\n"); + errno = EINVAL; + return -1; + } + + if (dcc->sess->resolver_start(&dcc->fd, &dcc->resolver, GG_RELAY_HOST) == -1) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_get_relay_addr() resolving failed (errno=%d, %s)\n", errno, strerror(errno)); + return -1; + } + + dcc->state = GG_STATE_RESOLVING_RELAY; + dcc->check = GG_CHECK_READ; + dcc->timeout = GG_DEFAULT_TIMEOUT; - if (!sess || !dcc) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_connect() invalid parameters\n"); + return 0; +} + +/** + * \internal Nawiązuje połączenie bezpośrednie + * + * \param dcc Struktura połączenia + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +static int gg_dcc7_connect(struct gg_dcc7 *dcc) +{ + gg_debug_dcc(dcc, GG_DEBUG_FUNCTION, "** gg_dcc7_connect(%p)\n", dcc); + + if (dcc == NULL) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_connect() invalid parameters\n"); errno = EINVAL; return -1; } if ((dcc->fd = gg_connect(&dcc->remote_addr, dcc->remote_port, 1)) == -1) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_connect() connection failed\n"); + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_connect() connection failed\n"); return -1; } @@ -260,21 +296,39 @@ static int gg_dcc7_listen_and_send_info(struct gg_dcc7 *dcc) { struct gg_dcc7_info pkt; + uint16_t external_port; + uint16_t local_port; gg_debug_dcc(dcc, GG_DEBUG_FUNCTION, "** gg_dcc7_listen_and_send_info(%p)\n", dcc); - // XXX dać możliwość konfiguracji? + if (!dcc->sess->client_port) + local_port = dcc->sess->external_port; + else + local_port = dcc->sess->client_port; - dcc->local_addr = dcc->sess->client_addr; + if (gg_dcc7_listen(dcc, local_port) == -1) + return -1; + + if (!dcc->sess->external_port || dcc->local_port != local_port) + external_port = dcc->local_port; + else + external_port = dcc->sess->external_port; - if (gg_dcc7_listen(dcc, 0) == -1) - return -1; + if (!dcc->sess->external_addr || dcc->local_port != local_port) + dcc->local_addr = dcc->sess->client_addr; + else + dcc->local_addr = dcc->sess->external_addr; + + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// dcc7_listen_and_send_info() sending IP address %s and port %d\n", inet_ntoa(*((struct in_addr*) &dcc->local_addr)), external_port); memset(&pkt, 0, sizeof(pkt)); pkt.uin = gg_fix32(dcc->peer_uin); pkt.type = GG_DCC7_TYPE_P2P; pkt.id = dcc->cid; - snprintf((char*) pkt.info, sizeof(pkt.info), "%s %d", inet_ntoa(*((struct in_addr*) &dcc->local_addr)), dcc->local_port); + snprintf((char*) pkt.info, sizeof(pkt.info), "%s %d", inet_ntoa(*((struct in_addr*) &dcc->local_addr)), external_port); + // TODO: implement hash count + // we MUST fill hash to recive from server request for server connection + snprintf((char*) pkt.hash, sizeof(pkt.hash), "0"); return gg_send_packet(dcc->sess, GG_DCC7_INFO, &pkt, sizeof(pkt), NULL); } @@ -583,9 +637,9 @@ * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ -int gg_dcc7_handle_id(struct gg_session *sess, struct gg_event *e, void *payload, int len) +int gg_dcc7_handle_id(struct gg_session *sess, struct gg_event *e, const void *payload, int len) { - struct gg_dcc7_id_reply *p = payload; + const struct gg_dcc7_id_reply *p = payload; struct gg_dcc7 *tmp; gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_id(%p, %p, %p, %d)\n", sess, e, payload, len); @@ -633,9 +687,9 @@ * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ -int gg_dcc7_handle_accept(struct gg_session *sess, struct gg_event *e, void *payload, int len) +int gg_dcc7_handle_accept(struct gg_session *sess, struct gg_event *e, const void *payload, int len) { - struct gg_dcc7_accept *p = payload; + const struct gg_dcc7_accept *p = payload; struct gg_dcc7 *dcc; gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_accept(%p, %p, %p, %d)\n", sess, e, payload, len); @@ -673,35 +727,92 @@ * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ -int gg_dcc7_handle_info(struct gg_session *sess, struct gg_event *e, void *payload, int len) +int gg_dcc7_handle_info(struct gg_session *sess, struct gg_event *e, const void *payload, int len) { - struct gg_dcc7_info *p = payload; + const struct gg_dcc7_info *p = payload; struct gg_dcc7 *dcc; char *tmp; gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_info(%p, %p, %p, %d)\n", sess, e, payload, len); + gg_debug_session(sess, GG_DEBUG_FUNCTION, "// gg_dcc7_handle_info() received address: %s, hash: %s\n", p->info, p->hash); if (!(dcc = gg_dcc7_session_find(sess, p->id, gg_fix32(p->uin)))) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() unknown dcc session\n"); return 0; } - - if (p->type != GG_DCC7_TYPE_P2P) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() unhandled transfer type (%d)\n", p->type); - e->type = GG_EVENT_DCC7_ERROR; - e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + + if (dcc->state == GG_STATE_CONNECTED) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() state is already connected\n"); return 0; } - if ((dcc->remote_addr = inet_addr(p->info)) == INADDR_NONE) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid IP address\n"); - e->type = GG_EVENT_DCC7_ERROR; - e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; - return 0; - } + switch (p->type) + { + case GG_DCC7_TYPE_P2P: + if ((dcc->remote_addr = inet_addr(p->info)) == INADDR_NONE) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid IP address\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + return 0; + } + + if (!(tmp = strchr(p->info, ' ')) || !(dcc->remote_port = atoi(tmp + 1))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid IP port\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + return 0; + } + + if (dcc->state == GG_STATE_WAITING_FOR_INFO) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() wainting for info so send one\n"); + gg_dcc7_listen_and_send_info(dcc); + return 0; + } + + break; + + case GG_DCC7_TYPE_SERVER: + if (!(tmp = strstr(p->info, "GG"))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() unknown info packet\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + return 0; + } - if (!(tmp = strchr(p->info, ' ')) || !(dcc->remote_port = atoi(tmp + 1))) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid IP port\n"); +#if defined(HAVE_UINT64_T) && defined(HAVE_STRTOULL) + { + uint64_t cid; + + cid = strtoull(tmp + 2, NULL, 0); + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() info.str=%s, info.id=%llu, sess.id=%llu\n", tmp + 2, cid, *((unsigned long long*) &dcc->cid)); + + cid = gg_fix64(cid); + + if (memcmp(&dcc->cid, &cid, sizeof(cid)) != 0) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid session id\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + return 0; + } + } +#endif + + if (gg_dcc7_get_relay_addr(dcc) == -1) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_handle_info() unable to retrieve relay address\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_RELAY; + return 0; + } + + // XXX wysyłać dopiero jeśli uda się połączyć z serwerem? + + gg_send_packet(dcc->sess, GG_DCC7_INFO, payload, len, NULL); + + break; + + default: + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() unhandled transfer type (%d)\n", p->type); e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; return 0; @@ -710,19 +821,19 @@ // jeśli nadal czekamy na połączenie przychodzące, a druga strona nie // daje rady i oferuje namiary na siebie, bierzemy co dają. - if (dcc->state != GG_STATE_WAITING_FOR_INFO && (dcc->state != GG_STATE_LISTENING || dcc->reverse)) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid state\n"); - e->type = GG_EVENT_DCC7_ERROR; - e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; - return 0; - } +// if (dcc->state != GG_STATE_WAITING_FOR_INFO && (dcc->state != GG_STATE_LISTENING || dcc->reverse)) { +// gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid state\n"); +// e->type = GG_EVENT_DCC7_ERROR; +// e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; +// return 0; +// } if (dcc->state == GG_STATE_LISTENING) { close(dcc->fd); dcc->fd = -1; dcc->reverse = 1; } - + if (dcc->type == GG_SESSION_DCC7_SEND) { e->type = GG_EVENT_DCC7_ACCEPT; e->event.dcc7_accept.dcc7 = dcc; @@ -734,7 +845,7 @@ e->event.dcc7_pending.dcc7 = dcc; } - if (gg_dcc7_connect(sess, dcc) == -1) { + if (gg_dcc7_connect(dcc) == -1) { if (gg_dcc7_reverse_connect(dcc) == -1) { e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_NET; @@ -755,9 +866,9 @@ * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ -int gg_dcc7_handle_reject(struct gg_session *sess, struct gg_event *e, void *payload, int len) +int gg_dcc7_handle_reject(struct gg_session *sess, struct gg_event *e, const void *payload, int len) { - struct gg_dcc7_reject *p = payload; + const struct gg_dcc7_reject *p = payload; struct gg_dcc7 *dcc; gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_reject(%p, %p, %p, %d)\n", sess, e, payload, len); @@ -793,9 +904,9 @@ * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ -int gg_dcc7_handle_new(struct gg_session *sess, struct gg_event *e, void *payload, int len) +int gg_dcc7_handle_new(struct gg_session *sess, struct gg_event *e, const void *payload, int len) { - struct gg_dcc7_new *p = payload; + const struct gg_dcc7_new *p = payload; struct gg_dcc7 *dcc; gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_new(%p, %p, %p, %d)\n", sess, e, payload, len); @@ -1003,15 +1114,32 @@ if (error || (res = getsockopt(dcc->fd, SOL_SOCKET, SO_ERROR, &error, &error_size)) == -1 || error != 0) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection failed (%s)\n", (res == -1) ? strerror(errno) : strerror(error)); - if (gg_dcc7_reverse_connect(dcc) != -1) { - e->type = GG_EVENT_DCC7_PENDING; - e->event.dcc7_pending.dcc7 = dcc; + if (dcc->relay) { + for (dcc->relay_index++; dcc->relay_index < dcc->relay_count; dcc->relay_index++) { + dcc->remote_addr = dcc->relay_list[dcc->relay_index].addr; + dcc->remote_port = dcc->relay_list[dcc->relay_index].port; + + if (gg_dcc7_connect(dcc) == 0) + break; + } + + if (dcc->relay_index >= dcc->relay_count) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() no relay available"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_RELAY; + return e; + } } else { - e->type = GG_EVENT_DCC7_ERROR; - e->event.dcc_error = GG_ERROR_DCC7_NET; + if (gg_dcc7_reverse_connect(dcc) != -1) { + e->type = GG_EVENT_DCC7_PENDING; + e->event.dcc7_pending.dcc7 = dcc; + } else { + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_NET; + } + + return e; } - - return e; } gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connected, sending id\n"); @@ -1026,23 +1154,45 @@ case GG_STATE_READING_ID: { - gg_dcc7_id_t id; int res; gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_READING_ID\n"); - if ((res = read(dcc->fd, &id, sizeof(id))) != sizeof(id)) { - gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (%d, %s)\n", res, strerror(errno)); - e->type = GG_EVENT_DCC7_ERROR; - e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE; - return e; - } + if (!dcc->relay) { + struct gg_dcc7_welcome_p2p welcome, welcome_ok; + welcome_ok.id = dcc->cid; + + if ((res = read(dcc->fd, &welcome, sizeof(welcome))) != sizeof(welcome)) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (%d, %s)\n", res, strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE; + return e; + } - if (memcmp(&id, &dcc->cid, sizeof(id))) { - gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() invalid id\n"); - e->type = GG_EVENT_DCC7_ERROR; - e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE; - return e; + if (memcmp(&welcome, &welcome_ok, sizeof(welcome))) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() invalid id\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE; + return e; + } + } else { + struct gg_dcc7_welcome_server welcome, welcome_ok; + welcome_ok.magic = GG_DCC7_WELCOME_SERVER; + welcome_ok.id = dcc->cid; + + if ((res = read(dcc->fd, &welcome, sizeof(welcome))) != sizeof(welcome)) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (%d, %s)\n", res, strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE; + return e; + } + + if (memcmp(&welcome, &welcome_ok, sizeof(welcome)) != 0) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() invalid id\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE; + return e; + } } if (dcc->incoming) { @@ -1063,11 +1213,29 @@ gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_SENDING_ID\n"); - if ((res = write(dcc->fd, &dcc->cid, sizeof(dcc->cid))) != sizeof(dcc->cid)) { - gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() write() failed (%d, %s)", res, strerror(errno)); - e->type = GG_EVENT_DCC7_ERROR; - e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE; - return e; + if (!dcc->relay) { + struct gg_dcc7_welcome_p2p welcome; + + welcome.id = dcc->cid; + + if ((res = write(dcc->fd, &welcome, sizeof(welcome))) != sizeof(welcome)) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() write() failed (%d, %s)\n", res, strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE; + return e; + } + } else { + struct gg_dcc7_welcome_server welcome; + + welcome.magic = gg_fix32(GG_DCC7_WELCOME_SERVER); + welcome.id = dcc->cid; + + if ((res = write(dcc->fd, &welcome, sizeof(welcome))) != sizeof(welcome)) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() write() failed (%d, %s)\n", res, strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_HANDSHAKE; + return e; + } } if (dcc->incoming) { @@ -1092,6 +1260,7 @@ if (dcc->offset >= dcc->size) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() offset >= size, finished\n"); e->type = GG_EVENT_DCC7_DONE; + e->event.dcc7_done.dcc7 = dcc; return e; } @@ -1124,6 +1293,7 @@ if (dcc->offset >= dcc->size) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() finished\n"); e->type = GG_EVENT_DCC7_DONE; + e->event.dcc7_done.dcc7 = dcc; return e; } @@ -1144,6 +1314,7 @@ if (dcc->offset >= dcc->size) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() finished\n"); e->type = GG_EVENT_DCC7_DONE; + e->event.dcc7_done.dcc7 = dcc; return e; } @@ -1168,6 +1339,7 @@ if (dcc->offset >= dcc->size) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() finished\n"); e->type = GG_EVENT_DCC7_DONE; + e->event.dcc7_done.dcc7 = dcc; return e; } @@ -1178,6 +1350,157 @@ return e; } + case GG_STATE_RESOLVING_RELAY: + { + struct in_addr addr; + + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_RESOLVING_RELAY\n"); + + if (read(dcc->fd, &addr, sizeof(addr)) < sizeof(addr) || addr.s_addr == INADDR_NONE) { + int errno_save = errno; + + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() resolving failed\n"); + close(dcc->fd); + dcc->fd = -1; + dcc->sess->resolver_cleanup(&dcc->resolver, 0); + errno = errno_save; + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_RELAY; + return e; + } + + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() resolved, connecting to %s:%d\n", inet_ntoa(addr), GG_RELAY_PORT); + + if ((dcc->fd = gg_connect(&addr, GG_RELAY_PORT, 1)) == -1) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection failed (errno=%d, %s), critical\n", errno, strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_RELAY; + return e; + } + + dcc->state = GG_STATE_CONNECTING_RELAY; + dcc->check = GG_CHECK_WRITE; + dcc->timeout = GG_DEFAULT_TIMEOUT; + + return e; + } + + case GG_STATE_CONNECTING_RELAY: + { + int res; + unsigned int res_size = sizeof(res); + struct gg_dcc7_relay_req pkt; + + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_CONNECTING_RELAY\n"); + + if (getsockopt(dcc->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) != 0 || res != 0) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection failed (errno=%d, %s)\n", res, strerror(res)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_RELAY; + return e; + } + + memset(&pkt, 0, sizeof(pkt)); + pkt.magic = gg_fix32(GG_DCC7_RELAY_REQUEST); + pkt.len = gg_fix32(sizeof(pkt)); + pkt.id = dcc->cid; + pkt.type = gg_fix16(GG_DCC7_RELAY_TYPE_SERVER); + pkt.dunno1 = gg_fix16(GG_DCC7_RELAY_DUNNO1); + + gg_debug_dcc(dcc, GG_DEBUG_DUMP, "// gg_dcc7_watch_fd() send pkt(0x%.2x)\n", gg_fix32(pkt.magic)); + gg_debug_dump_dcc(dcc, GG_DEBUG_DUMP, (const char*) &pkt, sizeof(pkt)); + + if ((res = write(dcc->fd, &pkt, sizeof(pkt))) != sizeof(pkt)) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() sending failed\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_RELAY; + return e; + } + + dcc->state = GG_STATE_READING_RELAY; + dcc->check = GG_CHECK_READ; + dcc->timeout = GG_DEFAULT_TIMEOUT; + + return e; + } + + case GG_STATE_READING_RELAY: + { + char buf[256]; + struct gg_dcc7_relay_reply *pkt; + struct gg_dcc7_relay_reply_server srv; + int res; + int i; + + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_READING_RELAY\n"); + + if ((res = read(dcc->fd, buf, sizeof(buf))) < sizeof(*pkt)) { + if (res == 0) + errno = ECONNRESET; + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (%d, %s)\n", res, strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_RELAY; + return e; + } + + pkt = (struct gg_dcc7_relay_reply*) buf; + + if (gg_fix32(pkt->magic) != GG_DCC7_RELAY_REPLY || gg_fix32(pkt->rcount) < 1 || gg_fix32(pkt->rcount > 256) || gg_fix32(pkt->len) < sizeof(*pkt) + gg_fix32(pkt->rcount) * sizeof(srv)) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_wathc_fd() invalid reply\n"); + errno = EINVAL; + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_RELAY; + return e; + } + + gg_debug_dcc(dcc, GG_DEBUG_DUMP, "// gg_dcc7_get_relay() read pkt(0x%.2x)\n", gg_fix32(pkt->magic)); + gg_debug_dump_dcc(dcc, GG_DEBUG_DUMP, buf, res); + + free(dcc->relay_list); + + dcc->relay_index = 0; + dcc->relay_count = gg_fix32(pkt->rcount); + dcc->relay_list = malloc(dcc->relay_count * sizeof(gg_dcc7_relay_t)); + + if (dcc->relay_list == NULL) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() not enough memory"); + dcc->relay_count = 0; + free(e); + return NULL; + } + + for (i = 0; i < dcc->relay_count; i++) { + struct in_addr addr; + + memcpy(&srv, buf + sizeof(*pkt) + i * sizeof(srv), sizeof(srv)); + dcc->relay_list[i].addr = srv.addr; + dcc->relay_list[i].port = gg_fix16(srv.port); + dcc->relay_list[i].family = srv.family; + + addr.s_addr = srv.addr; + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// %s %d %d\n", inet_ntoa(addr), gg_fix16(srv.port), srv.family); + } + + dcc->relay = 1; + + for (; dcc->relay_index < dcc->relay_count; dcc->relay_index++) { + dcc->remote_addr = dcc->relay_list[dcc->relay_index].addr; + dcc->remote_port = dcc->relay_list[dcc->relay_index].port; + + if (gg_dcc7_connect(dcc) == 0) + break; + } + + if (dcc->relay_index >= dcc->relay_count) { + gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() no relay available"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc_error = GG_ERROR_DCC7_RELAY; + return e; + } + + return e; + } + default: { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_???\n"); @@ -1214,6 +1537,8 @@ if (dcc->sess) gg_dcc7_session_remove(dcc->sess, dcc); + free(dcc->relay_list); + free(dcc); } diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/lib/debug.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/gg/lib/debug.c Fri Apr 01 13:47:51 2011 +0900 @@ -0,0 +1,298 @@ +/* + * (C) Copyright 2001-2006 Wojtek Kaniewski + * Robert J. Woźny + * Arkadiusz Miśkiewicz + * Tomasz Chiliński + * Adam Wysocki + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/** + * \file debug.c + * + * \brief Funkcje odpluskwiania + */ +#include +#include +#include +#include +#include + +#include "libgadu.h" +#include "debug.h" + +/** + * Poziom rejestracji informacji odpluskwiających. Zmienna jest maską bitową + * składającą się ze stałych \c GG_DEBUG_... + * + * \ingroup debug + */ +int gg_debug_level = 0; + +/** + * Funkcja, do której są przekazywane informacje odpluskwiające. Jeśli zarówno + * ten \c gg_debug_handler, jak i \c gg_debug_handler_session, są równe + * \c NULL, informacje są wysyłane do standardowego wyjścia błędu (\c stderr). + * + * \param level Poziom rejestracji + * \param format Format wiadomości (zgodny z \c printf) + * \param ap Lista argumentów (zgodna z \c printf) + * + * \note Funkcja jest przesłaniana przez \c gg_debug_handler_session. + * + * \ingroup debug + */ +void (*gg_debug_handler)(int level, const char *format, va_list ap) = NULL; + +/** + * Funkcja, do której są przekazywane informacje odpluskwiające. Jeśli zarówno + * ten \c gg_debug_handler, jak i \c gg_debug_handler_session, są równe + * \c NULL, informacje są wysyłane do standardowego wyjścia błędu. + * + * \param sess Sesja której dotyczy informacja lub \c NULL + * \param level Poziom rejestracji + * \param format Format wiadomości (zgodny z \c printf) + * \param ap Lista argumentów (zgodna z \c printf) + * + * \note Funkcja przesłania przez \c gg_debug_handler_session. + * + * \ingroup debug + */ +void (*gg_debug_handler_session)(struct gg_session *sess, int level, const char *format, va_list ap) = NULL; + +/** + * Plik, do którego będą przekazywane informacje odpluskwiania. + * + * Funkcja \c gg_debug() i pochodne mogą być przechwytywane przez aplikację + * korzystającą z biblioteki, by wyświetlić je na żądanie użytkownika lub + * zapisać do późniejszej analizy. Jeśli nie określono pliku, wybrane + * informacje będą wysyłane do standardowego wyjścia błędu (\c stderr). + * + * \ingroup debug + */ +FILE *gg_debug_file = NULL; + +#ifndef GG_DEBUG_DISABLE + +/** + * \internal Przekazuje informacje odpluskwiania do odpowiedniej funkcji. + * + * Jeśli aplikacja ustawiła odpowiednią funkcję obsługi w + * \c gg_debug_handler_session lub \c gg_debug_handler, jest ona wywoływana. + * W przeciwnym wypadku wynik jest wysyłany do standardowego wyjścia błędu. + * + * \param sess Struktura sesji (może być \c NULL) + * \param level Poziom informacji + * \param format Format wiadomości (zgodny z \c printf) + * \param ap Lista argumentów (zgodna z \c printf) + */ +void gg_debug_common(struct gg_session *sess, int level, const char *format, va_list ap) +{ + if (gg_debug_handler_session != NULL) + (*gg_debug_handler_session)(sess, level, format, ap); + else if (gg_debug_handler != NULL) + (*gg_debug_handler)(level, format, ap); + else if ((gg_debug_level & level) != 0) + vfprintf((gg_debug_file) ? gg_debug_file : stderr, format, ap); +} + + +/** + * \internal Przekazuje informację odpluskawiania. + * + * \param level Poziom wiadomości + * \param format Format wiadomości (zgodny z \c printf) + * + * \ingroup debug + */ +void gg_debug(int level, const char *format, ...) +{ + va_list ap; + int old_errno = errno; + + va_start(ap, format); + gg_debug_common(NULL, level, format, ap); + va_end(ap); + errno = old_errno; +} + +/** + * \internal Przekazuje informację odpluskwiania związaną z sesją. + * + * \param gs Struktura sesji + * \param level Poziom wiadomości + * \param format Format wiadomości (zgodny z \c printf) + * + * \ingroup debug + */ +void gg_debug_session(struct gg_session *gs, int level, const char *format, ...) +{ + va_list ap; + int old_errno = errno; + + va_start(ap, format); + gg_debug_common(gs, level, format, ap); + va_end(ap); + errno = old_errno; +} + +/** + * \internal Przekazuje zrzut bufora do odpluskwiania. + * + * \param gs Struktura sesji + * \param level Poziom wiadomości + * \param buf Bufor danych + * \param len Długość bufora danych + * + * \ingroup debug + */ +void gg_debug_dump(struct gg_session *gs, int level, const char *buf, size_t len) +{ + char line[80]; + int i, j; + + for (i = 0; i < len; i += 16) { + int ofs; + + sprintf(line, "%.4x: ", i); + ofs = 6; + + for (j = 0; j < 16; j++) { + if (i + j < len) + sprintf(line + ofs, " %02x", (unsigned char) buf[i + j]); + else + sprintf(line + ofs, " "); + + ofs += 3; + } + + sprintf(line + ofs, " "); + ofs += 2; + + for (j = 0; j < 16; j++) { + unsigned char ch; + + if (i + j < len) { + ch = buf[i + j]; + + if (ch < 32 || ch > 126) + ch = '.'; + } else { + ch = ' '; + } + + line[ofs++] = ch; + } + + line[ofs++] = '\n'; + line[ofs++] = 0; + + gg_debug_session(gs, level, "%s", line); + } +} + +/** + * \internal Zwraca ciąg z nazwą podanego stanu sesji. + * + * \param state Stan sesji. + * + * \return Ciąg z nazwą stanu + * + * \ingroup debug + */ +const char *gg_debug_state(enum gg_state_t state) +{ + switch (state) { +#define GG_DEBUG_STATE(x) case x: return #x; + GG_DEBUG_STATE(GG_STATE_IDLE) + GG_DEBUG_STATE(GG_STATE_RESOLVING) + GG_DEBUG_STATE(GG_STATE_CONNECTING) + GG_DEBUG_STATE(GG_STATE_READING_DATA) + GG_DEBUG_STATE(GG_STATE_ERROR) + GG_DEBUG_STATE(GG_STATE_CONNECTING_HUB) + GG_DEBUG_STATE(GG_STATE_CONNECTING_GG) + GG_DEBUG_STATE(GG_STATE_READING_KEY) + GG_DEBUG_STATE(GG_STATE_READING_REPLY) + GG_DEBUG_STATE(GG_STATE_CONNECTED) + GG_DEBUG_STATE(GG_STATE_SENDING_QUERY) + GG_DEBUG_STATE(GG_STATE_READING_HEADER) + GG_DEBUG_STATE(GG_STATE_PARSING) + GG_DEBUG_STATE(GG_STATE_DONE) + GG_DEBUG_STATE(GG_STATE_LISTENING) + GG_DEBUG_STATE(GG_STATE_READING_UIN_1) + GG_DEBUG_STATE(GG_STATE_READING_UIN_2) + GG_DEBUG_STATE(GG_STATE_SENDING_ACK) + GG_DEBUG_STATE(GG_STATE_READING_ACK) + GG_DEBUG_STATE(GG_STATE_READING_REQUEST) + GG_DEBUG_STATE(GG_STATE_SENDING_REQUEST) + GG_DEBUG_STATE(GG_STATE_SENDING_FILE_INFO) + GG_DEBUG_STATE(GG_STATE_READING_PRE_FILE_INFO) + GG_DEBUG_STATE(GG_STATE_READING_FILE_INFO) + GG_DEBUG_STATE(GG_STATE_SENDING_FILE_ACK) + GG_DEBUG_STATE(GG_STATE_READING_FILE_ACK) + GG_DEBUG_STATE(GG_STATE_SENDING_FILE_HEADER) + GG_DEBUG_STATE(GG_STATE_READING_FILE_HEADER) + GG_DEBUG_STATE(GG_STATE_GETTING_FILE) + GG_DEBUG_STATE(GG_STATE_SENDING_FILE) + GG_DEBUG_STATE(GG_STATE_READING_VOICE_ACK) + GG_DEBUG_STATE(GG_STATE_READING_VOICE_HEADER) + GG_DEBUG_STATE(GG_STATE_READING_VOICE_SIZE) + GG_DEBUG_STATE(GG_STATE_READING_VOICE_DATA) + GG_DEBUG_STATE(GG_STATE_SENDING_VOICE_ACK) + GG_DEBUG_STATE(GG_STATE_SENDING_VOICE_REQUEST) + GG_DEBUG_STATE(GG_STATE_READING_TYPE) + GG_DEBUG_STATE(GG_STATE_TLS_NEGOTIATION) + GG_DEBUG_STATE(GG_STATE_REQUESTING_ID) + GG_DEBUG_STATE(GG_STATE_WAITING_FOR_ACCEPT) + GG_DEBUG_STATE(GG_STATE_WAITING_FOR_INFO) + GG_DEBUG_STATE(GG_STATE_READING_ID) + GG_DEBUG_STATE(GG_STATE_SENDING_ID) + GG_DEBUG_STATE(GG_STATE_RESOLVING_GG) + GG_DEBUG_STATE(GG_STATE_RESOLVING_RELAY) + GG_DEBUG_STATE(GG_STATE_CONNECTING_RELAY) + GG_DEBUG_STATE(GG_STATE_READING_RELAY) + GG_DEBUG_STATE(GG_STATE_DISCONNECTING) + + // Celowo nie ma default, żeby kompilator wyłapał brakujące stany + + } + + return NULL; +} + +#else + +#undef gg_debug_common +void gg_debug_common(struct gg_session *sess, int level, const char *format, va_list ap) +{ +} + +#undef gg_debug +void gg_debug(int level, const char *format, ...) +{ +} + +#undef gg_debug_session +void gg_debug_session(struct gg_session *gs, int level, const char *format, ...) +{ +} + +#undef gg_debug_dump +void gg_debug_dump(struct gg_session *gs, int level, const char *buf, int len) +{ +} + +#endif diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/lib/encoding.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/gg/lib/encoding.c Fri Apr 01 13:47:51 2011 +0900 @@ -0,0 +1,274 @@ +/* + * (C) Copyright 2008-2009 Jakub Zawadzki + * Wojtek Kaniewski + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include +#include +#include + +#include "libgadu.h" + +/** + * \file encoding.c + * + * \brief Funkcje konwersji kodowania tekstu + */ + +/** + * \internal Tablica konwersji CP1250 na Unikod. + */ +static const uint16_t table_cp1250[] = +{ + 0x20ac, '?', 0x201a, '?', 0x201e, 0x2026, 0x2020, 0x2021, + '?', 0x2030, 0x0160, 0x2039, 0x015a, 0x0164, 0x017d, 0x0179, + '?', 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + '?', 0x2122, 0x0161, 0x203a, 0x015b, 0x0165, 0x017e, 0x017a, + 0x00a0, 0x02c7, 0x02d8, 0x0141, 0x00a4, 0x0104, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x015e, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x017b, + 0x00b0, 0x00b1, 0x02db, 0x0142, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x0105, 0x015f, 0x00bb, 0x013d, 0x02dd, 0x013e, 0x017c, + 0x0154, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x0139, 0x0106, 0x00c7, + 0x010c, 0x00c9, 0x0118, 0x00cb, 0x011a, 0x00cd, 0x00ce, 0x010e, + 0x0110, 0x0143, 0x0147, 0x00d3, 0x00d4, 0x0150, 0x00d6, 0x00d7, + 0x0158, 0x016e, 0x00da, 0x0170, 0x00dc, 0x00dd, 0x0162, 0x00df, + 0x0155, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x013a, 0x0107, 0x00e7, + 0x010d, 0x00e9, 0x0119, 0x00eb, 0x011b, 0x00ed, 0x00ee, 0x010f, + 0x0111, 0x0144, 0x0148, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x00f7, + 0x0159, 0x016f, 0x00fa, 0x0171, 0x00fc, 0x00fd, 0x0163, 0x02d9, +}; + +/** + * \internal Zamienia tekst kodowany CP1250 na UTF-8. + * + * \param src Tekst źródłowy w CP1250. + * \param src_length Długość ciągu źródłowego (nigdy ujemna). + * \param dst_length Długość ciągu docelowego (jeśli -1, nieograniczona). + * + * \return Zaalokowany bufor z tekstem w UTF-8. + */ +static char *gg_encoding_convert_cp1250_utf8(const char *src, int src_length, int dst_length) +{ + int i, j, len; + char *result = NULL; + + for (i = 0, len = 0; (src[i] != 0) && (i < src_length); i++) { + uint16_t uc; + + if ((unsigned char) src[i] < 0x80) + uc = (unsigned char) src[i]; + else + uc = table_cp1250[(unsigned char) src[i] - 128]; + + if (uc < 0x80) + len += 1; + else if (uc < 0x800) + len += 2; + else + len += 3; + } + + if ((dst_length != -1) && (len > dst_length)) + len = dst_length; + + result = malloc(len + 1); + + if (result == NULL) + return NULL; + + for (i = 0, j = 0; (src[i] != 0) && (i < src_length) && (j < len); i++) { + uint16_t uc; + + if ((unsigned char) src[i] < 0x80) + uc = (unsigned char) src[i]; + else + uc = table_cp1250[(unsigned char) src[i] - 128]; + + if (uc < 0x80) + result[j++] = uc; + else if (uc < 0x800) { + if (j + 1 > len) + break; + result[j++] = 0xc0 | ((uc >> 6) & 0x1f); + result[j++] = 0x80 | (uc & 0x3f); + } else { + if (j + 2 > len) + break; + result[j++] = 0xe0 | ((uc >> 12) & 0x1f); + result[j++] = 0x80 | ((uc >> 6) & 0x3f); + result[j++] = 0x80 | (uc & 0x3f); + } + } + + result[j] = 0; + + return result; +} + +/** + * \internal Zamienia tekst kodowany UTF-8 na CP1250. + * + * \param src Tekst źródłowy w UTF-8. + * \param src_length Długość ciągu źródłowego (nigdy ujemna). + * \param dst_length Długość ciągu docelowego (jeśli -1, nieograniczona). + * + * \return Zaalokowany bufor z tekstem w CP1250. + */ +static char *gg_encoding_convert_utf8_cp1250(const char *src, int src_length, int dst_length) +{ + char *result; + int i, j, len, uc_left = 0; + uint32_t uc = 0, uc_min = 0; + + for (i = 0, len = 0; (src[i] != 0) && (i < src_length); i++) { + if ((src[i] & 0xc0) == 0xc0) { + len++; + } else if ((src[i] & 0x80) == 0x00) { + len++; + } + } + + if ((dst_length != -1) && (len > dst_length)) + len = dst_length; + + result = malloc(len + 1); + + if (result == NULL) + return NULL; + + for (i = 0, j = 0; (src[i] != 0) && (i < src_length) && (j < len); i++) { + if ((unsigned char) src[i] >= 0xf5) { + if (uc_left != 0) + result[j++] = '?'; + /* Restricted sequences */ + result[j++] = '?'; + uc_left = 0; + } else if ((src[i] & 0xf8) == 0xf0) { + if (uc_left != 0) + result[j++] = '?'; + uc = src[i] & 0x07; + uc_left = 3; + uc_min = 0x10000; + } else if ((src[i] & 0xf0) == 0xe0) { + if (uc_left != 0) + result[j++] = '?'; + uc = src[i] & 0x0f; + uc_left = 2; + uc_min = 0x800; + } else if ((src[i] & 0xe0) == 0xc0) { + if (uc_left != 0) + result[j++] = '?'; + uc = src[i] & 0x1f; + uc_left = 1; + uc_min = 0x80; + } else if ((src[i] & 0xc0) == 0x80) { + if (uc_left > 0) { + uc <<= 6; + uc |= src[i] & 0x3f; + uc_left--; + + if (uc_left == 0) { + int valid = 0; + int k; + + if (uc >= uc_min) { + for (k = 0; k < 128; k++) { + if (uc == table_cp1250[k]) { + result[j++] = k + 128; + valid = 1; + break; + } + } + } + + if (!valid && uc != 0xfeff) /* Byte Order Mark */ + result[j++] = '?'; + } + } + } else { + if (uc_left != 0) { + result[j++] = '?'; + uc_left = 0; + } + result[j++] = src[i]; + } + } + + if ((uc_left != 0) && (src[i] == 0)) + result[j++] = '?'; + + result[j] = 0; + + return result; +} + +/** + * \internal Zamienia kodowanie tekstu. + * + * \param src Tekst źródłowy. + * \param src_encoding Kodowanie tekstu źródłowego. + * \param dst_encoding Kodowanie tekstu docelowego. + * \param src_length Długość ciągu źródłowego w bajtach (nigdy ujemna). + * \param dst_length Długość ciągu docelowego w bajtach (jeśli -1, nieograniczona). + * + * \return Zaalokowany bufor z tekstem w kodowaniu docelowym. + */ +char *gg_encoding_convert(const char *src, gg_encoding_t src_encoding, gg_encoding_t dst_encoding, int src_length, int dst_length) +{ + char *result; + + if (src == NULL) { + errno = EINVAL; + return NULL; + } + + // specjalny przypadek obsługiwany ekspresowo + if ((dst_encoding == src_encoding) && (dst_length == -1) && (src_length == -1)) + return strdup(src); + + if (src_length == -1) + src_length = strlen(src); + + if (dst_encoding == src_encoding) { + int len; + + if (dst_length == -1) + len = src_length; + else + len = (src_length < dst_length) ? src_length : dst_length; + + result = malloc(len + 1); + + if (result == NULL) + return NULL; + + strncpy(result, src, len); + result[len] = 0; + + return result; + } + + if (dst_encoding == GG_ENCODING_CP1250 && src_encoding == GG_ENCODING_UTF8) + return gg_encoding_convert_utf8_cp1250(src, src_length, dst_length); + + if (dst_encoding == GG_ENCODING_UTF8 && src_encoding == GG_ENCODING_CP1250) + return gg_encoding_convert_cp1250_utf8(src, src_length, dst_length); + + errno = EINVAL; + return NULL; +} + diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/lib/encoding.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/gg/lib/encoding.h Fri Apr 01 13:47:51 2011 +0900 @@ -0,0 +1,27 @@ +/* + * (C) Copyright 2008-2009 Jakub Zawadzki + * Wojtek Kaniewski + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef LIBGADU_ENCODING_H +#define LIBGADU_ENCODING_H + +#include "libgadu.h" + +char *gg_encoding_convert(const char *src, gg_encoding_t src_encoding, gg_encoding_t dst_encoding, int src_length, int dst_length); + +#endif /* LIBGADU_SESSION_H */ diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/lib/events.c --- a/libpurple/protocols/gg/lib/events.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/gg/lib/events.c Fri Apr 01 13:47:51 2011 +0900 @@ -1,4 +1,4 @@ -/* $Id: events.c 855 2009-10-12 21:42:51Z wojtekka $ */ +/* $Id: events.c 1062 2011-03-13 18:10:24Z wojtekka $ */ /* * (C) Copyright 2001-2006 Wojtek Kaniewski @@ -29,6 +29,7 @@ #include "libgadu.h" #include "libgadu-internal.h" +#include "libgadu-debug.h" #include @@ -41,6 +42,8 @@ #include "compat.h" #include "protocol.h" +#include "encoding.h" +#include "session.h" #include #include @@ -49,6 +52,10 @@ #include #include #include +#ifdef GG_CONFIG_HAVE_GNUTLS +# include +# include +#endif #ifdef GG_CONFIG_HAVE_OPENSSL # include # include @@ -73,9 +80,11 @@ switch (e->type) { case GG_EVENT_MSG: + case GG_EVENT_MULTILOGON_MSG: free(e->event.msg.message); free(e->event.msg.formats); free(e->event.msg.recipients); + free(e->event.msg.xhtml_message); break; case GG_EVENT_NOTIFY: @@ -129,6 +138,36 @@ case GG_EVENT_XML_EVENT: free(e->event.xml_event.data); break; + + case GG_EVENT_USER_DATA: + { + int i, j; + + for (i = 0; i < e->event.user_data.user_count; i++) { + for (j = 0; j < e->event.user_data.users[i].attr_count; j++) { + free(e->event.user_data.users[i].attrs[j].key); + free(e->event.user_data.users[i].attrs[j].value); + } + + free(e->event.user_data.users[i].attrs); + } + + free(e->event.user_data.users); + + break; + } + + case GG_EVENT_MULTILOGON_INFO: + { + int i; + + for (i = 0; i < e->event.multilogon_info.count; i++) + free(e->event.multilogon_info.sessions[i].name); + + free(e->event.multilogon_info.sessions); + + break; + } } free(e); @@ -174,1236 +213,6 @@ return 0; } -/** - * \internal Analizuje przychodzący pakiet z obrazkiem. - * - * \param e Struktura zdarzenia - * \param p Bufor z danymi - * \param len Długość bufora - * \param sess Struktura sesji - * \param sender Numer nadawcy - */ -static void gg_image_queue_parse(struct gg_event *e, char *p, unsigned int len, struct gg_session *sess, uin_t sender) -{ - struct gg_msg_image_reply *i = (void*) p; - struct gg_image_queue *q, *qq; - - if (!p || !sess || !e) { - errno = EFAULT; - return; - } - - /* znajdź dany obrazek w kolejce danej sesji */ - - for (qq = sess->images, q = NULL; qq; qq = qq->next) { - if (sender == qq->sender && i->size == qq->size && i->crc32 == qq->crc32) { - q = qq; - break; - } - } - - if (!q) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_queue_parse() unknown image from %d, size=%d, crc32=%.8x\n", sender, i->size, i->crc32); - return; - } - - if (p[0] == 0x05) { - q->done = 0; - - len -= sizeof(struct gg_msg_image_reply); - p += sizeof(struct gg_msg_image_reply); - - if (memchr(p, 0, len) == NULL) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_queue_parse() malformed packet from %d, unlimited filename\n", sender); - return; - } - - if (!(q->filename = strdup(p))) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_queue_parse() not enough memory for filename\n"); - return; - } - - len -= strlen(p) + 1; - p += strlen(p) + 1; - } else { - len -= sizeof(struct gg_msg_image_reply); - p += sizeof(struct gg_msg_image_reply); - } - - if (q->done + len > q->size) - len = q->size - q->done; - - memcpy(q->image + q->done, p, len); - q->done += len; - - /* jeśli skończono odbierać obrazek, wygeneruj zdarzenie */ - - if (q->done >= q->size) { - e->type = GG_EVENT_IMAGE_REPLY; - e->event.image_reply.sender = sender; - e->event.image_reply.size = q->size; - e->event.image_reply.crc32 = q->crc32; - e->event.image_reply.filename = q->filename; - e->event.image_reply.image = q->image; - - gg_image_queue_remove(sess, q, 0); - - free(q); - } -} - -/** - * \internal Analizuje informacje rozszerzone wiadomości. - * - * \param sess Struktura sesji. - * \param e Struktura zdarzenia. - * \param sender Numer nadawcy. - * \param p Wskaźnik na dane rozszerzone. - * \param packet_end Wskaźnik na koniec pakietu. - * - * \return 0 jeśli się powiodło, -1 jeśli wiadomość obsłużono i wynik ma - * zostać przekazany aplikacji, -2 jeśli wystąpił błąd ogólny, -3 jeśli - * wiadomość jest niepoprawna. - */ -static int gg_handle_recv_msg_options(struct gg_session *sess, struct gg_event *e, uin_t sender, char *p, char *packet_end) -{ - while (p < packet_end) { - switch (*p) { - case 0x01: /* konferencja */ - { - struct gg_msg_recipients *m = (void*) p; - uint32_t i, count; - - p += sizeof(*m); - - if (p > packet_end) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (1)\n"); - goto malformed; - } - - count = gg_fix32(m->count); - - if (p + count * sizeof(uin_t) > packet_end || p + count * sizeof(uin_t) < p || count > 0xffff) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (1.5)\n"); - goto malformed; - } - - if (!(e->event.msg.recipients = (void*) malloc(count * sizeof(uin_t)))) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() not enough memory for recipients data\n"); - goto fail; - } - - for (i = 0; i < count; i++, p += sizeof(uint32_t)) { - uint32_t u; - memcpy(&u, p, sizeof(uint32_t)); - e->event.msg.recipients[i] = gg_fix32(u); - } - - e->event.msg.recipients_count = count; - - break; - } - - case 0x02: /* richtext */ - { - uint16_t len; - char *buf; - - if (p + 3 > packet_end) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (2)\n"); - goto malformed; - } - - memcpy(&len, p + 1, sizeof(uint16_t)); - len = gg_fix16(len); - - if (!(buf = malloc(len))) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() not enough memory for richtext data\n"); - goto fail; - } - - p += 3; - - if (p + len > packet_end) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (3)\n"); - free(buf); - goto malformed; - } - - memcpy(buf, p, len); - - e->event.msg.formats = buf; - e->event.msg.formats_length = len; - - p += len; - - break; - } - - case 0x04: /* image_request */ - { - struct gg_msg_image_request *i = (void*) p; - - if (p + sizeof(*i) > packet_end) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (3)\n"); - goto malformed; - } - - e->event.image_request.sender = sender; - e->event.image_request.size = gg_fix32(i->size); - e->event.image_request.crc32 = gg_fix32(i->crc32); - - e->type = GG_EVENT_IMAGE_REQUEST; - - goto handled; - } - - case 0x05: /* image_reply */ - case 0x06: - { - struct gg_msg_image_reply *rep = (void*) p; - - if (p + sizeof(struct gg_msg_image_reply) == packet_end) { - - /* pusta odpowiedź - klient po drugiej stronie nie ma żądanego obrazka */ - - e->type = GG_EVENT_IMAGE_REPLY; - e->event.image_reply.sender = sender; - e->event.image_reply.size = 0; - e->event.image_reply.crc32 = gg_fix32(rep->crc32); - e->event.image_reply.filename = NULL; - e->event.image_reply.image = NULL; - goto handled; - - } else if (p + sizeof(struct gg_msg_image_reply) + 1 > packet_end) { - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (4)\n"); - goto malformed; - } - - rep->size = gg_fix32(rep->size); - rep->crc32 = gg_fix32(rep->crc32); - gg_image_queue_parse(e, p, (unsigned int)(packet_end - p), sess, sender); - - goto handled; - } - - default: - { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() unknown payload 0x%.2x\n", *p); - p = packet_end; - } - } - } - - return 0; - -handled: - return -1; - -fail: - return -2; - -malformed: - return -3; -} - -/** - * \internal Analizuje przychodzący pakiet z wiadomością. - * - * Rozbija pakiet na poszczególne składniki -- tekst, informacje - * o konferencjach, formatowani itd. - * - * \param h Wskaźnik do odebranego pakietu - * \param e Struktura zdarzenia - * \param sess Struktura sesji - * - * \return 0 jeśli się powiodło, -1 w przypadku błędu - */ -static int gg_handle_recv_msg(struct gg_header *h, struct gg_event *e, struct gg_session *sess) -{ - struct gg_recv_msg *r = (struct gg_recv_msg*) ((char*) h + sizeof(struct gg_header)); - char *p, *packet_end = (char*) r + h->length; - - gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_handle_recv_msg(%p, %p);\n", h, e); - - if (!r->seq && !r->msgclass) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() oops, silently ignoring the bait\n"); - e->type = GG_EVENT_NONE; - return 0; - } - - /* znajdź \0 */ - for (p = (char*) r + sizeof(*r); ; p++) { - if (p >= packet_end) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() malformed packet, message out of bounds (0)\n"); - goto malformed; - } - - if (*p == 0x02 && p == packet_end - 1) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() received ctcp packet\n"); - break; - } - - if (!*p) - break; - } - - p++; - - switch (gg_handle_recv_msg_options(sess, e, gg_fix32(r->sender), p, packet_end)) { - case -1: // handled - return 0; - - case -2: // failed - goto fail; - - case -3: // malformed - goto malformed; - } - - e->type = GG_EVENT_MSG; - e->event.msg.msgclass = gg_fix32(r->msgclass); - e->event.msg.sender = gg_fix32(r->sender); - e->event.msg.time = gg_fix32(r->time); - e->event.msg.seq = gg_fix32(r->seq); - e->event.msg.message = (unsigned char*) strdup((char*) r + sizeof(*r)); - - return 0; - -malformed: - e->type = GG_EVENT_NONE; - free(e->event.msg.message); - free(e->event.msg.recipients); - free(e->event.msg.formats); - - return 0; - -fail: - free(e->event.msg.message); - free(e->event.msg.recipients); - free(e->event.msg.formats); - return -1; -} - -/** - * \internal Zamienia tekst w formacie HTML na czysty tekst. - * - * \param dst Bufor wynikowy (może być \c NULL) - * \param html Tekst źródłowy - * - * \note Dokleja \c \\0 na końcu bufora wynikowego. - * - * \return Długość tekstu wynikowego bez \c \\0 (nawet jeśli \c dst to \c NULL). - */ -static int gg_convert_from_html(char *dst, const char *html) -{ - const char *src, *entity, *tag; - int len, in_tag, in_entity; - - len = 0; - in_tag = 0; - tag = NULL; - in_entity = 0; - entity = NULL; - - for (src = html; *src != 0; src++) { - if (*src == '<') { - tag = src; - in_tag = 1; - continue; - } - - if (in_tag && (*src == '>')) { - if (strncmp(tag, "seq && !r->msgclass) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() oops, silently ignoring the bait\n"); - goto malformed; - } - - offset_plain = gg_fix32(r->offset_plain); - offset_attr = gg_fix32(r->offset_attr); - - if (offset_plain < sizeof(struct gg_recv_msg80) || offset_plain >= h->length) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, message out of bounds (0)\n"); - goto malformed; - } - - if (offset_attr < sizeof(struct gg_recv_msg80) || offset_attr > h->length) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, attr out of bounds (1)\n"); - offset_attr = 0; /* nie parsuj attr. */ - /* goto ignore; */ - } - - /* Normalna sytuacja, więc nie podpada pod powyższy warunek. */ - if (offset_attr == h->length) - offset_attr = 0; - - if (memchr(packet + offset_plain, 0, h->length - offset_plain) == NULL) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, message out of bounds (2)\n"); - goto malformed; - } - - if (offset_plain > sizeof(struct gg_recv_msg80) && memchr(packet + sizeof(struct gg_recv_msg80), 0, offset_plain - sizeof(struct gg_recv_msg80)) == NULL) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, message out of bounds (3)\n"); - goto malformed; - } - - e->type = GG_EVENT_MSG; - e->event.msg.msgclass = gg_fix32(r->msgclass); - e->event.msg.sender = gg_fix32(r->sender); - e->event.msg.time = gg_fix32(r->time); - e->event.msg.seq = gg_fix32(r->seq); - - if (sess->encoding == GG_ENCODING_CP1250) { - e->event.msg.message = (unsigned char*) strdup(packet + offset_plain); - } else { - if (offset_plain > sizeof(struct gg_recv_msg80)) { - int len; - - len = gg_convert_from_html(NULL, packet + sizeof(struct gg_recv_msg80)); - - e->event.msg.message = malloc(len + 1); - - if (e->event.msg.message == NULL) - goto fail; - - gg_convert_from_html((char*) e->event.msg.message, packet + sizeof(struct gg_recv_msg80)); - } else { - e->event.msg.message = (unsigned char*) gg_cp_to_utf8(packet + offset_plain); - } - } - - if (offset_plain > sizeof(struct gg_recv_msg80)) { - if (sess->encoding == GG_ENCODING_UTF8) - e->event.msg.xhtml_message = strdup(packet + sizeof(struct gg_recv_msg80)); - else - e->event.msg.xhtml_message = gg_utf8_to_cp(packet + sizeof(struct gg_recv_msg80)); - } else { - e->event.msg.xhtml_message = NULL; - } - - if (offset_attr != 0) { - switch (gg_handle_recv_msg_options(sess, e, gg_fix32(r->sender), packet + offset_attr, packet + h->length)) { - case -1: // handled - return 0; - - case -2: // failed - goto fail; - - case -3: // malformed - goto malformed; - } - } - - return 0; - -fail: - free(e->event.msg.message); - free(e->event.msg.xhtml_message); - free(e->event.msg.recipients); - free(e->event.msg.formats); - return -1; - -malformed: - e->type = GG_EVENT_NONE; - free(e->event.msg.message); - free(e->event.msg.xhtml_message); - free(e->event.msg.recipients); - free(e->event.msg.formats); - return 0; -} - -/** - * \internal Odbiera pakiet od serwera. - * - * Analizuje pakiet i wypełnia strukturę zdarzenia. - * - * \param sess Struktura sesji - * \param e Struktura zdarzenia - * - * \return 0 jeśli się powiodło, -1 jeśli wystąpił błąd - */ -static int gg_watch_fd_connected(struct gg_session *sess, struct gg_event *e) -{ - struct gg_header *h = NULL; - char *p; - - gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_watch_fd_connected(%p, %p);\n", sess, e); - - if (!sess) { - errno = EFAULT; - return -1; - } - - if (!(h = gg_recv_packet(sess))) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() gg_recv_packet failed (errno=%d, %s)\n", errno, strerror(errno)); - goto fail; - } - - p = (char*) h + sizeof(struct gg_header); - - switch (h->type) { - case GG_RECV_MSG: - { - if (h->length >= sizeof(struct gg_recv_msg)) - if (gg_handle_recv_msg(h, e, sess)) - goto fail; - - break; - } - - case GG_RECV_MSG80: - { - if (h->length >= sizeof(struct gg_recv_msg80)) - if (gg_handle_recv_msg80(h, e, sess)) - goto fail; - - break; - } - - - case GG_NOTIFY_REPLY: - { - struct gg_notify_reply *n = (void*) p; - unsigned int count, i; - char *tmp; - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); - - if (h->length < sizeof(*n)) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() incomplete packet\n"); - errno = EINVAL; - goto fail; - } - - if (gg_fix32(n->status) == GG_STATUS_BUSY_DESCR || gg_fix32(n->status) == GG_STATUS_NOT_AVAIL_DESCR || gg_fix32(n->status) == GG_STATUS_AVAIL_DESCR) { - e->type = GG_EVENT_NOTIFY_DESCR; - - if (!(e->event.notify_descr.notify = (void*) malloc(sizeof(*n) * 2))) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); - goto fail; - } - e->event.notify_descr.notify[1].uin = 0; - memcpy(e->event.notify_descr.notify, p, sizeof(*n)); - e->event.notify_descr.notify[0].uin = gg_fix32(e->event.notify_descr.notify[0].uin); - e->event.notify_descr.notify[0].status = gg_fix32(e->event.notify_descr.notify[0].status); - e->event.notify_descr.notify[0].remote_port = gg_fix16(e->event.notify_descr.notify[0].remote_port); - e->event.notify_descr.notify[0].version = gg_fix32(e->event.notify_descr.notify[0].version); - - count = h->length - sizeof(*n); - if (!(tmp = malloc(count + 1))) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); - goto fail; - } - memcpy(tmp, p + sizeof(*n), count); - tmp[count] = 0; - e->event.notify_descr.descr = tmp; - - } else { - e->type = GG_EVENT_NOTIFY; - - if (!(e->event.notify = (void*) malloc(h->length + 2 * sizeof(*n)))) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); - goto fail; - } - - memcpy(e->event.notify, p, h->length); - count = h->length / sizeof(*n); - e->event.notify[count].uin = 0; - - for (i = 0; i < count; i++) { - e->event.notify[i].uin = gg_fix32(e->event.notify[i].uin); - e->event.notify[i].status = gg_fix32(e->event.notify[i].status); - e->event.notify[i].remote_port = gg_fix16(e->event.notify[i].remote_port); - e->event.notify[i].version = gg_fix32(e->event.notify[i].version); - } - } - - break; - } - - case GG_STATUS: - { - struct gg_status *s = (void*) p; - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n"); - - if (h->length >= sizeof(*s)) { - e->type = GG_EVENT_STATUS; - memcpy(&e->event.status, p, sizeof(*s)); - e->event.status.uin = gg_fix32(e->event.status.uin); - e->event.status.status = gg_fix32(e->event.status.status); - if (h->length > sizeof(*s)) { - int len = h->length - sizeof(*s); - char *buf = malloc(len + 1); - if (buf) { - memcpy(buf, p + sizeof(*s), len); - buf[len] = 0; - } - e->event.status.descr = buf; - } else - e->event.status.descr = NULL; - } - - break; - } - - case GG_NOTIFY_REPLY77: - case GG_NOTIFY_REPLY80BETA: - { - struct gg_notify_reply77 *n = (void*) p; - unsigned int length = h->length, i = 0; - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); - - e->type = GG_EVENT_NOTIFY60; - e->event.notify60 = malloc(sizeof(*e->event.notify60)); - - if (!e->event.notify60) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); - goto fail; - } - - e->event.notify60[0].uin = 0; - - while (length >= sizeof(struct gg_notify_reply77)) { - uin_t uin = gg_fix32(n->uin); - char *tmp; - - e->event.notify60[i].uin = uin & 0x00ffffff; - e->event.notify60[i].status = n->status; - e->event.notify60[i].remote_ip = n->remote_ip; - e->event.notify60[i].remote_port = gg_fix16(n->remote_port); - e->event.notify60[i].version = n->version; - e->event.notify60[i].image_size = n->image_size; - e->event.notify60[i].descr = NULL; - e->event.notify60[i].time = 0; - - if (uin & 0x40000000) - e->event.notify60[i].version |= GG_HAS_AUDIO_MASK; - if (uin & 0x20000000) - e->event.notify60[i].version |= GG_HAS_AUDIO7_MASK; - if (uin & 0x08000000) - e->event.notify60[i].version |= GG_ERA_OMNIX_MASK; - - if (GG_S_D(n->status)) { - unsigned char descr_len = *((char*) n + sizeof(struct gg_notify_reply77)); - - if (sizeof(struct gg_notify_reply77) + descr_len <= length) { - char *descr; - - if (!(descr = malloc(descr_len + 1))) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); - goto fail; - } - - memcpy(descr, (char*) n + sizeof(struct gg_notify_reply77) + 1, descr_len); - descr[descr_len] = 0; - - if (h->type == GG_NOTIFY_REPLY80BETA && sess->encoding != GG_ENCODING_UTF8) { - char *cp_descr = gg_utf8_to_cp(descr); - - if (!cp_descr) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); - free(descr); - goto fail; - } - - free(descr); - descr = cp_descr; - } - - e->event.notify60[i].descr = descr; - - /* XXX czas */ - - length -= sizeof(struct gg_notify_reply77) + descr_len + 1; - n = (void*) ((char*) n + sizeof(struct gg_notify_reply77) + descr_len + 1); - } else { - length = 0; - } - - } else { - length -= sizeof(struct gg_notify_reply77); - n = (void*) ((char*) n + sizeof(struct gg_notify_reply77)); - } - - if (!(tmp = realloc(e->event.notify60, (i + 2) * sizeof(*e->event.notify60)))) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); - free(e->event.notify60); - goto fail; - } - - e->event.notify60 = (void*) tmp; - e->event.notify60[++i].uin = 0; - } - - break; - } - - case GG_STATUS77: - case GG_STATUS80BETA: - { - struct gg_status77 *s = (void*) p; - uint32_t uin; - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n"); - - if (h->length < sizeof(*s)) - break; - - uin = gg_fix32(s->uin); - - e->type = GG_EVENT_STATUS60; - e->event.status60.uin = uin & 0x00ffffff; - e->event.status60.status = s->status; - e->event.status60.remote_ip = s->remote_ip; - e->event.status60.remote_port = gg_fix16(s->remote_port); - e->event.status60.version = s->version; - e->event.status60.image_size = s->image_size; - e->event.status60.descr = NULL; - e->event.status60.time = 0; - - if (uin & 0x40000000) - e->event.status60.version |= GG_HAS_AUDIO_MASK; - if (uin & 0x20000000) - e->event.status60.version |= GG_HAS_AUDIO7_MASK; - if (uin & 0x08000000) - e->event.status60.version |= GG_ERA_OMNIX_MASK; - - if (h->length > sizeof(*s)) { - int len = h->length - sizeof(*s); - char *buf = malloc(len + 1); - - /* XXX, jesli malloc() sie nie uda to robic tak samo jak przy GG_NOTIFY_REPLY* ? - * - goto fail; (?) - */ - if (buf) { - memcpy(buf, (char*) p + sizeof(*s), len); - buf[len] = 0; - - if (h->type == GG_STATUS80BETA && sess->encoding != GG_ENCODING_UTF8) { - char *cp_buf = gg_utf8_to_cp(buf); - free(buf); - buf = cp_buf; - } - } - - e->event.status60.descr = buf; - - if (len > 4 && p[h->length - 5] == 0) { - uint32_t t; - memcpy(&t, p + h->length - 4, sizeof(uint32_t)); - e->event.status60.time = gg_fix32(t); - } - } - - break; - } - - case GG_NOTIFY_REPLY60: - { - struct gg_notify_reply60 *n = (void*) p; - unsigned int length = h->length, i = 0; - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); - - e->type = GG_EVENT_NOTIFY60; - e->event.notify60 = malloc(sizeof(*e->event.notify60)); - - if (!e->event.notify60) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); - goto fail; - } - - e->event.notify60[0].uin = 0; - - while (length >= sizeof(struct gg_notify_reply60)) { - uin_t uin = gg_fix32(n->uin); - char *tmp; - - e->event.notify60[i].uin = uin & 0x00ffffff; - e->event.notify60[i].status = n->status; - e->event.notify60[i].remote_ip = n->remote_ip; - e->event.notify60[i].remote_port = gg_fix16(n->remote_port); - e->event.notify60[i].version = n->version; - e->event.notify60[i].image_size = n->image_size; - e->event.notify60[i].descr = NULL; - e->event.notify60[i].time = 0; - - if (uin & 0x40000000) - e->event.notify60[i].version |= GG_HAS_AUDIO_MASK; - if (uin & 0x08000000) - e->event.notify60[i].version |= GG_ERA_OMNIX_MASK; - - if (GG_S_D(n->status)) { - unsigned char descr_len = *((char*) n + sizeof(struct gg_notify_reply60)); - - if (sizeof(struct gg_notify_reply60) + descr_len <= length) { - if (!(e->event.notify60[i].descr = malloc(descr_len + 1))) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); - goto fail; - } - - memcpy(e->event.notify60[i].descr, (char*) n + sizeof(struct gg_notify_reply60) + 1, descr_len); - e->event.notify60[i].descr[descr_len] = 0; - - /* XXX czas */ - - length -= sizeof(struct gg_notify_reply60) + descr_len + 1; - n = (void*) ((char*) n + sizeof(struct gg_notify_reply60) + descr_len + 1); - } else { - length = 0; - } - - } else { - length -= sizeof(struct gg_notify_reply60); - n = (void*) ((char*) n + sizeof(struct gg_notify_reply60)); - } - - if (!(tmp = realloc(e->event.notify60, (i + 2) * sizeof(*e->event.notify60)))) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); - free(e->event.notify60); - goto fail; - } - - e->event.notify60 = (void*) tmp; - e->event.notify60[++i].uin = 0; - } - - break; - } - - case GG_STATUS60: - { - struct gg_status60 *s = (void*) p; - uint32_t uin; - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n"); - - if (h->length < sizeof(*s)) - break; - - uin = gg_fix32(s->uin); - - e->type = GG_EVENT_STATUS60; - e->event.status60.uin = uin & 0x00ffffff; - e->event.status60.status = s->status; - e->event.status60.remote_ip = s->remote_ip; - e->event.status60.remote_port = gg_fix16(s->remote_port); - e->event.status60.version = s->version; - e->event.status60.image_size = s->image_size; - e->event.status60.descr = NULL; - e->event.status60.time = 0; - - if (uin & 0x40000000) - e->event.status60.version |= GG_HAS_AUDIO_MASK; - if (uin & 0x08000000) - e->event.status60.version |= GG_ERA_OMNIX_MASK; - - if (h->length > sizeof(*s)) { - int len = h->length - sizeof(*s); - char *buf = malloc(len + 1); - - if (buf) { - memcpy(buf, (char*) p + sizeof(*s), len); - buf[len] = 0; - } - - e->event.status60.descr = buf; - - if (len > 4 && p[h->length - 5] == 0) { - uint32_t t; - memcpy(&t, p + h->length - 4, sizeof(uint32_t)); - e->event.status60.time = gg_fix32(t); - } - } - - break; - } - - case GG_STATUS80: - { - struct gg_notify_reply80 *s = (void*) p; - uint32_t descr_len; - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n"); - - if (h->length < sizeof(*s)) - break; - - e->type = GG_EVENT_STATUS60; - e->event.status60.uin = gg_fix32(s->uin); - e->event.status60.status = gg_fix32(s->status); - e->event.status60.remote_ip = s->remote_ip; - e->event.status60.remote_port = gg_fix16(s->remote_port); - e->event.status60.image_size = s->image_size; - e->event.status60.descr = NULL; - e->event.status60.version = 0x00; /* not-supported */ - e->event.status60.time = 0; /* not-supported */ - - descr_len = gg_fix32(s->descr_len); - - if (descr_len > 0 && h->length-sizeof(*s) >= descr_len) { - char *buf = malloc(descr_len + 1); - - if (buf) { - memcpy(buf, (char*) p + sizeof(*s), descr_len); - buf[descr_len] = 0; - - if (sess->encoding != GG_ENCODING_UTF8) { - char *cp_buf = gg_utf8_to_cp(buf); - free(buf); - buf = cp_buf; - } - } - - e->event.status60.descr = buf; - } - break; - } - - case GG_NOTIFY_REPLY80: - { - struct gg_notify_reply80 *n = (void*) p; - unsigned int length = h->length, i = 0; - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); - - e->type = GG_EVENT_NOTIFY60; - e->event.notify60 = malloc(sizeof(*e->event.notify60)); - - if (!e->event.notify60) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); - goto fail; - } - - e->event.notify60[0].uin = 0; - - while (length >= sizeof(struct gg_notify_reply80)) { - uint32_t descr_len; - char *tmp; - - e->event.notify60[i].uin = gg_fix32(n->uin); - e->event.notify60[i].status = gg_fix32(n->status); - e->event.notify60[i].remote_ip = n->remote_ip; - e->event.notify60[i].remote_port= gg_fix16(n->remote_port); - e->event.notify60[i].image_size = n->image_size; - e->event.notify60[i].descr = NULL; - e->event.notify60[i].version = 0x00; /* not-supported */ - e->event.notify60[i].time = 0; /* not-supported */ - - descr_len = gg_fix32(n->descr_len); - - length -= sizeof(struct gg_notify_reply80); - n = (void*) ((char*) n + sizeof(struct gg_notify_reply80)); - - if (descr_len) { - if (length >= descr_len) { - /* XXX, GG_S_D(n->status) */ - char *descr; - - if (!(descr = malloc(descr_len + 1))) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); - goto fail; - } - - memcpy(descr, n, descr_len); - descr[descr_len] = 0; - - if (sess->encoding != GG_ENCODING_UTF8) { - char *cp_descr = gg_utf8_to_cp(descr); - - if (!cp_descr) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); - free(descr); - goto fail; - } - - free(descr); - descr = cp_descr; - } - e->event.notify60[i].descr = descr; - - length -= descr_len; - n = (void*) ((char*) n + descr_len); - } else - length = 0; - } - - if (!(tmp = realloc(e->event.notify60, (i + 2) * sizeof(*e->event.notify60)))) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); - free(e->event.notify60); - goto fail; - } - - e->event.notify60 = (void*) tmp; - e->event.notify60[++i].uin = 0; - } - break; - } - - case GG_SEND_MSG_ACK: - { - struct gg_send_msg_ack *s = (void*) p; - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a message ack\n"); - - if (h->length < sizeof(*s)) - break; - - e->type = GG_EVENT_ACK; - e->event.ack.status = gg_fix32(s->status); - e->event.ack.recipient = gg_fix32(s->recipient); - e->event.ack.seq = gg_fix32(s->seq); - - break; - } - - case GG_PONG: - { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a pong\n"); - - e->type = GG_EVENT_PONG; - sess->last_pong = time(NULL); - - break; - } - - case GG_DISCONNECTING: - { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received disconnection warning\n"); - e->type = GG_EVENT_DISCONNECT; - break; - } - - case GG_DISCONNECT_ACK: - { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received disconnection acknowledge\n"); - e->type = GG_EVENT_DISCONNECT_ACK; - break; - } - - case GG_XML_EVENT: - { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received XML event\n"); - e->type = GG_EVENT_XML_EVENT; - if (!(e->event.xml_event.data = (char *) malloc(h->length + 1))) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for XML event data\n"); - goto fail; - } - memcpy(e->event.xml_event.data, p, h->length); - e->event.xml_event.data[h->length] = 0; - break; - } - - case GG_PUBDIR50_REPLY: - { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received pubdir/search reply\n"); - if (gg_pubdir50_handle_reply_sess(sess, e, p, h->length) == -1) - goto fail; - break; - } - - case GG_USERLIST_REPLY: - { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received userlist reply\n"); - - if (h->length < 1) - break; - - /* jeśli odpowiedź na eksport, wywołaj zdarzenie tylko - * gdy otrzymano wszystkie odpowiedzi */ - if (p[0] == GG_USERLIST_PUT_REPLY || p[0] == GG_USERLIST_PUT_MORE_REPLY) { - if (--sess->userlist_blocks) - break; - - p[0] = GG_USERLIST_PUT_REPLY; - } - - if (h->length > 1) { - char *tmp; - unsigned int len = (sess->userlist_reply) ? strlen(sess->userlist_reply) : 0; - - gg_debug_session(sess, GG_DEBUG_MISC, "userlist_reply=%p, len=%d\n", sess->userlist_reply, len); - - if (!(tmp = realloc(sess->userlist_reply, len + h->length))) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for userlist reply\n"); - free(sess->userlist_reply); - sess->userlist_reply = NULL; - goto fail; - } - - sess->userlist_reply = tmp; - sess->userlist_reply[len + h->length - 1] = 0; - memcpy(sess->userlist_reply + len, p + 1, h->length - 1); - } - - if (p[0] == GG_USERLIST_GET_MORE_REPLY) - break; - - e->type = GG_EVENT_USERLIST; - e->event.userlist.type = p[0]; - e->event.userlist.reply = sess->userlist_reply; - sess->userlist_reply = NULL; - - break; - } - - case GG_DCC7_ID_REPLY: - { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 id packet\n"); - - if (h->length < sizeof(struct gg_dcc7_id_reply)) - break; - - if (gg_dcc7_handle_id(sess, e, p, h->length) == -1) - goto fail; - - break; - } - - case GG_DCC7_ACCEPT: - { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 accept\n"); - - if (h->length < sizeof(struct gg_dcc7_accept)) - break; - - if (gg_dcc7_handle_accept(sess, e, p, h->length) == -1) - goto fail; - - break; - } - - case GG_DCC7_NEW: - { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 request\n"); - - if (h->length < sizeof(struct gg_dcc7_new)) - break; - - if (gg_dcc7_handle_new(sess, e, p, h->length) == -1) - goto fail; - - break; - } - - case GG_DCC7_REJECT: - { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 reject\n"); - - if (h->length < sizeof(struct gg_dcc7_reject)) - break; - - if (gg_dcc7_handle_reject(sess, e, p, h->length) == -1) - goto fail; - - break; - } - - case GG_DCC7_INFO: - { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 info\n"); - - if (h->length < sizeof(struct gg_dcc7_info)) - break; - - if (gg_dcc7_handle_info(sess, e, p, h->length) == -1) - goto fail; - - break; - } - - default: - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received unknown packet 0x%.2x\n", h->type); - } - - free(h); - return 0; - -fail: - free(h); - return -1; -} - /** \endcond */ /** @@ -1465,6 +274,8 @@ memmove(sess->send_buf, sess->send_buf + res, sess->send_left - res); sess->send_left -= res; } + + res = 0; } switch (sess->state) { @@ -1568,15 +379,14 @@ auth = gg_proxy_auth(); -#ifdef GG_CONFIG_HAVE_OPENSSL - if (sess->ssl) { +#if defined(GG_CONFIG_HAVE_GNUTLS) || defined(GG_CONFIG_HAVE_OPENSSL) + if (sess->ssl != NULL) { snprintf(buf, sizeof(buf) - 1, - "GET %s/appsvc/appmsg3.asp?fmnumber=%u&version=%s&lastmsg=%d HTTP/1.0\r\n" + "GET %s/appsvc/appmsg_ver10.asp?fmnumber=%u&fmt=2&lastmsg=%d&version=%s&age=2&gender=1 HTTP/1.0\r\n" + "Connection: close\r\n" "Host: " GG_APPMSG_HOST "\r\n" - "User-Agent: " GG_HTTP_USERAGENT "\r\n" - "Pragma: no-cache\r\n" "%s" - "\r\n", host, sess->uin, client, sess->last_sysmsg, (auth) ? auth : ""); + "\r\n", host, sess->uin, sess->last_sysmsg, client, (auth) ? auth : ""); } else #endif { @@ -1590,12 +400,6 @@ free(auth); free(client); - /* zwolnij pamięć po wersji klienta. */ - if (sess->client_version) { - free(sess->client_version); - sess->client_version = NULL; - } - gg_debug_session(sess, GG_DEBUG_MISC, "=> -----BEGIN-HTTP-QUERY-----\n%s\n=> -----END-HTTP-QUERY-----\n", buf); /* zapytanie jest krótkie, więc zawsze zmieści się @@ -1681,15 +485,10 @@ /* analizujemy otrzymane dane. */ tmp = buf; -#ifdef GG_CONFIG_HAVE_OPENSSL - if (!sess->ssl) -#endif - { - while (*tmp && *tmp != ' ') - tmp++; - while (*tmp && *tmp == ' ') - tmp++; - } + while (*tmp && *tmp != ' ') + tmp++; + while (*tmp && *tmp == ' ') + tmp++; while (*tmp && *tmp != ' ') tmp++; while (*tmp && *tmp == ' ') @@ -1730,6 +529,67 @@ sess->port = port; + /* Jeśli podano nazwę, nie adres serwera... */ + if (sess->server_addr == INADDR_NONE) { + if (sess->resolver_start(&sess->fd, &sess->resolver, host) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_login() resolving failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_resolving; + } + + sess->state = GG_STATE_RESOLVING_GG; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + break; + } + + /* łączymy się z właściwym serwerem. */ + if ((sess->fd = gg_connect(&addr, sess->port, sess->async)) == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", errno, strerror(errno)); + + sess->port = GG_HTTPS_PORT; + + /* nie wyszło? próbujemy portu 443. */ + if ((sess->fd = gg_connect(&addr, GG_HTTPS_PORT, sess->async)) == -1) { + /* ostatnia deska ratunku zawiodła? + * w takim razie zwijamy manatki. */ + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + sess->soft_timeout = 1; + + break; + } + + case GG_STATE_RESOLVING_GG: + { + struct in_addr addr; + int failed = 0; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_RESOLVING_GG\n"); + + if (read(sess->fd, &addr, sizeof(addr)) < (signed)sizeof(addr) || addr.s_addr == INADDR_NONE) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() resolving failed\n"); + failed = 1; + errno2 = errno; + } + + close(sess->fd); + sess->fd = -1; + + sess->resolver_cleanup(&sess->resolver, 0); + + if (failed) { + errno = errno2; + goto fail_resolving; + } + + sess->server_addr = addr.s_addr; + /* łączymy się z właściwym serwerem. */ if ((sess->fd = gg_connect(&addr, sess->port, sess->async)) == -1) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", errno, strerror(errno)); @@ -1779,7 +639,7 @@ errno = ETIMEDOUT; #endif -#ifdef GG_CONFIG_HAVE_OPENSSL +#if defined(GG_CONFIG_HAVE_GNUTLS) || defined(GG_CONFIG_HAVE_OPENSSL) /* jeśli logujemy się po TLS, nie próbujemy * się łączyć już z niczym innym w przypadku * błędu. nie dość, że nie ma sensu, to i @@ -1858,9 +718,14 @@ } } +#if defined(GG_CONFIG_HAVE_GNUTLS) || defined(GG_CONFIG_HAVE_OPENSSL) + if (sess->ssl != NULL) { +#ifdef GG_CONFIG_HAVE_GNUTLS + gnutls_transport_set_ptr(GG_SESSION_GNUTLS(sess), (gnutls_transport_ptr_t) sess->fd); +#endif #ifdef GG_CONFIG_HAVE_OPENSSL - if (sess->ssl) { SSL_set_fd(sess->ssl, sess->fd); +#endif sess->state = GG_STATE_TLS_NEGOTIATION; sess->check = GG_CHECK_WRITE; @@ -1877,6 +742,83 @@ break; } +#ifdef GG_CONFIG_HAVE_GNUTLS + case GG_STATE_TLS_NEGOTIATION: + { + int res; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_TLS_NEGOTIATION\n"); + +gnutls_handshake_repeat: + res = gnutls_handshake(GG_SESSION_GNUTLS(sess)); + + if (res == GNUTLS_E_AGAIN) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS handshake GNUTLS_E_AGAIN\n"); + + sess->state = GG_STATE_TLS_NEGOTIATION; + if (gnutls_record_get_direction(GG_SESSION_GNUTLS(sess)) == 0) + sess->check = GG_CHECK_READ; + else + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + break; + } + + if (res == GNUTLS_E_INTERRUPTED) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS handshake GNUTLS_E_INTERRUPTED\n"); + goto gnutls_handshake_repeat; + } + + if (res != 0) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS handshake error %d\n", res); + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_TLS; + sess->state = GG_STATE_IDLE; + close(sess->fd); + sess->fd = -1; + break; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS negotiation succeded:\n"); + gg_debug_session(sess, GG_DEBUG_MISC, "// cipher: VERS-%s:%s:%s:%s:COMP-%s\n", + gnutls_protocol_get_name(gnutls_protocol_get_version(GG_SESSION_GNUTLS(sess))), + gnutls_cipher_get_name(gnutls_cipher_get(GG_SESSION_GNUTLS(sess))), + gnutls_kx_get_name(gnutls_kx_get(GG_SESSION_GNUTLS(sess))), + gnutls_mac_get_name(gnutls_mac_get(GG_SESSION_GNUTLS(sess))), + gnutls_compression_get_name(gnutls_compression_get(GG_SESSION_GNUTLS(sess)))); + + if (gnutls_certificate_type_get(GG_SESSION_GNUTLS(sess)) == GNUTLS_CRT_X509) { + unsigned int peer_count; + const gnutls_datum_t *peers; + gnutls_x509_crt_t cert; + + if (gnutls_x509_crt_init(&cert) >= 0) { + peers = gnutls_certificate_get_peers(GG_SESSION_GNUTLS(sess), &peer_count); + + if (peers != NULL) { + char buf[256]; + size_t size; + + if (gnutls_x509_crt_import(cert, &peers[0], GNUTLS_X509_FMT_DER) >= 0) { + size = sizeof(buf); + gnutls_x509_crt_get_dn(cert, buf, &size); + gg_debug_session(sess, GG_DEBUG_MISC, "// cert subject: %s\n", buf); + size = sizeof(buf); + gnutls_x509_crt_get_issuer_dn(cert, buf, &size); + gg_debug_session(sess, GG_DEBUG_MISC, "// cert issuer: %s\n", buf); + } + } + } + } + + sess->state = GG_STATE_READING_KEY; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } +#endif + #ifdef GG_CONFIG_HAVE_OPENSSL case GG_STATE_TLS_NEGOTIATION: { @@ -1916,7 +858,7 @@ break; } else { - char buf[1024]; + char buf[256]; ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); @@ -1938,7 +880,7 @@ if (!peer) gg_debug_session(sess, GG_DEBUG_MISC, "// WARNING! unable to get peer certificate!\n"); else { - char buf[1024]; + char buf[256]; X509_NAME_oneline(X509_get_subject_name(peer), buf, sizeof(buf)); gg_debug_session(sess, GG_DEBUG_MISC, "// cert subject: %s\n", buf); @@ -1956,20 +898,17 @@ #endif case GG_STATE_READING_KEY: + case GG_STATE_READING_REPLY: + case GG_STATE_CONNECTED: + case GG_STATE_DISCONNECTING: { - struct gg_header *h; - struct gg_welcome *w; - unsigned char *password = (unsigned char*) sess->password; - int ret; - uint8_t login_hash[64]; + struct gg_header *gh; - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_KEY\n"); - - memset(login_hash, 0, sizeof(login_hash)); + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTED\n"); /* XXX bardzo, bardzo, bardzo głupi pomysł na pozbycie * się tekstu wrzucanego przez proxy. */ - if (sess->proxy_addr && sess->proxy_port) { + if (sess->state == GG_STATE_READING_KEY && sess->proxy_addr && sess->proxy_port) { char buf[100]; strcpy(buf, ""); @@ -1991,249 +930,30 @@ break; } - /* czytaj pierwszy pakiet. */ - if (!(h = gg_recv_packet(sess))) { - if (errno == EAGAIN) { - sess->check = GG_CHECK_READ; - break; - } - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() didn't receive packet (errno=%d, %s)\n", errno, strerror(errno)); - - e->type = GG_EVENT_CONN_FAILED; - e->event.failure = GG_FAILURE_READING; - sess->state = GG_STATE_IDLE; - errno2 = errno; - close(sess->fd); - errno = errno2; - sess->fd = -1; - break; - } - - if (h->type != GG_WELCOME) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid packet received\n"); - free(h); - close(sess->fd); - sess->fd = -1; - errno = EINVAL; - e->type = GG_EVENT_CONN_FAILED; - e->event.failure = GG_FAILURE_INVALID; - sess->state = GG_STATE_IDLE; - break; - } - - w = (struct gg_welcome*) ((char*) h + sizeof(struct gg_header)); - w->key = gg_fix32(w->key); - - switch (sess->hash_type) { - case GG_LOGIN_HASH_GG32: - { - unsigned int hash; - - hash = gg_fix32(gg_login_hash(password, w->key)); - gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_watch_fd() challenge %.4x --> GG32 hash %.8x\n", w->key, hash); - memcpy(login_hash, &hash, sizeof(hash)); - - break; - } - - case GG_LOGIN_HASH_SHA1: - { - char tmp[41]; - int i; - - gg_login_hash_sha1((char*) password, w->key, login_hash); - for (i = 0; i < 40; i += 2) - snprintf(tmp + i, sizeof(tmp) - i, "%02x", login_hash[i / 2]); - - gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_watch_fd() challenge %.4x --> SHA1 hash: %s\n", w->key, tmp); - - break; - } - } - - free(h); - free(sess->password); - sess->password = NULL; - - if (gg_dcc_ip == (unsigned long) inet_addr("255.255.255.255")) { - struct sockaddr_in sin; - socklen_t sin_len = sizeof(sin); - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() detecting address\n"); - - if (!getsockname(sess->fd, (struct sockaddr*) &sin, &sin_len)) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() detected address to %s\n", inet_ntoa(sin.sin_addr)); - sess->client_addr = sin.sin_addr.s_addr; - } else { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() unable to detect address\n"); - sess->client_addr = 0; - } - } else - sess->client_addr = gg_dcc_ip; - - if (sess->protocol_version >= 0x2e) { - struct gg_login80 l; - - uint32_t tmp_version_len = gg_fix32(strlen(GG8_VERSION)); - uint32_t tmp_descr_len = gg_fix32((sess->initial_descr) ? strlen(sess->initial_descr) : 0); - - memset(&l, 0, sizeof(l)); - l.uin = gg_fix32(sess->uin); - memcpy(l.language, GG8_LANG, sizeof(l.language)); - l.hash_type = sess->hash_type; - memcpy(l.hash, login_hash, sizeof(login_hash)); - l.status = gg_fix32(sess->initial_status ? sess->initial_status : GG_STATUS_AVAIL); - l.flags = gg_fix32(0x00800001); - l.features = gg_fix32(sess->protocol_features); - l.image_size = sess->image_size; - l.dunno2 = 0x64; - - gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending GG_LOGIN80 packet\n"); - ret = gg_send_packet(sess, GG_LOGIN80, - &l, sizeof(l), - &tmp_version_len, sizeof(uint32_t), GG8_VERSION, strlen(GG8_VERSION), - &tmp_descr_len, sizeof(uint32_t), sess->initial_descr, (sess->initial_descr) ? strlen(sess->initial_descr) : 0, - NULL); - - } else if (sess->protocol_version == 0x2d) { - struct gg_login70 l; - - memset(&l, 0, sizeof(l)); - l.uin = gg_fix32(sess->uin); - l.local_ip = (sess->external_addr) ? sess->external_addr : sess->client_addr; - l.local_port = gg_fix16((sess->external_port > 1023) ? sess->external_port : gg_dcc_port); - l.hash_type = sess->hash_type; - memcpy(l.hash, login_hash, sizeof(login_hash)); - l.image_size = sess->image_size; - l.dunno2 = 0x64; - l.status = gg_fix32(sess->initial_status ? sess->initial_status : GG_STATUS_AVAIL); - l.version = gg_fix32(sess->protocol_version | sess->protocol_flags); - - gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending GG_LOGIN80BETA packet\n"); - ret = gg_send_packet(sess, GG_LOGIN80BETA, - &l, sizeof(l), - sess->initial_descr, (sess->initial_descr) ? strlen(sess->initial_descr) : 0, - (sess->initial_descr) ? "\0" : NULL, (sess->initial_descr) ? 1 : 0, - NULL); - } else { - struct gg_login70 l; - - memset(&l, 0, sizeof(l)); - l.local_ip = (sess->external_addr) ? sess->external_addr : sess->client_addr; - l.uin = gg_fix32(sess->uin); - l.local_port = gg_fix16((sess->external_port > 1023) ? sess->external_port : gg_dcc_port); - l.hash_type = sess->hash_type; - memcpy(l.hash, login_hash, sizeof(login_hash)); - l.image_size = sess->image_size; - l.dunno2 = 0xbe; - l.status = gg_fix32(sess->initial_status ? sess->initial_status : GG_STATUS_AVAIL); - l.version = gg_fix32(sess->protocol_version | sess->protocol_flags); - - gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending GG_LOGIN70 packet\n"); - ret = gg_send_packet(sess, GG_LOGIN70, - &l, sizeof(l), - sess->initial_descr, (sess->initial_descr) ? strlen(sess->initial_descr) : 0, - NULL); - } - - free(sess->initial_descr); - sess->initial_descr = NULL; - - if (ret == -1) { - gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending packet failed. (errno=%d, %s)\n", errno, strerror(errno)); - errno2 = errno; - close(sess->fd); - errno = errno2; - sess->fd = -1; - e->type = GG_EVENT_CONN_FAILED; - e->event.failure = GG_FAILURE_WRITING; - sess->state = GG_STATE_IDLE; - break; - } - - sess->state = GG_STATE_READING_REPLY; - sess->check = GG_CHECK_READ; - - break; - } - - case GG_STATE_READING_REPLY: - { - struct gg_header *h; - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_REPLY\n"); - - if (!(h = gg_recv_packet(sess))) { - if (errno == EAGAIN) { - sess->check = GG_CHECK_READ; - break; - } - - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() didn't receive packet (errno=%d, %s)\n", errno, strerror(errno)); - e->type = GG_EVENT_CONN_FAILED; - e->event.failure = GG_FAILURE_READING; - sess->state = GG_STATE_IDLE; - errno2 = errno; - close(sess->fd); - errno = errno2; - sess->fd = -1; - break; - } - - if (h->type == GG_LOGIN_OK || h->type == GG_NEED_EMAIL || h->type == GG_LOGIN80_OK) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() login succeded\n"); - e->type = GG_EVENT_CONN_SUCCESS; - sess->state = GG_STATE_CONNECTED; - sess->check = GG_CHECK_READ; - sess->timeout = -1; - sess->status = (sess->initial_status) ? sess->initial_status : GG_STATUS_AVAIL; - free(h); - break; - } - - if (h->type == GG_LOGIN_FAILED) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() login failed\n"); - e->event.failure = GG_FAILURE_PASSWORD; - errno = EACCES; - } else if (h->type == GG_DISCONNECTING) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() too many incorrect password attempts\n"); - e->event.failure = GG_FAILURE_INTRUDER; - errno = EACCES; - } else { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid packet\n"); - e->event.failure = GG_FAILURE_INVALID; - errno = EINVAL; - } - - e->type = GG_EVENT_CONN_FAILED; - sess->state = GG_STATE_IDLE; - errno2 = errno; - close(sess->fd); - errno = errno2; - sess->fd = -1; - free(h); - - break; - } - - case GG_STATE_CONNECTED: - { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTED\n"); - sess->last_event = time(NULL); - if ((res = gg_watch_fd_connected(sess, e)) == -1) { + gh = gg_recv_packet(sess); - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() watch_fd_connected failed (errno=%d, %s)\n", errno, strerror(errno)); - + if (gh == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() gg_recv_packet failed (errno=%d, %s)\n", errno, strerror(errno)); if (errno == EAGAIN) { e->type = GG_EVENT_NONE; res = 0; - } else + } else { res = -1; + } + + goto done; } + if (gg_session_handle_packet(sess, gh->type, (const char *) gh + sizeof(struct gg_header), gh->length, e) == -1) { + free(gh); + res = -1; + goto done; + } + + free(gh); + sess->check = GG_CHECK_READ; break; diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/lib/handlers.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/gg/lib/handlers.c Fri Apr 01 13:47:51 2011 +0900 @@ -0,0 +1,1792 @@ +/* + * (C) Copyright 2001-2011 Wojtek Kaniewski + * Robert J. Woźny + * Arkadiusz Miśkiewicz + * Tomasz Chiliński + * Adam Wysocki + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/** + * \file handlers.c + * + * \brief Funkcje obsługi przychodzących pakietów + */ + +#include + +#ifndef _WIN32 +# include +# include +# include +# ifdef sun +# include +# endif +#endif + +#include "compat.h" +#include "libgadu.h" +#include "resolver.h" +#include "session.h" +#include "protocol.h" +#include "encoding.h" +#include "message.h" +#include "libgadu-internal.h" + +#include +#ifndef _WIN32 +# include +#endif +#include +#include +#include +#include +#include +#include +#include +#ifdef GG_CONFIG_HAVE_OPENSSL +# include +# include +#endif + +/** + * \internal Struktura opisująca funkcję obsługi pakietu. + */ +typedef struct { + /* Typ pakietu */ + uint32_t type; + /* Stan w którym pakiet jest obsługiwany */ + int state; + /* Minimalny rozmiar danych pakietu */ + int min_length; + /* Funkcja obsługująca pakiet. Patrz gg_session_handle_packet(). */ + int (*handler)(struct gg_session *, uint32_t, const char *, size_t, struct gg_event *); +} gg_packet_handler_t; + +/** + * \internal Obsługuje pakiet GG_WELCOME. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_welcome(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + struct gg_welcome *w; + int ret; + uint8_t hash_buf[64]; + uint32_t local_ip; + + if (len < sizeof(struct gg_welcome)) { + ge->type = GG_EVENT_CONN_FAILED; + ge->event.failure = GG_FAILURE_INVALID; + gs->state = GG_STATE_IDLE; + close(gs->fd); + gs->fd = -1; + return 0; + } + + w = (struct gg_welcome*) ptr; + w->key = gg_fix32(w->key); + + memset(hash_buf, 0, sizeof(hash_buf)); + + switch (gs->hash_type) { + case GG_LOGIN_HASH_GG32: + { + uint32_t hash; + + hash = gg_fix32(gg_login_hash((unsigned char*) gs->password, w->key)); + gg_debug_session(gs, GG_DEBUG_DUMP, "// gg_watch_fd() challenge %.4x --> GG32 hash %.8x\n", w->key, hash); + memcpy(hash_buf, &hash, sizeof(hash)); + + break; + } + + case GG_LOGIN_HASH_SHA1: + { +#ifndef GG_DEBUG_DISABLE + char tmp[41]; + int i; +#endif + + gg_login_hash_sha1(gs->password, w->key, hash_buf); + +#ifndef GG_DEBUG_DISABLE + for (i = 0; i < 40; i += 2) + snprintf(tmp + i, sizeof(tmp) - i, "%02x", hash_buf[i / 2]); + + gg_debug_session(gs, GG_DEBUG_DUMP, "// gg_watch_fd() challenge %.4x --> SHA1 hash: %s\n", w->key, tmp); +#endif + + break; + } + + default: + break; + } + +#if 0 + if (gs->password != NULL && (gs->flags & (1 << GG_SESSION_FLAG_CLEAR_PASSWORD))) { + memset(gs->password, 0, strlen(gs->password)); + free(gs->password); + gs->password = NULL; + } +#endif + + if (gg_dcc_ip == (unsigned long) inet_addr("255.255.255.255")) { + struct sockaddr_in sin; + unsigned int sin_len = sizeof(sin); + + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() detecting address\n"); + + if (!getsockname(gs->fd, (struct sockaddr*) &sin, &sin_len)) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() detected address to %s\n", inet_ntoa(sin.sin_addr)); + local_ip = sin.sin_addr.s_addr; + } else { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() unable to detect address\n"); + local_ip = 0; + } + } else + local_ip = gg_dcc_ip; + + gs->client_addr = local_ip; + + if (GG_SESSION_IS_PROTOCOL_8_0(gs)) { + struct gg_login80 l80; + const char *version, *descr; + uint32_t version_len, descr_len; + + memset(&l80, 0, sizeof(l80)); + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() sending GG_LOGIN80 packet\n"); + l80.uin = gg_fix32(gs->uin); + memcpy(l80.language, GG8_LANG, sizeof(l80.language)); + l80.hash_type = gs->hash_type; + memcpy(l80.hash, hash_buf, sizeof(l80.hash)); + l80.status = gg_fix32(gs->initial_status ? gs->initial_status : GG_STATUS_AVAIL); + l80.flags = gg_fix32(gs->status_flags); + l80.features = gg_fix32(gs->protocol_features); + l80.image_size = gs->image_size; + l80.dunno2 = 0x64; + + version = (gs->client_version != NULL) ? gs->client_version : GG_DEFAULT_CLIENT_VERSION; + version_len = gg_fix32(strlen(GG8_VERSION) + strlen(version)); + + descr = (gs->initial_descr != NULL) ? gs->initial_descr : ""; + descr_len = (gs->initial_descr != NULL) ? gg_fix32(strlen(gs->initial_descr)) : 0; + + ret = gg_send_packet(gs, + GG_LOGIN80, + &l80, sizeof(l80), + &version_len, sizeof(version_len), + GG8_VERSION, strlen(GG8_VERSION), + version, strlen(version), + &descr_len, sizeof(descr_len), + descr, strlen(descr), + NULL); + } else { + struct gg_login70 l70; + + memset(&l70, 0, sizeof(l70)); + l70.uin = gg_fix32(gs->uin); + l70.hash_type = gs->hash_type; + memcpy(l70.hash, hash_buf, sizeof(l70.hash)); + l70.status = gg_fix32(gs->initial_status ? gs->initial_status : GG_STATUS_AVAIL); + l70.version = gg_fix32(gs->protocol_version | gs->protocol_flags); + if (gs->external_addr && gs->external_port > 1023) { + l70.local_ip = gs->external_addr; + l70.local_port = gg_fix16(gs->external_port); + } else { + l70.local_ip = local_ip; + l70.local_port = gg_fix16(gg_dcc_port); + } + + l70.image_size = gs->image_size; + l70.dunno2 = 0xbe; + + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() sending GG_LOGIN70 packet\n"); + ret = gg_send_packet(gs, GG_LOGIN70, &l70, sizeof(l70), gs->initial_descr, (gs->initial_descr) ? strlen(gs->initial_descr) : 0, NULL); + } + + if (ret == -1) { + int errno_copy; + + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() sending packet failed. (errno=%d, %s)\n", errno, strerror(errno)); + errno_copy = errno; + close(gs->fd); + errno = errno_copy; + gs->fd = -1; + ge->type = GG_EVENT_CONN_FAILED; + ge->event.failure = GG_FAILURE_WRITING; + gs->state = GG_STATE_IDLE; + return -1; + } + + gs->state = GG_STATE_READING_REPLY; + gs->check = GG_CHECK_READ; + + return 0; +} + +/** + * \internal Obsługuje pakiet GG_LOGIN_OK. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_login_ok(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() login succeded\n"); + ge->type = GG_EVENT_CONN_SUCCESS; + gs->state = GG_STATE_CONNECTED; + gs->check = GG_CHECK_READ; + gs->timeout = -1; + gs->status = (gs->initial_status) ? gs->initial_status : GG_STATUS_AVAIL; +#if 0 + free(gs->status_descr); + gs->status_descr = gs->initial_descr; +#else + free(gs->initial_descr); +#endif + gs->initial_descr = NULL; + + return 0; +} + +/** + * \internal Obsługuje pakiet GG_LOGIN_FAILED. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_login_failed(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + if (type != GG_DISCONNECTING) + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() login failed\n"); + else + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd() too many incorrect password attempts\n"); + ge->type = GG_EVENT_CONN_FAILED; + ge->event.failure = (type != GG_DISCONNECTING) ? GG_FAILURE_PASSWORD : GG_FAILURE_INTRUDER; + gs->state = GG_STATE_IDLE; + close(gs->fd); + gs->fd = -1; + errno = EACCES; + + return 0; +} + +/** + * \internal Obsługuje pakiet GG_SEND_MSG_ACK. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_send_msg_ack(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + struct gg_send_msg_ack *s = (struct gg_send_msg_ack*) ptr; + + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a message ack\n"); + + ge->type = GG_EVENT_ACK; + ge->event.ack.status = gg_fix32(s->status); + ge->event.ack.recipient = gg_fix32(s->recipient); + ge->event.ack.seq = gg_fix32(s->seq); + + return 0; +} + +/** + * \internal Obsługuje pakiet GG_PONG. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_pong(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a pong\n"); + + ge->type = GG_EVENT_PONG; + + gs->last_pong = time(NULL); + + return 0; +} + +/** + * \internal Obsługuje pakiet GG_DISCONNECTING. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_disconnecting(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received disconnection warning\n"); + + ge->type = GG_EVENT_DISCONNECT; + + return 0; +} + +/** + * \internal Obsługuje pakiet GG_DISCONNECT_ACK. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_disconnect_ack(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received logoff acknowledge\n"); + + ge->type = GG_EVENT_DISCONNECT_ACK; + + return 0; +} + +/** + * \internal Obsługuje pakiety GG_XML_EVENT i GG_XML_ACTION. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_xml_event(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received XML event\n"); + + ge->type = GG_EVENT_XML_EVENT; + ge->event.xml_event.data = malloc(len + 1); + + if (ge->event.xml_event.data == NULL) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); + return -1; + } + + memcpy(ge->event.xml_event.data, ptr, len); + ge->event.xml_event.data[len] = 0; + + return 0; +} + +/** + * \internal Obsługuje pakiet GG_PUBDIR50_REPLY. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_pubdir50_reply(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received pubdir/search reply\n"); + + return gg_pubdir50_handle_reply_sess(gs, ge, ptr, len); +} + +/** + * \internal Obsługuje pakiet GG_USERLIST_REPLY. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_userlist_reply(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + char reply_type; + + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received userlist reply\n"); + + reply_type = ptr[0]; + + /* jeśli odpowiedź na eksport, wywołaj zdarzenie tylko + * gdy otrzymano wszystkie odpowiedzi */ + if (reply_type == GG_USERLIST_PUT_REPLY || reply_type == GG_USERLIST_PUT_MORE_REPLY) { + if (--gs->userlist_blocks) + return 0; + + reply_type = GG_USERLIST_PUT_REPLY; + } + + if (len > 1) { + unsigned int reply_len = (gs->userlist_reply != NULL) ? strlen(gs->userlist_reply) : 0; + char *tmp; + + gg_debug_session(gs, GG_DEBUG_MISC, "userlist_reply=%p, len=%d\n", gs->userlist_reply, len); + + tmp = realloc(gs->userlist_reply, reply_len + len); + + if (tmp == NULL) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); + return -1; + } + + gs->userlist_reply = tmp; + memcpy(gs->userlist_reply + reply_len, ptr + 1, len - 1); + gs->userlist_reply[reply_len + len - 1] = 0; + } + + if (reply_type == GG_USERLIST_GET_MORE_REPLY) + return 0; + + ge->type = GG_EVENT_USERLIST; + ge->event.userlist.type = reply_type; + ge->event.userlist.reply = gs->userlist_reply; + + gs->userlist_reply = NULL; + + return 0; +} + +/** + * \internal Obsługuje pakiet GG_DCC7_ID_REPLY. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_dcc7_id_reply(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 id packet\n"); + + return gg_dcc7_handle_id(gs, ge, ptr, len); +} + +/** + * \internal Obsługuje pakiet GG_DCC7_ACCEPT. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_dcc7_accept(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 accept\n"); + + return gg_dcc7_handle_accept(gs, ge, ptr, len); +} + +/** + * \internal Obsługuje pakiet GG_DCC7_NEW. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_dcc7_new(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 request\n"); + + return gg_dcc7_handle_new(gs, ge, ptr, len); +} + +/** + * \internal Obsługuje pakiet GG_DCC7_REJECT. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_dcc7_reject(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 reject\n"); + + return gg_dcc7_handle_reject(gs, ge, ptr, len); +} + +/** + * \internal Obsługuje pakiet GG_DCC7_INFO. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_dcc7_info(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 info\n"); + + return gg_dcc7_handle_info(gs, ge, ptr, len); +} + +/** + * \internal Analizuje przychodzący pakiet z obrazkiem. + * + * \param e Struktura zdarzenia + * \param p Bufor z danymi + * \param len Długość bufora + * \param sess Struktura sesji + * \param sender Numer nadawcy + */ +static void gg_image_queue_parse(struct gg_event *e, const char *p, unsigned int len, struct gg_session *sess, uin_t sender) +{ + struct gg_msg_image_reply *i = (void*) p; + struct gg_image_queue *q, *qq; + + if (!p || !sess || !e) { + errno = EFAULT; + return; + } + + /* znajdź dany obrazek w kolejce danej sesji */ + + for (qq = sess->images, q = NULL; qq; qq = qq->next) { + if (sender == qq->sender && i->size == qq->size && i->crc32 == qq->crc32) { + q = qq; + break; + } + } + + if (!q) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_queue_parse() unknown image from %d, size=%d, crc32=%.8x\n", sender, i->size, i->crc32); + return; + } + + if (p[0] == GG_MSG_OPTION_IMAGE_REPLY) { + q->done = 0; + + len -= sizeof(struct gg_msg_image_reply); + p += sizeof(struct gg_msg_image_reply); + + if (memchr(p, 0, len) == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_queue_parse() malformed packet from %d, unlimited filename\n", sender); + return; + } + + if (!(q->filename = strdup(p))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_queue_parse() out of memory\n"); + return; + } + + len -= strlen(p) + 1; + p += strlen(p) + 1; + } else { + len -= sizeof(struct gg_msg_image_reply); + p += sizeof(struct gg_msg_image_reply); + } + + if (q->done + len > q->size) + len = q->size - q->done; + + memcpy(q->image + q->done, p, len); + q->done += len; + + /* jeśli skończono odbierać obrazek, wygeneruj zdarzenie */ + + if (q->done >= q->size) { + e->type = GG_EVENT_IMAGE_REPLY; + e->event.image_reply.sender = sender; + e->event.image_reply.size = q->size; + e->event.image_reply.crc32 = q->crc32; + e->event.image_reply.filename = q->filename; + e->event.image_reply.image = q->image; + + gg_image_queue_remove(sess, q, 0); + + free(q); + } +} + +/** + * \internal Analizuje informacje rozszerzone wiadomości. + * + * \param sess Struktura sesji. + * \param e Struktura zdarzenia. + * \param sender Numer nadawcy. + * \param p Wskaźnik na dane rozszerzone. + * \param packet_end Wskaźnik na koniec pakietu. + * + * \return 0 jeśli się powiodło, -1 jeśli wiadomość obsłużono i wynik ma + * zostać przekazany aplikacji, -2 jeśli wystąpił błąd ogólny, -3 jeśli + * wiadomość jest niepoprawna. + */ +static int gg_handle_recv_msg_options(struct gg_session *sess, struct gg_event *e, uin_t sender, const char *p, const char *packet_end) +{ + while (p < packet_end) { + switch (*p) { + case GG_MSG_OPTION_CONFERENCE: + { + struct gg_msg_recipients *m = (void*) p; + uint32_t i, count; + + p += sizeof(*m); + + if (p > packet_end) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() packet out of bounds (1)\n"); + goto malformed; + } + + count = gg_fix32(m->count); + + if (p + count * sizeof(uin_t) > packet_end || p + count * sizeof(uin_t) < p || count > 0xffff) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() packet out of bounds (1.5)\n"); + goto malformed; + } + + if (e->event.msg.recipients != NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() e->event.msg.recipients already exist\n"); + goto malformed; + } + + e->event.msg.recipients = malloc(count * sizeof(uin_t)); + + if (e->event.msg.recipients == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() not enough memory for recipients data\n"); + goto fail; + } + + memcpy(e->event.msg.recipients, p, count * sizeof(uin_t)); + p += count * sizeof(uin_t); + + for (i = 0; i < count; i++) + e->event.msg.recipients[i] = gg_fix32(e->event.msg.recipients[i]); + + e->event.msg.recipients_count = count; + + break; + } + + case GG_MSG_OPTION_ATTRIBUTES: + { + uint16_t len; + char *buf; + + if (p + 3 > packet_end) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() packet out of bounds (2)\n"); + goto malformed; + } + + memcpy(&len, p + 1, sizeof(uint16_t)); + len = gg_fix16(len); + + if (e->event.msg.formats != NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() e->event.msg.formats already exist\n"); + goto malformed; + } + + buf = malloc(len); + + if (buf == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() not enough memory for richtext data\n"); + goto fail; + } + + p += 3; + + if (p + len > packet_end) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() packet out of bounds (3)\n"); + free(buf); + goto malformed; + } + + memcpy(buf, p, len); + + e->event.msg.formats = buf; + e->event.msg.formats_length = len; + + p += len; + + break; + } + + case GG_MSG_OPTION_IMAGE_REQUEST: + { + struct gg_msg_image_request *i = (void*) p; + + if (p + sizeof(*i) > packet_end) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (3)\n"); + goto malformed; + } + + if (e->event.msg.formats != NULL || e->event.msg.recipients != NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() mixed options (1)\n"); + goto malformed; + } + + e->event.image_request.sender = sender; + e->event.image_request.size = gg_fix32(i->size); + e->event.image_request.crc32 = gg_fix32(i->crc32); + + e->type = GG_EVENT_IMAGE_REQUEST; + + goto handled; + } + + case GG_MSG_OPTION_IMAGE_REPLY: + case GG_MSG_OPTION_IMAGE_REPLY_MORE: + { + struct gg_msg_image_reply *rep = (void*) p; + + if (e->event.msg.formats != NULL || e->event.msg.recipients != NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() mixed options (2)\n"); + goto malformed; + } + + if (p + sizeof(struct gg_msg_image_reply) == packet_end) { + + /* pusta odpowiedź - klient po drugiej stronie nie ma żądanego obrazka */ + + e->type = GG_EVENT_IMAGE_REPLY; + e->event.image_reply.sender = sender; + e->event.image_reply.size = 0; + e->event.image_reply.crc32 = gg_fix32(rep->crc32); + e->event.image_reply.filename = NULL; + e->event.image_reply.image = NULL; + goto handled; + + } else if (p + sizeof(struct gg_msg_image_reply) + 1 > packet_end) { + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() packet out of bounds (4)\n"); + goto malformed; + } + + rep->size = gg_fix32(rep->size); + rep->crc32 = gg_fix32(rep->crc32); + gg_image_queue_parse(e, p, (unsigned int)(packet_end - p), sess, sender); + + goto handled; + } + + default: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() unknown payload 0x%.2x\n", *p); + p = packet_end; + } + } + } + + return 0; + +handled: + return -1; + +fail: + return -2; + +malformed: + return -3; +} + +/** + * \internal Wysyła potwierdzenie odebrania wiadomości. + * + * \param gs Struktura sesji + * + * \return 0 jeśli się powiodło, -1 jeśli wystąpił błąd + */ +static int gg_session_send_msg_ack(struct gg_session *gs) +{ + struct gg_recv_msg_ack pkt; + + gg_debug_session(gs, GG_DEBUG_FUNCTION, "** gg_session_send_msg_ack(%p);\n", gs); + + if ((gs->protocol_features & GG_FEATURE_MSG_ACK) == 0) + return 0; + + gs->recv_msg_count++; + pkt.count = gg_fix32(gs->recv_msg_count); + + return gg_send_packet(gs, GG_RECV_MSG_ACK, &pkt, sizeof(pkt), NULL); +} + +/** + * \internal Obsługuje pakiet GG_RECV_MSG. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_recv_msg(struct gg_session *sess, uint32_t type, const char *packet, size_t length, struct gg_event *e) +{ + const struct gg_recv_msg *r = (const struct gg_recv_msg*) packet; + const char *payload = packet + sizeof(struct gg_recv_msg); + const char *payload_end = packet + length; + char *tmp; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_handle_recv_msg(%p, %d, %p);\n", packet, length, e); + + if ((r->seq == 0) && (r->msgclass == 0)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() oops, silently ignoring the bait\n"); + goto malformed; + } + + // jednobajtowa wiadomość o treści \x02 to żądanie połączenia DCC + if (*payload == GG_MSG_CALLBACK && payload == payload_end - 1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() received ctcp packet\n"); + length = 1; + } else { + const char *options; + + options = memchr(payload, 0, (size_t) (payload_end - payload)); + + if (options == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() malformed packet, message out of bounds (0)\n"); + goto malformed; + } + + length = (size_t) (options - payload); + + switch (gg_handle_recv_msg_options(sess, e, gg_fix32(r->sender), options + 1, payload_end)) { + case -1: // handled + gg_session_send_msg_ack(sess); + return 0; + + case -2: // failed + goto fail; + + case -3: // malformed + goto malformed; + } + } + + e->type = GG_EVENT_MSG; + e->event.msg.msgclass = gg_fix32(r->msgclass); + e->event.msg.sender = gg_fix32(r->sender); + e->event.msg.time = gg_fix32(r->time); + e->event.msg.seq = gg_fix32(r->seq); + + tmp = gg_encoding_convert(payload, GG_ENCODING_CP1250, sess->encoding, length, -1); + if (tmp == NULL) + goto fail; + e->event.msg.message = (unsigned char*) tmp; + + gg_session_send_msg_ack(sess); + return 0; + +fail: + free(e->event.msg.message); + free(e->event.msg.recipients); + free(e->event.msg.formats); + return -1; + +malformed: + e->type = GG_EVENT_NONE; + free(e->event.msg.message); + free(e->event.msg.xhtml_message); + free(e->event.msg.recipients); + free(e->event.msg.formats); + gg_session_send_msg_ack(sess); + return 0; +} + +/** + * \internal Obsługuje pakiet GG_RECV_MSG80. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_recv_msg_80(struct gg_session *sess, uint32_t type, const char *packet, size_t length, struct gg_event *e) +{ + const struct gg_recv_msg80 *r = (const struct gg_recv_msg80*) packet; + uint32_t offset_plain; + uint32_t offset_attr; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_handle_recv_msg80(%p, %d, %p);\n", packet, length, e); + + if (r->seq == 0 && r->msgclass == 0) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() oops, silently ignoring the bait\n"); + goto malformed; + } + + offset_plain = gg_fix32(r->offset_plain); + offset_attr = gg_fix32(r->offset_attr); + + if (offset_plain < sizeof(struct gg_recv_msg80) || offset_plain >= length) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, message out of bounds (0)\n"); + goto malformed; + } + + if (offset_attr < sizeof(struct gg_recv_msg80) || offset_attr > length) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, attr out of bounds (1)\n"); + offset_attr = 0; /* nie parsuj attr. */ + } + + /* Normalna sytuacja, więc nie podpada pod powyższy warunek. */ + if (offset_attr == length) + offset_attr = 0; + + if (memchr(packet + offset_plain, 0, length - offset_plain) == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, message out of bounds (2)\n"); + goto malformed; + } + + if (offset_plain > sizeof(struct gg_recv_msg80) && memchr(packet + sizeof(struct gg_recv_msg80), 0, offset_plain - sizeof(struct gg_recv_msg80)) == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, message out of bounds (3)\n"); + goto malformed; + } + + e->type = (type != GG_RECV_OWN_MSG) ? GG_EVENT_MSG : GG_EVENT_MULTILOGON_MSG; + e->event.msg.msgclass = gg_fix32(r->msgclass); + e->event.msg.sender = gg_fix32(r->sender); + e->event.msg.time = gg_fix32(r->time); + e->event.msg.seq = gg_fix32(r->seq); + + if (offset_attr != 0) { + switch (gg_handle_recv_msg_options(sess, e, gg_fix32(r->sender), packet + offset_attr, packet + length)) { + case -1: // handled + gg_session_send_msg_ack(sess); + return 0; + + case -2: // failed + goto fail; + + case -3: // malformed + goto malformed; + } + } + + if (sess->encoding == GG_ENCODING_CP1250) { + e->event.msg.message = (unsigned char*) strdup(packet + offset_plain); + } else { + if (offset_plain > sizeof(struct gg_recv_msg80)) { + int len; + + len = gg_message_html_to_text(NULL, packet + sizeof(struct gg_recv_msg80)); + e->event.msg.message = malloc(len + 1); + + if (e->event.msg.message == NULL) + goto fail; + + gg_message_html_to_text((char*) e->event.msg.message, packet + sizeof(struct gg_recv_msg80)); + } else { + e->event.msg.message = (unsigned char*) gg_encoding_convert(packet + offset_plain, GG_ENCODING_CP1250, sess->encoding, -1, -1); + } + } + + if (offset_plain > sizeof(struct gg_recv_msg80)) + e->event.msg.xhtml_message = gg_encoding_convert(packet + sizeof(struct gg_recv_msg80), GG_ENCODING_UTF8, sess->encoding, -1, -1); + else + e->event.msg.xhtml_message = NULL; + + gg_session_send_msg_ack(sess); + return 0; + +fail: + free(e->event.msg.message); + free(e->event.msg.xhtml_message); + free(e->event.msg.recipients); + free(e->event.msg.formats); + return -1; + +malformed: + e->type = GG_EVENT_NONE; + free(e->event.msg.message); + free(e->event.msg.xhtml_message); + free(e->event.msg.recipients); + free(e->event.msg.formats); + gg_session_send_msg_ack(sess); + return 0; +} + +/** + * \internal Obsługuje pakiet GG_STATUS. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_status(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + struct gg_status *s = (void*) ptr; + + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n"); + + ge->type = GG_EVENT_STATUS; + ge->event.status.uin = gg_fix32(s->uin); + ge->event.status.status = gg_fix32(s->status); + ge->event.status.descr = NULL; + + if (len > sizeof(*s)) { + ge->event.status.descr = gg_encoding_convert(ptr + sizeof(*s), GG_ENCODING_CP1250, gs->encoding, len - sizeof(*s), -1); + + if (ge->event.status.descr == NULL) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); + return -1; + } + } + + return 0; +} + +/** + * \internal Obsługuje pakiety GG_STATUS60, GG_STATUS77 i GG_STATUS80BETA. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_status_60_77_80beta(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + struct gg_status60 *s60 = (void*) ptr; + struct gg_status77 *s77 = (void*) ptr; + size_t struct_len; + uint32_t uin; + + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n"); + + ge->type = GG_EVENT_STATUS60; + ge->event.status60.descr = NULL; + ge->event.status60.time = 0; + + if (type == GG_STATUS60) { + uin = gg_fix32(s60->uin); + ge->event.status60.status = s60->status; + ge->event.status60.remote_ip = s60->remote_ip; + ge->event.status60.remote_port = gg_fix16(s60->remote_port); + ge->event.status60.version = s60->version; + ge->event.status60.image_size = s60->image_size; + struct_len = sizeof(*s60); + } else { + uin = gg_fix32(s77->uin); + ge->event.status60.status = s77->status; + ge->event.status60.remote_ip = s77->remote_ip; + ge->event.status60.remote_port = gg_fix16(s77->remote_port); + ge->event.status60.version = s77->version; + ge->event.status60.image_size = s77->image_size; + struct_len = sizeof(*s77); + } + + ge->event.status60.uin = uin & 0x00ffffff; + + if (uin & 0x40000000) + ge->event.status60.version |= GG_HAS_AUDIO_MASK; + if (uin & 0x20000000) + ge->event.status60.version |= GG_HAS_AUDIO7_MASK; + if (uin & 0x08000000) + ge->event.status60.version |= GG_ERA_OMNIX_MASK; + + if (len > struct_len) { + size_t descr_len; + + descr_len = len - struct_len; + + ge->event.status60.descr = gg_encoding_convert(ptr + struct_len, (type == GG_STATUS80BETA) ? GG_ENCODING_UTF8 : GG_ENCODING_CP1250, gs->encoding, descr_len, -1); + + if (ge->event.status60.descr == NULL) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); + return -1; + } + + if (descr_len > 4 && ptr[len - 5] == 0) { + uint32_t t; + memcpy(&t, ptr + len - 4, sizeof(uint32_t)); + ge->event.status60.time = gg_fix32(t); + } + } + + return 0; +} + +/** + * \internal Obsługuje pakiet GG_NOTIFY_REPLY. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_notify_reply(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + struct gg_notify_reply *n = (void*) ptr; + char *descr; + + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); + + if (gg_fix32(n->status) == GG_STATUS_BUSY_DESCR || gg_fix32(n->status) == GG_STATUS_NOT_AVAIL_DESCR || gg_fix32(n->status) == GG_STATUS_AVAIL_DESCR) { + size_t descr_len; + + ge->type = GG_EVENT_NOTIFY_DESCR; + + if (!(ge->event.notify_descr.notify = (void*) malloc(sizeof(*n) * 2))) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); + return -1; + } + ge->event.notify_descr.notify[1].uin = 0; + memcpy(ge->event.notify_descr.notify, ptr, sizeof(*n)); + ge->event.notify_descr.notify[0].uin = gg_fix32(ge->event.notify_descr.notify[0].uin); + ge->event.notify_descr.notify[0].status = gg_fix32(ge->event.notify_descr.notify[0].status); + ge->event.notify_descr.notify[0].remote_port = gg_fix16(ge->event.notify_descr.notify[0].remote_port); + ge->event.notify_descr.notify[0].version = gg_fix32(ge->event.notify_descr.notify[0].version); + + descr_len = len - sizeof(*n); + + descr = gg_encoding_convert(ptr + sizeof(*n), GG_ENCODING_CP1250, gs->encoding, descr_len, -1); + + if (descr == NULL) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); + return -1; + } + + ge->event.notify_descr.descr = descr; + + } else { + unsigned int i, count; + + ge->type = GG_EVENT_NOTIFY; + + if (!(ge->event.notify = (void*) malloc(len + 2 * sizeof(*n)))) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); + return -1; + } + + memcpy(ge->event.notify, ptr, len); + count = len / sizeof(*n); + ge->event.notify[count].uin = 0; + + for (i = 0; i < count; i++) { + ge->event.notify[i].uin = gg_fix32(ge->event.notify[i].uin); + ge->event.notify[i].status = gg_fix32(ge->event.notify[i].status); + ge->event.notify[i].remote_port = gg_fix16(ge->event.notify[i].remote_port); + ge->event.notify[i].version = gg_fix32(ge->event.notify[i].version); + } + } + + return 0; +} + +/** + * \internal Obsługuje pakiet GG_STATUS80. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_status_80(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + struct gg_notify_reply80 *n = (void*) ptr; + size_t descr_len; + + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n"); + + ge->type = GG_EVENT_STATUS60; + ge->event.status60.uin = gg_fix32(n->uin); + ge->event.status60.status = gg_fix32(n->status); + ge->event.status60.remote_ip = n->remote_ip; + ge->event.status60.remote_port = gg_fix16(n->remote_port); + ge->event.status60.version = 0; + ge->event.status60.image_size = n->image_size; + ge->event.status60.descr = NULL; + ge->event.status60.time = 0; + + descr_len = gg_fix32(n->descr_len); + + if (descr_len != 0 && sizeof(struct gg_notify_reply80) + descr_len <= len) { + ge->event.status60.descr = gg_encoding_convert((char*) n + sizeof(struct gg_notify_reply80), GG_ENCODING_UTF8, gs->encoding, descr_len, -1); + + if (ge->event.status60.descr == NULL) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); + return -1; + } + + /* XXX czas */ + } + + return 0; +} + +/** + * \internal Obsługuje pakiet GG_NOTIFY_REPLY80. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_notify_reply_80(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + struct gg_notify_reply80 *n = (void*) ptr; + unsigned int length = len, i = 0; + + // TODO: najpierw przeanalizować strukturę i określić + // liczbę rekordów, żeby obyć się bez realloc() + + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); + + ge->type = GG_EVENT_NOTIFY60; + ge->event.notify60 = malloc(sizeof(*ge->event.notify60)); + + if (!ge->event.notify60) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); + return -1; + } + + ge->event.notify60[0].uin = 0; + + while (length >= sizeof(struct gg_notify_reply80)) { + uin_t uin = gg_fix32(n->uin); + int descr_len; + char *tmp; + + ge->event.notify60[i].uin = uin; + ge->event.notify60[i].status = gg_fix32(n->status); + ge->event.notify60[i].remote_ip = n->remote_ip; + ge->event.notify60[i].remote_port = gg_fix16(n->remote_port); + ge->event.notify60[i].version = 0; + ge->event.notify60[i].image_size = n->image_size; + ge->event.notify60[i].descr = NULL; + ge->event.notify60[i].time = 0; + + descr_len = gg_fix32(n->descr_len); + + if (descr_len != 0) { + if (sizeof(struct gg_notify_reply80) + descr_len <= length) { + ge->event.notify60[i].descr = gg_encoding_convert((char*) n + sizeof(struct gg_notify_reply80), GG_ENCODING_UTF8, gs->encoding, descr_len, -1); + + if (ge->event.notify60[i].descr == NULL) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); + return -1; + } + + /* XXX czas */ + + length -= sizeof(struct gg_notify_reply80) + descr_len; + n = (void*) ((char*) n + sizeof(struct gg_notify_reply80) + descr_len); + } else { + length = 0; + } + + } else { + length -= sizeof(struct gg_notify_reply80); + n = (void*) ((char*) n + sizeof(struct gg_notify_reply80)); + } + + if (!(tmp = realloc(ge->event.notify60, (i + 2) * sizeof(*ge->event.notify60)))) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); + free(ge->event.notify60); + return -1; + } + + ge->event.notify60 = (void*) tmp; + ge->event.notify60[++i].uin = 0; + } + + return 0; +} + +/** + * \internal Obsługuje pakiety GG_NOTIFY_REPLY77 i GG_NOTIFY_REPLY80BETA. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_notify_reply_77_80beta(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + struct gg_notify_reply77 *n = (void*) ptr; + unsigned int length = len, i = 0; + + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); + + ge->type = GG_EVENT_NOTIFY60; + ge->event.notify60 = malloc(sizeof(*ge->event.notify60)); + + if (ge->event.notify60 == NULL) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); + return -1; + } + + ge->event.notify60[0].uin = 0; + + while (length >= sizeof(struct gg_notify_reply77)) { + uin_t uin = gg_fix32(n->uin); + char *tmp; + + ge->event.notify60[i].uin = uin & 0x00ffffff; + ge->event.notify60[i].status = n->status; + ge->event.notify60[i].remote_ip = n->remote_ip; + ge->event.notify60[i].remote_port = gg_fix16(n->remote_port); + ge->event.notify60[i].version = n->version; + ge->event.notify60[i].image_size = n->image_size; + ge->event.notify60[i].descr = NULL; + ge->event.notify60[i].time = 0; + + if (uin & 0x40000000) + ge->event.notify60[i].version |= GG_HAS_AUDIO_MASK; + if (uin & 0x20000000) + ge->event.notify60[i].version |= GG_HAS_AUDIO7_MASK; + if (uin & 0x08000000) + ge->event.notify60[i].version |= GG_ERA_OMNIX_MASK; + + if (GG_S_D(n->status)) { + unsigned char descr_len = *((char*) n + sizeof(struct gg_notify_reply77)); + + if (sizeof(struct gg_notify_reply77) + descr_len <= length) { + ge->event.notify60[i].descr = gg_encoding_convert((char*) n + sizeof(struct gg_notify_reply77) + 1, (type == GG_NOTIFY_REPLY80BETA) ? GG_ENCODING_UTF8 : GG_ENCODING_CP1250, gs->encoding, descr_len, -1); + + if (ge->event.notify60[i].descr == NULL) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); + return -1; + } + + /* XXX czas */ + + length -= sizeof(struct gg_notify_reply77) + descr_len + 1; + n = (void*) ((char*) n + sizeof(struct gg_notify_reply77) + descr_len + 1); + } else { + length = 0; + } + + } else { + length -= sizeof(struct gg_notify_reply77); + n = (void*) ((char*) n + sizeof(struct gg_notify_reply77)); + } + + if (!(tmp = realloc(ge->event.notify60, (i + 2) * sizeof(*ge->event.notify60)))) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); + free(ge->event.notify60); + return -1; + } + + ge->event.notify60 = (void*) tmp; + ge->event.notify60[++i].uin = 0; + } + + return 0; +} + +/** + * \internal Obsługuje pakiet GG_NOTIFY_REPLY60. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_notify_reply_60(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + struct gg_notify_reply60 *n = (void*) ptr; + unsigned int length = len, i = 0; + + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); + + ge->type = GG_EVENT_NOTIFY60; + ge->event.notify60 = malloc(sizeof(*ge->event.notify60)); + + if (ge->event.notify60 == NULL) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); + return -1; + } + + ge->event.notify60[0].uin = 0; + + while (length >= sizeof(struct gg_notify_reply60)) { + uin_t uin = gg_fix32(n->uin); + char *tmp; + + ge->event.notify60[i].uin = uin & 0x00ffffff; + ge->event.notify60[i].status = n->status; + ge->event.notify60[i].remote_ip = n->remote_ip; + ge->event.notify60[i].remote_port = gg_fix16(n->remote_port); + ge->event.notify60[i].version = n->version; + ge->event.notify60[i].image_size = n->image_size; + ge->event.notify60[i].descr = NULL; + ge->event.notify60[i].time = 0; + + if (uin & 0x40000000) + ge->event.notify60[i].version |= GG_HAS_AUDIO_MASK; + if (uin & 0x08000000) + ge->event.notify60[i].version |= GG_ERA_OMNIX_MASK; + + if (GG_S_D(n->status)) { + unsigned char descr_len = *((char*) n + sizeof(struct gg_notify_reply60)); + + if (sizeof(struct gg_notify_reply60) + descr_len <= length) { + char *descr; + + descr = gg_encoding_convert((char*) n + sizeof(struct gg_notify_reply60) + 1, GG_ENCODING_CP1250, gs->encoding, descr_len, -1); + + if (descr == NULL) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); + return -1; + } + + ge->event.notify60[i].descr = descr; + + /* XXX czas */ + + length -= sizeof(struct gg_notify_reply60) + descr_len + 1; + n = (void*) ((char*) n + sizeof(struct gg_notify_reply60) + descr_len + 1); + } else { + length = 0; + } + + } else { + length -= sizeof(struct gg_notify_reply60); + n = (void*) ((char*) n + sizeof(struct gg_notify_reply60)); + } + + if (!(tmp = realloc(ge->event.notify60, (i + 2) * sizeof(*ge->event.notify60)))) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() out of memory\n"); + free(ge->event.notify60); + return -1; + } + + ge->event.notify60 = (void*) tmp; + ge->event.notify60[++i].uin = 0; + } + + return 0; +} + +/** + * \internal Obsługuje pakiet GG_USER_DATA. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_user_data(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + struct gg_user_data d; + char *p = (char*) ptr; + char *packet_end = (char*) ptr + len; + struct gg_event_user_data_user *users; + int i, j; + int res = 0; + + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received user data\n"); + + ge->event.user_data.user_count = 0; + ge->event.user_data.users = NULL; + + if (ptr + sizeof(d) > packet_end) + goto malformed; + + memcpy(&d, p, sizeof(d)); + p += sizeof(d); + + d.type = gg_fix32(d.type); + d.user_count = gg_fix32(d.user_count); + + if (d.user_count > 0xffff) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() malformed packet (1)\n"); + goto malformed; + } + + if (d.user_count > 0) { + users = calloc(d.user_count, sizeof(struct gg_event_user_data_user)); + + if (users == NULL) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() out of memory (%d*%d)\n", d.user_count, sizeof(struct gg_event_user_data_user)); + goto fail; + } + } else { + users = NULL; + } + + ge->type = GG_EVENT_USER_DATA; + ge->event.user_data.type = d.type; + ge->event.user_data.user_count = d.user_count; + ge->event.user_data.users = users; + + gg_debug_session(gs, GG_DEBUG_DUMP, "type=%d, count=%d\n", d.type, d.user_count); + + for (i = 0; i < d.user_count; i++) { + struct gg_user_data_user u; + struct gg_event_user_data_attr *attrs; + + if (p + sizeof(u) > packet_end) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() malformed packet (2)\n"); + goto malformed; + } + + memcpy(&u, p, sizeof(u)); + p += sizeof(u); + + u.uin = gg_fix32(u.uin); + u.attr_count = gg_fix32(u.attr_count); + + if (u.attr_count > 0xffff) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() malformed packet (2)\n"); + goto malformed; + } + + if (u.attr_count > 0) { + attrs = calloc(u.attr_count, sizeof(struct gg_event_user_data_attr)); + + if (attrs == NULL) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() out of memory (%d*%d)\n", u.attr_count, sizeof(struct gg_event_user_data_attr)); + goto fail; + } + } else { + attrs = NULL; + } + + users[i].uin = u.uin; + users[i].attr_count = u.attr_count; + users[i].attrs = attrs; + + gg_debug_session(gs, GG_DEBUG_DUMP, " uin=%d, count=%d\n", u.uin, u.attr_count); + + for (j = 0; j < u.attr_count; j++) { + uint32_t key_size; + uint32_t attr_type; + uint32_t value_size; + char *key; + char *value; + + if (p + sizeof(key_size) > packet_end) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() malformed packet (3)\n"); + goto malformed; + } + + memcpy(&key_size, p, sizeof(key_size)); + p += sizeof(key_size); + + key_size = gg_fix32(key_size); + + if (key_size > 0xffff || p + key_size > packet_end) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() malformed packet (3)\n"); + goto malformed; + } + + key = malloc(key_size + 1); + + if (key == NULL) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() out of memory (%d)\n", key_size + 1); + goto fail; + } + + memcpy(key, p, key_size); + p += key_size; + + key[key_size] = 0; + + attrs[j].key = key; + + if (p + sizeof(attr_type) + sizeof(value_size) > packet_end) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() malformed packet (4)\n"); + goto malformed; + } + + memcpy(&attr_type, p, sizeof(attr_type)); + p += sizeof(attr_type); + memcpy(&value_size, p, sizeof(value_size)); + p += sizeof(value_size); + + attrs[j].type = gg_fix32(attr_type); + value_size = gg_fix32(value_size); + + if (value_size > 0xffff || p + value_size > packet_end) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() malformed packet (5)\n"); + goto malformed; + } + + value = malloc(value_size + 1); + + if (value == NULL) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_user_data() out of memory (%d)\n", value_size + 1); + goto fail; + } + + memcpy(value, p, value_size); + p += value_size; + + value[value_size] = 0; + + attrs[j].value = value; + + gg_debug_session(gs, GG_DEBUG_DUMP, " key=\"%s\", type=%d, value=\"%s\"\n", key, attr_type, value); + } + } + + return 0; + +fail: + res = -1; + +malformed: + ge->type = GG_EVENT_NONE; + + for (i = 0; i < ge->event.user_data.user_count; i++) { + for (j = 0; j < ge->event.user_data.users[i].attr_count; j++) { + free(ge->event.user_data.users[i].attrs[j].key); + free(ge->event.user_data.users[i].attrs[j].value); + } + + free(ge->event.user_data.users[i].attrs); + } + + free(ge->event.user_data.users); + + return res; +} + +/** + * \internal Obsługuje pakiet GG_TYPING_NOTIFICATION. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_typing_notification(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + struct gg_typing_notification *n = (void*) ptr; + uin_t uin; + + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received typing notification\n"); + + memcpy(&uin, &n->uin, sizeof(uin_t)); + + ge->type = GG_EVENT_TYPING_NOTIFICATION; + ge->event.typing_notification.uin = gg_fix32(uin); + ge->event.typing_notification.length = gg_fix16(n->length); + + return 0; +} + +/** + * \internal Obsługuje pakiet GG_MULTILOGON_INFO. + * + * Patrz gg_packet_handler_t + */ +static int gg_session_handle_multilogon_info(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + char *packet_end = (char*) ptr + len; + struct gg_multilogon_info *info = (struct gg_multilogon_info*) ptr; + char *p = (char*) ptr + sizeof(*info); + struct gg_multilogon_session *sessions = NULL; + size_t count; + size_t i; + int res = 0; + + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_watch_fd_connected() received multilogon info\n"); + + count = gg_fix32(info->count); + + if (count > 0xffff) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_handle_multilogon_info() malformed packet (1)\n"); + goto malformed; + } + + sessions = calloc(count, sizeof(struct gg_multilogon_session)); + + if (sessions == NULL) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_handle_multilogon_info() out of memory (%d*%d)\n", count, sizeof(struct gg_multilogon_session)); + return -1; + } + + ge->type = GG_EVENT_MULTILOGON_INFO; + ge->event.multilogon_info.count = count; + ge->event.multilogon_info.sessions = sessions; + + for (i = 0; i < count; i++) { + struct gg_multilogon_info_item item; + size_t name_size; + + if (p + sizeof(item) > packet_end) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_handle_multilogon_info() malformed packet (2)\n"); + goto malformed; + } + + memcpy(&item, p, sizeof(item)); + + sessions[i].id = item.conn_id; + sessions[i].remote_addr = item.addr; + sessions[i].status_flags = gg_fix32(item.flags); + sessions[i].protocol_features = gg_fix32(item.features); + sessions[i].logon_time = gg_fix32(item.logon_time); + + p += sizeof(item); + + name_size = gg_fix32(item.name_size); + + if (name_size > 0xffff || p + name_size > packet_end) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_handle_multilogon_info() malformed packet (3)\n"); + goto malformed; + } + + sessions[i].name = malloc(name_size + 1); + + if (sessions[i].name == NULL) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_handle_multilogon_info() out of memory (%d)\n", name_size); + goto fail; + } + + memcpy(sessions[i].name, p, name_size); + sessions[i].name[name_size] = 0; + + p += name_size; + } + + return 0; + +fail: + res = -1; + +malformed: + ge->type = GG_EVENT_NONE; + + for (i = 0; i < ge->event.multilogon_info.count; i++) + free(ge->event.multilogon_info.sessions[i].name); + + free(ge->event.multilogon_info.sessions); + + return res; +} + +/** + * \internal Tablica obsługiwanych pakietów + */ +static const gg_packet_handler_t handlers[] = +{ + { GG_WELCOME, GG_STATE_READING_KEY, 0, gg_session_handle_welcome }, + { GG_LOGIN_OK, GG_STATE_READING_REPLY, 0, gg_session_handle_login_ok }, + { GG_LOGIN80_OK, GG_STATE_READING_REPLY, 0, gg_session_handle_login_ok }, + { GG_NEED_EMAIL, GG_STATE_READING_REPLY, 0, gg_session_handle_login_ok }, + { GG_LOGIN_FAILED, GG_STATE_READING_REPLY, 0, gg_session_handle_login_failed }, + { GG_LOGIN80_FAILED, GG_STATE_READING_REPLY, 0, gg_session_handle_login_failed }, + { GG_SEND_MSG_ACK, GG_STATE_CONNECTED, sizeof(struct gg_send_msg_ack), gg_session_handle_send_msg_ack }, + { GG_PONG, GG_STATE_CONNECTED, 0, gg_session_handle_pong }, + { GG_DISCONNECTING, GG_STATE_CONNECTED, 0, gg_session_handle_disconnecting }, + { GG_DISCONNECT_ACK, GG_STATE_DISCONNECTING, 0, gg_session_handle_disconnect_ack }, + { GG_XML_EVENT, GG_STATE_CONNECTED, 0, gg_session_handle_xml_event }, + { GG_PUBDIR50_REPLY, GG_STATE_CONNECTED, 0, gg_session_handle_pubdir50_reply }, + { GG_USERLIST_REPLY, GG_STATE_CONNECTED, 0, gg_session_handle_userlist_reply }, + { GG_DCC7_ID_REPLY, GG_STATE_CONNECTED, sizeof(struct gg_dcc7_id_reply), gg_session_handle_dcc7_id_reply }, + { GG_DCC7_ACCEPT, GG_STATE_CONNECTED, sizeof(struct gg_dcc7_accept), gg_session_handle_dcc7_accept }, + { GG_DCC7_NEW, GG_STATE_CONNECTED, sizeof(struct gg_dcc7_new), gg_session_handle_dcc7_new }, + { GG_DCC7_REJECT, GG_STATE_CONNECTED, sizeof(struct gg_dcc7_reject), gg_session_handle_dcc7_reject }, + { GG_DCC7_INFO, GG_STATE_CONNECTED, sizeof(struct gg_dcc7_info), gg_session_handle_dcc7_info }, + { GG_RECV_MSG, GG_STATE_CONNECTED, sizeof(struct gg_recv_msg), gg_session_handle_recv_msg }, + { GG_RECV_MSG80, GG_STATE_CONNECTED, sizeof(struct gg_recv_msg80), gg_session_handle_recv_msg_80 }, + { GG_STATUS, GG_STATE_CONNECTED, sizeof(struct gg_status), gg_session_handle_status }, + { GG_STATUS60, GG_STATE_CONNECTED, sizeof(struct gg_status60), gg_session_handle_status_60_77_80beta }, + { GG_STATUS77, GG_STATE_CONNECTED, sizeof(struct gg_status77), gg_session_handle_status_60_77_80beta }, + { GG_STATUS80BETA, GG_STATE_CONNECTED, sizeof(struct gg_status77), gg_session_handle_status_60_77_80beta }, + { GG_STATUS80, GG_STATE_CONNECTED, sizeof(struct gg_notify_reply80), gg_session_handle_status_80 }, + { GG_NOTIFY_REPLY, GG_STATE_CONNECTED, sizeof(struct gg_notify_reply), gg_session_handle_notify_reply }, + { GG_NOTIFY_REPLY60, GG_STATE_CONNECTED, sizeof(struct gg_notify_reply60), gg_session_handle_notify_reply_60 }, + { GG_NOTIFY_REPLY77, GG_STATE_CONNECTED, sizeof(struct gg_notify_reply77), gg_session_handle_notify_reply_77_80beta }, + { GG_NOTIFY_REPLY80BETA, GG_STATE_CONNECTED, sizeof(struct gg_notify_reply77), gg_session_handle_notify_reply_77_80beta }, + { GG_NOTIFY_REPLY80, GG_STATE_CONNECTED, sizeof(struct gg_notify_reply80), gg_session_handle_notify_reply_80 }, + { GG_USER_DATA, GG_STATE_CONNECTED, sizeof(struct gg_user_data), gg_session_handle_user_data }, + { GG_TYPING_NOTIFICATION, GG_STATE_CONNECTED, sizeof(struct gg_typing_notification), gg_session_handle_typing_notification }, + { GG_MULTILOGON_INFO, GG_STATE_CONNECTED, sizeof(struct gg_multilogon_info), gg_session_handle_multilogon_info }, + { GG_XML_ACTION, GG_STATE_CONNECTED, 0, gg_session_handle_xml_event }, + { GG_RECV_OWN_MSG, GG_STATE_CONNECTED, sizeof(struct gg_recv_msg80), gg_session_handle_recv_msg_80 }, +}; + +/** + * \internal Analizuje przychodzący pakiet danych. + * + * \param gs Struktura sesji + * \param type Typ pakietu + * \param ptr Wskaźnik do bufora pakietu + * \param len Długość bufora pakietu + * \param[out] ge Struktura zdarzenia + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +int gg_session_handle_packet(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge) +{ + int i; + + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_packet(%d, %p, %d)\n", type, ptr, len); + + gs->last_event = time(NULL); + +#if 0 + if ((gs->flags & (1 << GG_SESSION_FLAG_RAW_PACKET)) != 0) { + char *tmp; + + tmp = malloc(len); + + if (tmp == NULL) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_packet() out of memory (%d bytes)\n", len); + return -1; + } + + memcpy(tmp, ptr, len); + + ge->type = GG_EVENT_RAW_PACKET; + ge->event.raw_packet.type = type; + ge->event.raw_packet.length = len; + ge->event.raw_packet.data = tmp; + + return 0; + } +#endif + + for (i = 0; i < sizeof(handlers) / sizeof(handlers[0]); i++) { + if (handlers[i].type != 0 && handlers[i].type != type) + continue; + + if (handlers[i].state != 0 && handlers[i].state != gs->state) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_packet() packet 0x%02x unexpected in state %d\n", type, gs->state); + continue; + } + + if (len < handlers[i].min_length) { + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_packet() packet 0x%02x too short (%d bytes)\n", type, len); + continue; + } + + return (*handlers[i].handler)(gs, type, ptr, len, ge); + } + + gg_debug_session(gs, GG_DEBUG_MISC, "// gg_session_handle_packet() unhandled packet 0x%02x, len %d, state %d\n", type, len, gs->state); + + return 0; +} diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/lib/http.c --- a/libpurple/protocols/gg/lib/http.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/gg/lib/http.c Fri Apr 01 13:47:51 2011 +0900 @@ -1,4 +1,4 @@ -/* $Id: http.c 833 2009-10-01 20:48:01Z wojtekka $ */ +/* $Id: http.c 1036 2010-12-15 00:02:28Z wojtekka $ */ /* * (C) Copyright 2001-2002 Wojtek Kaniewski @@ -34,7 +34,6 @@ # include #endif -#include "compat.h" #include "resolver.h" #include @@ -136,21 +135,26 @@ h->check = GG_CHECK_READ; h->timeout = GG_DEFAULT_TIMEOUT; } else { - struct in_addr addr; + struct in_addr *addr_list = NULL; + int addr_count; - if (gg_gethostbyname_real(hostname, &addr, 0) == -1) { + if (gg_gethostbyname_real(hostname, &addr_list, &addr_count, 0) == -1 || addr_count == 0) { gg_debug(GG_DEBUG_MISC, "// gg_http_connect() host not found\n"); gg_http_free(h); + free(addr_list); errno = ENOENT; return NULL; } - if ((h->fd = gg_connect(&addr, port, 0)) == -1) { + if ((h->fd = gg_connect(&addr_list[0], port, 0)) == -1) { gg_debug(GG_DEBUG_MISC, "// gg_http_connect() connection failed (errno=%d, %s)\n", errno, strerror(errno)); gg_http_free(h); + free(addr_list); return NULL; } + free(addr_list); + h->state = GG_STATE_CONNECTING; while (h->state != GG_STATE_ERROR && h->state != GG_STATE_PARSING) { @@ -257,7 +261,7 @@ } if (h->state == GG_STATE_SENDING_QUERY) { - ssize_t res; + size_t res; if ((res = write(h->fd, h->query, strlen(h->query))) < 1) { gg_debug(GG_DEBUG_MISC, "=> http, write() failed (len=%d, res=%d, errno=%d)\n", strlen(h->query), res, errno); diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/lib/libgadu-debug.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/gg/lib/libgadu-debug.h Fri Apr 01 13:47:51 2011 +0900 @@ -0,0 +1,27 @@ +/* + * (C) Copyright 2009 Wojtek Kaniewski + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef LIBGADU_DEBUG_H +#define LIBGADU_DEBUG_H + +#include "libgadu.h" + +const char *gg_debug_state(enum gg_state_t state); +void gg_debug_dump(struct gg_session *sess, int level, const char *buf, size_t len); + +#endif /* LIBGADU_DEBUG_H */ diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/lib/libgadu-internal.h --- a/libpurple/protocols/gg/lib/libgadu-internal.h Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/gg/lib/libgadu-internal.h Fri Apr 01 13:47:51 2011 +0900 @@ -23,9 +23,22 @@ #include "libgadu.h" -char *gg_cp_to_utf8(const char *b); -char *gg_utf8_to_cp(const char *b); +struct gg_dcc7_relay { + uint32_t addr; + uint16_t port; + uint8_t family; +}; + +typedef struct gg_dcc7_relay gg_dcc7_relay_t; + int gg_pubdir50_handle_reply_sess(struct gg_session *sess, struct gg_event *e, const char *packet, int length); -void gg_debug_common(struct gg_session *sess, int level, const char *format, va_list ap); + +int gg_resolve(int *fd, int *pid, const char *hostname); +int gg_resolve_pthread(int *fd, void **resolver, const char *hostname); +void gg_resolve_pthread_cleanup(void *resolver, int kill); + +#ifdef HAVE_UINT64_T +uint64_t gg_fix64(uint64_t x); +#endif #endif /* LIBGADU_INTERNAL_H */ diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/lib/libgadu.c --- a/libpurple/protocols/gg/lib/libgadu.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/gg/lib/libgadu.c Fri Apr 01 13:47:51 2011 +0900 @@ -1,7 +1,7 @@ -/* $Id: libgadu.c 878 2009-11-16 23:48:19Z wojtekka $ */ +/* $Id: libgadu.c 1062 2011-03-13 18:10:24Z wojtekka $ */ /* - * (C) Copyright 2001-2009 Wojtek Kaniewski + * (C) Copyright 2001-2010 Wojtek Kaniewski * Robert J. Woźny * Arkadiusz Miśkiewicz * Tomasz Chiliński @@ -31,6 +31,7 @@ #include "libgadu.h" #include "libgadu-config.h" #include "libgadu-internal.h" +#include "libgadu-debug.h" #include @@ -51,6 +52,8 @@ #include "compat.h" #include "protocol.h" #include "resolver.h" +#include "encoding.h" +#include "session.h" #ifndef _WIN32 # include /* on Win32 this is included above */ @@ -64,51 +67,15 @@ #include #include #include +#ifdef GG_CONFIG_HAVE_GNUTLS +# include +#endif #ifdef GG_CONFIG_HAVE_OPENSSL # include # include #endif -#define GG_LIBGADU_VERSION "1.9.0" - -/** - * Poziom rejestracji informacji odpluskwiających. Zmienna jest maską bitową - * składającą się ze stałych \c GG_DEBUG_... - * - * \ingroup debug - */ -int gg_debug_level = 0; - -/** - * Funkcja, do której są przekazywane informacje odpluskwiające. Jeśli zarówno - * ten \c gg_debug_handler, jak i \c gg_debug_handler_session, są równe - * \c NULL, informacje są wysyłane do standardowego wyjścia błędu (\c stderr). - * - * \param level Poziom rejestracji - * \param format Format wiadomości (zgodny z \c printf) - * \param ap Lista argumentów (zgodna z \c printf) - * - * \note Funkcja jest przesłaniana przez \c gg_debug_handler_session. - * - * \ingroup debug - */ -void (*gg_debug_handler)(int level, const char *format, va_list ap) = NULL; - -/** - * Funkcja, do której są przekazywane informacje odpluskwiające. Jeśli zarówno - * ten \c gg_debug_handler, jak i \c gg_debug_handler_session, są równe - * \c NULL, informacje są wysyłane do standardowego wyjścia błędu. - * - * \param sess Sesja której dotyczy informacja lub \c NULL - * \param level Poziom rejestracji - * \param format Format wiadomości (zgodny z \c printf) - * \param ap Lista argumentów (zgodna z \c printf) - * - * \note Funkcja przesłania przez \c gg_debug_handler_session. - * - * \ingroup debug - */ -void (*gg_debug_handler_session)(struct gg_session *sess, int level, const char *format, va_list ap) = NULL; +#define GG_LIBGADU_VERSION "1.10.1" /** * Port gniazda nasłuchującego dla połączeń bezpośrednich. @@ -180,7 +147,7 @@ #ifdef __GNUC__ __attribute__ ((unused)) #endif -= "$Id: libgadu.c 923 2010-03-09 20:03:29Z wojtekka $"; += "$Id: libgadu.c 1062 2011-03-13 18:10:24Z wojtekka $"; #endif #endif /* DOXYGEN */ @@ -197,6 +164,37 @@ return GG_LIBGADU_VERSION; } +#ifdef GG_CONFIG_HAVE_UINT64_T +/** + * \internal Zamienia kolejność bajtów w 64-bitowym słowie. + * + * Ze względu na little-endianowość protokołu Gadu-Gadu, na maszynach + * big-endianowych odwraca kolejność bajtów w słowie. + * + * \param x Liczba do zamiany + * + * \return Liczba z odpowiednią kolejnością bajtów + * + * \ingroup helper + */ +uint64_t gg_fix64(uint64_t x) +{ +#ifndef GG_CONFIG_BIGENDIAN + return x; +#else + return (uint64_t) + (((x & (uint64_t) 0x00000000000000ffULL) << 56) | + ((x & (uint64_t) 0x000000000000ff00ULL) << 40) | + ((x & (uint64_t) 0x0000000000ff0000ULL) << 24) | + ((x & (uint64_t) 0x00000000ff000000ULL) << 8) | + ((x & (uint64_t) 0x000000ff00000000ULL) >> 8) | + ((x & (uint64_t) 0x0000ff0000000000ULL) >> 24) | + ((x & (uint64_t) 0x00ff000000000000ULL) >> 40) | + ((x & (uint64_t) 0xff00000000000000ULL) >> 56)); +#endif +} +#endif /* GG_CONFIG_HAVE_UINT64_T */ + /** * \internal Zamienia kolejność bajtów w 32-bitowym słowie. * @@ -280,7 +278,9 @@ /** * \internal Odbiera od serwera dane binarne. * - * Funkcja odbiera dane od serwera zajmując się TLS w razie konieczności. + * Funkcja odbiera dane od serwera zajmując się SSL/TLS w razie konieczności. + * Obsługuje EINTR, więc użytkownik nie musi się przejmować przerwanymi + * wywołaniami systemowymi. * * \param sess Struktura sesji * \param buf Bufor na danymi @@ -290,32 +290,135 @@ */ int gg_read(struct gg_session *sess, char *buf, int length) { - int res; +#ifdef GG_CONFIG_HAVE_GNUTLS + if (sess->ssl != NULL) { + for (;;) { + int res; + + res = gnutls_record_recv(GG_SESSION_GNUTLS(sess), buf, length); + + if (res < 0) { + if (!gnutls_error_is_fatal(res) || res == GNUTLS_E_INTERRUPTED) + continue; + + if (res == GNUTLS_E_AGAIN) + errno = EAGAIN; + else + errno = EINVAL; + + return -1; + } + + return res; + } + } +#endif #ifdef GG_CONFIG_HAVE_OPENSSL - if (sess->ssl) { - int err; - - res = SSL_read(sess->ssl, buf, length); - - if (res < 0) { - err = SSL_get_error(sess->ssl, res); - - if (err == SSL_ERROR_WANT_READ) - errno = EAGAIN; - - return -1; + if (sess->ssl != NULL) { + for (;;) { + int res, err; + + res = SSL_read(sess->ssl, buf, length); + + if (res < 0) { + err = SSL_get_error(sess->ssl, res); + + if (err == SSL_ERROR_SYSCALL && errno == EINTR) + continue; + + if (err == SSL_ERROR_WANT_READ) + errno = EAGAIN; + else if (err != SSL_ERROR_SYSCALL) + errno = EINVAL; + + return -1; + } + + return res; } - } else + } #endif - res = read(sess->fd, buf, length); - - return res; + + return read(sess->fd, buf, length); } /** * \internal Wysyła do serwera dane binarne. * + * Funkcja wysyła dane do serwera zajmując się SSL/TLS w razie konieczności. + * Obsługuje EINTR, więc użytkownik nie musi się przejmować przerwanymi + * wywołaniami systemowymi. + * + * \note Funkcja nie zajmuje się buforowaniem wysyłanych danych (patrz + * gg_write()). + * + * \param sess Struktura sesji + * \param buf Bufor z danymi + * \param length Długość bufora + * + * \return To samo co funkcja systemowa \c write + */ +static int gg_write_common(struct gg_session *sess, const char *buf, int length) +{ +#ifdef GG_CONFIG_HAVE_GNUTLS + if (sess->ssl != NULL) { + for (;;) { + int res; + + res = gnutls_record_send(GG_SESSION_GNUTLS(sess), buf, length); + + if (res < 0) { + if (!gnutls_error_is_fatal(res) || res == GNUTLS_E_INTERRUPTED) + continue; + + if (res == GNUTLS_E_AGAIN) + errno = EAGAIN; + else + errno = EINVAL; + + return -1; + } + + return res; + } + } +#endif + +#ifdef GG_CONFIG_HAVE_OPENSSL + if (sess->ssl != NULL) { + for (;;) { + int res, err; + + res = SSL_write(sess->ssl, buf, length); + + if (res < 0) { + err = SSL_get_error(sess->ssl, res); + + if (err == SSL_ERROR_SYSCALL && errno == EINTR) + continue; + + if (err == SSL_ERROR_WANT_WRITE) + errno = EAGAIN; + else if (err != SSL_ERROR_SYSCALL) + errno = EINVAL; + + return -1; + } + + return res; + } + } +#endif + + return write(sess->fd, buf, length); +} + + + +/** + * \internal Wysyła do serwera dane binarne. + * * Funkcja wysyła dane do serwera zajmując się TLS w razie konieczności. * * \param sess Struktura sesji @@ -328,66 +431,41 @@ { int res = 0; -#ifdef GG_CONFIG_HAVE_OPENSSL - if (sess->ssl) { - int err; - - res = SSL_write(sess->ssl, buf, length); - - if (res < 0) { - err = SSL_get_error(sess->ssl, res); - - if (err == SSL_ERROR_WANT_WRITE) - errno = EAGAIN; - - return -1; + if (!sess->async) { + int written = 0; + + while (written < length) { + res = gg_write_common(sess, buf + written, length - written); + + if (res == -1) + return -1; + + written += res; + res = written; } - } else -#endif - { - if (!sess->async) { - int written = 0; - - while (written < length) { - res = write(sess->fd, buf + written, length - written); - - if (res == -1) { - if (errno != EINTR) - break; - - continue; - } - - written += res; - res = written; + } else { + res = 0; + + if (sess->send_buf == NULL) { + res = gg_write_common(sess, buf, length); + + if (res == -1) + return -1; + } + + if (res < length) { + char *tmp; + + if (!(tmp = realloc(sess->send_buf, sess->send_left + length - res))) { + errno = ENOMEM; + return -1; } - } else { - if (!sess->send_buf) - res = write(sess->fd, buf, length); - else - res = 0; - - if (res == -1) { - if (errno != EAGAIN) - return res; - - res = 0; - } - - if (res < length) { - char *tmp; - - if (!(tmp = realloc(sess->send_buf, sess->send_left + length - res))) { - errno = ENOMEM; - return -1; - } - - sess->send_buf = tmp; - - memcpy(sess->send_buf + sess->send_left, buf + res, length - res); - - sess->send_left += length - res; - } + + sess->send_buf = tmp; + + memcpy(sess->send_buf + sess->send_left, buf + res, length - res); + + sess->send_left += length - res; } } @@ -443,11 +521,6 @@ } if (ret == -1) { - if (errno == EINTR) { - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() interrupted system call, resuming\n"); - continue; - } - if (errno == EAGAIN) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() incomplete header received\n"); @@ -513,10 +586,7 @@ offset += ret; size -= ret; } else if (ret == -1) { - int errno2 = errno; - gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed (errno=%d, %s)\n", errno, strerror(errno)); - errno = errno2; if (errno == EAGAIN) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() %d bytes received, %d left\n", offset, size); @@ -525,23 +595,16 @@ sess->recv_done = offset; return NULL; } - if (errno != EINTR) { - free(buf); - return NULL; - } + + free(buf); + return NULL; } } sess->recv_left = 0; - if ((gg_debug_level & GG_DEBUG_DUMP)) { - unsigned int i; - - gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_recv_packet(%.2x)", h.type); - for (i = 0; i < sizeof(h) + h.length; i++) - gg_debug_session(sess, GG_DEBUG_DUMP, " %.2x", (unsigned char) buf[i]); - gg_debug_session(sess, GG_DEBUG_DUMP, "\n"); - } + gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_recv_packet(type=0x%.2x, length=%d)\n", h.type, h.length); + gg_debug_dump(sess, GG_DEBUG_DUMP, buf, sizeof(h) + h.length); return buf; } @@ -609,14 +672,8 @@ h->type = gg_fix32(type); h->length = gg_fix32(tmp_length - sizeof(struct gg_header)); - if ((gg_debug_level & GG_DEBUG_DUMP)) { - unsigned int i; - - gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_send_packet(0x%.2x)", gg_fix32(h->type)); - for (i = 0; i < tmp_length; ++i) - gg_debug_session(sess, GG_DEBUG_DUMP, " %.2x", (unsigned char) tmp[i]); - gg_debug_session(sess, GG_DEBUG_DUMP, "\n"); - } + gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_send_packet(type=0x%.2x, length=%d)\n", gg_fix32(h->type), gg_fix32(h->length)); + gg_debug_dump(sess, GG_DEBUG_DUMP, tmp, tmp_length); res = gg_write(sess, tmp, tmp_length); @@ -739,14 +796,22 @@ sess->server_addr = p->server_addr; sess->external_port = p->external_port; sess->external_addr = p->external_addr; - - sess->protocol_features = (p->protocol_features & ~(GG_FEATURE_STATUS77 | GG_FEATURE_MSG77)); - - if (!(p->protocol_features & GG_FEATURE_STATUS77)) - sess->protocol_features |= GG_FEATURE_STATUS80; - - if (!(p->protocol_features & GG_FEATURE_MSG77)) - sess->protocol_features |= GG_FEATURE_MSG80; + sess->client_port = p->client_port; + + if (p->protocol_features == 0) { + sess->protocol_features = GG_FEATURE_MSG80 | GG_FEATURE_STATUS80 | GG_FEATURE_DND_FFC | GG_FEATURE_IMAGE_DESCR | GG_FEATURE_UNKNOWN_100 | GG_FEATURE_USER_DATA | GG_FEATURE_MSG_ACK | GG_FEATURE_TYPING_NOTIFICATION; + } else { + sess->protocol_features = (p->protocol_features & ~(GG_FEATURE_STATUS77 | GG_FEATURE_MSG77)); + + if (!(p->protocol_features & GG_FEATURE_STATUS77)) + sess->protocol_features |= GG_FEATURE_STATUS80; + + if (!(p->protocol_features & GG_FEATURE_MSG77)) + sess->protocol_features |= GG_FEATURE_MSG80; + } + + if (!(sess->status_flags = p->status_flags)) + sess->status_flags = GG_STATUS_FLAG_UNKNOWN | GG_STATUS_FLAG_SPAM; sess->protocol_version = (p->protocol_version) ? p->protocol_version : GG_DEFAULT_PROTOCOL_VERSION; @@ -774,8 +839,8 @@ else max_length = GG_STATUS_DESCR_MAXSIZE_PRE_8_0; - if (sess->protocol_version >= 0x2d && p->encoding != GG_ENCODING_UTF8) - sess->initial_descr = gg_cp_to_utf8(p->status_descr); + if (sess->protocol_version >= 0x2d) + sess->initial_descr = gg_encoding_convert(p->status_descr, p->encoding, GG_ENCODING_UTF8, -1, -1); else sess->initial_descr = strdup(p->status_descr); @@ -791,7 +856,25 @@ } if (p->tls == 1) { -#ifdef GG_CONFIG_HAVE_OPENSSL +#ifdef GG_CONFIG_HAVE_GNUTLS + gg_session_gnutls_t *tmp; + + tmp = malloc(sizeof(gg_session_gnutls_t)); + + if (tmp == NULL) { + gg_debug(GG_DEBUG_MISC, "// gg_login() out of memory for GnuTLS session\n"); + goto fail; + } + + sess->ssl = tmp; + + gnutls_global_init(); + gnutls_certificate_allocate_credentials(&tmp->xcred); + gnutls_init(&tmp->session, GNUTLS_CLIENT); + gnutls_priority_set_direct(tmp->session, "NORMAL:-VERS-TLS", NULL); +// gnutls_priority_set_direct(tmp->session, "NONE:+VERS-SSL3.0:+AES-128-CBC:+RSA:+SHA1:+COMP-NULL", NULL); + gnutls_credentials_set(tmp->session, GNUTLS_CRD_CERTIFICATE, tmp->xcred); +#elif defined(GG_CONFIG_HAVE_OPENSSL) char buf[1024]; OpenSSL_add_ssl_algorithms(); @@ -810,7 +893,7 @@ RAND_seed((void *) &rstruct, sizeof(rstruct)); } - sess->ssl_ctx = SSL_CTX_new(TLSv1_client_method()); + sess->ssl_ctx = SSL_CTX_new(SSLv3_client_method()); if (!sess->ssl_ctx) { ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); @@ -850,10 +933,18 @@ if (!sess->server_addr) { if ((addr.s_addr = inet_addr(hostname)) == INADDR_NONE) { - if (gg_gethostbyname_real(hostname, &addr, 0) == -1) { + struct in_addr *addr_list = NULL; + int addr_count; + + if (gg_gethostbyname_real(hostname, &addr_list, &addr_count, 0) == -1 || addr_count == 0) { gg_debug(GG_DEBUG_MISC, "// gg_login() host \"%s\" not found\n", hostname); + free(addr_list); goto fail; } + + addr = addr_list[0]; + + free(addr_list); } } else { addr.s_addr = sess->server_addr; @@ -976,7 +1067,10 @@ * * \note Jeśli w buforze nadawczym połączenia z serwerem znajdują się jeszcze * dane (np. z powodu strat pakietów na łączu), prawdopodobnie zostaną one - * utracone przy zrywaniu połączenia. + * utracone przy zrywaniu połączenia. Aby mieć pewność, że opis statusu + * zostanie zachowany, należy ustawić stan \c GG_STATUS_NOT_AVAIL_DESCR + * za pomocą funkcji \c gg_change_status_descr() i poczekać na zdarzenie + * \c GG_EVENT_DISCONNECT_ACK. * * \param sess Struktura sesji * @@ -989,11 +1083,13 @@ gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_logoff(%p);\n", sess); - if (GG_S_NA(sess->status)) - gg_change_status(sess, GG_STATUS_NOT_AVAIL); +#ifdef GG_CONFIG_HAVE_GNUTLS + if (sess->ssl != NULL) + gnutls_bye(GG_SESSION_GNUTLS(sess), GNUTLS_SHUT_RDWR); +#endif #ifdef GG_CONFIG_HAVE_OPENSSL - if (sess->ssl) + if (sess->ssl != NULL) SSL_shutdown(sess->ssl); #endif @@ -1005,6 +1101,18 @@ sess->fd = -1; } +#ifdef GG_CONFIG_HAVE_GNUTLS + if (sess->ssl != NULL) { + gg_session_gnutls_t *tmp; + + tmp = (gg_session_gnutls_t*) sess->ssl; + gnutls_deinit(tmp->session); + gnutls_certificate_free_credentials(tmp->xcred); + gnutls_global_deinit(); + free(sess->ssl); + } +#endif + if (sess->send_buf) { free(sess->send_buf); sess->send_buf = NULL; @@ -1103,7 +1211,7 @@ if (sess->protocol_version >= 0x2d) { if (descr != NULL && sess->encoding != GG_ENCODING_UTF8) { - new_descr = gg_cp_to_utf8(descr); + new_descr = gg_encoding_convert(descr, GG_ENCODING_CP1250, GG_ENCODING_UTF8, -1, -1); if (!new_descr) return -1; @@ -1140,7 +1248,7 @@ struct gg_new_status80 p; p.status = gg_fix32(status); - p.flags = gg_fix32(0x00800001); + p.flags = gg_fix32(sess->status_flags); p.description_size = gg_fix32(descr_len); res = gg_send_packet(sess, packet_type, @@ -1168,6 +1276,10 @@ } free(new_descr); + + if (GG_S_NA(status)) + sess->state = GG_STATE_DISCONNECTING; + return res; } @@ -1228,6 +1340,33 @@ } /** + * Funkcja zmieniająca flagi statusu. + * + * \param sess Struktura sesji + * \param flags Nowe flagi statusu + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \note Aby zmiany weszły w życie, należy ponownie ustawić status za pomocą + * funkcji z rodziny \c gg_change_status(). + * + * \ingroup status + */ +int gg_change_status_flags(struct gg_session *sess, int flags) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status_flags(%p, 0x%08x);\n", sess, flags); + + if (sess == NULL) { + errno = EFAULT; + return -1; + } + + sess->status_flags = flags; + + return 0; +} + +/** * Wysyła wiadomość do użytkownika. * * Zwraca losowy numer sekwencyjny, który można zignorować albo wykorzystać @@ -1377,7 +1516,7 @@ format_idx += 3; - if ((attr & (GG_FONT_BOLD | GG_FONT_ITALIC | GG_FONT_UNDERLINE | GG_FONT_COLOR)) != 0) { + if ((attr & (GG_FONT_BOLD | GG_FONT_ITALIC | GG_FONT_UNDERLINE | GG_FONT_COLOR)) != 0 || (attr == 0 && old_attr != 0)) { if (char_pos != 0) { if ((old_attr & GG_FONT_UNDERLINE) != 0) gg_append(dst, &len, "", 4); @@ -1388,7 +1527,8 @@ if ((old_attr & GG_FONT_BOLD) != 0) gg_append(dst, &len, "", 4); - gg_append(dst, &len, "", 7); + if (src[i] != 0) + gg_append(dst, &len, "", 7); } if (((attr & GG_FONT_COLOR) != 0) && (format_idx + 3 <= format_len)) { @@ -1398,9 +1538,11 @@ color = (const unsigned char*) "\x00\x00\x00"; } - if (dst != NULL) - sprintf(&dst[len], span_fmt, color[0], color[1], color[2]); - len += span_len; + if (src[i] != 0) { + if (dst != NULL) + sprintf(&dst[len], span_fmt, color[0], color[1], color[2]); + len += span_len; + } } else if (char_pos == 0 && src[0] != 0) { if (dst != NULL) sprintf(&dst[len], span_fmt, 0, 0, 0); @@ -1543,13 +1685,13 @@ } if (sess->encoding == GG_ENCODING_UTF8) { - if (!(cp_msg = gg_utf8_to_cp((const char *) message))) + if (!(cp_msg = gg_encoding_convert((const char *) message, GG_ENCODING_UTF8, GG_ENCODING_CP1250, -1, -1))) return -1; utf_msg = (char*) message; } else { if (sess->protocol_version >= 0x2d) { - if (!(utf_msg = gg_cp_to_utf8((const char *) message))) + if (!(utf_msg = gg_encoding_convert((const char *) message, GG_ENCODING_CP1250, GG_ENCODING_UTF8, -1, -1))) return -1; } @@ -2192,6 +2334,47 @@ return gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), request, len, NULL); } +/** + * Informuje rozmówcę o pisaniu wiadomości. + * + * \param sess Struktura sesji + * \param recipient Numer adresata + * \param length Długość wiadomości lub 0 jeśli jest pusta + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup messages + */ +int gg_typing_notification(struct gg_session *sess, uin_t recipient, int length){ + struct gg_typing_notification pkt; + uin_t uin; + + pkt.length = gg_fix16(length); + uin = gg_fix32(recipient); + memcpy(&pkt.uin, &uin, sizeof(uin_t)); + + return gg_send_packet(sess, GG_TYPING_NOTIFICATION, &pkt, sizeof(pkt), NULL); +} + +/** + * Rozłącza inną sesję multilogowania. + * + * \param gs Struktura sesji + * \param conn_id Sesja do rozłączenia + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + * + * \ingroup login + */ +int gg_multilogon_disconnect(struct gg_session *gs, gg_multilogon_id_t conn_id) +{ + struct gg_multilogon_disconnect pkt; + + pkt.conn_id = conn_id; + + return gg_send_packet(gs, GG_MULTILOGON_DISCONNECT, &pkt, sizeof(pkt), NULL); +} + /* @} */ /* diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/lib/libgadu.h --- a/libpurple/protocols/gg/lib/libgadu.h Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/gg/lib/libgadu.h Fri Apr 01 13:47:51 2011 +0900 @@ -1,4 +1,4 @@ -/* $Id: libgadu.h.in 878 2009-11-16 23:48:19Z wojtekka $ */ +/* $Id: libgadu.h.in 1037 2010-12-17 22:18:08Z wojtekka $ */ /* * (C) Copyright 2001-2009 Wojtek Kaniewski @@ -71,7 +71,10 @@ /* Defined if this machine supports long long. */ #undef GG_CONFIG_HAVE_LONG_LONG -/* Defined if libgadu was compiled and linked with TLS support. */ +/* Defined if libgadu was compiled and linked with GnuTLS support. */ +#undef GG_CONFIG_HAVE_GNUTLS + +/* Defined if libgadu was compiled and linked with OpenSSL support. */ #undef GG_CONFIG_HAVE_OPENSSL /* Defined if uintX_t types are defined in . */ @@ -148,6 +151,13 @@ } gg_dcc7_id_t; /** + * Identyfikator sesji multilogowania. + */ +typedef struct { + uint8_t id[8]; +} gg_multilogon_id_t; + +/** * Makro deklarujące pola wspólne dla struktur sesji. */ #define gg_common_head(x) \ @@ -173,6 +183,8 @@ struct gg_dcc7; +struct gg_dcc7_relay; + /** * Sposób rozwiązywania nazw serwerów. */ @@ -280,6 +292,8 @@ void (*resolver_cleanup)(void **private_data, int force); /**< Funkcja zwalniająca zasoby po rozwiązaniu nazwy */ int protocol_features; /**< Opcje protokołu */ + int status_flags; /**< Flagi statusu */ + int recv_msg_count; /**< Liczba odebranych wiadomości */ }; /** @@ -387,7 +401,8 @@ #define GG_DCC7_HASH_LEN 20 /**< Maksymalny rozmiar skrótu pliku w połączeniach bezpośrenich */ #define GG_DCC7_FILENAME_LEN 255 /**< Maksymalny rozmiar nazwy pliku w połączeniach bezpośrednich */ -#define GG_DCC7_INFO_LEN 64 /**< Maksymalny rozmiar informacji o połączeniach bezpośrednich */ +#define GG_DCC7_INFO_LEN 32 /**< Maksymalny rozmiar informacji o połączeniach bezpośrednich */ +#define GG_DCC7_INFO_HASH_LEN 32 /**< Maksymalny rozmiar skrótu ip informacji o połączeniach bezpośrednich */ /** * Połączenie bezpośrednie od wersji Gadu-Gadu 7.x. @@ -429,6 +444,13 @@ int soft_timeout; /**< Flaga mówiąca, że po przekroczeniu \c timeout należy wywołać \c gg_dcc7_watch_fd() */ int seek; /**< Flaga mówiąca, że można zmieniać położenie w wysyłanym pliku */ + + void *resolver; /**< Dane prywatne procesu lub wątku rozwiązującego nazwę serwera */ + + int relay; /**< Flaga mówiąca, że laczymy sie przez serwer */ + int relay_index; /**< Numer serwera pośredniczącego, do którego się łączymy */ + int relay_count; /**< Rozmiar listy serwerów pośredniczących */ + struct gg_dcc7_relay *relay_list; /**< Lista serwerów pośredniczących */ }; /** @@ -524,7 +546,14 @@ GG_STATE_WAITING_FOR_INFO, /**< Oczekiwanie na informacje o połączeniu bezpośrednim */ GG_STATE_READING_ID, /**< Odebranie identyfikatora połączenia bezpośredniego */ - GG_STATE_SENDING_ID /**< Wysłano identyfikatora połączenia bezpośredniego */ + GG_STATE_SENDING_ID, /**< Wysłano identyfikator połączenia bezpośredniego */ + GG_STATE_RESOLVING_GG, /**< Oczekiwanie na rozwiązanie nazwy serwera Gadu-Gadu */ + + GG_STATE_RESOLVING_RELAY, /**< Oczekiwanie na rozwiązanie nazwy serwera pośredniczącego */ + GG_STATE_CONNECTING_RELAY, /**< Oczekiwanie na połączenie z serwerem pośredniczącym */ + GG_STATE_READING_RELAY, /**< Odbieranie danych */ + + GG_STATE_DISCONNECTING, /**< Oczekiwanie na potwierdzenie rozłączenia */ }; /** @@ -576,9 +605,10 @@ gg_encoding_t encoding; /**< Rodzaj kodowania używanego w sesji (domyślnie CP1250) */ gg_resolver_t resolver; /**< Sposób rozwiązywania nazw (patrz \ref build-resolver) */ int protocol_features; /**< Opcje protokołu (flagi GG_FEATURE_*). */ + int status_flags; /**< Flagi statusu (flagi GG_STATUS_FLAG_*, patrz \ref status). */ #ifndef DOXYGEN - char dummy[2 * sizeof(int)]; /**< \internal Miejsce na kilka kolejnych + char dummy[1 * sizeof(int)]; /**< \internal Miejsce na kilka kolejnych parametrów, żeby wraz z dodawaniem kolejnych parametrów nie zmieniał się rozmiar struktury */ #endif @@ -591,6 +621,7 @@ int gg_change_status(struct gg_session *sess, int status); int gg_change_status_descr(struct gg_session *sess, int status, const char *descr); int gg_change_status_descr_time(struct gg_session *sess, int status, const char *descr, int time); +int gg_change_status_flags(struct gg_session *sess, int flags); int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message); int gg_send_message_richtext(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, const unsigned char *format, int formatlen); int gg_send_message_confer(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message); @@ -600,6 +631,7 @@ int gg_userlist_request(struct gg_session *sess, char type, const char *request); int gg_image_request(struct gg_session *sess, uin_t recipient, int size, uint32_t crc32); int gg_image_reply(struct gg_session *sess, uin_t recipient, const char *filename, const char *image, int size); +int gg_typing_notification(struct gg_session *sess, uin_t recipient, int length); uint32_t gg_crc32(uint32_t crc, const unsigned char *buf, int len); @@ -615,6 +647,8 @@ gg_resolver_t gg_global_get_resolver(void); int gg_global_set_custom_resolver(int (*resolver_start)(int*, void**, const char*), void (*resolver_cleanup)(void**, int)); +int gg_multilogon_disconnect(struct gg_session *gs, gg_multilogon_id_t conn_id); + /** * Rodzaj zdarzenia. * @@ -623,7 +657,7 @@ enum gg_event_t { GG_EVENT_NONE = 0, /**< Nie wydarzyło się nic wartego uwagi */ GG_EVENT_MSG, /**< \brief Otrzymano wiadomość. Przekazuje również wiadomości systemowe od numeru 0. */ - GG_EVENT_NOTIFY, /**< \brief Informacja o statusach osób z listy kontaktów (przed 6.0). Zdarzenie należy obsługiwać, jeśli planuje się używać protokołu w wersji starszej niż domyślna. */ + GG_EVENT_NOTIFY, /**< \brief Informacja o statusach osób z listy kontaktów (przed 6.0). Zdarzenie należy obsługiwać, jeśli planuje się używać protokołu w wersji starszej niż domyślna. Ostatni element tablicy zawiera uin równy 0, a pozostałe pola są niezainicjowane. */ GG_EVENT_NOTIFY_DESCR, /**< \brief Informacja o statusie opisowym osoby z listy kontaktów (przed 6.0). Zdarzenie należy obsługiwać, jeśli planuje się używać protokołu w wersji starszej niż domyślna. */ GG_EVENT_STATUS, /**< \brief Zmiana statusu osoby z listy kontaktów (przed 6.0). Zdarzenie należy obsługiwać, jeśli planuje się używać protokołu w wersji starszej niż domyślna. */ GG_EVENT_ACK, /**< Potwierdzenie doręczenia wiadomości */ @@ -647,9 +681,9 @@ GG_EVENT_PUBDIR50_WRITE, /**< Zmieniono własne dane w katalogu publicznym */ GG_EVENT_STATUS60, /**< Zmiana statusu osoby z listy kontaktów */ - GG_EVENT_NOTIFY60, /**< Informacja o statusach osób z listy kontaktów */ + GG_EVENT_NOTIFY60, /**< Informacja o statusach osób z listy kontaktów. Ostatni element tablicy zawiera uin równy 0, a pozostałe pola są niezainicjowane. */ GG_EVENT_USERLIST, /**< Wynik importu lub eksportu listy kontaktów */ - GG_EVENT_IMAGE_REQUEST, /**< Żądanie przesłania obrazka z wiadommości */ + GG_EVENT_IMAGE_REQUEST, /**< Żądanie przesłania obrazka z wiadomości */ GG_EVENT_IMAGE_REPLY, /**< Przysłano obrazek z wiadomości */ GG_EVENT_DCC_ACK, /**< Potwierdzenie transmisji w połączeniu bezpośrednim (6.x) */ @@ -663,6 +697,10 @@ GG_EVENT_XML_EVENT, /**< Otrzymano komunikat systemowy (7.7) */ GG_EVENT_DISCONNECT_ACK, /**< \brief Potwierdzenie zakończenia sesji. Informuje o tym, że zmiana stanu na niedostępny z opisem dotarła do serwera i można zakończyć połączenie TCP. */ + GG_EVENT_TYPING_NOTIFICATION, /**< Powiadomienie o pisaniu */ + GG_EVENT_USER_DATA, /**< Informacja o kontaktach */ + GG_EVENT_MULTILOGON_MSG, /**< Wiadomość wysłana z innej sesji multilogowania */ + GG_EVENT_MULTILOGON_INFO, /**< Informacja o innych sesjach multilogowania */ }; #define GG_EVENT_SEARCH50_REPLY GG_EVENT_PUBDIR50_SEARCH_REPLY @@ -707,7 +745,8 @@ GG_ERROR_DCC7_FILE, /**< Błąd odczytu/zapisu pliku */ GG_ERROR_DCC7_EOF, /**< Przedwczesny koniec pliku */ GG_ERROR_DCC7_NET, /**< Błąd wysyłania/odbierania */ - GG_ERROR_DCC7_REFUSED /**< Połączenie odrzucone */ + GG_ERROR_DCC7_REFUSED, /**< Połączenie odrzucone */ + GG_ERROR_DCC7_RELAY, /**< Problem z serwerem pośredniczącym */ }; /** @@ -742,10 +781,10 @@ typedef struct gg_pubdir50_s *gg_pubdir50_t; /** - * Opis zdarzenia \c GG_EVENT_MSG. + * Opis zdarzeń \c GG_EVENT_MSG i \c GG_EVENT_MULTILOGON_MSG. */ struct gg_event_msg { - uin_t sender; /**< Numer nadawcy */ + uin_t sender; /**< Numer nadawcy/odbiorcy */ int msgclass; /**< Klasa wiadomości */ time_t time; /**< Czas nadania */ unsigned char *message; /**< Treść wiadomości */ @@ -795,7 +834,7 @@ * Opis zdarzenia \c GG_EVENT_NOTIFY_REPLY60. */ struct gg_event_notify60 { - uin_t uin; /**< Numer Gadu-Gadu */ + uin_t uin; /**< Numer Gadu-Gadu. W ostatnim elemencie jest równy 0, a pozostałe pola są niezainicjowane. */ int status; /**< Nowy status */ uint32_t remote_ip; /**< Adres IP dla połączeń bezpośrednich */ uint16_t remote_port; /**< Port dla połączeń bezpośrednich */ @@ -862,7 +901,6 @@ */ struct gg_event_dcc7_connected { struct gg_dcc7 *dcc7; /**< Struktura połączenia */ - // XXX czy coś się przyda? }; /** @@ -891,6 +929,68 @@ }; /** + * Opis zdarzenia \c GG_EVENT_DCC7_DONE. + */ +struct gg_event_dcc7_done { + struct gg_dcc7 *dcc7; /**< Struktura połączenia */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_TYPING_NOTIFICATION. + */ +struct gg_event_typing_notification { + uin_t uin; /**< Numer rozmówcy */ + int length; /**< Długość tekstu */ +}; + +/** + * Atrybut użytkownika. + */ +struct gg_event_user_data_attr { + int type; /**< Typ atrybutu */ + char *key; /**< Klucz */ + char *value; /**< Wartość */ +}; + +/** + * Struktura opisująca kontakt w zdarzeniu GG_EVENT_USER_DATA. + */ +struct gg_event_user_data_user { + uin_t uin; /**< Numer kontaktu */ + size_t attr_count; /**< Liczba atrybutów */ + struct gg_event_user_data_attr *attrs; /**< Lista atrybutów */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_USER_DATA. + */ +struct gg_event_user_data { + int type; /**< Rodzaj informacji o kontaktach */ + size_t user_count; /**< Liczba kontaktów */ + struct gg_event_user_data_user *users; /**< Lista kontaktów */ +}; + +/** + * Struktura opisująca sesję multilogowania. + */ +struct gg_multilogon_session { + gg_multilogon_id_t id; /**< Identyfikator sesji */ + char *name; /**< Nazwa sesji (podana w \c gg_login_params.client_version) */ + uint32_t remote_addr; /**< Adres sesji */ + int status_flags; /**< Flagi statusu sesji */ + int protocol_features; /**< Opcje protokolu sesji */ + time_t logon_time; /**< Czas zalogowania */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_MULTILOGON_INFO. + */ +struct gg_event_multilogon_info { + int count; /**< Liczba sesji */ + struct gg_multilogon_session *sessions; /** Lista sesji */ +}; + +/** * Unia wszystkich zdarzeń zwracanych przez funkcje \c gg_watch_fd(), * \c gg_dcc_watch_fd() i \c gg_dcc7_watch_fd(). * @@ -919,6 +1019,11 @@ struct gg_event_dcc7_pending dcc7_pending; /**< Trwa próba połączenia bezpośredniego (\c GG_EVENT_DCC7_PENDING) */ struct gg_event_dcc7_reject dcc7_reject; /**< Odrzucono połączenia bezpośredniego (\c GG_EVENT_DCC7_REJECT) */ struct gg_event_dcc7_accept dcc7_accept; /**< Zaakceptowano połączenie bezpośrednie (\c GG_EVENT_DCC7_ACCEPT) */ + struct gg_event_dcc7_done dcc7_done; /**< Zakończono połączenie bezpośrednie (\c GG_EVENT_DCC7_DONE) */ + struct gg_event_typing_notification typing_notification; /**< Powiadomienie o pisaniu */ + struct gg_event_user_data user_data; /**< Informacje o kontaktach */ + struct gg_event_msg multilogon_msg; /**< Inna sesja wysłała wiadomość (\c GG_EVENT_MULTILOGON_MSG) */ + struct gg_event_multilogon_info multilogon_info; /**< Informacja o innych sesjach multilogowania (\c GG_EVENT_MULTILOGON_INFO) */ }; /** @@ -1318,8 +1423,8 @@ int gg_send_packet(struct gg_session *sess, int type, ...) GG_DEPRECATED; unsigned int gg_login_hash(const unsigned char *password, unsigned int seed) GG_DEPRECATED; void gg_login_hash_sha1(const char *password, uint32_t seed, uint8_t *result) GG_DEPRECATED; -uint32_t gg_fix32(uint32_t x) GG_DEPRECATED; -uint16_t gg_fix16(uint16_t x) GG_DEPRECATED; +uint32_t gg_fix32(uint32_t x) GG_DEPRECATED;; +uint16_t gg_fix16(uint16_t x) GG_DEPRECATED;; #define fix16 gg_fix16 #define fix32 gg_fix32 char *gg_proxy_auth(void) GG_DEPRECATED; @@ -1341,11 +1446,11 @@ struct gg_image_queue *next; /**< Kolejny element listy */ } GG_DEPRECATED; -int gg_dcc7_handle_id(struct gg_session *sess, struct gg_event *e, void *payload, int len) GG_DEPRECATED; -int gg_dcc7_handle_new(struct gg_session *sess, struct gg_event *e, void *payload, int len) GG_DEPRECATED; -int gg_dcc7_handle_info(struct gg_session *sess, struct gg_event *e, void *payload, int len) GG_DEPRECATED; -int gg_dcc7_handle_accept(struct gg_session *sess, struct gg_event *e, void *payload, int len) GG_DEPRECATED; -int gg_dcc7_handle_reject(struct gg_session *sess, struct gg_event *e, void *payload, int len) GG_DEPRECATED; +int gg_dcc7_handle_id(struct gg_session *sess, struct gg_event *e, const void *payload, int len) GG_DEPRECATED; +int gg_dcc7_handle_new(struct gg_session *sess, struct gg_event *e, const void *payload, int len) GG_DEPRECATED; +int gg_dcc7_handle_info(struct gg_session *sess, struct gg_event *e, const void *payload, int len) GG_DEPRECATED; +int gg_dcc7_handle_accept(struct gg_session *sess, struct gg_event *e, const void *payload, int len) GG_DEPRECATED; +int gg_dcc7_handle_reject(struct gg_session *sess, struct gg_event *e, const void *payload, int len) GG_DEPRECATED; #define GG_APPMSG_HOST "appmsg.gadu-gadu.pl" #define GG_APPMSG_PORT 80 @@ -1355,12 +1460,14 @@ #define GG_REGISTER_PORT 80 #define GG_REMIND_HOST "retr.gadu-gadu.pl" #define GG_REMIND_PORT 80 +#define GG_RELAY_HOST "relay.gadu-gadu.pl" +#define GG_RELAY_PORT 80 #define GG_DEFAULT_PORT 8074 #define GG_HTTPS_PORT 443 #define GG_HTTP_USERAGENT "Mozilla/4.7 [en] (Win98; I)" -#define GG_DEFAULT_CLIENT_VERSION "8.0.0.7669" +#define GG_DEFAULT_CLIENT_VERSION "10.1.0.11070" #define GG_DEFAULT_PROTOCOL_VERSION 0x2e #define GG_DEFAULT_TIMEOUT 30 #define GG_HAS_AUDIO_MASK 0x40000000 @@ -1370,16 +1477,28 @@ #ifndef DOXYGEN -#define GG_FEATURE_MSG77 0x01 -#define GG_FEATURE_STATUS77 0x02 -#define GG_FEATURE_DND_FFC 0x10 -#define GG_FEATURE_IMAGE_DESCR 0x20 +#define GG_FEATURE_MSG77 0x0001 +#define GG_FEATURE_STATUS77 0x0002 +#define GG_FEATURE_UNKNOWN_4 0x0004 +#define GG_FEATURE_UNKNOWN_8 0x0008 +#define GG_FEATURE_DND_FFC 0x0010 +#define GG_FEATURE_IMAGE_DESCR 0x0020 +#define GG_FEATURE_UNKNOWN_40 0x0040 +#define GG_FEATURE_UNKNOWN_80 0x0080 +#define GG_FEATURE_UNKNOWN_100 0x0100 +#define GG_FEATURE_USER_DATA 0x0200 +#define GG_FEATURE_MSG_ACK 0x0400 +#define GG_FEATURE_UNKNOWN_800 0x0800 +#define GG_FEATURE_UNKNOWN_1000 0x1000 +#define GG_FEATURE_TYPING_NOTIFICATION 0x2000 +#define GG_FEATURE_MULTILOGON 0x4000 /* Poniższe makra zostały zachowane dla zgodności API */ #define GG_FEATURE_MSG80 0 #define GG_FEATURE_STATUS80 0 #define GG_FEATURE_STATUS80BETA 0 -#define GG_FEATURE_ALL (GG_FEATURE_DND_FFC | GG_FEATURE_IMAGE_DESCR) + +#define GG_FEATURE_ALL (GG_FEATURE_MSG80 | GG_FEATURE_STATUS80 | GG_FEATURE_DND_FFC | GG_FEATURE_IMAGE_DESCR | GG_FEATURE_UNKNOWN_100 | GG_FEATURE_USER_DATA | GG_FEATURE_MSG_ACK | GG_FEATURE_TYPING_NOTIFICATION) #else @@ -1509,6 +1628,11 @@ #define GG_STATUS_DESCR_MASK 0x4000 #define GG_STATUS_FRIENDS_MASK 0x8000 +#define GG_STATUS_FLAG_UNKNOWN 0x00000001 +#define GG_STATUS_FLAG_VIDEO 0x00000002 +#define GG_STATUS_FLAG_MOBILE 0x00100000 +#define GG_STATUS_FLAG_SPAM 0x00800000 + #else /** @@ -1535,6 +1659,18 @@ GG_STATUS_FRIENDS_MASK, /**< Flaga bitowa dostępności tylko dla znajomych */ }; +/** + * Rodzaje statusów użytkownika. Mapa bitowa. + * + * \ingroup status + */ +enum { + GG_STATUS_FLAG_UNKNOWN, /**< Przeznaczenie nieznane, ale występuje zawsze */ + GG_STATUS_FLAG_VIDEO, /**< Klient obsługuje wideorozmowy */ + GG_STATUS_FLAG_MOBILE, /**< Klient mobilny (ikona telefonu komórkowego) */ + GG_STATUS_FLAG_SPAM, /**< Klient chce otrzymywać linki od nieznajomych */ +}; + #endif /* DOXYGEN */ /** @@ -1985,6 +2121,7 @@ uint32_t type; /* sposób połączenia */ gg_dcc7_id_t id; /* identyfikator połączenia */ char info[GG_DCC7_INFO_LEN]; /* informacje o połączeniu "ip port" */ + char hash[GG_DCC7_INFO_HASH_LEN];/* skrót "ip" */ } GG_PACKED; #define GG_DCC7_NEW 0x20 @@ -2055,14 +2192,14 @@ #define GG_DCC7_TIMEOUT_FILE_ACK 300 /* 5 minut */ #define GG_DCC7_TIMEOUT_VOICE_ACK 300 /* 5 minut */ +#ifdef _WIN32 +#pragma pack(pop) +#endif + #ifdef __cplusplus } #endif -#ifdef _WIN32 -#pragma pack(pop) -#endif - #endif /* __GG_LIBGADU_H */ /* diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/lib/message.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/gg/lib/message.c Fri Apr 01 13:47:51 2011 +0900 @@ -0,0 +1,657 @@ +/* + * (C) Copyright 2001-2010 Wojtek Kaniewski + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +/** + * \file message.c + * + * \brief Obsługa wiadomości + * + * Plik zawiera funkcje dotyczące obsługi "klasy" gg_message_t, które + * w przyszłości zostaną dołączone do API. Obecnie używane są funkcje + * konwersji między tekstem z atrybutami i HTML. + */ + +#include +#include +#include +#include +#include + +#include "message.h" + +#if 0 + +gg_message_t *gg_message_new(void) +{ + gg_message_t *gm; + + gm = malloc(sizeof(gg_message_t)); + + if (gm == NULL) + return NULL; + + memset(gm, 0, sizeof(gg_message_t)); + + gm->msgclass = GG_CLASS_CHAT; + gm->seq = (uint32_t) -1; + + return gm; +} + +int gg_message_init(gg_message_t *gm, int msgclass, int seq, uin_t *recipients, size_t recipient_count, char *text, char *html, char *attributes, size_t attributes_length, int auto_convert) +{ + GG_MESSAGE_CHECK(gm, -1); + + memset(gm, 0, sizeof(gg_message_t)); + gm->recipients = recipients; + gm->recipient_count = recipient_count; + gm->text = text; + gm->html = html; + gm->attributes = attributes; + gm->attributes_length = attributes_length; + gm->msgclass = msgclass; + gm->seq = seq; + gm->auto_convert = auto_convert; + + return 0; +} + +void gg_message_free(gg_message_t *gm) +{ + if (gm == NULL) { + errno = EINVAL; + return; + } + + free(gm->text); + free(gm->text_converted); + free(gm->html); + free(gm->html_converted); + free(gm->recipients); + free(gm->attributes); + + free(gm); +} + +int gg_message_set_auto_convert(gg_message_t *gm, int auto_convert) +{ + GG_MESSAGE_CHECK(gm, -1); + + gm->auto_convert = !!auto_convert; + + if (!gm->auto_convert) { + free(gm->text_converted); + free(gm->html_converted); + gm->text_converted = NULL; + gm->html_converted = NULL; + } + + return 0; +} + +int gg_message_get_auto_convert(gg_message_t *gm) +{ + GG_MESSAGE_CHECK(gm, -1); + + return gm->auto_convert; +} + +int gg_message_set_recipients(gg_message_t *gm, const uin_t *recipients, size_t recipient_count) +{ + GG_MESSAGE_CHECK(gm, -1); + + if (recipient_count >= INT_MAX / sizeof(uin_t)) { + errno = EINVAL; + return -1; + } + + if ((recipients == NULL) || (recipient_count == 0)) { + free(gm->recipients); + gm->recipients = NULL; + gm->recipient_count = 0; + } else { + uin_t *tmp; + + tmp = realloc(gm->recipients, recipient_count * sizeof(uin_t)); + + if (tmp == NULL) + return -1; + + memcpy(tmp, recipients, recipient_count * sizeof(uin_t)); + + gm->recipients = tmp; + gm->recipient_count = recipient_count; + } + + return 0; +} + +int gg_message_set_recipient(gg_message_t *gm, uin_t recipient) +{ + return gg_message_set_recipients(gm, &recipient, 1); +} + +int gg_message_get_recipients(gg_message_t *gm, const uin_t **recipients, size_t *recipient_count) +{ + GG_MESSAGE_CHECK(gm, -1); + + if (recipients != NULL) + *recipients = gm->recipients; + + if (recipient_count != NULL) + *recipient_count = gm->recipient_count; + + return 0; +} + +uin_t gg_message_get_recipient(gg_message_t *gm) +{ + GG_MESSAGE_CHECK(gm, (uin_t) -1); + + if ((gm->recipients == NULL) || (gm->recipient_count < 1)) { + // errno = XXX; + return (uin_t) -1; + } + + return gm->recipients[0]; +} + +int gg_message_set_class(gg_message_t *gm, uint32_t msgclass) +{ + GG_MESSAGE_CHECK(gm, -1); + + gm->msgclass = msgclass; + + return 0; +} + +uint32_t gg_message_get_class(gg_message_t *gm) +{ + GG_MESSAGE_CHECK(gm, (uint32_t) -1); + + return gm->msgclass; +} + +int gg_message_set_seq(gg_message_t *gm, uint32_t seq) +{ + GG_MESSAGE_CHECK(gm, -1); + + gm->seq = seq; + + return 0; +} + +uint32_t gg_message_get_seq(gg_message_t *gm) +{ + GG_MESSAGE_CHECK(gm, (uint32_t) -1); + + return gm->seq; +} + +int gg_message_set_text(gg_message_t *gm, const char *text) +{ + GG_MESSAGE_CHECK(gm, -1); + + if (text == NULL) { + free(gm->text); + gm->text = NULL; + } else { + char *tmp; + + tmp = strdup(text); + + if (tmp == NULL) + return -1; + + free(gm->text); + gm->text = tmp; + } + + free(gm->html_converted); + gm->html_converted = NULL; + + return 0; +} + +const char *gg_message_get_text(gg_message_t *gm) +{ + GG_MESSAGE_CHECK(gm, NULL); + + if (gm->text_converted != NULL) + return gm->text_converted; + + if (gm->text == NULL && gm->html != NULL && gm->auto_convert) { + size_t len; + + free(gm->text_converted); + + len = gg_message_html_to_text(NULL, gm->html); + + gm->text_converted = malloc(len + 1); + + if (gm->text_converted == NULL) + return NULL; + + gg_message_html_to_text(gm->text_converted, gm->html); + + return gm->text_converted; + } + + return gm->text; +} + +int gg_message_set_html(gg_message_t *gm, const char *html) +{ + GG_MESSAGE_CHECK(gm, -1); + + if (html == NULL) { + free(gm->html); + gm->html = NULL; + } else { + char *tmp; + + tmp = strdup(html); + + if (tmp == NULL) + return -1; + + free(gm->html); + gm->html = tmp; + } + + free(gm->text_converted); + gm->text_converted = NULL; + + return 0; +} + +const char *gg_message_get_html(gg_message_t *gm) +{ + GG_MESSAGE_CHECK(gm, NULL); + + if (gm->html_converted != NULL) + return gm->html_converted; + + if (gm->html == NULL && gm->text != NULL && gm->auto_convert) { + size_t len; + + free(gm->html_converted); + + len = gg_message_text_to_html(NULL, gm->text, gm->attributes, gm->attributes_length); + + gm->html_converted = malloc(len + 1); + + if (gm->html_converted == NULL) + return NULL; + + gg_message_text_to_html(gm->html_converted, gm->text, gm->attributes, gm->attributes_length); + + return gm->html_converted; + } + + return gm->html; +} + +int gg_message_set_attributes(gg_message_t *gm, const char *attributes, size_t length) +{ + GG_MESSAGE_CHECK(gm, -1); + + if (length > 0xfffd) { + // errno = XXX; + return -1; + } + + if ((attributes == NULL) || (length == 0)) { + free(gm->attributes); + gm->attributes = NULL; + gm->attributes_length = 0; + } else { + char *tmp; + + tmp = realloc(gm->attributes, length); + + if (tmp == NULL) + return -1; + + gm->attributes = tmp; + gm->attributes_length = length; + } + + free(gm->html_converted); + gm->html_converted = NULL; + + return 0; +} + +int gg_message_get_attributes(gg_message_t *gm, const char **attributes, size_t *attributes_length) +{ + GG_MESSAGE_CHECK(gm, -1); + + if (attributes != NULL) + *attributes = gm->attributes; + + if (attributes_length != NULL) + *attributes_length = gm->attributes_length; + + return 0; +} + +#endif + +/** + * \internal Dodaje tekst na koniec bufora. + * + * \param dst Wskaźnik na bufor roboczy + * \param pos Wskaźnik na aktualne położenie w buforze roboczym + * \param src Dodawany tekst + * \param len Długość dodawanego tekstu + */ +static void gg_append(char *dst, int *pos, const void *src, int len) +{ + if (dst != NULL) + memcpy(&dst[*pos], src, len); + + *pos += len; +} + +/** + * \internal Zamienia tekst z formatowaniem Gadu-Gadu na HTML. + * + * \param dst Bufor wynikowy (może być \c NULL) + * \param src Tekst źródłowy w UTF-8 + * \param format Atrybuty tekstu źródłowego + * \param format_len Długość bloku atrybutów tekstu źródłowego + * + * \note Wynikowy tekst nie jest idealnym kodem HTML, ponieważ ma jak + * dokładniej odzwierciedlać to, co wygenerowałby oryginalny klient. + * + * \note Dokleja \c \\0 na końcu bufora wynikowego. + * + * \return Długość tekstu wynikowego bez \c \\0 (nawet jeśli \c dst to \c NULL). + */ +size_t gg_message_text_to_html(char *dst, const char *src, const char *format, size_t format_len) +{ + const char span_fmt[] = ""; + const int span_len = 75; + const char img_fmt[] = ""; + const int img_len = 29; + int char_pos = 0; + int format_idx = 0; + unsigned char old_attr = 0; + const unsigned char *color = (const unsigned char*) "\x00\x00\x00"; + int len, i; + const unsigned char *format_ = (const unsigned char*) format; + + len = 0; + + /* Nie mamy atrybutów dla pierwsze znaku, a tekst nie jest pusty, więc + * tak czy inaczej trzeba otworzyć . */ + + if (src[0] != 0 && (format_idx + 3 > format_len || (format_[format_idx] | (format_[format_idx + 1] << 8)) != 0)) { + if (dst != NULL) + sprintf(&dst[len], span_fmt, 0, 0, 0); + + len += span_len; + } + + /* Pętla przechodzi też przez kończące \0, żeby móc dokleić obrazek + * na końcu tekstu. */ + + for (i = 0; ; i++) { + /* Analizuj atrybuty tak długo jak dotyczą aktualnego znaku. */ + for (;;) { + unsigned char attr; + int attr_pos; + + if (format_idx + 3 > format_len) + break; + + attr_pos = format_[format_idx] | (format_[format_idx + 1] << 8); + + if (attr_pos != char_pos) + break; + + attr = format_[format_idx + 2]; + + /* Nie doklejaj atrybutów na końcu, co najwyżej obrazki. */ + + if (src[i] == 0) + attr &= ~(GG_FONT_BOLD | GG_FONT_ITALIC | GG_FONT_UNDERLINE | GG_FONT_COLOR); + + format_idx += 3; + + if ((attr & (GG_FONT_BOLD | GG_FONT_ITALIC | GG_FONT_UNDERLINE | GG_FONT_COLOR)) != 0 || (attr == 0 && old_attr != 0)) { + if (char_pos != 0) { + if ((old_attr & GG_FONT_UNDERLINE) != 0) + gg_append(dst, &len, "", 4); + + if ((old_attr & GG_FONT_ITALIC) != 0) + gg_append(dst, &len, "", 4); + + if ((old_attr & GG_FONT_BOLD) != 0) + gg_append(dst, &len, "", 4); + + if (src[i] != 0) + gg_append(dst, &len, "", 7); + } + + if (((attr & GG_FONT_COLOR) != 0) && (format_idx + 3 <= format_len)) { + color = &format_[format_idx]; + format_idx += 3; + } else { + color = (unsigned char*) "\x00\x00\x00"; + } + + if (src[i] != 0) { + if (dst != NULL) + sprintf(&dst[len], span_fmt, color[0], color[1], color[2]); + len += span_len; + } + } else if (char_pos == 0 && src[0] != 0) { + if (dst != NULL) + sprintf(&dst[len], span_fmt, 0, 0, 0); + len += span_len; + } + + if ((attr & GG_FONT_BOLD) != 0) + gg_append(dst, &len, "", 3); + + if ((attr & GG_FONT_ITALIC) != 0) + gg_append(dst, &len, "", 3); + + if ((attr & GG_FONT_UNDERLINE) != 0) + gg_append(dst, &len, "", 3); + + if (((attr & GG_FONT_IMAGE) != 0) && (format_idx + 10 <= format_len)) { + if (dst != NULL) { + sprintf(&dst[len], img_fmt, + format_[format_idx + 9], + format_[format_idx + 8], + format_[format_idx + 7], + format_[format_idx + 6], + format_[format_idx + 5], + format_[format_idx + 4], + format_[format_idx + 3], + format_[format_idx + 2]); + } + + len += img_len; + format_idx += 10; + } + + old_attr = attr; + } + + /* Doklej znak zachowując htmlowe escapowanie. */ + + switch (src[i]) { + case '&': + gg_append(dst, &len, "&", 5); + break; + case '<': + gg_append(dst, &len, "<", 4); + break; + case '>': + gg_append(dst, &len, ">", 4); + break; + case '\'': + gg_append(dst, &len, "'", 6); + break; + case '\"': + gg_append(dst, &len, """, 6); + break; + case '\n': + gg_append(dst, &len, "
", 4); + break; + case '\r': + case 0: + break; + default: + if (dst != NULL) + dst[len] = src[i]; + len++; + } + + /* Sprawdź, czy bajt nie jest kontynuacją znaku unikodowego. */ + + if ((src[i] & 0xc0) != 0xc0) + char_pos++; + + if (src[i] == 0) + break; + } + + /* Zamknij tagi. */ + + if ((old_attr & GG_FONT_UNDERLINE) != 0) + gg_append(dst, &len, "
", 4); + + if ((old_attr & GG_FONT_ITALIC) != 0) + gg_append(dst, &len, "
", 4); + + if ((old_attr & GG_FONT_BOLD) != 0) + gg_append(dst, &len, "
", 4); + + if (src[0] != 0) + gg_append(dst, &len, "
", 7); + + if (dst != NULL) + dst[len] = 0; + + return len; +} + +/** + * \internal Zamienia tekst w formacie HTML na czysty tekst. + * + * \param dst Bufor wynikowy (może być \c NULL) + * \param html Tekst źródłowy + * + * \note Dokleja \c \\0 na końcu bufora wynikowego. + * + * \note Funkcja służy do zachowania kompatybilności przy przesyłaniu + * wiadomości HTML do klientów, które tego formatu nie obsługują. Z tego + * powodu funkcja nie zachowuje formatowania, a jedynie usuwa tagi i + * zamienia podstawowe encje na ich odpowiedniki ASCII. + * + * \return Długość tekstu wynikowego bez \c \\0 (nawet jeśli \c dst to \c NULL). + */ +size_t gg_message_html_to_text(char *dst, const char *html) +{ + const char *src, *entity, *tag; + int in_tag, in_entity; + size_t len; + + len = 0; + in_tag = 0; + tag = NULL; + in_entity = 0; + entity = NULL; + + for (src = html; *src != 0; src++) { + if (*src == '<') { + tag = src; + in_tag = 1; + continue; + } + + if (in_tag && (*src == '>')) { + if (strncmp(tag, " + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef LIBGADU_MESSAGE_H +#define LIBGADU_MESSAGE_H + +#include +#include +#include "libgadu.h" + +#if 0 + +struct gg_message { + uin_t *recipients; + size_t recipient_count; + char *text; + char *html; + char *attributes; + size_t attributes_length; + uint32_t msgclass; + uint32_t seq; + + int auto_convert; + char *text_converted; + char *html_converted; +}; + +#define GG_MESSAGE_CHECK(gm, result) \ + if ((gm) == NULL) { \ + errno = EINVAL; \ + return (result); \ + } + +int gg_message_init(gg_message_t *gm, int msgclass, int seq, uin_t *recipients, size_t recipient_count, char *text, char *xhtml, char *attributes, size_t attributes_length, int auto_convert); + +#endif + +size_t gg_message_html_to_text(char *dst, const char *html); +size_t gg_message_text_to_html(char *dst, const char *utf_msg, const char *format, size_t format_len); + +#endif /* LIBGADU_MESSAGE_H */ diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/lib/obsolete.c --- a/libpurple/protocols/gg/lib/obsolete.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/gg/lib/obsolete.c Fri Apr 01 13:47:51 2011 +0900 @@ -1,4 +1,4 @@ -/* $Id: obsolete.c 854 2009-10-12 21:06:28Z wojtekka $ */ +/* $Id: obsolete.c 1036 2010-12-15 00:02:28Z wojtekka $ */ /* * (C) Copyright 2001-2003 Wojtek Kaniewski diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/lib/protocol.h --- a/libpurple/protocols/gg/lib/protocol.h Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/gg/lib/protocol.h Fri Apr 01 13:47:51 2011 +0900 @@ -1,7 +1,9 @@ /* $Id$ */ /* - * (C) Copyright 2009 Jakub Zawadzki + * (C) Copyright 2009-2010 Jakub Zawadzki + * Bartłomiej Zimoń + * Wojtek Kaniewski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License Version @@ -39,7 +41,7 @@ #define GG_FEATURE_STATUS80 0x05 #define GG8_LANG "pl" -#define GG8_VERSION "Gadu-Gadu Client Build 8.0.0.8731" +#define GG8_VERSION "Gadu-Gadu Client Build " struct gg_login80 { uint32_t uin; /* mój numerek */ @@ -61,6 +63,22 @@ #define GG_LOGIN80_OK 0x0035 +/** + * Logowanie powiodło się (pakiet \c GG_LOGIN80_OK) + */ +struct gg_login80_ok { + uint32_t unknown1; /* 0x00000001 */ +} GG_PACKED; + +/** + * Logowanie nie powiodło się (pakiet \c GG_LOGIN80_FAILED) + */ +#define GG_LOGIN80_FAILED 0x0043 + +struct gg_login80_failed { + uint32_t unknown1; /* 0x00000001 */ +} GG_PACKED; + #define GG_NEW_STATUS80BETA 0x0028 #define GG_NEW_STATUS80 0x0038 @@ -83,12 +101,12 @@ struct gg_notify_reply80 { uint32_t uin; /* numerek plus flagi w najstarszym bajcie */ uint32_t status; /* status danej osoby */ - uint32_t flags; /* flagi (przeznaczenie nieznane) */ + uint32_t features; /* opcje protokołu */ uint32_t remote_ip; /* adres IP bezpośrednich połączeń */ uint16_t remote_port; /* port bezpośrednich połączeń */ uint8_t image_size; /* maksymalny rozmiar obrazków w KB */ - uint8_t unknown2; /* 0x00 */ - uint32_t unknown3; /* 0x00000000 */ + uint8_t unknown1; /* 0x00 */ + uint32_t flags; /* flagi połączenia */ uint32_t descr_len; /* rozmiar opisu */ } GG_PACKED; @@ -115,6 +133,65 @@ #define GG_DISCONNECT_ACK 0x000d +#define GG_RECV_MSG_ACK 0x0046 + +struct gg_recv_msg_ack { + uint32_t count; +} GG_PACKED; + +#define GG_USER_DATA 0x0044 + +struct gg_user_data { + uint32_t type; + uint32_t user_count; +} GG_PACKED; + +struct gg_user_data_user { + uint32_t uin; + uint32_t attr_count; +} GG_PACKED; + +#define GG_TYPING_NOTIFICATION 0x0059 + +struct gg_typing_notification { + uint16_t length; + uint32_t uin; +} GG_PACKED; + +#define GG_XML_ACTION 0x002c + +#define GG_RECV_OWN_MSG 0x005a + +#define GG_MULTILOGON_INFO 0x005b + +struct gg_multilogon_info { + uint32_t count; +} GG_PACKED; + +struct gg_multilogon_info_item { + uint32_t addr; + uint32_t flags; + uint32_t features; + uint32_t logon_time; + gg_multilogon_id_t conn_id; + uint32_t unknown1; + uint32_t name_size; +} GG_PACKED; + +#define GG_MULTILOGON_DISCONNECT 0x0062 + +struct gg_multilogon_disconnect { + gg_multilogon_id_t conn_id; +} GG_PACKED; + +#define GG_MSG_CALLBACK 0x02 /**< Żądanie zwrotnego połączenia bezpośredniego */ + +#define GG_MSG_OPTION_CONFERENCE 0x01 +#define GG_MSG_OPTION_ATTRIBUTES 0x02 +#define GG_MSG_OPTION_IMAGE_REQUEST 0x04 +#define GG_MSG_OPTION_IMAGE_REPLY 0x05 +#define GG_MSG_OPTION_IMAGE_REPLY_MORE 0x06 + #define GG_DCC7_VOICE_RETRIES 0x11 /* 17 powtorzen */ #define GG_DCC7_RESERVED1 0xdeadc0de @@ -158,6 +235,48 @@ uint32_t id; /* id tego co potwierdzamy [0x1 - 0x5] */ } GG_PACKED; +#define GG_DCC7_RELAY_TYPE_SERVER 0x01 /* adres serwera, na który spytać o proxy */ +#define GG_DCC7_RELAY_TYPE_PROXY 0x08 /* adresy proxy, na które sie łączyć */ + +#define GG_DCC7_RELAY_DUNNO1 0x02 + +#define GG_DCC7_RELAY_REQUEST 0x0a + +struct gg_dcc7_relay_req { + uint32_t magic; /* 0x0a */ + uint32_t len; /* długość całego pakietu */ + gg_dcc7_id_t id; /* identyfikator połączenia */ + uint16_t type; /* typ zapytania */ + uint16_t dunno1; /* 0x02 */ +} GG_PACKED; + +#define GG_DCC7_RELAY_REPLY_RCOUNT 0x02 + +#define GG_DCC7_RELAY_REPLY 0x0b + +struct gg_dcc7_relay_reply { + uint32_t magic; /* 0x0b */ + uint32_t len; /* długość całego pakietu */ + uint32_t rcount; /* ilość serwerów */ +} GG_PACKED; + +struct gg_dcc7_relay_reply_server { + uint32_t addr; /* adres ip serwera */ + uint16_t port; /* port serwera */ + uint8_t family; /* rodzina adresów (na końcu?!) AF_INET=2 */ +} GG_PACKED; + +#define GG_DCC7_WELCOME_SERVER 0xc0debabe + +struct gg_dcc7_welcome_server { + uint32_t magic; /* 0xc0debabe */ + gg_dcc7_id_t id; /* identyfikator połączenia */ +} GG_PACKED; + +struct gg_dcc7_welcome_p2p { + gg_dcc7_id_t id; /* identyfikator połączenia */ +} GG_PACKED; + #ifdef _WIN32 #pragma pack(pop) #endif diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/lib/pubdir50.c --- a/libpurple/protocols/gg/lib/pubdir50.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/gg/lib/pubdir50.c Fri Apr 01 13:47:51 2011 +0900 @@ -1,5 +1,3 @@ -/* $Id: pubdir50.c 854 2009-10-12 21:06:28Z wojtekka $ */ - /* * (C) Copyright 2003 Wojtek Kaniewski * @@ -22,6 +20,9 @@ * \file pubdir50.c * * \brief Obsługa katalogu publicznego od wersji Gadu-Gadu 5.x + * + * \todo Zoptymalizować konwersję CP1250<->UTF8. Obecnie robiona jest + * testowa konwersja, żeby poznać długość tekstu wynikowego. */ #include @@ -32,6 +33,7 @@ #include "libgadu.h" #include "libgadu-internal.h" +#include "encoding.h" /** * Tworzy nowe zapytanie katalogu publicznego. @@ -225,7 +227,8 @@ } else { char *tmp; - tmp = gg_utf8_to_cp(req->entries[i].field); + // XXX \todo zoptymalizować + tmp = gg_encoding_convert(req->entries[i].field, sess->encoding, GG_ENCODING_CP1250, -1, -1); if (tmp == NULL) return -1; @@ -234,7 +237,8 @@ free(tmp); - tmp = gg_utf8_to_cp(req->entries[i].value); + // XXX \todo zoptymalizować + tmp = gg_encoding_convert(req->entries[i].value, sess->encoding, GG_ENCODING_CP1250, -1, -1); if (tmp == NULL) return -1; @@ -272,7 +276,8 @@ } else { char *tmp; - tmp = gg_utf8_to_cp(req->entries[i].field); + // XXX \todo zoptymalizować + tmp = gg_encoding_convert(req->entries[i].field, sess->encoding, GG_ENCODING_CP1250, -1, -1); if (tmp == NULL) { free(buf); @@ -283,7 +288,9 @@ p += strlen(tmp) + 1; free(tmp); - tmp = gg_utf8_to_cp(req->entries[i].value); + // XXX \todo zoptymalizować + tmp = gg_encoding_convert(req->entries[i].value, sess->encoding, GG_ENCODING_CP1250, -1, -1); + if (tmp == NULL) { free(buf); @@ -417,7 +424,7 @@ } else { char *tmp; - tmp = gg_cp_to_utf8(value); + tmp = gg_encoding_convert(value, GG_ENCODING_CP1250, sess->encoding, -1, -1); if (tmp == NULL) goto failure; diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/lib/resolver.c --- a/libpurple/protocols/gg/lib/resolver.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/gg/lib/resolver.c Fri Apr 01 13:47:51 2011 +0900 @@ -1,5 +1,3 @@ -/* $Id$ */ - /* * (C) Copyright 2001-2009 Wojtek Kaniewski * Robert J. Woźny @@ -44,6 +42,7 @@ #include "libgadu.h" #include "resolver.h" #include "compat.h" +#include "session.h" /** Sposób rozwiązywania nazw serwerów */ static gg_resolver_t gg_global_resolver_type = GG_RESOLVER_DEFAULT; @@ -80,15 +79,17 @@ * \internal Odpowiednik \c gethostbyname zapewniający współbieżność. * * Jeśli dany system dostarcza \c gethostbyname_r, używa się tej wersji, jeśli - * nie, to zwykłej \c gethostbyname. + * nie, to zwykłej \c gethostbyname. Wynikiem jest tablica adresów zakończona + * wartością INADDR_NONE, którą należy zwolnić po użyciu. * * \param hostname Nazwa serwera - * \param addr Wskaźnik na rezultat rozwiązywania nazwy + * \param result Wskaźnik na wskaźnik z tablicą adresów zakończoną INADDR_NONE + * \param count Wskaźnik na zmienną, do ktorej zapisze się liczbę wyników * \param pthread Flaga blokowania unicestwiania wątku podczas alokacji pamięci * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ -int gg_gethostbyname_real(const char *hostname, struct in_addr *addr, int pthread) +int gg_gethostbyname_real(const char *hostname, struct in_addr **result, int *count, int pthread) { #ifdef GG_CONFIG_HAVE_GETHOSTBYNAME_R char *buf = NULL; @@ -96,13 +97,18 @@ struct hostent he; struct hostent *he_ptr = NULL; size_t buf_len = 1024; - int result = -1; + int res = -1; int h_errnop; int ret = 0; #ifdef GG_CONFIG_HAVE_PTHREAD int old_state; #endif + if (result == NULL) { + errno = EINVAL; + return -1; + } + #ifdef GG_CONFIG_HAVE_PTHREAD pthread_cleanup_push(gg_gethostbyname_cleaner, &buf); @@ -146,9 +152,41 @@ } } - if (ret == 0 && he_ptr != NULL) { - memcpy(addr, he_ptr->h_addr, sizeof(struct in_addr)); - result = 0; + if (ret == 0 && he_ptr != NULL && he_ptr->h_addr_list[0] != NULL) { + int i; + + /* Policz liczbę adresów */ + + for (i = 0; he_ptr->h_addr_list[i] != NULL; i++) + ; + + /* Zaalokuj */ + +#ifdef GG_CONFIG_HAVE_PTHREAD + if (pthread) + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); +#endif + + *result = malloc((i + 1) * sizeof(struct in_addr)); + +#ifdef GG_CONFIG_HAVE_PTHREAD + if (pthread) + pthread_setcancelstate(old_state, NULL); +#endif + + if (*result == NULL) + return -1; + + /* Kopiuj */ + + for (i = 0; he_ptr->h_addr_list[i] != NULL; i++) + memcpy(&((*result)[i]), he_ptr->h_addr_list[i], sizeof(struct in_addr)); + + (*result)[i].s_addr = INADDR_NONE; + + *count = i; + + res = 0; } #ifdef GG_CONFIG_HAVE_PTHREAD @@ -169,26 +207,92 @@ pthread_cleanup_pop(1); #endif - return result; -#else + return res; +#else /* GG_CONFIG_HAVE_GETHOSTBYNAME_R */ struct hostent *he; + int i; + + + if (result == NULL || count == NULL) { + errno = EINVAL; + return -1; + } he = gethostbyname(hostname); - if (he == NULL) + if (he == NULL || he->h_addr_list[0] == NULL) return -1; - memcpy(addr, he->h_addr, sizeof(struct in_addr)); + /* Policz liczbę adresów */ + + for (i = 0; he->h_addr_list[i] != NULL; i++) + ; + + /* Zaalokuj */ + + *result = malloc((i + 1) * sizeof(struct in_addr)); + + if (*result == NULL) + return -1; + + /* Kopiuj */ + + for (i = 0; he->h_addr_list[i] != NULL; i++) + memcpy(&((*result)[i]), he->h_addr_list[0], sizeof(struct in_addr)); + + (*result)[i].s_addr = INADDR_NONE; + + *count = i; return 0; #endif /* GG_CONFIG_HAVE_GETHOSTBYNAME_R */ } /** + * \internal Rozwiązuje nazwę i zapisuje wynik do podanego desktyptora. + * + * \param fd Deskryptor + * \param hostname Nazwa serwera + * + * \return 0 jeśli się powiodło, -1 w przypadku błędu + */ +int gg_resolver_run(int fd, const char *hostname) +{ + struct in_addr addr_ip[2], *addr_list; + int addr_count; + int res = 0; + + gg_debug(GG_DEBUG_MISC, "// gg_resolver_run(%d, %s)\n", fd, hostname); + + if ((addr_ip[0].s_addr = inet_addr(hostname)) == INADDR_NONE) { + if (gg_gethostbyname_real(hostname, &addr_list, &addr_count, 1) == -1) { + addr_list = addr_ip; + /* addr_ip[0] już zawiera INADDR_NONE */ + } + } else { + addr_list = addr_ip; + addr_ip[1].s_addr = INADDR_NONE; + addr_count = 1; + } + + gg_debug(GG_DEBUG_MISC, "// gg_resolver_run() count = %d\n", addr_count); + + if (write(fd, addr_list, (addr_count + 1) * sizeof(struct in_addr)) != (addr_count + 1) * sizeof(struct in_addr)) + res = -1; + + if (addr_list != addr_ip) + free(addr_list); + + return res; +} + +/** * \internal Odpowiednik \c gethostbyname zapewniający współbieżność. * * Jeśli dany system dostarcza \c gethostbyname_r, używa się tej wersji, jeśli - * nie, to zwykłej \c gethostbyname. + * nie, to zwykłej \c gethostbyname. Funkcja służy do zachowania zgodności + * ABI i służy do pobierania tylko pierwszego adresu -- pozostałe mogą + * zostać zignorowane przez aplikację. * * \param hostname Nazwa serwera * @@ -196,16 +300,13 @@ */ struct in_addr *gg_gethostbyname(const char *hostname) { - struct in_addr *addr; + struct in_addr *result; + int count; - if (!(addr = malloc(sizeof(struct in_addr)))) + if (gg_gethostbyname_real(hostname, &result, &count, 0) == -1) return NULL; - if (gg_gethostbyname_real(hostname, addr, 0)) { - free(addr); - return NULL; - } - return addr; + return result; } /** @@ -215,8 +316,6 @@ int pid; /*< Identyfikator procesu */ }; - - #ifdef _WIN32 /** * Deal with the fact that you can't select() on a win32 file fd. @@ -353,29 +452,28 @@ static DWORD WINAPI gg_resolve_win32thread_thread(LPVOID arg) { struct gg_resolve_win32thread_data *d = arg; - struct in_addr a; + struct in_addr addr_ip[2], *addr_list; + int addr_count; gg_debug(GG_DEBUG_MISC, "// gg_resolve_win32thread_thread() host: %s, fd: %i called\n", d->hostname, d->fd); - if ((a.s_addr = inet_addr(d->hostname)) == INADDR_NONE) { + if ((addr_ip[0].s_addr = inet_addr(d->hostname)) == INADDR_NONE) { /* W przypadku błędu gg_gethostbyname_real() zwróci -1 * i nie zmieni &addr. Tam jest już INADDR_NONE, * więc nie musimy robić nic więcej. */ - gg_gethostbyname_real(d->hostname, &a, 0); + if (gg_gethostbyname_real(d->hostname, &addr_list, &addr_count, 0) == -1) + { + addr_list = addr_ip; + } + } else { + addr_list = addr_ip; + addr_ip[1].s_addr = INADDR_NONE; + addr_count = 1; } - // if ((a.s_addr = inet_addr(d->hostname)) == INADDR_NONE) { - // struct in_addr *hn; + gg_debug(GG_DEBUG_MISC, "// gg_resolve_win32thread_thread() count = %d\n", addr_count); - // if (!(hn = gg_gethostbyname(d->hostname))) - // a.s_addr = INADDR_NONE; - // else { - // a.s_addr = hn->s_addr; - // free(hn); - // } - // } - - write(d->fd, &a, sizeof(a)); + write(d->fd, addr_list, (addr_count+1) * sizeof(struct in_addr)); close(d->fd); free(d->hostname); @@ -383,6 +481,9 @@ free(d); + if (addr_list != addr_ip) + free(addr_list); + gg_debug(GG_DEBUG_MISC, "// gg_resolve_win32thread_thread() done\n"); return 0; @@ -509,7 +610,6 @@ static int gg_resolver_fork_start(int *fd, void **priv_data, const char *hostname) { struct gg_resolver_fork_data *data = NULL; - struct in_addr addr; int pipes[2], new_errno; gg_debug(GG_DEBUG_FUNCTION, "** gg_resolver_fork_start(%p, %p, \"%s\");\n", fd, priv_data, hostname); @@ -543,17 +643,10 @@ if (data->pid == 0) { close(pipes[0]); - if ((addr.s_addr = inet_addr(hostname)) == INADDR_NONE) { - /* W przypadku błędu gg_gethostbyname_real() zwróci -1 - * i nie zmieni &addr. Tam jest już INADDR_NONE, - * więc nie musimy robić nic więcej. */ - gg_gethostbyname_real(hostname, &addr, 0); - } - - if (write(pipes[1], &addr, sizeof(addr)) != sizeof(addr)) + if (gg_resolver_run(pipes[1], hostname) == -1) _exit(1); - - _exit(0); + else + _exit(0); } close(pipes[1]); @@ -660,21 +753,13 @@ static void *gg_resolver_pthread_thread(void *arg) { struct gg_resolver_pthread_data *data = arg; - struct in_addr addr; pthread_detach(pthread_self()); - if ((addr.s_addr = inet_addr(data->hostname)) == INADDR_NONE) { - /* W przypadku błędu gg_gethostbyname_real() zwróci -1 - * i nie zmieni &addr. Tam jest już INADDR_NONE, - * więc nie musimy robić nic więcej. */ - gg_gethostbyname_real(data->hostname, &addr, 1); - } - - if (write(data->wfd, &addr, sizeof(addr)) == sizeof(addr)) + if (gg_resolver_run(data->wfd, data->hostname) == -1) + pthread_exit((void*) -1); + else pthread_exit(NULL); - else - pthread_exit((void*) -1); return NULL; /* żeby kompilator nie marudził */ } @@ -745,10 +830,10 @@ return 0; cleanup: - if (data) { + if (data != NULL) free(data->hostname); - free(data); - } + + free(data); close(pipes[0]); close(pipes[1]); @@ -770,10 +855,7 @@ */ int gg_session_set_resolver(struct gg_session *gs, gg_resolver_t type) { - if (gs == NULL) { - errno = EINVAL; - return -1; - } + GG_SESSION_CHECK(gs, -1); if (type == GG_RESOLVER_DEFAULT) { if (gg_global_resolver_type != GG_RESOLVER_DEFAULT) { @@ -796,13 +878,13 @@ switch (type) { #ifdef _WIN32 - case GG_RESOLVER_WIN32: + case GG_RESOLVER_WIN32: gs->resolver_type = type; gs->resolver_start = gg_resolve_win32thread; gs->resolver_cleanup = gg_resolve_win32thread_cleanup; return 0; #else - case GG_RESOLVER_FORK: + case GG_RESOLVER_FORK: gs->resolver_type = type; gs->resolver_start = gg_resolver_fork_start; gs->resolver_cleanup = gg_resolver_fork_cleanup; @@ -832,10 +914,7 @@ */ gg_resolver_t gg_session_get_resolver(struct gg_session *gs) { - if (gs == NULL) { - errno = EINVAL; - return GG_RESOLVER_INVALID; - } + GG_SESSION_CHECK(gs, (gg_resolver_t) -1); return gs->resolver_type; } @@ -847,11 +926,32 @@ * \param resolver_start Funkcja rozpoczynająca rozwiązywanie nazwy * \param resolver_cleanup Funkcja zwalniająca zasoby * + * Parametry funkcji rozpoczynającej rozwiązywanie nazwy wyglądają następująco: + * - \c "int *fd" — wskaźnik na zmienną, gdzie zostanie umieszczony deskryptor potoku + * - \c "void **priv_data" — wskaźnik na zmienną, gdzie można umieścić wskaźnik do prywatnych danych na potrzeby rozwiązywania nazwy + * - \c "const char *name" — nazwa serwera do rozwiązania + * + * Parametry funkcji zwalniającej zasoby wyglądają następująco: + * - \c "void **priv_data" — wskaźnik na zmienną przechowującą wskaźnik do prywatnych danych, należy go ustawić na \c NULL po zakończeniu + * - \c "int force" — flaga mówiąca o tym, że zasoby są zwalniane przed zakończeniem rozwiązywania nazwy, np. z powodu zamknięcia sesji. + * + * Własny kod rozwiązywania nazwy powinien stworzyć potok, parę gniazd lub + * inny deskryptor pozwalający na co najmniej jednostronną komunikację i + * przekazać go w parametrze \c fd. Po zakończeniu rozwiązywania nazwy, + * powinien wysłać otrzymany adres IP w postaci sieciowej (big-endian) do + * deskryptora. Jeśli rozwiązywanie nazwy się nie powiedzie, należy wysłać + * \c INADDR_NONE. Następnie zostanie wywołana funkcja zwalniająca zasoby + * z parametrem \c force równym \c 0. Gdyby sesja została zakończona przed + * rozwiązaniem nazwy, np. za pomocą funkcji \c gg_logoff(), funkcja + * zwalniająca zasoby zostanie wywołana z parametrem \c force równym \c 1. + * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_session_set_custom_resolver(struct gg_session *gs, int (*resolver_start)(int*, void**, const char*), void (*resolver_cleanup)(void**, int)) { - if (gs == NULL || resolver_start == NULL || resolver_cleanup == NULL) { + GG_SESSION_CHECK(gs, -1); + + if (resolver_start == NULL || resolver_cleanup == NULL) { errno = EINVAL; return -1; } @@ -899,13 +999,13 @@ switch (type) { #ifdef _WIN32 - case GG_RESOLVER_WIN32: + case GG_RESOLVER_WIN32: gh->resolver_type = type; gh->resolver_start = gg_resolve_win32thread; gh->resolver_cleanup = gg_resolve_win32thread_cleanup; return 0; #else - case GG_RESOLVER_FORK: + case GG_RESOLVER_FORK: gh->resolver_type = type; gh->resolver_start = gg_resolver_fork_start; gh->resolver_cleanup = gg_resolver_fork_cleanup; @@ -1028,24 +1128,7 @@ * \param resolver_start Funkcja rozpoczynająca rozwiązywanie nazwy * \param resolver_cleanup Funkcja zwalniająca zasoby * - * Parametry funkcji rozpoczynającej rozwiązywanie nazwy wyglądają następująco: - * - \c "int *fd" — wskaźnik na zmienną, gdzie zostanie umieszczony deskryptor potoku - * - \c "void **priv_data" — wskaźnik na zmienną, gdzie można umieścić wskaźnik do prywatnych danych na potrzeby rozwiązywania nazwy - * - \c "const char *name" — nazwa serwera do rozwiązania - * - * Parametry funkcji zwalniającej zasoby wyglądają następująco: - * - \c "void **priv_data" — wskaźnik na zmienną przechowującą wskaźnik do prywatnych danych, należy go ustawić na \c NULL po zakończeniu - * - \c "int force" — flaga mówiąca o tym, że zasoby są zwalniane przed zakończeniem rozwiązywania nazwy, np. z powodu zamknięcia sesji. - * - * Własny kod rozwiązywania nazwy powinien stworzyć potok, parę gniazd lub - * inny deskryptor pozwalający na co najmniej jednostronną komunikację i - * przekazać go w parametrze \c fd. Po zakończeniu rozwiązywania nazwy, - * powinien wysłać otrzymany adres IP w postaci sieciowej (big-endian) do - * deskryptora. Jeśli rozwiązywanie nazwy się nie powiedzie, należy wysłać - * \c INADDR_NONE. Następnie zostanie wywołana funkcja zwalniająca zasoby - * z parametrem \c force równym \c 0. Gdyby sesja została zakończona przed - * rozwiązaniem nazwy, np. za pomocą funkcji \c gg_logoff(), funkcja - * zwalniająca zasoby zostanie wywołana z parametrem \c force równym \c 1. + * Patrz \ref gg_session_set_custom_resolver. * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/lib/resolver.h --- a/libpurple/protocols/gg/lib/resolver.h Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/gg/lib/resolver.h Fri Apr 01 13:47:51 2011 +0900 @@ -1,5 +1,3 @@ -/* $Id$ */ - /* * (C) Copyright 2008 Wojtek Kaniewski * @@ -25,6 +23,6 @@ # include #endif -int gg_gethostbyname_real(const char *hostname, struct in_addr *result, int pthread); +int gg_gethostbyname_real(const char *hostname, struct in_addr **result, int *count, int pthread); #endif /* LIBGADU_RESOLVER_H */ diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/gg/lib/session.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/gg/lib/session.h Fri Apr 01 13:47:51 2011 +0900 @@ -0,0 +1,70 @@ +/* + * (C) Copyright 2008-2010 Wojtek Kaniewski + * + * This program 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 program 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef LIBGADU_SESSION_H +#define LIBGADU_SESSION_H + +#ifdef GG_CONFIG_HAVE_GNUTLS +# include +#endif + +#define GG_SESSION_CHECK(gs, result) \ + do { \ + if ((gs) == NULL) { \ + errno = EINVAL; \ + return (result); \ + } \ + } while (0) + +#define GG_SESSION_CHECK_CONNECTED(gs, result) \ + do { \ + GG_SESSION_CHECK(gs, result); \ + \ + if (!GG_SESSION_IS_CONNECTED(gs)) { \ + errno = ENOTCONN; \ + return (result); \ + } \ + } while (0) + +#define GG_SESSION_IS_PROTOCOL_7_7(gs) ((gs)->protocol_version >= 0x2a) +#define GG_SESSION_IS_PROTOCOL_8_0(gs) ((gs)->protocol_version >= 0x2d) + +#define GG_SESSION_IS_IDLE(gs) ((gs)->state == GG_STATE_IDLE) +#define GG_SESSION_IS_CONNECTING(gs) ((gs)->state != GG_STATE_IDLE && (gs)->state != GG_STATE_CONNECTED) +#define GG_SESSION_IS_CONNECTED(gs) ((gs)->state == GG_STATE_CONNECTED) + +#ifdef GG_CONFIG_HAVE_GNUTLS + +typedef struct { + gnutls_session_t session; + gnutls_certificate_credentials_t xcred; +} gg_session_gnutls_t; + +#define GG_SESSION_GNUTLS(gs) ((gg_session_gnutls_t*) (gs)->ssl)->session + +#endif /* GG_CONFIG_HAVE_GNUTLS */ + +#ifdef GG_CONFIG_HAVE_OPENSSL + +#define GG_SESSION_OPENSSL(gs) ((SSL*) (gs)->ssl) + +#endif /* GG_CONFIG_HAVE_OPENSSL */ + +int gg_session_handle_packet(struct gg_session *gs, uint32_t type, const char *ptr, size_t len, struct gg_event *ge); + +#endif /* LIBGADU_SESSION_H */ diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/irc/irc.c --- a/libpurple/protocols/irc/irc.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/irc/irc.c Fri Apr 01 13:47:51 2011 +0900 @@ -990,7 +990,9 @@ NULL, /* get_media_caps */ NULL, /* get_moods */ NULL, /* set_public_alias */ - NULL /* get_public_alias */ + NULL, /* get_public_alias */ + NULL, /* add_buddy_with_invite */ + NULL /* add_buddies_with_invite */ }; static gboolean load_plugin (PurplePlugin *plugin) { diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/jabber/jabber.h --- a/libpurple/protocols/jabber/jabber.h Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/jabber/jabber.h Fri Apr 01 13:47:51 2011 +0900 @@ -100,7 +100,7 @@ { int fd; - PurpleSrvQueryData *srv_query_data; + PurpleSrvTxtQueryData *srv_query_data; xmlParserCtxt *context; xmlnode *current; diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/jabber/libxmpp.c --- a/libpurple/protocols/jabber/libxmpp.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/jabber/libxmpp.c Fri Apr 01 13:47:51 2011 +0900 @@ -129,7 +129,9 @@ jabber_get_media_caps, /* get_media_caps */ jabber_get_moods, /* get_moods */ NULL, /* set_public_alias */ - NULL /* get_public_alias */ + NULL, /* get_public_alias */ + NULL, /* add_buddy_with_invite */ + NULL /* add_buddies_with_invite */ }; static gboolean load_plugin(PurplePlugin *plugin) diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/msn/msn.c --- a/libpurple/protocols/msn/msn.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/msn/msn.c Fri Apr 01 13:47:51 2011 +0900 @@ -95,13 +95,6 @@ MsnObject *obj; } MsnEmoticon; -typedef struct -{ - PurpleConnection *pc; - PurpleBuddy *buddy; - PurpleGroup *group; -} MsnAddReqData; - static const char * msn_normalize(const PurpleAccount *account, const char *str) { @@ -1807,29 +1800,18 @@ } static void -finish_auth_request(MsnAddReqData *data, char *msg) +msn_add_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group, const char *message) { - PurpleConnection *pc; - PurpleBuddy *buddy; - PurpleGroup *group; PurpleAccount *account; + const char *bname, *gname; MsnSession *session; MsnUserList *userlist; - const char *who, *gname; MsnUser *user; - pc = data->pc; - buddy = data->buddy; - group = data->group; - g_free(data); - account = purple_connection_get_account(pc); - session = pc->proto_data; - userlist = session->userlist; - - who = msn_normalize(account, purple_buddy_get_name(buddy)); - gname = group ? purple_group_get_name(group) : NULL; - purple_debug_info("msn", "Add user:%s to group:%s\n", who, gname ? gname : "(null)"); + session = purple_connection_get_protocol_data(pc); + bname = purple_buddy_get_name(buddy); + if (!session->logged_in) { purple_debug_error("msn", "msn_add_buddy called before connected\n"); @@ -1843,22 +1825,47 @@ * KingAnt: But PurpleBuddys must always exist inside PurpleGroups, so * won't group always be non-NULL here? */ - user = msn_userlist_find_user(userlist, who); + bname = msn_normalize(account, bname); + gname = group ? purple_group_get_name(group) : NULL; + purple_debug_info("msn", "Add user:%s to group:%s\n", + bname, gname ? gname : "(null)"); + + if (!msn_email_is_valid(bname)) { + gchar *buf; + buf = g_strdup_printf(_("Unable to add the buddy %s because the username is invalid. Usernames must be valid email addresses."), bname); + if (!purple_conv_present_error(bname, account, buf)) + purple_notify_error(pc, NULL, _("Unable to Add"), buf); + g_free(buf); + + /* Remove from local list */ + purple_blist_remove_buddy(buddy); + + return; + } + + /* Make sure name is normalized */ + purple_blist_rename_buddy(buddy, bname); + + userlist = session->userlist; + user = msn_userlist_find_user(userlist, bname); + if (user && user->authorized) { + message = NULL; + } if ((user != NULL) && (user->networkid != MSN_NETWORK_UNKNOWN)) { /* We already know this buddy and their network. This function knows what to do with users already in the list and stuff... */ - msn_user_set_invite_message(user, msg); - msn_userlist_add_buddy(userlist, who, gname); + msn_user_set_invite_message(user, message); + msn_userlist_add_buddy(userlist, bname, gname); } else { char **tokens; char *fqy; /* We need to check the network for this buddy first */ - user = msn_user_new(userlist, who, NULL); - msn_user_set_invite_message(user, msg); + user = msn_user_new(userlist, bname, NULL); + msn_user_set_invite_message(user, message); msn_user_set_pending_group(user, gname); msn_user_set_network(user, MSN_NETWORK_UNKNOWN); /* Should probably re-use the msn_add_contact_xml function here */ - tokens = g_strsplit(who, "@", 2); + tokens = g_strsplit(bname, "@", 2); fqy = g_strdup_printf("", tokens[1], tokens[0]); @@ -1872,57 +1879,6 @@ } static void -cancel_auth_request(MsnAddReqData *data, char *msg) -{ - /* Remove from local list */ - purple_blist_remove_buddy(data->buddy); - - g_free(data); -} - -static void -msn_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) -{ - const char *bname; - MsnAddReqData *data; - MsnSession *session; - MsnUser *user; - - bname = purple_buddy_get_name(buddy); - - if (!msn_email_is_valid(bname)) { - gchar *buf; - buf = g_strdup_printf(_("Unable to add the buddy %s because the username is invalid. Usernames must be valid email addresses."), bname); - if (!purple_conv_present_error(bname, purple_connection_get_account(gc), buf)) - purple_notify_error(gc, NULL, _("Unable to Add"), buf); - g_free(buf); - - /* Remove from local list */ - purple_blist_remove_buddy(buddy); - - return; - } - - data = g_new0(MsnAddReqData, 1); - data->pc = gc; - data->buddy = buddy; - data->group = group; - - session = purple_connection_get_protocol_data(gc); - user = msn_userlist_find_user(session->userlist, bname); - if (user && user->authorized) { - finish_auth_request(data, NULL); - } else { - purple_request_input(gc, NULL, _("Authorization Request Message:"), - NULL, _("Please authorize me!"), TRUE, FALSE, NULL, - _("_OK"), G_CALLBACK(finish_auth_request), - _("_Cancel"), G_CALLBACK(cancel_auth_request), - purple_connection_get_account(gc), bname, NULL, - data); - } -} - -static void msn_rem_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) { MsnSession *session; @@ -3009,7 +2965,7 @@ static PurplePluginProtocolInfo prpl_info = { - OPT_PROTO_MAIL_CHECK, + OPT_PROTO_MAIL_CHECK|OPT_PROTO_INVITE_MESSAGE, NULL, /* user_splits */ NULL, /* protocol_options */ {"png,gif", 0, 0, 96, 96, 0, PURPLE_ICON_SCALE_SEND}, /* icon_spec */ @@ -3030,7 +2986,7 @@ msn_set_status, /* set_away */ msn_set_idle, /* set_idle */ NULL, /* change_passwd */ - msn_add_buddy, /* add_buddy */ + NULL, /* add_buddy */ NULL, /* add_buddies */ msn_rem_buddy, /* remove_buddy */ NULL, /* remove_buddies */ @@ -3080,7 +3036,9 @@ NULL, /* get_media_caps */ NULL, /* get_moods */ msn_set_public_alias, /* set_public_alias */ - msn_get_public_alias /* get_public_alias */ + msn_get_public_alias, /* get_public_alias */ + msn_add_buddy, /* add_buddy_with_invite */ + NULL /* add_buddies_with_invite */ }; static PurplePluginInfo info = diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/mxit/mxit.c --- a/libpurple/protocols/mxit/mxit.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/mxit/mxit.c Fri Apr 01 13:47:51 2011 +0900 @@ -742,7 +742,9 @@ mxit_media_caps, /* get_media_caps */ mxit_get_moods, /* get_moods */ NULL, /* set_public_alias */ - NULL /* get_public_alias */ + NULL, /* get_public_alias */ + NULL, /* add_buddy_with_invite */ + NULL /* add_buddies_with_invite */ }; diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/myspace/myspace.c --- a/libpurple/protocols/myspace/myspace.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/myspace/myspace.c Fri Apr 01 13:47:51 2011 +0900 @@ -3097,7 +3097,9 @@ NULL, /* get_media_caps */ NULL, /* get_moods */ NULL, /* set_public_alias */ - NULL /* get_public_alias */ + NULL, /* get_public_alias */ + NULL, /* add_buddy_with_invite */ + NULL /* add_buddies_with_invite */ }; /** diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/novell/novell.c --- a/libpurple/protocols/novell/novell.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/novell/novell.c Fri Apr 01 13:47:51 2011 +0900 @@ -3531,7 +3531,9 @@ NULL, /* get_media_caps */ NULL, /* get_moods */ NULL, /* set_public_alias */ - NULL /* get_public_alias */ + NULL, /* get_public_alias */ + NULL, /* add_buddy_with_invite */ + NULL /* add_buddies_with_invite */ }; static PurplePluginInfo info = { diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/null/nullprpl.c --- a/libpurple/protocols/null/nullprpl.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/null/nullprpl.c Fri Apr 01 13:47:51 2011 +0900 @@ -1120,9 +1120,11 @@ NULL, /* get_account_text_table */ NULL, /* initiate_media */ NULL, /* get_media_caps */ + NULL, /* get_moods */ NULL, /* set_public_alias */ NULL, /* get_public_alias */ - NULL /* get_moods */ + NULL, /* add_buddy_with_invite */ + NULL /* add_buddies_with_invite */ }; static void nullprpl_init(PurplePlugin *plugin) diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/oscar/authorization.c --- a/libpurple/protocols/oscar/authorization.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/oscar/authorization.c Fri Apr 01 13:47:51 2011 +0900 @@ -25,20 +25,19 @@ #include "oscar.h" #include "request.h" -static void -oscar_auth_request(struct name_data *data, char *msg) +/* When you ask other people for authorization */ +void +oscar_auth_sendrequest(PurpleConnection *gc, const char *bname, const char *msg) { - PurpleConnection *gc; OscarData *od; PurpleAccount *account; PurpleBuddy *buddy; PurpleGroup *group; - const char *bname, *gname; + const char *gname; - gc = data->gc; od = purple_connection_get_protocol_data(gc); account = purple_connection_get_account(gc); - buddy = purple_find_buddy(account, data->name); + buddy = purple_find_buddy(account, bname); if (buddy != NULL) group = purple_buddy_get_group(buddy); else @@ -46,11 +45,10 @@ if (group != NULL) { - bname = purple_buddy_get_name(buddy); gname = purple_group_get_name(group); purple_debug_info("oscar", "ssi: adding buddy %s to group %s\n", bname, gname); - aim_ssi_sendauthrequest(od, data->name, msg ? msg : _("Please authorize me so I can add you to my buddy list.")); + aim_ssi_sendauthrequest(od, bname, msg ? msg : _("Please authorize me so I can add you to my buddy list.")); if (!aim_ssi_itemlist_finditem(od->ssi.local, gname, bname, AIM_SSI_TYPE_BUDDY)) { aim_ssi_addbuddy(od, bname, gname, NULL, purple_buddy_get_alias_only(buddy), NULL, NULL, TRUE); @@ -66,8 +64,6 @@ } } } - - oscar_free_name_data(data); } static void @@ -105,24 +101,6 @@ data); } -/* When you ask other people for authorization */ -void -oscar_auth_sendrequest(PurpleConnection *gc, const char *name) -{ - struct name_data *data; - - data = g_new0(struct name_data, 1); - data->gc = gc; - data->name = g_strdup(name); - - purple_request_input(data->gc, NULL, _("Authorization Request Message:"), - NULL, _("Please authorize me!"), TRUE, FALSE, NULL, - _("_OK"), G_CALLBACK(oscar_auth_request), - _("_Cancel"), G_CALLBACK(oscar_free_name_data), - purple_connection_get_account(gc), name, NULL, - data); -} - void oscar_auth_sendrequest_menu(PurpleBlistNode *node, gpointer ignored) { @@ -133,7 +111,7 @@ buddy = (PurpleBuddy *) node; gc = purple_account_get_connection(purple_buddy_get_account(buddy)); - oscar_auth_sendrequest(gc, purple_buddy_get_name(buddy)); + oscar_auth_sendrequest(gc, purple_buddy_get_name(buddy), NULL); } /* When other people ask you for authorization */ @@ -150,4 +128,4 @@ purple_account_request_authorization(account, data->name, NULL, data->nick, reason, purple_find_buddy(account, data->name) != NULL, oscar_auth_grant, oscar_auth_dontgrant_msgprompt, data); -} \ No newline at end of file +} diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/oscar/encoding.c --- a/libpurple/protocols/oscar/encoding.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/oscar/encoding.c Fri Apr 01 13:47:51 2011 +0900 @@ -21,6 +21,60 @@ #include "encoding.h" static gchar * +encoding_multi_convert_to_utf8(const gchar *text, gssize textlen, const gchar *encodings, GError **error, gboolean fallback) +{ + gchar *utf8 = NULL; + const gchar *begin = encodings; + const gchar *end = NULL; + gchar *curr_encoding = NULL; /* allocated buffer for encoding name */ + const gchar *curr_encoding_ro = NULL; /* read-only encoding name */ + + if (!encodings) { + purple_debug_error("oscar", "encodings is NULL"); + return NULL; + } + + for (;;) + { + /* extract next encoding */ + end = strchr(begin, ','); + if (!end) { + curr_encoding_ro = begin; + } else { /* allocate buffer for encoding */ + curr_encoding = g_strndup(begin, end - begin); + if (!curr_encoding) { + purple_debug_error("oscar", "Error allocating memory for encoding"); + break; + } + curr_encoding_ro = curr_encoding; + } + + if (!g_ascii_strcasecmp(curr_encoding_ro, "utf-8") && g_utf8_validate(text, textlen, NULL)) { + break; + } + + utf8 = g_convert(text, textlen, "UTF-8", curr_encoding_ro, NULL, NULL, NULL); + + if (!end) /* last occurence. do not free curr_encoding: buffer was'nt allocated */ + break; + + g_free(curr_encoding); /* free allocated buffer for encoding here */ + + if (utf8) /* text was successfully converted */ + break; + + begin = end + 1; + } + + if (!utf8 && fallback) + { /* "begin" points to last encoding */ + utf8 = g_convert_with_fallback(text, textlen, "UTF-8", begin, "?", NULL, NULL, error); + } + + return utf8; +} + +static gchar * encoding_extract(const char *encoding) { char *begin, *end; @@ -84,7 +138,7 @@ } if (glib_encoding != NULL) { - utf8 = g_convert(text, textlen, "UTF-8", glib_encoding, NULL, NULL, NULL); + utf8 = encoding_multi_convert_to_utf8(text, textlen, glib_encoding, NULL, FALSE); } /* @@ -121,7 +175,7 @@ charset = purple_account_get_string(account, "encoding", NULL); if(charset && *charset) - ret = g_convert(msg, -1, "UTF-8", charset, NULL, NULL, NULL); + ret = encoding_multi_convert_to_utf8(msg, -1, charset, NULL, FALSE); if(!ret) ret = purple_utf8_try_convert(msg); @@ -141,10 +195,7 @@ return NULL; if (g_ascii_strcasecmp("UTF-8", charsetstr)) { - if (fallback) - ret = g_convert_with_fallback(data, datalen, "UTF-8", charsetstr, "?", NULL, NULL, &err); - else - ret = g_convert(data, datalen, "UTF-8", charsetstr, NULL, NULL, &err); + ret = encoding_multi_convert_to_utf8(data, datalen, charsetstr, &err, fallback); if (err != NULL) { purple_debug_warning("oscar", "Conversion from %s failed: %s.\n", charsetstr, err->message); diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/oscar/family_feedbag.c --- a/libpurple/protocols/oscar/family_feedbag.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/oscar/family_feedbag.c Fri Apr 01 13:47:51 2011 +0900 @@ -1682,7 +1682,7 @@ * granted, denied, or dropped. * */ -int aim_ssi_sendauthrequest(OscarData *od, char *bn, const char *msg) +int aim_ssi_sendauthrequest(OscarData *od, const char *bn, const char *msg) { FlapConnection *conn; ByteStream bs; @@ -1759,7 +1759,7 @@ * if reply=0x01 then grant * */ -int aim_ssi_sendauthreply(OscarData *od, char *bn, guint8 reply, const char *msg) +int aim_ssi_sendauthreply(OscarData *od, const char *bn, guint8 reply, const char *msg) { FlapConnection *conn; ByteStream bs; diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/oscar/libaim.c --- a/libpurple/protocols/oscar/libaim.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/oscar/libaim.c Fri Apr 01 13:47:51 2011 +0900 @@ -29,7 +29,7 @@ static PurplePluginProtocolInfo prpl_info = { - OPT_PROTO_MAIL_CHECK | OPT_PROTO_IM_IMAGE, + OPT_PROTO_MAIL_CHECK | OPT_PROTO_IM_IMAGE | OPT_PROTO_INVITE_MESSAGE, NULL, /* user_splits */ NULL, /* protocol_options */ {"gif,jpeg,bmp,ico", 0, 0, 100, 100, 7168, PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */ @@ -50,7 +50,7 @@ oscar_set_status, /* set_status */ oscar_set_idle, /* set_idle */ oscar_change_passwd, /* change_passwd */ - oscar_add_buddy, /* add_buddy */ + NULL, /* add_buddy */ NULL, /* add_buddies */ oscar_remove_buddy, /* remove_buddy */ NULL, /* remove_buddies */ @@ -100,7 +100,9 @@ NULL, /* get_media_caps */ NULL, /* get_moods */ NULL, /* set_public_alias */ - NULL /* get_public_alias */ + NULL, /* get_public_alias */ + oscar_add_buddy, /* add_buddy_with_invite */ + NULL /* add_buddies_with_invite */ }; static PurplePluginInfo info = diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/oscar/libicq.c --- a/libpurple/protocols/oscar/libicq.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/oscar/libicq.c Fri Apr 01 13:47:51 2011 +0900 @@ -38,7 +38,7 @@ static PurplePluginProtocolInfo prpl_info = { - OPT_PROTO_MAIL_CHECK | OPT_PROTO_IM_IMAGE, + OPT_PROTO_MAIL_CHECK | OPT_PROTO_IM_IMAGE | OPT_PROTO_INVITE_MESSAGE, NULL, /* user_splits */ NULL, /* protocol_options */ {"gif,jpeg,bmp,ico", 0, 0, 100, 100, 7168, PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */ @@ -59,7 +59,7 @@ oscar_set_status, /* set_status */ oscar_set_idle, /* set_idle */ oscar_change_passwd, /* change_passwd */ - oscar_add_buddy, /* add_buddy */ + NULL, /* add_buddy */ NULL, /* add_buddies */ oscar_remove_buddy, /* remove_buddy */ NULL, /* remove_buddies */ @@ -110,7 +110,9 @@ NULL, /* can_do_media */ oscar_get_purple_moods, /* get_moods */ NULL, /* set_public_alias */ - NULL /* get_public_alias */ + NULL, /* get_public_alias */ + oscar_add_buddy, /* add_buddy_with_invite */ + NULL /* add_buddies_with_invite */ }; static PurplePluginInfo info = diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/oscar/oscar.c --- a/libpurple/protocols/oscar/oscar.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/oscar/oscar.c Fri Apr 01 13:47:51 2011 +0900 @@ -3660,7 +3660,9 @@ purple_debug_info("oscar", "Set status to %s\n", purple_status_get_name(status)); - if (!purple_status_is_active(status)) + /* Either setting a new status active or setting a status inactive. + * (Only possible for independent status (i.e. X-Status moods.) */ + if (!purple_status_is_active(status) && !purple_status_is_independent(status)) return; if (!purple_account_is_connected(account)) @@ -3685,7 +3687,8 @@ } void -oscar_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) { +oscar_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group, const char *msg) +{ OscarData *od; PurpleAccount *account; const char *bname, *gname; @@ -3725,7 +3728,7 @@ aim_ssi_itemlist_findparentname(od->ssi.local, bname), bname)) { /* Not authorized -- Re-request authorization */ - oscar_auth_sendrequest(gc, bname); + oscar_auth_sendrequest(gc, bname, msg); } } @@ -4175,7 +4178,7 @@ case 0x000e: { /* buddy requires authorization */ if ((retval->action == SNAC_SUBTYPE_FEEDBAG_ADD) && (retval->name)) - oscar_auth_sendrequest(gc, retval->name); + oscar_auth_sendrequest(gc, retval->name, NULL); } break; default: { /* La la la */ @@ -5060,16 +5063,22 @@ { PurpleBuddy *buddy; PurpleConnection *gc; + OscarData *od; PurpleAccount *account; + const char *bname; g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); buddy = (PurpleBuddy *)node; - gc = purple_account_get_connection(buddy->account); - account = purple_connection_get_account(gc); - purple_debug_info("oscar", "Manual X-Status Get From %s to %s:\n", purple_buddy_get_name(buddy), account->username); - - icq_im_xstatus_request(gc->proto_data, purple_buddy_get_name(buddy)); + bname = purple_buddy_get_name(buddy); + + account = purple_buddy_get_account(buddy); + gc = purple_account_get_connection(account); + od = purple_connection_get_protocol_data(gc); + + purple_debug_info("oscar", "Manual X-Status Get From %s to %s:\n", bname, purple_account_get_username(account)); + + icq_im_xstatus_request(od, bname); } static void diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/oscar/oscar.h --- a/libpurple/protocols/oscar/oscar.h Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/oscar/oscar.h Fri Apr 01 13:47:51 2011 +0900 @@ -913,8 +913,8 @@ /* 0x0007 */ int aim_ssi_enable(OscarData *od); /* 0x0011 */ int aim_ssi_modbegin(OscarData *od); /* 0x0012 */ int aim_ssi_modend(OscarData *od); -/* 0x0018 */ int aim_ssi_sendauthrequest(OscarData *od, char *bn, const char *msg); -/* 0x001a */ int aim_ssi_sendauthreply(OscarData *od, char *bn, guint8 reply, const char *msg); +/* 0x0018 */ int aim_ssi_sendauthrequest(OscarData *od, const char *bn, const char *msg); +/* 0x001a */ int aim_ssi_sendauthreply(OscarData *od, const char *bn, guint8 reply, const char *msg); /* Client functions for retrieving SSI data */ struct aim_ssi_item *aim_ssi_itemlist_find(struct aim_ssi_item *list, guint16 gid, guint16 bid); @@ -1312,7 +1312,7 @@ void oscar_user_info_display_aim(OscarData *od, aim_userinfo_t *userinfo); /* authorization.c - OSCAR authorization requests */ -void oscar_auth_sendrequest(PurpleConnection *gc, const char *name); +void oscar_auth_sendrequest(PurpleConnection *gc, const char *name, const char *msg); void oscar_auth_sendrequest_menu(PurpleBlistNode *node, gpointer ignored); void oscar_auth_recvrequest(PurpleConnection *gc, gchar *name, gchar *nick, gchar *reason); diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/oscar/oscarcommon.h --- a/libpurple/protocols/oscar/oscarcommon.h Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/oscar/oscarcommon.h Fri Apr 01 13:47:51 2011 +0900 @@ -79,7 +79,7 @@ void oscar_set_status(PurpleAccount *account, PurpleStatus *status); void oscar_set_idle(PurpleConnection *gc, int time); void oscar_change_passwd(PurpleConnection *gc, const char *old, const char *new); -void oscar_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group); +void oscar_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group, const char *msg); void oscar_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group); void oscar_add_permit(PurpleConnection *gc, const char *who); void oscar_add_deny(PurpleConnection *gc, const char *who); diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/oscar/userinfo.c --- a/libpurple/protocols/oscar/userinfo.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/oscar/userinfo.c Fri Apr 01 13:47:51 2011 +0900 @@ -437,6 +437,10 @@ tm->tm_mon = (int)info->birthmonth - 1; tm->tm_year = (int)info->birthyear - 1900; + /* Ignore dst setting of today to avoid timezone shift between + * dates in summer and winter time. */ + tm->tm_isdst = -1; + /* To be 100% sure that the fields are re-normalized. * If you're sure strftime() ALWAYS does this EVERYWHERE, * feel free to remove it. --rlaager */ diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/qq/qq.c --- a/libpurple/protocols/qq/qq.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/qq/qq.c Fri Apr 01 13:47:51 2011 +0900 @@ -1042,7 +1042,9 @@ NULL, /* get_media_caps */ NULL, /* get_moods */ NULL, /* set_public_alias */ - NULL /* get_public_alias */ + NULL, /* get_public_alias */ + NULL, /* add_buddy_with_invite */ + NULL /* add_buddies_with_invite */ }; static PurplePluginInfo info = { diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/silc/silc.c --- a/libpurple/protocols/silc/silc.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/silc/silc.c Fri Apr 01 13:47:51 2011 +0900 @@ -2123,7 +2123,9 @@ NULL, /* get_media_caps */ NULL, /* get_moods */ NULL, /* set_public_alias */ - NULL /* get_public_alias */ + NULL, /* get_public_alias */ + NULL, /* add_buddy_with_invite */ + NULL /* add_buddies_with_invite */ }; static PurplePluginInfo info = diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/silc10/silc.c --- a/libpurple/protocols/silc10/silc.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/silc10/silc.c Fri Apr 01 13:47:51 2011 +0900 @@ -1845,7 +1845,9 @@ NULL, /* get_media_caps */ NULL, /* get_moods */ NULL, /* set_public_alias */ - NULL /* get_public_alias */ + NULL, /* get_public_alias */ + NULL, /* add_buddy_with_invite */ + NULL /* add_buddies_with_invite */ }; static PurplePluginInfo info = diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/simple/simple.c --- a/libpurple/protocols/simple/simple.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/simple/simple.c Fri Apr 01 13:47:51 2011 +0900 @@ -2112,7 +2112,9 @@ NULL, /* get_media_caps */ NULL, /* get_moods */ NULL, /* set_public_alias */ - NULL /* get_public_alias */ + NULL, /* get_public_alias */ + NULL, /* add_buddy_with_invite */ + NULL /* add_buddies_with_invite */ }; diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/simple/simple.h --- a/libpurple/protocols/simple/simple.h Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/simple/simple.h Fri Apr 01 13:47:51 2011 +0900 @@ -83,7 +83,7 @@ gchar *username; gchar *password; PurpleDnsQueryData *query_data; - PurpleSrvQueryData *srv_query_data; + PurpleSrvTxtQueryData *srv_query_data; PurpleNetworkListenData *listen_data; int fd; int cseq; diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/yahoo/libyahoo.c --- a/libpurple/protocols/yahoo/libyahoo.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/yahoo/libyahoo.c Fri Apr 01 13:47:51 2011 +0900 @@ -268,7 +268,9 @@ NULL, /* get_media_caps */ NULL, /* get_moods */ NULL, /* set_public_alias */ - NULL /* get_public_alias */ + NULL, /* get_public_alias */ + NULL, /* add_buddy_with_invite */ + NULL /* add_buddies_with_invite */ }; static PurplePluginInfo info = diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/yahoo/libyahoojp.c --- a/libpurple/protocols/yahoo/libyahoojp.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/yahoo/libyahoojp.c Fri Apr 01 13:47:51 2011 +0900 @@ -164,7 +164,9 @@ NULL, /* get_media_caps */ NULL, /* get_moods */ NULL, /* set_public_alias */ - NULL /* get_public_alias */ + NULL, /* get_public_alias */ + NULL, /* add_buddy_with_invite */ + NULL /* add_buddies_with_invite */ }; static PurplePluginInfo info = diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/protocols/zephyr/zephyr.c --- a/libpurple/protocols/zephyr/zephyr.c Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/protocols/zephyr/zephyr.c Fri Apr 01 13:47:51 2011 +0900 @@ -2911,7 +2911,9 @@ NULL, /* get_media_caps */ NULL, /* get_moods */ NULL, /* set_public_alias */ - NULL /* get_public_alias */ + NULL, /* get_public_alias */ + NULL, /* add_buddy_with_invite */ + NULL /* add_buddies_with_invite */ }; static PurplePluginInfo info = { diff -r 7281d151e492 -r 8b9e9c61d061 libpurple/prpl.h --- a/libpurple/prpl.h Thu Mar 17 20:25:26 2011 +0900 +++ b/libpurple/prpl.h Fri Apr 01 13:47:51 2011 +0900 @@ -202,7 +202,14 @@ * Used as a hint that unknown commands should not be sent as messages. * @since 2.1.0 */ - OPT_PROTO_SLASH_COMMANDS_NATIVE = 0x00000400 + OPT_PROTO_SLASH_COMMANDS_NATIVE = 0x00000400, + + /** + * Indicates that this protocol supports sending a user-supplied message + * along with an invitation. + * @since 2.8.0 + */ + OPT_PROTO_INVITE_MESSAGE = 0x00000800 } PurpleProtocolOptions; @@ -333,6 +340,9 @@ * already in the specified group. If the protocol supports * authorization and the user is not already authorized to see the * status of \a buddy, \a add_buddy should request authorization. + * + * @deprecated Since 2.8.0, add_buddy_with_invite is preferred. + * @see add_buddy_with_invite */ void (*add_buddy)(PurpleConnection *, PurpleBuddy *buddy, PurpleGroup *group); void (*add_buddies)(PurpleConnection *, GList *buddies, GList *groups); @@ -622,6 +632,21 @@ void (*get_public_alias)(PurpleConnection *gc, PurpleGetPublicAliasSuccessCallback success_cb, PurpleGetPublicAliasFailureCallback failure_cb); + + /** + * Add a buddy to a group on the server. + * + * This PRPL function may be called in situations in which the buddy is + * already in the specified group. If the protocol supports + * authorization and the user is not already authorized to see the + * status of \a buddy, \a add_buddy should request authorization. + * + * If authorization is required, then use the supplied invite message. + * + * @since 2.8.0 + */ + void (*add_buddy_with_invite)(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group, const char *message); + void (*add_buddies_with_invite)(PurpleConnection *pc, GList *buddies, GList *groups, const char *message); }; #define PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(prpl, member) \ diff -r 7281d151e492 -r 8b9e9c61d061 pidgin/gtkaccount.c --- a/pidgin/gtkaccount.c Thu Mar 17 20:25:26 2011 +0900 +++ b/pidgin/gtkaccount.c Fri Apr 01 13:47:51 2011 +0900 @@ -46,6 +46,7 @@ #include "gtkutils.h" #include "gtkstatusbox.h" #include "pidginstock.h" +#include "minidialog.h" enum { @@ -1173,7 +1174,7 @@ add_voice_options(AccountPrefsDialog *dialog) { #ifdef USE_VV - if (!dialog->prpl_info || !dialog->prpl_info->initiate_media) { + if (!dialog->prpl_info || !PURPLE_PROTOCOL_PLUGIN_HAS_FUNC(dialog->prpl_info, initiate_media)) { if (dialog->voice_frame) { gtk_widget_destroy(dialog->voice_frame); dialog->voice_frame = NULL; @@ -2501,6 +2502,28 @@ ar->deny_cb(ar->data); } +static gboolean +get_user_info_cb(GtkWidget *label, + const gchar *uri, + gpointer data) +{ + struct auth_request *ar = data; + if (!strcmp(uri, "viewinfo")) { + pidgin_retrieve_user_info(purple_account_get_connection(ar->account), ar->username); + return TRUE; + } + return FALSE; +} + +static void +send_im_cb(PidginMiniDialog *mini_dialog, + GtkButton *button, + gpointer data) +{ + struct auth_request *ar = data; + pidgin_dialogs_im_with_user(ar->account, ar->username); +} + static void * pidgin_accounts_request_authorization(PurpleAccount *account, const char *remote_user, @@ -2515,27 +2538,47 @@ char *buffer; PurpleConnection *gc; GtkWidget *alert; + PidginMiniDialog *dialog; GdkPixbuf *prpl_icon; struct auth_request *aa; + const char *our_name; gboolean have_valid_alias = alias && *alias; gc = purple_account_get_connection(account); if (message != NULL && *message == '\0') message = NULL; - buffer = g_strdup_printf(_("%s%s%s%s wants to add you (%s) to his or her buddy list%s%s"), - remote_user, - (have_valid_alias ? " (" : ""), - (have_valid_alias ? alias : ""), - (have_valid_alias ? ")" : ""), - (id != NULL - ? id - : (purple_connection_get_display_name(gc) != NULL - ? purple_connection_get_display_name(gc) - : purple_account_get_username(account))), - (message != NULL ? ": " : "."), - (message != NULL ? message : "")); - + our_name = (id != NULL) ? id : + (purple_connection_get_display_name(gc) != NULL) ? purple_connection_get_display_name(gc) : + purple_account_get_username(account); + + if (pidgin_mini_dialog_links_supported()) { + char *escaped_remote_user = g_markup_escape_text(remote_user, -1); + char *escaped_alias = alias != NULL ? g_markup_escape_text(alias, -1) : g_strdup(""); + char *escaped_our_name = g_markup_escape_text(our_name, -1); + char *escaped_message = message != NULL ? g_markup_escape_text(message, -1) : g_strdup(""); + buffer = g_strdup_printf(_("%s%s%s%s wants to add you (%s) to his or her buddy list%s%s"), + escaped_remote_user, + (have_valid_alias ? " (" : ""), + escaped_alias, + (have_valid_alias ? ")" : ""), + escaped_our_name, + (have_valid_alias ? ": " : "."), + escaped_message); + g_free(escaped_remote_user); + g_free(escaped_alias); + g_free(escaped_our_name); + g_free(escaped_message); + } else { + buffer = g_strdup_printf(_("%s%s%s%s wants to add you (%s) to his or her buddy list%s%s"), + remote_user, + (have_valid_alias ? " (" : ""), + (have_valid_alias ? alias : ""), + (have_valid_alias ? ")" : ""), + our_name, + (message != NULL ? ": " : "."), + (message != NULL ? message : "")); + } prpl_icon = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL); @@ -2550,11 +2593,19 @@ alert = pidgin_make_mini_dialog_with_custom_icon( gc, prpl_icon, - _("Authorize buddy?"), buffer, aa, + _("Authorize buddy?"), NULL, aa, _("Authorize"), authorize_and_add_cb, _("Deny"), deny_no_add_cb, NULL); + dialog = PIDGIN_MINI_DIALOG(alert); + if (pidgin_mini_dialog_links_supported()) { + pidgin_mini_dialog_enable_description_markup(dialog); + pidgin_mini_dialog_set_link_callback(dialog, G_CALLBACK(get_user_info_cb), aa); + } + pidgin_mini_dialog_set_description(dialog, buffer); + pidgin_mini_dialog_add_non_closing_button(dialog, _("Send Instant Message"), send_im_cb, aa); + g_signal_connect_swapped(G_OBJECT(alert), "destroy", G_CALLBACK(free_auth_request), aa); g_signal_connect(G_OBJECT(alert), "destroy", G_CALLBACK(purple_account_request_close), NULL); pidgin_blist_add_alert(alert); diff -r 7281d151e492 -r 8b9e9c61d061 pidgin/gtkblist.c --- a/pidgin/gtkblist.c Thu Mar 17 20:25:26 2011 +0900 +++ b/pidgin/gtkblist.c Fri Apr 01 13:47:51 2011 +0900 @@ -90,6 +90,7 @@ GtkWidget *combo; GtkWidget *entry; GtkWidget *entry_for_alias; + GtkWidget *entry_for_invite; } PidginAddBuddyData; @@ -5139,7 +5140,7 @@ account); if(err->type == PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT) - pidgin_mini_dialog_add_button(PIDGIN_MINI_DIALOG(mini_dialog), + pidgin_mini_dialog_add_non_closing_button(PIDGIN_MINI_DIALOG(mini_dialog), _("SSL FAQs"), ssl_faq_clicked_cb, NULL); g_signal_connect_after(mini_dialog, "destroy", @@ -6972,8 +6973,24 @@ add_buddy_select_account_cb(GObject *w, PurpleAccount *account, PidginAddBuddyData *data) { + PurpleConnection *pc = NULL; + PurplePlugin *prpl = NULL; + PurplePluginProtocolInfo *prpl_info = NULL; + gboolean invite_enabled = TRUE; + /* Save our account */ data->rq_data.account = account; + + if (account) + pc = purple_account_get_connection(account); + if (pc) + prpl = purple_connection_get_prpl(pc); + if (prpl) + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl); + if (prpl_info && !(prpl_info->options & OPT_PROTO_INVITE_MESSAGE)) + invite_enabled = FALSE; + + gtk_widget_set_sensitive(data->entry_for_invite, invite_enabled); } static void @@ -6985,7 +7002,7 @@ static void add_buddy_cb(GtkWidget *w, int resp, PidginAddBuddyData *data) { - const char *grp, *who, *whoalias; + const char *grp, *who, *whoalias, *invite; PurpleAccount *account; PurpleGroup *g; PurpleBuddy *b; @@ -6999,6 +7016,9 @@ whoalias = gtk_entry_get_text(GTK_ENTRY(data->entry_for_alias)); if (*whoalias == '\0') whoalias = NULL; + invite = gtk_entry_get_text(GTK_ENTRY(data->entry_for_invite)); + if (*invite == '\0') + invite = NULL; account = data->rq_data.account; @@ -7024,7 +7044,7 @@ purple_blist_add_buddy(b, NULL, g, NULL); } - purple_account_add_buddy(account, b); + purple_account_add_buddy_with_invite(account, b, invite); /* Offer to merge people with the same alias. */ if (whoalias != NULL && g != NULL) @@ -7062,9 +7082,11 @@ { PidginAddBuddyData *data = g_new0(PidginAddBuddyData, 1); + if (account == NULL) + account = purple_connection_get_account(purple_connections_get_all()->data); + make_blist_request_dialog((PidginBlistRequestData *)data, - (account != NULL - ? account : purple_connection_get_account(purple_connections_get_all()->data)), + account, _("Add Buddy"), "add_buddy", _("Add a buddy.\n"), G_CALLBACK(add_buddy_select_account_cb), NULL, @@ -7108,11 +7130,19 @@ if (username != NULL) gtk_widget_grab_focus(GTK_WIDGET(data->entry_for_alias)); + data->entry_for_invite = gtk_entry_new(); + pidgin_add_widget_to_vbox(data->rq_data.vbox, _("(Optional) _Invite message:"), + data->rq_data.sg, data->entry_for_invite, TRUE, + NULL); + data->combo = pidgin_text_combo_box_entry_new(group, groups_tree()); pidgin_add_widget_to_vbox(data->rq_data.vbox, _("Add buddy to _group:"), data->rq_data.sg, data->combo, TRUE, NULL); gtk_widget_show_all(data->rq_data.window); + + /* Force update of invite message entry sensitivity */ + add_buddy_select_account_cb(NULL, account, data); } static void diff -r 7281d151e492 -r 8b9e9c61d061 pidgin/gtkconv.c --- a/pidgin/gtkconv.c Thu Mar 17 20:25:26 2011 +0900 +++ b/pidgin/gtkconv.c Fri Apr 01 13:47:51 2011 +0900 @@ -415,23 +415,21 @@ return PURPLE_CMD_RET_OK; } -static void clear_conversation_scrollback(PurpleConversation *conv) +static void clear_conversation_scrollback_cb(PurpleConversation *conv, + void *data) { PidginConversation *gtkconv = NULL; - GList *iter; gtkconv = PIDGIN_CONVERSATION(conv); - - gtk_imhtml_clear(GTK_IMHTML(gtkconv->imhtml)); - for (iter = gtkconv->convs; iter; iter = iter->next) - purple_conversation_clear_message_history(iter->data); + if (gtkconv) + gtk_imhtml_clear(GTK_IMHTML(gtkconv->imhtml)); } static PurpleCmdRet clear_command_cb(PurpleConversation *conv, const char *cmd, char **args, char **error, void *data) { - clear_conversation_scrollback(conv); + purple_conversation_clear_message_history(conv); return PURPLE_CMD_RET_OK; } @@ -439,7 +437,7 @@ clearall_command_cb(PurpleConversation *conv, const char *cmd, char **args, char **error, void *data) { - purple_conversation_foreach(clear_conversation_scrollback); + purple_conversation_foreach(purple_conversation_clear_message_history); return PURPLE_CMD_RET_OK; } @@ -1169,7 +1167,7 @@ PurpleConversation *conv; conv = pidgin_conv_window_get_active_conversation(win); - clear_conversation_scrollback(conv); + purple_conversation_clear_message_history(conv); } static void @@ -8178,6 +8176,8 @@ purple_signal_connect(purple_conversations_get_handle(), "received-im-msg", handle, G_CALLBACK(received_im_msg_cb), NULL); + purple_signal_connect(purple_conversations_get_handle(), "cleared-message-history", + handle, G_CALLBACK(clear_conversation_scrollback_cb), NULL); purple_conversations_set_ui_ops(&conversation_ui_ops); diff -r 7281d151e492 -r 8b9e9c61d061 pidgin/gtkdialogs.c --- a/pidgin/gtkdialogs.c Thu Mar 17 20:25:26 2011 +0900 +++ b/pidgin/gtkdialogs.c Fri Apr 01 13:47:51 2011 +0900 @@ -101,6 +101,7 @@ /* Order: Alphabetical by Last Name */ static const struct developer patch_writers[] = { + {"Jakub 'haakon' Adam", NULL, NULL}, {"Peter 'Fmoo' Ruibal", NULL, NULL}, {"Gabriel 'Nix' Schulhof", NULL, NULL}, {NULL, NULL, NULL} @@ -184,11 +185,12 @@ {N_("Gujarati"), "gu", "Ankit Patel", "ankit_patel@users.sf.net"}, {N_("Gujarati"), "gu", N_("Gujarati Language Team"), "indianoss-gujarati@lists.sourceforge.net"}, {N_("Hebrew"), "he", "Shalom Craimer", "scraimer@gmail.com"}, + {N_("Hindi"), "hi", "Sangeeta Kumari", "sangeeta_0975@yahoo.com"}, {N_("Hindi"), "hi", "Rajesh Ranjan", "rajeshkajha@yahoo.com"}, {N_("Hungarian"), "hu", "Kelemen Gábor", "kelemeng@gnome.hu"}, {N_("Armenian"), "hy", "David Avsharyan", "avsharyan@gmail.com"}, {N_("Indonesian"), "id", "Rai S. Regawa", "raireg@yahoo.com"}, - {N_("Italian"), "it", "Claudio Satriano", "satriano@na.infn.it"}, + {N_("Italian"), "it", "Claudio Satriano", "satriano@gmail.com"}, {N_("Japanese"), "ja", "Takashi Aihana", "aihana@gnome.gr.jp"}, {N_("Georgian"), "ka", N_("Ubuntu Georgian Translators"), "alexander.didebulidze@stusta.mhn.de"}, {N_("Khmer"), "km", "Khoem Sokhem", "khoemsokhem@khmeros.info"}, @@ -199,6 +201,7 @@ {N_("Kurdish"), "ku", "Rizoyê Xerzî", "rizoxerzi@hotmail.com"}, {N_("Lao"), "lo", "Anousak Souphavah", "anousak@gmail.com"}, {N_("Maithili"), "mai", "Sangeeta Kumari", "sangeeta_0975@yahoo.com"}, + {N_("Maithili"), "mai", "Rajesh Ranjan", "rajeshkajha@yahoo.com"}, {N_("Meadow Mari"), "mhr", "David Preece", "davidpreece1@gmail.com"}, {N_("Macedonian"), "mk", "Arangel Angov ", "arangel@linux.net.mk"}, {N_("Macedonian"), "mk", "Ivana Kirkovska", "ivana.kirkovska@gmail.com"}, diff -r 7281d151e492 -r 8b9e9c61d061 pidgin/gtkmedia.c --- a/pidgin/gtkmedia.c Thu Mar 17 20:25:26 2011 +0900 +++ b/pidgin/gtkmedia.c Fri Apr 01 13:47:51 2011 +0900 @@ -93,7 +93,7 @@ GtkWidget *pause; GtkWidget *send_progress; - GtkWidget *recv_progress; + GHashTable *recv_progressbars; PidginMediaState state; @@ -102,7 +102,7 @@ GtkWidget *recv_widget; GtkWidget *button_widget; GtkWidget *local_video; - GtkWidget *remote_video; + GHashTable *remote_videos; guint timeout_id; PurpleMediaSessionType request_type; @@ -352,18 +352,110 @@ g_signal_connect(G_OBJECT(media), "delete-event", G_CALLBACK(pidgin_media_delete_event_cb), media); + + media->priv->recv_progressbars = + g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + media->priv->remote_videos = + g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); +} + +static gchar * +create_key(const gchar *session_id, const gchar *participant) +{ + return g_strdup_printf("%s_%s", session_id, participant); +} + +static void +pidgin_media_insert_widget(PidginMedia *gtkmedia, GtkWidget *widget, + const gchar *session_id, const gchar *participant) +{ + gchar *key = create_key(session_id, participant); + PurpleMediaSessionType type = + purple_media_get_session_type(gtkmedia->priv->media, session_id); + + if (type & PURPLE_MEDIA_AUDIO) + g_hash_table_insert(gtkmedia->priv->recv_progressbars, key, widget); + else if (type & PURPLE_MEDIA_VIDEO) + g_hash_table_insert(gtkmedia->priv->remote_videos, key, widget); +} + +static GtkWidget * +pidgin_media_get_widget(PidginMedia *gtkmedia, + const gchar *session_id, const gchar *participant) +{ + GtkWidget *widget = NULL; + gchar *key = create_key(session_id, participant); + PurpleMediaSessionType type = + purple_media_get_session_type(gtkmedia->priv->media, session_id); + + if (type & PURPLE_MEDIA_AUDIO) + widget = g_hash_table_lookup(gtkmedia->priv->recv_progressbars, key); + else if (type & PURPLE_MEDIA_VIDEO) + widget = g_hash_table_lookup(gtkmedia->priv->remote_videos, key); + + g_free(key); + return widget; +} + +static void +pidgin_media_remove_widget(PidginMedia *gtkmedia, + const gchar *session_id, const gchar *participant) +{ + GtkWidget *widget = pidgin_media_get_widget(gtkmedia, session_id, participant); + + if (widget) { + PurpleMediaSessionType type = + purple_media_get_session_type(gtkmedia->priv->media, session_id); + gchar *key = create_key(session_id, participant); + GtkRequisition req; + + if (type & PURPLE_MEDIA_AUDIO) { + g_hash_table_remove(gtkmedia->priv->recv_progressbars, key); + + if (g_hash_table_size(gtkmedia->priv->recv_progressbars) == 0 && + gtkmedia->priv->send_progress) { + + gtk_widget_destroy(gtkmedia->priv->send_progress); + gtkmedia->priv->send_progress = NULL; + + gtk_widget_destroy(gtkmedia->priv->mute); + gtkmedia->priv->mute = NULL; + } + } else if (type & PURPLE_MEDIA_VIDEO) { + g_hash_table_remove(gtkmedia->priv->remote_videos, key); + + if (g_hash_table_size(gtkmedia->priv->remote_videos) == 0 && + gtkmedia->priv->local_video) { + + gtk_widget_destroy(gtkmedia->priv->local_video); + gtkmedia->priv->local_video = NULL; + + gtk_widget_destroy(gtkmedia->priv->pause); + gtkmedia->priv->pause = NULL; + } + } + + g_free(key); + + gtk_widget_destroy(widget); + + gtk_widget_size_request(GTK_WIDGET(gtkmedia), &req); + gtk_window_resize(GTK_WINDOW(gtkmedia), req.width, req.height); + } } static void level_message_cb(PurpleMedia *media, gchar *session_id, gchar *participant, double level, PidginMedia *gtkmedia) { - GtkWidget *progress; + GtkWidget *progress = NULL; if (participant == NULL) progress = gtkmedia->priv->send_progress; else - progress = gtkmedia->priv->recv_progress; - gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), level * 5); + progress = pidgin_media_get_widget(gtkmedia, session_id, participant); + + if (progress) + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), level * 5); } @@ -402,6 +494,13 @@ if (gtkmedia->priv->timeout_id != 0) g_source_remove(gtkmedia->priv->timeout_id); + if (gtkmedia->priv->recv_progressbars) { + g_hash_table_destroy(gtkmedia->priv->recv_progressbars); + g_hash_table_destroy(gtkmedia->priv->remote_videos); + gtkmedia->priv->recv_progressbars = NULL; + gtkmedia->priv->remote_videos = NULL; + } + G_OBJECT_CLASS(parent_class)->dispose(media); } @@ -436,24 +535,30 @@ realize_cb_cb(PidginMediaRealizeData *data) { PidginMediaPrivate *priv = data->gtkmedia->priv; - gulong window_id; + GdkWindow *window = NULL; -#ifdef _WIN32 if (data->participant == NULL) - window_id = GDK_WINDOW_HWND(priv->local_video->window); - else - window_id = GDK_WINDOW_HWND(priv->remote_video->window); + window = gtk_widget_get_window(priv->local_video); + else { + GtkWidget *widget = pidgin_media_get_widget(data->gtkmedia, + data->session_id, data->participant); + if (widget) + window = gtk_widget_get_window(widget); + } + + if (window) { + gulong window_id; +#ifdef _WIN32 + window_id = GDK_WINDOW_HWND(window); #elif defined(HAVE_X11) - if (data->participant == NULL) - window_id = GDK_WINDOW_XWINDOW(priv->local_video->window); - else - window_id = GDK_WINDOW_XWINDOW(priv->remote_video->window); + window_id = GDK_WINDOW_XWINDOW(window); #else -# error "Unsupported windowing system" +# error "Unsupported windowing system" #endif - purple_media_set_output_window(priv->media, data->session_id, - data->participant, window_id); + purple_media_set_output_window(priv->media, data->session_id, + data->participant, window_id); + } g_free(data->session_id); g_free(data->participant); @@ -490,8 +595,13 @@ static void pidgin_media_reject_cb(PurpleMedia *media, int index) { - purple_media_stream_info(media, PURPLE_MEDIA_INFO_REJECT, - NULL, NULL, TRUE); + GList *iter = purple_media_get_session_ids(media); + for (; iter; iter = g_list_delete_link(iter, iter)) { + const gchar *sessionid = iter->data; + if (!purple_media_accepted(media, sessionid, NULL)) + purple_media_stream_info(media, PURPLE_MEDIA_INFO_REJECT, + sessionid, NULL, TRUE); + } } static gboolean @@ -563,9 +673,17 @@ purple_media_set_output_volume(media, NULL, NULL, val); } +static void +destroy_parent_widget_cb(GtkWidget *widget, GtkWidget *parent) +{ + g_return_if_fail(GTK_IS_WIDGET(parent)); + + gtk_widget_destroy(parent); +} + static GtkWidget * pidgin_media_add_audio_widget(PidginMedia *gtkmedia, - PurpleMediaSessionType type) + PurpleMediaSessionType type, const gchar *sid) { GtkWidget *volume_widget, *progress_parent, *volume, *progress; double value; @@ -619,9 +737,14 @@ g_signal_connect (G_OBJECT(volume), "value-changed", G_CALLBACK(pidgin_media_output_volume_changed), gtkmedia->priv->media); - gtkmedia->priv->recv_progress = progress; + + pidgin_media_insert_widget(gtkmedia, progress, sid, gtkmedia->priv->screenname); } + g_signal_connect(G_OBJECT(progress), "destroy", + G_CALLBACK(destroy_parent_widget_cb), + volume_widget); + gtk_widget_show_all(volume_widget); return volume_widget; @@ -691,13 +814,17 @@ G_CALLBACK(realize_cb), data); gtk_container_add(GTK_CONTAINER(aspect), remote_video); gtk_widget_set_size_request (GTK_WIDGET(remote_video), 320, 240); + g_signal_connect(G_OBJECT(remote_video), "destroy", + G_CALLBACK(destroy_parent_widget_cb), aspect); + gtk_widget_show(remote_video); gtk_widget_show(aspect); - gtkmedia->priv->remote_video = remote_video; + pidgin_media_insert_widget(gtkmedia, remote_video, + data->session_id, data->participant); } - if (type & PURPLE_MEDIA_SEND_VIDEO) { + if (type & PURPLE_MEDIA_SEND_VIDEO && !gtkmedia->priv->local_video) { PidginMediaRealizeData *data; GtkWidget *aspect; GtkWidget *local_video; @@ -718,6 +845,8 @@ G_CALLBACK(realize_cb), data); gtk_container_add(GTK_CONTAINER(aspect), local_video); gtk_widget_set_size_request (GTK_WIDGET(local_video), 80, 60); + g_signal_connect(G_OBJECT(local_video), "destroy", + G_CALLBACK(destroy_parent_widget_cb), aspect); gtk_widget_show(local_video); gtk_widget_show(aspect); @@ -736,7 +865,7 @@ if (type & PURPLE_MEDIA_RECV_AUDIO) { gtk_box_pack_end(GTK_BOX(recv_widget), pidgin_media_add_audio_widget(gtkmedia, - PURPLE_MEDIA_RECV_AUDIO), FALSE, FALSE, 0); + PURPLE_MEDIA_RECV_AUDIO, sid), FALSE, FALSE, 0); } if (type & PURPLE_MEDIA_SEND_AUDIO) { @@ -751,7 +880,7 @@ gtk_box_pack_end(GTK_BOX(recv_widget), pidgin_media_add_audio_widget(gtkmedia, - PURPLE_MEDIA_SEND_AUDIO), FALSE, FALSE, 0); + PURPLE_MEDIA_SEND_AUDIO, NULL), FALSE, FALSE, 0); } if (type & PURPLE_MEDIA_AUDIO && @@ -804,8 +933,10 @@ { purple_debug_info("gtkmedia", "state: %d sid: %s name: %s\n", state, sid ? sid : "(null)", name ? name : "(null)"); - if (sid == NULL && name == NULL) { - if (state == PURPLE_MEDIA_STATE_END) { + if (state == PURPLE_MEDIA_STATE_END) { + if (sid != NULL && name != NULL) { + pidgin_media_remove_widget(gtkmedia, sid, name); + } else if (sid == NULL && name == NULL) { pidgin_media_emit_message(gtkmedia, _("The call has been terminated.")); gtk_widget_destroy(GTK_WIDGET(gtkmedia)); diff -r 7281d151e492 -r 8b9e9c61d061 pidgin/gtkutils.h --- a/pidgin/gtkutils.h Thu Mar 17 20:25:26 2011 +0900 +++ b/pidgin/gtkutils.h Fri Apr 01 13:47:51 2011 +0900 @@ -364,7 +364,7 @@ * Add autocompletion of screenames to an entry, supporting a filtering function. * * @param entry The GtkEntry on which to setup autocomplete. - * @param optmenu A menu for accounts, returned by gaim_gtk_account_option_menu_new(). + * @param optmenu A menu for accounts, returned by pidgin_account_option_menu_new(). * If @a optmenu is not @c NULL, it'll be updated when a username is chosen * from the autocomplete list. * @param filter_func A function for checking if an autocomplete entry diff -r 7281d151e492 -r 8b9e9c61d061 pidgin/minidialog.c --- a/pidgin/minidialog.c Thu Mar 17 20:25:26 2011 +0900 +++ b/pidgin/minidialog.c Fri Apr 01 13:47:51 2011 +0900 @@ -76,6 +76,7 @@ PROP_DESCRIPTION, PROP_ICON_NAME, PROP_CUSTOM_ICON, + PROP_ENABLE_DESCRIPTION_MARKUP, LAST_PROPERTY } HazeConnectionProperties; @@ -87,6 +88,7 @@ GtkLabel *title; GtkLabel *desc; GtkBox *buttons; + gboolean enable_description_markup; guint idle_destroy_cb_id; } PidginMiniDialogPrivate; @@ -138,6 +140,27 @@ } void +pidgin_mini_dialog_enable_description_markup(PidginMiniDialog *mini_dialog) +{ + g_object_set(G_OBJECT(mini_dialog), "enable-description-markup", TRUE, NULL); +} + +gboolean +pidgin_mini_dialog_links_supported() +{ +#if GTK_CHECK_VERSION(2,18,0) + return TRUE; +#else + return FALSE; +#endif +} + +void pidgin_mini_dialog_set_link_callback(PidginMiniDialog *mini_dialog, GCallback cb, gpointer user_data) +{ + g_signal_connect(PIDGIN_MINI_DIALOG_GET_PRIVATE(mini_dialog)->desc, "activate-link", cb, user_data); +} + +void pidgin_mini_dialog_set_icon_name(PidginMiniDialog *mini_dialog, const char *icon_name) { @@ -155,6 +178,7 @@ PidginMiniDialog *mini_dialog; PidginMiniDialogCallback callback; gpointer user_data; + gboolean close_dialog_after_click; }; guint @@ -178,12 +202,14 @@ PidginMiniDialogPrivate *priv = PIDGIN_MINI_DIALOG_GET_PRIVATE(data->mini_dialog); - /* Set up the destruction callback before calling the clicked callback, - * so that if the mini-dialog gets destroyed during the clicked callback - * the idle_destroy_cb is correctly removed by _finalize. - */ - priv->idle_destroy_cb_id = - g_idle_add((GSourceFunc) idle_destroy_cb, data->mini_dialog); + if (data->close_dialog_after_click) { + /* Set up the destruction callback before calling the clicked callback, + * so that if the mini-dialog gets destroyed during the clicked callback + * the idle_destroy_cb is correctly removed by _finalize. + */ + priv->idle_destroy_cb_id = + g_idle_add((GSourceFunc) idle_destroy_cb, data->mini_dialog); + } if (data->callback != NULL) data->callback(data->mini_dialog, button, data->user_data); @@ -198,11 +224,12 @@ g_free(data); } -void -pidgin_mini_dialog_add_button(PidginMiniDialog *self, - const char *text, - PidginMiniDialogCallback clicked_cb, - gpointer user_data) +static void +mini_dialog_add_button(PidginMiniDialog *self, + const char *text, + PidginMiniDialogCallback clicked_cb, + gpointer user_data, + gboolean close_dialog_after_click) { PidginMiniDialogPrivate *priv = PIDGIN_MINI_DIALOG_GET_PRIVATE(self); struct _mini_dialog_button_clicked_cb_data *callback_data @@ -218,6 +245,7 @@ callback_data->mini_dialog = self; callback_data->callback = clicked_cb; callback_data->user_data = user_data; + callback_data->close_dialog_after_click = close_dialog_after_click; g_signal_connect(G_OBJECT(button), "clicked", (GCallback) mini_dialog_button_clicked_cb, callback_data); g_signal_connect(G_OBJECT(button), "destroy", @@ -231,6 +259,24 @@ gtk_widget_show_all(GTK_WIDGET(button)); } +void +pidgin_mini_dialog_add_button(PidginMiniDialog *self, + const char *text, + PidginMiniDialogCallback clicked_cb, + gpointer user_data) +{ + mini_dialog_add_button(self, text, clicked_cb, user_data, TRUE); +} + +void +pidgin_mini_dialog_add_non_closing_button(PidginMiniDialog *self, + const char *text, + PidginMiniDialogCallback clicked_cb, + gpointer user_data) +{ + mini_dialog_add_button(self, text, clicked_cb, user_data, FALSE); +} + static void pidgin_mini_dialog_get_property(GObject *object, guint property_id, @@ -258,6 +304,9 @@ case PROP_CUSTOM_ICON: g_value_set_object(value, gtk_image_get_pixbuf(priv->icon)); break; + case PROP_ENABLE_DESCRIPTION_MARKUP: + g_value_set_boolean(value, priv->enable_description_markup); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -287,7 +336,7 @@ PidginMiniDialogPrivate *priv = PIDGIN_MINI_DIALOG_GET_PRIVATE(self); if(description) { - char *desc_esc = g_markup_escape_text(description, -1); + char *desc_esc = priv->enable_description_markup ? g_strdup(description) : g_markup_escape_text(description, -1); char *desc_markup = g_strdup_printf( "%s", desc_esc); @@ -333,6 +382,9 @@ case PROP_CUSTOM_ICON: gtk_image_set_from_pixbuf(priv->icon, g_value_get_object(value)); break; + case PROP_ENABLE_DESCRIPTION_MARKUP: + priv->enable_description_markup = g_value_get_boolean(value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -390,6 +442,12 @@ G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_CUSTOM_ICON, param_spec); + + param_spec = g_param_spec_boolean("enable-description-markup", "enable-description-markup", + "Use GMarkup in the description text", FALSE, + G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB | + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_ENABLE_DESCRIPTION_MARKUP, param_spec); } /* 16 is the width of the icon, due to PIDGIN_ICON_SIZE_TANGO_EXTRA_SMALL */ diff -r 7281d151e492 -r 8b9e9c61d061 pidgin/minidialog.h --- a/pidgin/minidialog.h Thu Mar 17 20:25:26 2011 +0900 +++ b/pidgin/minidialog.h Fri Apr 01 13:47:51 2011 +0900 @@ -139,6 +139,23 @@ void pidgin_mini_dialog_set_description(PidginMiniDialog *mini_dialog, const char *description); +/** Enable GMarkup elements in the mini-dialog's description. + * @param mini_dialog a mini-dialog + */ +void pidgin_mini_dialog_enable_description_markup(PidginMiniDialog *mini_dialog); + +/** Mini-dialogs support hyperlinks in their description + * (you should first call pidgin_mini_dialog_enable_description_markup() on a given + * dialog to enable them). */ +gboolean pidgin_mini_dialog_links_supported(void); + +/** Sets a callback which gets invoked when a hyperlink in the dialog's description is clicked on. + * @param mini_dialog a mini-dialog + * @param cb the callback to invoke + * @param user_data the user data to pass to the callback + */ +void pidgin_mini_dialog_set_link_callback(PidginMiniDialog *mini_dialog, GCallback cb, gpointer user_data); + /** Shortcut for setting a mini-dialog's icon via GObject properties. * @param mini_dialog a mini-dialog * @param icon_name the Gtk stock ID of an icon, or @c NULL for no icon. @@ -166,6 +183,13 @@ const char *text, PidginMiniDialogCallback clicked_cb, gpointer user_data); +/** Equivalent to pidgin_mini_dialog_add_button(), the only difference + * is that the mini-dialog won't be closed after the button is clicked. + */ +void pidgin_mini_dialog_add_non_closing_button(PidginMiniDialog *mini_dialog, + const char *text, PidginMiniDialogCallback clicked_cb, + gpointer user_data); + /** Gets the number of widgets packed into PidginMiniDialog.contents. * @param mini_dialog a mini-dialog * @return the number of widgets in @a mini_dialog->contents. diff -r 7281d151e492 -r 8b9e9c61d061 pidgin/plugins/vvconfig.c --- a/pidgin/plugins/vvconfig.c Thu Mar 17 20:25:26 2011 +0900 +++ b/pidgin/plugins/vvconfig.c Fri Apr 01 13:47:51 2011 +0900 @@ -505,19 +505,22 @@ gtk_widget_destroy(GTK_WIDGET(window)); } +typedef GtkWidget *(*FrameCreateCb)(PurplePlugin *plugin); + static void show_config(PurplePluginAction *action) { if (!window) { + FrameCreateCb create_frame = action->user_data; GtkWidget *vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); GtkWidget *hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER); - GtkWidget *config_frame = get_plugin_config_frame(NULL); + GtkWidget *config_frame = create_frame(NULL); GtkWidget *close = gtk_button_new_from_stock(GTK_STOCK_CLOSE); gtk_container_add(GTK_CONTAINER(vbox), config_frame); gtk_container_add(GTK_CONTAINER(vbox), hbox); - window = pidgin_create_window(_("Voice/Video Settings"), - PIDGIN_HIG_BORDER, NULL, TRUE); + window = pidgin_create_window(action->label, + PIDGIN_HIG_BORDER, NULL, FALSE); g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(config_destroy), NULL); g_signal_connect(G_OBJECT(close), "clicked", @@ -531,6 +534,180 @@ gtk_window_present(GTK_WINDOW(window)); } +static GstElement * +create_pipeline() +{ + GstElement *pipeline = gst_pipeline_new("voicetest"); + GstElement *src = create_audio_src(NULL, NULL, NULL); + GstElement *sink = create_audio_sink(NULL, NULL, NULL); + GstElement *volume = gst_element_factory_make("volume", "volume"); + GstElement *level = gst_element_factory_make("level", "level"); + GstElement *valve = gst_element_factory_make("valve", "valve"); + + gst_bin_add_many(GST_BIN(pipeline), src, volume, level, valve, sink, NULL); + gst_element_link_many(src, volume, level, valve, sink, NULL); + + gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PLAYING); + + return pipeline; +} + +static void +on_volume_change_cb(GtkRange *range, GstBin *pipeline) +{ + GstElement *volume; + + g_return_if_fail(pipeline != NULL); + + volume = gst_bin_get_by_name(pipeline, "volume"); + g_object_set(volume, "volume", gtk_range_get_value(range) / 10.0, NULL); +} + +static gdouble +gst_msg_db_to_percent(GstMessage *msg, gchar *value_name) +{ + const GValue *list; + const GValue *value; + gdouble value_db; + gdouble percent; + + list = gst_structure_get_value( + gst_message_get_structure(msg), value_name); + value = gst_value_list_get_value(list, 0); + value_db = g_value_get_double(value); + percent = pow(10, value_db / 20); + return (percent > 1.0) ? 1.0 : percent; +} + +typedef struct +{ + GtkProgressBar *level; + GtkRange *threshold; +} BusCbCtx; + +static gboolean +gst_bus_cb(GstBus *bus, GstMessage *msg, BusCbCtx *ctx) +{ + if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ELEMENT && + gst_structure_has_name(msg->structure, "level")) { + + GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg)); + gchar *name = gst_element_get_name(src); + + if (!strcmp(name, "level")) { + gdouble percent; + gdouble threshold; + GstElement *valve; + + percent = gst_msg_db_to_percent(msg, "rms"); + gtk_progress_bar_set_fraction(ctx->level, percent * 5); + + percent = gst_msg_db_to_percent(msg, "decay"); + threshold = gtk_range_get_value(ctx->threshold) / 100.0; + valve = gst_bin_get_by_name(GST_BIN(GST_ELEMENT_PARENT(src)), "valve"); + g_object_set(valve, "drop", (percent < threshold), NULL); + g_object_set(ctx->level, + "text", (percent < threshold) ? _("DROP") : " ", NULL); + } + + g_free(name); + } + + return TRUE; +} + +static void +voice_test_frame_destroy_cb(GtkObject *w, GstElement *pipeline) +{ + g_return_if_fail(GST_IS_ELEMENT(pipeline)); + + gst_element_set_state(pipeline, GST_STATE_NULL); + gst_object_unref(pipeline); +} + +static void +volume_scale_destroy_cb(GtkRange *volume, gpointer nul) +{ + purple_prefs_set_int("/purple/media/audio/volume/input", + gtk_range_get_value(volume)); +} + +static gchar* +threshold_value_format_cb(GtkScale *scale, gdouble value) +{ + return g_strdup_printf ("%.*f%%", gtk_scale_get_digits(scale), value); +} + +static void +threshold_scale_destroy_cb(GtkRange *threshold, gpointer nul) +{ + purple_prefs_set_int("/purple/media/audio/silence_threshold", + gtk_range_get_value(threshold)); +} + +static GtkWidget * +get_voice_test_frame(PurplePlugin *plugin) +{ + GtkWidget *vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); + GtkWidget *level = gtk_progress_bar_new(); + GtkWidget *volume = gtk_hscale_new_with_range(0, 100, 1); + GtkWidget *threshold = gtk_hscale_new_with_range(0, 100, 1); + GtkWidget *label; + GtkTable *table = GTK_TABLE(gtk_table_new(2, 2, FALSE)); + + GstElement *pipeline; + GstBus *bus; + BusCbCtx *ctx; + + g_object_set(vbox, "width-request", 500, NULL); + + gtk_table_set_row_spacings(table, PIDGIN_HIG_BOX_SPACE); + gtk_table_set_col_spacings(table, PIDGIN_HIG_BOX_SPACE); + + label = gtk_label_new(_("Volume:")); + g_object_set(label, "xalign", 0.0, NULL); + gtk_table_attach(table, label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); + gtk_table_attach_defaults(table, volume, 1, 2, 0, 1); + label = gtk_label_new(_("Silence threshold:")); + g_object_set(label, "xalign", 0.0, "yalign", 1.0, NULL); + gtk_table_attach(table, label, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 0, 0); + gtk_table_attach_defaults(table, threshold, 1, 2, 1, 2); + + gtk_container_add(GTK_CONTAINER(vbox), level); + gtk_container_add(GTK_CONTAINER(vbox), GTK_WIDGET(table)); + gtk_widget_show_all(vbox); + + pipeline = create_pipeline(); + bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline)); + gst_bus_add_signal_watch(bus); + ctx = g_new(BusCbCtx, 1); + ctx->level = GTK_PROGRESS_BAR(level); + ctx->threshold = GTK_RANGE(threshold); + g_signal_connect_data(bus, "message", G_CALLBACK(gst_bus_cb), + ctx, (GClosureNotify)g_free, 0); + gst_object_unref(bus); + + g_signal_connect(volume, "value-changed", + (GCallback)on_volume_change_cb, pipeline); + + gtk_range_set_value(GTK_RANGE(volume), + purple_prefs_get_int("/purple/media/audio/volume/input")); + gtk_widget_set(volume, "draw-value", FALSE, NULL); + + gtk_range_set_value(GTK_RANGE(threshold), + purple_prefs_get_int("/purple/media/audio/silence_threshold")); + + g_signal_connect(vbox, "destroy", + G_CALLBACK(voice_test_frame_destroy_cb), pipeline); + g_signal_connect(volume, "destroy", + G_CALLBACK(volume_scale_destroy_cb), NULL); + g_signal_connect(threshold, "format-value", + G_CALLBACK(threshold_value_format_cb), NULL); + g_signal_connect(threshold, "destroy", + G_CALLBACK(threshold_scale_destroy_cb), NULL); + + return vbox; +} static GList * actions(PurplePlugin *plugin, gpointer context) @@ -538,8 +715,14 @@ GList *l = NULL; PurplePluginAction *act = NULL; - act = purple_plugin_action_new(_("Voice and Video Settings"), + act = purple_plugin_action_new(_("Input and Output Settings"), show_config); + act->user_data = get_plugin_config_frame; + l = g_list_append(l, act); + + act = purple_plugin_action_new(_("Microphone Test"), + show_config); + act->user_data = get_voice_test_frame; l = g_list_append(l, act); return l; diff -r 7281d151e492 -r 8b9e9c61d061 po/l10n.xsl --- a/po/l10n.xsl Thu Mar 17 20:25:26 2011 +0900 +++ b/po/l10n.xsl Fri Apr 01 13:47:51 2011 +0900 @@ -12,7 +12,6 @@ <xsl:value-of select='@name'/> translation statistics -