1 /**
2  * @file gg.c Gadu-Gadu protocol plugin
3  *
4  * purple
5  *
6  * Copyright (C) 2005  Bartosz Oler <bartosz@bzimage.us>
7  *
8  * Some parts of the code are adapted or taken from the previous implementation
9  * of this plugin written by Arkadiusz Miskiewicz <misiek@pld.org.pl>
10  * Some parts Copyright (C) 2009  Krzysztof Klinikowski <grommasher@gmail.com>
11  *
12  * Thanks to Google's Summer of Code Program.
13  *
14  * This program is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with this program; if not, write to the Free Software
26  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
27  */
28 
29 #include "internal.h"
30 
31 #include "plugin.h"
32 #include "version.h"
33 #include "notify.h"
34 #include "status.h"
35 #include "blist.h"
36 #include "accountopt.h"
37 #include "debug.h"
38 #include "glibcompat.h"
39 #include "util.h"
40 #include "request.h"
41 #include "xmlnode.h"
42 
43 #include <libgadu.h>
44 
45 #include "gg.h"
46 #include "confer.h"
47 #include "search.h"
48 #include "buddylist.h"
49 #include "gg-utils.h"
50 
51 #define DISABLE_AVATARS 1
52 
53 static PurplePlugin *my_protocol = NULL;
54 
55 /* Prototypes */
56 static void ggp_set_status(PurpleAccount *account, PurpleStatus *status);
57 static int ggp_to_gg_status(PurpleStatus *status, char **msg);
58 
59 /* ---------------------------------------------------------------------- */
60 /* ----- EXTERNAL CALLBACKS --------------------------------------------- */
61 /* ---------------------------------------------------------------------- */
62 
63 
64 /* ----- HELPERS -------------------------------------------------------- */
65 
66 static PurpleInputCondition
ggp_tcpsocket_inputcond_gg_to_purple(enum gg_check_t check)67 ggp_tcpsocket_inputcond_gg_to_purple(enum gg_check_t check)
68 {
69 	PurpleInputCondition cond = 0;
70 
71 	if (check & GG_CHECK_READ)
72 		cond |= PURPLE_INPUT_READ;
73 	if (check & GG_CHECK_WRITE)
74 		cond |= PURPLE_INPUT_WRITE;
75 
76 	return cond;
77 }
78 
79 /**
80  * Set up libgadu's proxy.
81  *
82  * @param account Account for which to set up the proxy.
83  *
84  * @return Zero if proxy setup is valid, otherwise -1.
85  */
ggp_setup_proxy(PurpleAccount * account)86 static int ggp_setup_proxy(PurpleAccount *account)
87 {
88 	PurpleProxyInfo *gpi;
89 
90 	gpi = purple_proxy_get_setup(account);
91 
92 	if ((purple_proxy_info_get_type(gpi) != PURPLE_PROXY_NONE) &&
93 	    (purple_proxy_info_get_host(gpi) == NULL ||
94 	     purple_proxy_info_get_port(gpi) <= 0)) {
95 
96 		gg_proxy_enabled = 0;
97 		purple_notify_error(NULL, NULL, _("Invalid proxy settings"),
98 				  _("Either the host name or port number specified for your given proxy type is invalid."));
99 		return -1;
100 	} else if (purple_proxy_info_get_type(gpi) != PURPLE_PROXY_NONE) {
101 		gg_proxy_enabled = 1;
102 		gg_proxy_host = g_strdup(purple_proxy_info_get_host(gpi));
103 		gg_proxy_port = purple_proxy_info_get_port(gpi);
104 		gg_proxy_username = g_strdup(purple_proxy_info_get_username(gpi));
105 		gg_proxy_password = g_strdup(purple_proxy_info_get_password(gpi));
106 	} else {
107 		gg_proxy_enabled = 0;
108 	}
109 
110 	return 0;
111 }
112 
113 /* }}} */
114 
115 /* ---------------------------------------------------------------------- */
116 
ggp_callback_buddylist_save_ok(PurpleConnection * gc,const char * filename)117 static void ggp_callback_buddylist_save_ok(PurpleConnection *gc, const char *filename)
118 {
119 	PurpleAccount *account = purple_connection_get_account(gc);
120 
121 	char *buddylist = ggp_buddylist_dump(account);
122 
123 	purple_debug_info("gg", "Saving...\n");
124 	purple_debug_info("gg", "file = %s\n", filename);
125 
126 	if (buddylist == NULL) {
127 		purple_notify_info(account, _("Save Buddylist..."),
128 			 _("Your buddylist is empty, nothing was written to the file."),
129 			 NULL);
130 		return;
131 	}
132 
133 	if(purple_util_write_data_to_file_absolute(filename, buddylist, -1)) {
134 		purple_notify_info(account, _("Save Buddylist..."),
135 			 _("Buddylist saved successfully!"), NULL);
136 	} else {
137 		gchar *primary = g_strdup_printf(
138 			_("Couldn't write buddy list for %s to %s"),
139 			purple_account_get_username(account), filename);
140 		purple_notify_error(account, _("Save Buddylist..."),
141 			primary, NULL);
142 		g_free(primary);
143 	}
144 
145 	g_free(buddylist);
146 }
147 
ggp_callback_buddylist_load_ok(PurpleConnection * gc,gchar * file)148 static void ggp_callback_buddylist_load_ok(PurpleConnection *gc, gchar *file)
149 {
150 	PurpleAccount *account = purple_connection_get_account(gc);
151 	GError *error = NULL;
152 	char *buddylist = NULL;
153 	gsize length;
154 
155 	purple_debug_info("gg", "file_name = %s\n", file);
156 
157 	if (!g_file_get_contents(file, &buddylist, &length, &error)) {
158 		purple_notify_error(account,
159 				_("Couldn't load buddylist"),
160 				_("Couldn't load buddylist"),
161 				error->message);
162 
163 		purple_debug_error("gg",
164 			"Couldn't load buddylist. file = %s; error = %s\n",
165 			file, error->message);
166 
167 		g_error_free(error);
168 
169 		return;
170 	}
171 
172 	ggp_buddylist_load(gc, buddylist);
173 	g_free(buddylist);
174 
175 	purple_notify_info(account,
176 			 _("Load Buddylist..."),
177 			 _("Buddylist loaded successfully!"), NULL);
178 }
179 /* }}} */
180 
181 /*
182  */
183 /* static void ggp_action_buddylist_save(PurplePluginAction *action) {{{ */
ggp_action_buddylist_save(PurplePluginAction * action)184 static void ggp_action_buddylist_save(PurplePluginAction *action)
185 {
186 	PurpleConnection *gc = (PurpleConnection *)action->context;
187 
188 	purple_request_file(action, _("Save buddylist..."), NULL, TRUE,
189 			G_CALLBACK(ggp_callback_buddylist_save_ok), NULL,
190 			purple_connection_get_account(gc), NULL, NULL,
191 			gc);
192 }
193 
ggp_action_buddylist_load(PurplePluginAction * action)194 static void ggp_action_buddylist_load(PurplePluginAction *action)
195 {
196 	PurpleConnection *gc = (PurpleConnection *)action->context;
197 
198 	purple_request_file(action, _("Load buddylist from file..."), NULL,
199 			FALSE,
200 			G_CALLBACK(ggp_callback_buddylist_load_ok), NULL,
201 			purple_connection_get_account(gc), NULL, NULL,
202 			gc);
203 }
204 
205 /* ----- PUBLIC DIRECTORY SEARCH ---------------------------------------- */
206 
ggp_callback_show_next(PurpleConnection * gc,GList * row,gpointer user_data)207 static void ggp_callback_show_next(PurpleConnection *gc, GList *row, gpointer user_data)
208 {
209 	GGPInfo *info = gc->proto_data;
210 	GGPSearchForm *form = user_data;
211 	guint32 seq;
212 
213 	form->page_number++;
214 
215 	ggp_search_remove(info->searches, form->seq);
216 	purple_debug_info("gg", "ggp_callback_show_next(): Removed seq %u\n",
217 		form->seq);
218 
219 	seq = ggp_search_start(gc, form);
220 	ggp_search_add(info->searches, seq, form);
221 	purple_debug_info("gg", "ggp_callback_show_next(): Added seq %u\n",
222 		seq);
223 }
224 
ggp_callback_add_buddy(PurpleConnection * gc,GList * row,gpointer user_data)225 static void ggp_callback_add_buddy(PurpleConnection *gc, GList *row, gpointer user_data)
226 {
227 	purple_blist_request_add_buddy(purple_connection_get_account(gc),
228 				     g_list_nth_data(row, 0), NULL, NULL);
229 }
230 
ggp_callback_im(PurpleConnection * gc,GList * row,gpointer user_data)231 static void ggp_callback_im(PurpleConnection *gc, GList *row, gpointer user_data)
232 {
233 	PurpleAccount *account;
234 	PurpleConversation *conv;
235 	char *name;
236 
237 	account = purple_connection_get_account(gc);
238 
239 	name = g_list_nth_data(row, 0);
240 	conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, name);
241 	purple_conversation_present(conv);
242 }
243 
ggp_callback_find_buddies(PurpleConnection * gc,PurpleRequestFields * fields)244 static void ggp_callback_find_buddies(PurpleConnection *gc, PurpleRequestFields *fields)
245 {
246 	GGPInfo *info = gc->proto_data;
247 	GGPSearchForm *form;
248 	guint32 seq;
249 
250 	form = ggp_search_form_new(GGP_SEARCH_TYPE_FULL);
251 
252 	form->user_data = info;
253 	form->lastname = g_strdup(
254 		purple_request_fields_get_string(fields, "lastname"));
255 	form->firstname = g_strdup(
256 		purple_request_fields_get_string(fields, "firstname"));
257 	form->nickname = g_strdup(
258 		purple_request_fields_get_string(fields, "nickname"));
259 	form->city = g_strdup(
260 		purple_request_fields_get_string(fields, "city"));
261 	form->birthyear = g_strdup(
262 		purple_request_fields_get_string(fields, "year"));
263 
264 	switch (purple_request_fields_get_choice(fields, "gender")) {
265 		case 1:
266 			form->gender = g_strdup(GG_PUBDIR50_GENDER_MALE);
267 			break;
268 		case 2:
269 			form->gender = g_strdup(GG_PUBDIR50_GENDER_FEMALE);
270 			break;
271 		default:
272 			form->gender = NULL;
273 			break;
274 	}
275 
276 	form->active = purple_request_fields_get_bool(fields, "active")
277 				   ? g_strdup(GG_PUBDIR50_ACTIVE_TRUE) : NULL;
278 
279 	seq = ggp_search_start(gc, form);
280 	ggp_search_add(info->searches, seq, form);
281 	purple_debug_info("gg", "ggp_callback_find_buddies(): Added seq %u\n",
282 		seq);
283 }
284 
ggp_find_buddies(PurplePluginAction * action)285 static void ggp_find_buddies(PurplePluginAction *action)
286 {
287 	PurpleConnection *gc = (PurpleConnection *)action->context;
288 
289 	PurpleRequestFields *fields;
290 	PurpleRequestFieldGroup *group;
291 	PurpleRequestField *field;
292 
293 	fields = purple_request_fields_new();
294 	group = purple_request_field_group_new(NULL);
295 	purple_request_fields_add_group(fields, group);
296 
297 	field = purple_request_field_string_new("lastname",
298 			_("Last name"), NULL, FALSE);
299 	purple_request_field_string_set_masked(field, FALSE);
300 	purple_request_field_group_add_field(group, field);
301 
302 	field = purple_request_field_string_new("firstname",
303 			_("First name"), NULL, FALSE);
304 	purple_request_field_string_set_masked(field, FALSE);
305 	purple_request_field_group_add_field(group, field);
306 
307 	field = purple_request_field_string_new("nickname",
308 			_("Nickname"), NULL, FALSE);
309 	purple_request_field_string_set_masked(field, FALSE);
310 	purple_request_field_group_add_field(group, field);
311 
312 	field = purple_request_field_string_new("city",
313 			_("City"), NULL, FALSE);
314 	purple_request_field_string_set_masked(field, FALSE);
315 	purple_request_field_group_add_field(group, field);
316 
317 	field = purple_request_field_string_new("year",
318 			_("Year of birth"), NULL, FALSE);
319 	purple_request_field_group_add_field(group, field);
320 
321 	field = purple_request_field_choice_new("gender", _("Gender"), 0);
322 	purple_request_field_choice_add(field, _("Male or female"));
323 	purple_request_field_choice_add(field, _("Male"));
324 	purple_request_field_choice_add(field, _("Female"));
325 	purple_request_field_group_add_field(group, field);
326 
327 	field = purple_request_field_bool_new("active",
328 			_("Only online"), FALSE);
329 	purple_request_field_group_add_field(group, field);
330 
331 	purple_request_fields(gc,
332 		_("Find buddies"),
333 		_("Find buddies"),
334 		_("Please, enter your search criteria below"),
335 		fields,
336 		_("OK"), G_CALLBACK(ggp_callback_find_buddies),
337 		_("Cancel"), NULL,
338 		purple_connection_get_account(gc), NULL, NULL,
339 		gc);
340 }
341 
342 /* ----- CHANGE STATUS BROADCASTING ------------------------------------------------ */
343 
ggp_action_change_status_broadcasting_ok(PurpleConnection * gc,PurpleRequestFields * fields)344 static void ggp_action_change_status_broadcasting_ok(PurpleConnection *gc, PurpleRequestFields *fields)
345 {
346 	GGPInfo *info = gc->proto_data;
347 	int selected_field;
348 	PurpleAccount *account = purple_connection_get_account(gc);
349 	PurpleStatus *status;
350 
351 	selected_field = purple_request_fields_get_choice(fields, "status_broadcasting");
352 
353 	if (selected_field == 0)
354 		info->status_broadcasting = TRUE;
355 	else
356 		info->status_broadcasting = FALSE;
357 
358 	status = purple_account_get_active_status(account);
359 
360 	ggp_set_status(account, status);
361 }
362 
ggp_action_change_status_broadcasting(PurplePluginAction * action)363 static void ggp_action_change_status_broadcasting(PurplePluginAction *action)
364 {
365 	PurpleConnection *gc = (PurpleConnection *)action->context;
366 	GGPInfo *info = gc->proto_data;
367 
368 	PurpleRequestFields *fields;
369 	PurpleRequestFieldGroup *group;
370 	PurpleRequestField *field;
371 
372 	fields = purple_request_fields_new();
373 	group = purple_request_field_group_new(NULL);
374 	purple_request_fields_add_group(fields, group);
375 
376 	field = purple_request_field_choice_new("status_broadcasting", _("Show status to:"), 0);
377 	purple_request_field_choice_add(field, _("All people"));
378 	purple_request_field_choice_add(field, _("Only buddies"));
379 	purple_request_field_group_add_field(group, field);
380 
381 	if (info->status_broadcasting)
382 		purple_request_field_choice_set_default_value(field, 0);
383 	else
384 		purple_request_field_choice_set_default_value(field, 1);
385 
386 	purple_request_fields(gc,
387 		_("Change status broadcasting"),
388 		_("Change status broadcasting"),
389 		_("Please, select who can see your status"),
390 		fields,
391 		_("OK"), G_CALLBACK(ggp_action_change_status_broadcasting_ok),
392 		_("Cancel"), NULL,
393 		purple_connection_get_account(gc), NULL, NULL,
394 		gc);
395 }
396 
397 /* ----- CONFERENCES ---------------------------------------------------- */
398 
ggp_callback_add_to_chat_ok(PurpleBuddy * buddy,PurpleRequestFields * fields)399 static void ggp_callback_add_to_chat_ok(PurpleBuddy *buddy, PurpleRequestFields *fields)
400 {
401 	PurpleConnection *conn;
402 	PurpleRequestField *field;
403 	GList *sel;
404 
405 	conn = purple_account_get_connection(purple_buddy_get_account(buddy));
406 
407 	g_return_if_fail(conn != NULL);
408 
409 	field = purple_request_fields_get_field(fields, "name");
410 	sel = purple_request_field_list_get_selected(field);
411 
412 	if (sel == NULL) {
413 		purple_debug_error("gg", "No chat selected\n");
414 		return;
415 	}
416 
417 	ggp_confer_participants_add_uin(conn, sel->data,
418 					ggp_str_to_uin(purple_buddy_get_name(buddy)));
419 }
420 
ggp_bmenu_add_to_chat(PurpleBlistNode * node,gpointer ignored)421 static void ggp_bmenu_add_to_chat(PurpleBlistNode *node, gpointer ignored)
422 {
423 	PurpleBuddy *buddy;
424 	PurpleConnection *gc;
425 	GGPInfo *info;
426 
427 	PurpleRequestFields *fields;
428 	PurpleRequestFieldGroup *group;
429 	PurpleRequestField *field;
430 
431 	GList *l;
432 	gchar *msg;
433 
434 	buddy = (PurpleBuddy *)node;
435 	gc = purple_account_get_connection(purple_buddy_get_account(buddy));
436 	info = gc->proto_data;
437 
438 	fields = purple_request_fields_new();
439 	group = purple_request_field_group_new(NULL);
440 	purple_request_fields_add_group(fields, group);
441 
442 	field = purple_request_field_list_new("name", "Chat name");
443 	for (l = info->chats; l != NULL; l = l->next) {
444 		GGPChat *chat = l->data;
445 		purple_request_field_list_add(field, chat->name, chat->name);
446 	}
447 	purple_request_field_group_add_field(group, field);
448 
449 	msg = g_strdup_printf(_("Select a chat for buddy: %s"),
450 			      purple_buddy_get_alias(buddy));
451 	purple_request_fields(gc,
452 			_("Add to chat..."),
453 			_("Add to chat..."),
454 			msg,
455 			fields,
456 			_("Add"), G_CALLBACK(ggp_callback_add_to_chat_ok),
457 			_("Cancel"), NULL,
458 			purple_connection_get_account(gc), NULL, NULL,
459 			buddy);
460 	g_free(msg);
461 }
462 
463 /* ----- BLOCK BUDDIES -------------------------------------------------- */
464 
ggp_add_deny(PurpleConnection * gc,const char * who)465 static void ggp_add_deny(PurpleConnection *gc, const char *who)
466 {
467 	GGPInfo *info = gc->proto_data;
468 	uin_t uin = ggp_str_to_uin(who);
469 
470 	purple_debug_info("gg", "ggp_add_deny: %u\n", uin);
471 
472 	gg_remove_notify_ex(info->session, uin, GG_USER_NORMAL);
473 	gg_add_notify_ex(info->session, uin, GG_USER_BLOCKED);
474 }
475 
ggp_rem_deny(PurpleConnection * gc,const char * who)476 static void ggp_rem_deny(PurpleConnection *gc, const char *who)
477 {
478 	GGPInfo *info = gc->proto_data;
479 	uin_t uin = ggp_str_to_uin(who);
480 
481 	purple_debug_info("gg", "ggp_rem_deny: %u\n", uin);
482 
483 	gg_remove_notify_ex(info->session, uin, GG_USER_BLOCKED);
484 	gg_add_notify_ex(info->session, uin, GG_USER_NORMAL);
485 }
486 
487 /* ---------------------------------------------------------------------- */
488 /* ----- INTERNAL CALLBACKS --------------------------------------------- */
489 /* ---------------------------------------------------------------------- */
490 
491 #if !DISABLE_AVATARS
492 
493 struct gg_fetch_avatar_data
494 {
495 	PurpleConnection *gc;
496 	gchar *uin;
497 	gchar *avatar_url;
498 };
499 
500 
gg_fetch_avatar_cb(PurpleUtilFetchUrlData * url_data,gpointer user_data,const gchar * data,size_t len,const gchar * error_message)501 static void gg_fetch_avatar_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
502                const gchar *data, size_t len, const gchar *error_message) {
503 	struct gg_fetch_avatar_data *d = user_data;
504 	PurpleAccount *account;
505 	PurpleBuddy *buddy;
506 	gpointer buddy_icon_data;
507 
508 	purple_debug_info("gg", "gg_fetch_avatar_cb: got avatar image for %s\n",
509 		d->uin);
510 
511 	/* FIXME: This shouldn't be necessary */
512 	if (!PURPLE_CONNECTION_IS_VALID(d->gc)) {
513 		g_free(d->uin);
514 		g_free(d->avatar_url);
515 		g_free(d);
516 		g_return_if_reached();
517 	}
518 
519 	account = purple_connection_get_account(d->gc);
520 	buddy = purple_find_buddy(account, d->uin);
521 
522 	if (buddy == NULL)
523 		goto out;
524 
525 	buddy_icon_data = g_memdup2(data, len);
526 
527 	purple_buddy_icons_set_for_user(account, purple_buddy_get_name(buddy),
528 			buddy_icon_data, len, d->avatar_url);
529 	purple_debug_info("gg", "gg_fetch_avatar_cb: UIN %s should have avatar "
530 		"now\n", d->uin);
531 
532 out:
533 	g_free(d->uin);
534 	g_free(d->avatar_url);
535 	g_free(d);
536 }
537 
gg_get_avatar_url_cb(PurpleUtilFetchUrlData * url_data,gpointer user_data,const gchar * url_text,size_t len,const gchar * error_message)538 static void gg_get_avatar_url_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data,
539                const gchar *url_text, size_t len, const gchar *error_message) {
540 	struct gg_fetch_avatar_data *data;
541 	PurpleConnection *gc = user_data;
542 	PurpleAccount *account;
543 	PurpleBuddy *buddy;
544 	const char *uin;
545 	const char *is_blank;
546 	const char *checksum;
547 
548 	gchar *bigavatar = NULL;
549 	xmlnode *xml = NULL;
550 	xmlnode *xmlnode_users;
551 	xmlnode *xmlnode_user;
552 	xmlnode *xmlnode_avatars;
553 	xmlnode *xmlnode_avatar;
554 	xmlnode *xmlnode_bigavatar;
555 
556 	g_return_if_fail(PURPLE_CONNECTION_IS_VALID(gc));
557 	account = purple_connection_get_account(gc);
558 
559 	if (error_message != NULL)
560 		purple_debug_error("gg", "gg_get_avatars_cb error: %s\n", error_message);
561 	else if (len > 0 && url_text && *url_text) {
562 		xml = xmlnode_from_str(url_text, -1);
563 		if (xml == NULL)
564 			goto out;
565 
566 		xmlnode_users = xmlnode_get_child(xml, "users");
567 		if (xmlnode_users == NULL)
568 			goto out;
569 
570 		xmlnode_user = xmlnode_get_child(xmlnode_users, "user");
571 		if (xmlnode_user == NULL)
572 			goto out;
573 
574 		uin = xmlnode_get_attrib(xmlnode_user, "uin");
575 
576 		xmlnode_avatars = xmlnode_get_child(xmlnode_user, "avatars");
577 		if (xmlnode_avatars == NULL)
578 			goto out;
579 
580 		xmlnode_avatar = xmlnode_get_child(xmlnode_avatars, "avatar");
581 		if (xmlnode_avatar == NULL)
582 			goto out;
583 
584 		xmlnode_bigavatar = xmlnode_get_child(xmlnode_avatar, "originBigAvatar");
585 		if (xmlnode_bigavatar == NULL)
586 			goto out;
587 
588 		is_blank = xmlnode_get_attrib(xmlnode_avatar, "blank");
589 		bigavatar = xmlnode_get_data(xmlnode_bigavatar);
590 
591 		purple_debug_info("gg", "gg_get_avatar_url_cb: UIN %s, IS_BLANK %s, "
592 		                        "URL %s\n",
593 		                  uin ? uin : "(null)", is_blank ? is_blank : "(null)",
594 		                  bigavatar ? bigavatar : "(null)");
595 
596 		if (uin != NULL && bigavatar != NULL) {
597 			buddy = purple_find_buddy(account, uin);
598 			if (buddy == NULL)
599 				goto out;
600 
601 			checksum = purple_buddy_icons_get_checksum_for_user(buddy);
602 
603 			if (purple_strequal(is_blank, "1")) {
604 				purple_buddy_icons_set_for_user(account,
605 						purple_buddy_get_name(buddy), NULL, 0, NULL);
606 			} else if (!purple_strequal(checksum, bigavatar)) {
607 				data = g_new0(struct gg_fetch_avatar_data, 1);
608 				data->gc = gc;
609 				data->uin = g_strdup(uin);
610 				data->avatar_url = g_strdup(bigavatar);
611 
612 				purple_debug_info("gg", "gg_get_avatar_url_cb: "
613 					"requesting avatar for %s\n", uin);
614 				url_data = purple_util_fetch_url_request_len_with_account(account,
615 						bigavatar, TRUE, "Mozilla/4.0 (compatible; MSIE 5.0)",
616 						FALSE, NULL, FALSE, -1, gg_fetch_avatar_cb, data);
617 			}
618 		}
619 	}
620 
621 out:
622 	if (xml)
623 		xmlnode_free(xml);
624 	g_free(bigavatar);
625 }
626 
627 #endif
628 
629 /**
630  * Try to update avatar of the buddy.
631  *
632  * @param gc     PurpleConnection
633  * @param uin    UIN of the buddy.
634  */
ggp_update_buddy_avatar(PurpleConnection * gc,uin_t uin)635 static void ggp_update_buddy_avatar(PurpleConnection *gc, uin_t uin)
636 {
637 #if DISABLE_AVATARS
638 	purple_debug_warning("gg", "ggp_update_buddy_avatar: disabled, please "
639 		"update to 3.0.0, when available\n");
640 #else
641 	gchar *avatarurl;
642 	PurpleUtilFetchUrlData *url_data;
643 
644 	purple_debug_info("gg", "ggp_update_buddy_avatar(gc, %u)\n", uin);
645 
646 	avatarurl = g_strdup_printf("http://api.gadu-gadu.pl/avatars/%u/0.xml", uin);
647 
648 	url_data = purple_util_fetch_url_request_len_with_account(
649 			purple_connection_get_account(gc), avatarurl, TRUE,
650 			"Mozilla/4.0 (compatible; MSIE 5.5)", FALSE, NULL, FALSE, -1,
651 			gg_get_avatar_url_cb, gc);
652 
653 	g_free(avatarurl);
654 #endif
655 }
656 
657 /**
658  * Handle change of the status of the buddy.
659  *
660  * @param gc     PurpleConnection
661  * @param uin    UIN of the buddy.
662  * @param status ID of the status.
663  * @param descr  Description.
664  */
ggp_generic_status_handler(PurpleConnection * gc,uin_t uin,int status,const char * descr)665 static void ggp_generic_status_handler(PurpleConnection *gc, uin_t uin,
666 				       int status, const char *descr)
667 {
668 	gchar *from;
669 	const char *st;
670 	char *status_msg = NULL;
671 
672 	ggp_update_buddy_avatar(gc, uin);
673 
674 	from = g_strdup_printf("%u", uin);
675 
676 	switch (status) {
677 		case GG_STATUS_NOT_AVAIL:
678 		case GG_STATUS_NOT_AVAIL_DESCR:
679 			st = purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE);
680 			break;
681 		case GG_STATUS_FFC:
682 		case GG_STATUS_FFC_DESCR:
683 			st = purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE);
684 			break;
685 		case GG_STATUS_AVAIL:
686 		case GG_STATUS_AVAIL_DESCR:
687 			st = purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE);
688 			break;
689 		case GG_STATUS_BUSY:
690 		case GG_STATUS_BUSY_DESCR:
691 			st = purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY);
692 			break;
693 		case GG_STATUS_DND:
694 		case GG_STATUS_DND_DESCR:
695 			st = purple_primitive_get_id_from_type(PURPLE_STATUS_UNAVAILABLE);
696 			break;
697 		case GG_STATUS_BLOCKED:
698 			/* user is blocking us.... */
699 			st = "blocked";
700 			break;
701 		default:
702 			st = purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE);
703 			purple_debug_info("gg",
704 				"GG_EVENT_NOTIFY: Unknown status: %d\n", status);
705 			break;
706 	}
707 
708 	if (descr != NULL) {
709 		status_msg = g_strdup(descr);
710 		g_strstrip(status_msg);
711 		if (status_msg[0] == '\0') {
712 			g_free(status_msg);
713 			status_msg = NULL;
714 		}
715 	}
716 
717 	purple_debug_info("gg", "status of %u is %s [%s]\n", uin, st,
718 		status_msg ? status_msg : "");
719 	if (status_msg == NULL) {
720 		purple_prpl_got_user_status(purple_connection_get_account(gc),
721 			from, st, NULL);
722 	} else {
723 		purple_prpl_got_user_status(purple_connection_get_account(gc),
724 			from, st, "message", status_msg, NULL);
725 		g_free(status_msg);
726 	}
727 	g_free(from);
728 }
729 
ggp_sr_close_cb(gpointer user_data)730 static void ggp_sr_close_cb(gpointer user_data)
731 {
732 	GGPSearchForm *form = user_data;
733 	GGPInfo *info = form->user_data;
734 
735 	ggp_search_remove(info->searches, form->seq);
736 	purple_debug_info("gg", "ggp_sr_close_cb(): Removed seq %u\n",
737 		form->seq);
738 	ggp_search_form_destroy(form);
739 }
740 
741 /**
742  * Translate a status' ID to a more user-friendly name.
743  *
744  * @param id The ID of the status.
745  *
746  * @return The user-friendly name of the status.
747  */
ggp_status_by_id(unsigned int id)748 static const char *ggp_status_by_id(unsigned int id)
749 {
750 	const char *st;
751 
752 	purple_debug_info("gg", "ggp_status_by_id: %d\n", id);
753 	switch (id) {
754 		case GG_STATUS_NOT_AVAIL:
755 		case GG_STATUS_NOT_AVAIL_DESCR:
756 			st = _("Offline");
757 			break;
758 		case GG_STATUS_AVAIL:
759 		case GG_STATUS_AVAIL_DESCR:
760 			st = _("Available");
761 			break;
762 		case GG_STATUS_FFC:
763 		case GG_STATUS_FFC_DESCR:
764 			return _("Chatty");
765 		case GG_STATUS_DND:
766 		case GG_STATUS_DND_DESCR:
767 			return _("Do Not Disturb");
768 		case GG_STATUS_BUSY:
769 		case GG_STATUS_BUSY_DESCR:
770 			st = _("Away");
771 			break;
772 		default:
773 			st = _("Unknown");
774 			break;
775 	}
776 
777 	return st;
778 }
779 
ggp_pubdir_handle_info(PurpleConnection * gc,gg_pubdir50_t req,GGPSearchForm * form)780 static void ggp_pubdir_handle_info(PurpleConnection *gc, gg_pubdir50_t req,
781 				   GGPSearchForm *form)
782 {
783 	PurpleNotifyUserInfo *user_info;
784 	PurpleBuddy *buddy;
785 	char *val, *who;
786 
787 	user_info = purple_notify_user_info_new();
788 
789 	val = ggp_search_get_result(req, 0, GG_PUBDIR50_STATUS);
790 	/* XXX: Use of ggp_str_to_uin() is an ugly hack! */
791 	purple_notify_user_info_add_pair(user_info, _("Status"), ggp_status_by_id(ggp_str_to_uin(val)));
792 	g_free(val);
793 
794 	who = ggp_search_get_result(req, 0, GG_PUBDIR50_UIN);
795 	purple_notify_user_info_add_pair(user_info, _("UIN"), who);
796 
797 	val = ggp_search_get_result(req, 0, GG_PUBDIR50_FIRSTNAME);
798 	purple_notify_user_info_add_pair(user_info, _("First Name"), val);
799 	g_free(val);
800 
801 	val = ggp_search_get_result(req, 0, GG_PUBDIR50_NICKNAME);
802 	purple_notify_user_info_add_pair(user_info, _("Nickname"), val);
803 	g_free(val);
804 
805 	val = ggp_search_get_result(req, 0, GG_PUBDIR50_CITY);
806 	purple_notify_user_info_add_pair(user_info, _("City"), val);
807 	g_free(val);
808 
809 	val = ggp_search_get_result(req, 0, GG_PUBDIR50_BIRTHYEAR);
810 	if (strncmp(val, "0", 1)) {
811 		purple_notify_user_info_add_pair(user_info, _("Birth Year"), val);
812 	}
813 	g_free(val);
814 
815 	/*
816 	 * Include a status message, if exists and buddy is in the blist.
817 	 */
818 	buddy = purple_find_buddy(purple_connection_get_account(gc), who);
819 	if (NULL != buddy) {
820 		PurpleStatus *status;
821 		const char *msg;
822 		char *text;
823 
824 		status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
825 		msg = purple_status_get_attr_string(status, "message");
826 
827 		if (msg != NULL) {
828 			text = g_markup_escape_text(msg, -1);
829 			purple_notify_user_info_add_pair(user_info, _("Message"), text);
830 			g_free(text);
831 		}
832 	}
833 
834 	purple_notify_userinfo(gc, who, user_info, ggp_sr_close_cb, form);
835 	g_free(who);
836 	purple_notify_user_info_destroy(user_info);
837 }
838 
ggp_pubdir_handle_full(PurpleConnection * gc,gg_pubdir50_t req,GGPSearchForm * form)839 static void ggp_pubdir_handle_full(PurpleConnection *gc, gg_pubdir50_t req,
840 				   GGPSearchForm *form)
841 {
842 	PurpleNotifySearchResults *results;
843 	PurpleNotifySearchColumn *column;
844 	int res_count;
845 	int start;
846 	int i;
847 
848 	g_return_if_fail(form != NULL);
849 
850 	res_count = gg_pubdir50_count(req);
851 	res_count = (res_count > PUBDIR_RESULTS_MAX) ? PUBDIR_RESULTS_MAX : res_count;
852 	if (form->page_size == 0)
853 		form->page_size = res_count;
854 
855 	results = purple_notify_searchresults_new();
856 
857 	if (results == NULL) {
858 		purple_debug_error("gg", "ggp_pubdir_reply_handler: "
859 				 "Unable to display the search results.\n");
860 		purple_notify_error(gc, NULL,
861 				  _("Unable to display the search results."),
862 				  NULL);
863 		if (form->window == NULL)
864 			ggp_sr_close_cb(form);
865 		return;
866 	}
867 
868 	column = purple_notify_searchresults_column_new(_("UIN"));
869 	purple_notify_searchresults_column_add(results, column);
870 
871 	column = purple_notify_searchresults_column_new(_("First Name"));
872 	purple_notify_searchresults_column_add(results, column);
873 
874 	column = purple_notify_searchresults_column_new(_("Nickname"));
875 	purple_notify_searchresults_column_add(results, column);
876 
877 	column = purple_notify_searchresults_column_new(_("City"));
878 	purple_notify_searchresults_column_add(results, column);
879 
880 	column = purple_notify_searchresults_column_new(_("Birth Year"));
881 	purple_notify_searchresults_column_add(results, column);
882 
883 	purple_debug_info("gg", "Going with %d entries\n", res_count);
884 
885 	start = (int)ggp_str_to_uin(gg_pubdir50_get(req, 0, GG_PUBDIR50_START));
886 	purple_debug_info("gg", "start = %d\n", start);
887 
888 	for (i = 0; i < res_count; i++) {
889 		GList *row = NULL;
890 		char *birth = ggp_search_get_result(req, i, GG_PUBDIR50_BIRTHYEAR);
891 
892 		/* TODO: Status will be displayed as an icon. */
893 		/* row = g_list_append(row, ggp_search_get_result(req, i, GG_PUBDIR50_STATUS)); */
894 		row = g_list_append(row, ggp_search_get_result(req, i,
895 							GG_PUBDIR50_UIN));
896 		row = g_list_append(row, ggp_search_get_result(req, i,
897 							GG_PUBDIR50_FIRSTNAME));
898 		row = g_list_append(row, ggp_search_get_result(req, i,
899 							GG_PUBDIR50_NICKNAME));
900 		row = g_list_append(row, ggp_search_get_result(req, i,
901 							GG_PUBDIR50_CITY));
902 		row = g_list_append(row,
903 			(birth && strncmp(birth, "0", 1)) ? birth : g_strdup("-"));
904 
905 		purple_notify_searchresults_row_add(results, row);
906 	}
907 
908 	purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_CONTINUE,
909 					     ggp_callback_show_next);
910 	purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD,
911 					     ggp_callback_add_buddy);
912 	purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM,
913 					     ggp_callback_im);
914 
915 	if (form->window == NULL) {
916 		void *h = purple_notify_searchresults(gc,
917 				_("Gadu-Gadu Public Directory"),
918 				_("Search results"), NULL, results,
919 				(PurpleNotifyCloseCallback)ggp_sr_close_cb,
920 				form);
921 
922 		if (h == NULL) {
923 			purple_debug_error("gg", "ggp_pubdir_reply_handler: "
924 					 "Unable to display the search results.\n");
925 			purple_notify_error(gc, NULL,
926 					  _("Unable to display the search results."),
927 					  NULL);
928 			return;
929 		}
930 
931 		form->window = h;
932 	} else {
933 		purple_notify_searchresults_new_rows(gc, results, form->window);
934 	}
935 }
936 
ggp_pubdir_reply_handler(PurpleConnection * gc,gg_pubdir50_t req)937 static void ggp_pubdir_reply_handler(PurpleConnection *gc, gg_pubdir50_t req)
938 {
939 	GGPInfo *info = gc->proto_data;
940 	GGPSearchForm *form;
941 	int res_count;
942 	guint32 seq;
943 
944 	seq = gg_pubdir50_seq(req);
945 	form = ggp_search_get(info->searches, seq);
946 	purple_debug_info("gg",
947 		"ggp_pubdir_reply_handler(): seq %u --> form %p\n", seq, form);
948 	/*
949 	 * this can happen when user will request more results
950 	 * and close the results window before they arrive.
951 	 */
952 	g_return_if_fail(form != NULL);
953 
954 	res_count = gg_pubdir50_count(req);
955 	if (res_count < 1) {
956 		purple_debug_info("gg", "GG_EVENT_PUBDIR50_SEARCH_REPLY: Nothing found\n");
957 		purple_notify_error(gc, NULL,
958 			_("No matching users found"),
959 			_("There are no users matching your search criteria."));
960 		if (form->window == NULL)
961 			ggp_sr_close_cb(form);
962 		return;
963 	}
964 
965 	switch (form->search_type) {
966 		case GGP_SEARCH_TYPE_INFO:
967 			ggp_pubdir_handle_info(gc, req, form);
968 			break;
969 		case GGP_SEARCH_TYPE_FULL:
970 			ggp_pubdir_handle_full(gc, req, form);
971 			break;
972 		default:
973 			purple_debug_warning("gg", "Unknown search_type!\n");
974 			break;
975 	}
976 }
977 
ggp_recv_image_handler(PurpleConnection * gc,const struct gg_event * ev)978 static void ggp_recv_image_handler(PurpleConnection *gc, const struct gg_event *ev)
979 {
980 	gint imgid = 0;
981 	GGPInfo *info = gc->proto_data;
982 	GList *entry = g_list_first(info->pending_richtext_messages);
983 	gchar *handlerid = g_strdup_printf("IMGID_HANDLER-%i", ev->event.image_reply.crc32);
984 
985 	imgid = purple_imgstore_add_with_id(
986 		g_memdup2(ev->event.image_reply.image, ev->event.image_reply.size),
987 		ev->event.image_reply.size,
988 		ev->event.image_reply.filename);
989 
990 	purple_debug_info("gg", "ggp_recv_image_handler: got image with crc32: %u\n", ev->event.image_reply.crc32);
991 
992 	while(entry) {
993 		if (strstr((gchar *)entry->data, handlerid) != NULL) {
994 			gchar **split = g_strsplit((gchar *)entry->data, handlerid, 3);
995 			gchar *text = g_strdup_printf("%s%i%s", split[0], imgid, split[1]);
996 			purple_debug_info("gg", "ggp_recv_image_handler: found message matching crc32: %s\n", (gchar *)entry->data);
997 			g_strfreev(split);
998 			info->pending_richtext_messages = g_list_remove(info->pending_richtext_messages, entry->data);
999 			/* We don't have any more images to download */
1000 			if (strstr(text, "<IMG ID=\"IMGID_HANDLER") == NULL) {
1001 				gchar *buf = g_strdup_printf("%lu", (unsigned long int)ev->event.image_reply.sender);
1002 				serv_got_im(gc, buf, text, PURPLE_MESSAGE_IMAGES, time(NULL));
1003 				g_free(buf);
1004 				purple_debug_info("gg", "ggp_recv_image_handler: richtext message: %s\n", text);
1005 				g_free(text);
1006 				break;
1007 			}
1008 			info->pending_richtext_messages = g_list_append(info->pending_richtext_messages, text);
1009 			break;
1010 		}
1011 		entry = g_list_next(entry);
1012 	}
1013 	g_free(handlerid);
1014 
1015 	return;
1016 }
1017 
1018 
1019 /**
1020  * Dispatch a message received from a buddy.
1021  *
1022  * @param gc PurpleConnection.
1023  * @param ev Gadu-Gadu event structure.
1024  *
1025  * Image receiving, some code borrowed from Kadu http://www.kadu.net
1026  */
ggp_recv_message_handler(PurpleConnection * gc,const struct gg_event * ev)1027 static void ggp_recv_message_handler(PurpleConnection *gc, const struct gg_event *ev)
1028 {
1029 	GGPInfo *info = gc->proto_data;
1030 	PurpleConversation *conv;
1031 	gchar *from;
1032 	gchar *msg;
1033 	gchar *tmp;
1034 
1035 	if (ev->event.msg.message == NULL)
1036 	{
1037 		purple_debug_warning("gg", "ggp_recv_message_handler: NULL as message pointer\n");
1038 		return;
1039 	}
1040 
1041 	from = g_strdup_printf("%lu", (unsigned long int)ev->event.msg.sender);
1042 
1043 	/*
1044 	tmp = charset_convert((const char *)ev->event.msg.message,
1045 			      "CP1250", "UTF-8");
1046 	*/
1047 	tmp = g_strdup_printf("%s", ev->event.msg.message);
1048 	purple_str_strip_char(tmp, '\r');
1049 	msg = g_markup_escape_text(tmp, -1);
1050 	g_free(tmp);
1051 
1052 	/* We got richtext message */
1053 	if (ev->event.msg.formats_length)
1054 	{
1055 		gboolean got_image = FALSE, bold = FALSE, italic = FALSE, under = FALSE;
1056 		char *cformats = (char *)ev->event.msg.formats;
1057 		char *cformats_end = cformats + ev->event.msg.formats_length;
1058 		gint increased_len = 0;
1059 		struct gg_msg_richtext_format *actformat;
1060 		struct gg_msg_richtext_image *actimage;
1061 		GString *message = g_string_new(msg);
1062 		gchar *handlerid;
1063 
1064 		purple_debug_info("gg", "ggp_recv_message_handler: richtext msg from (%s): %s %i formats\n", from, msg, ev->event.msg.formats_length);
1065 
1066 		while (cformats < cformats_end)
1067 		{
1068 			gint byteoffset;
1069 			actformat = (struct gg_msg_richtext_format *)cformats;
1070 			cformats += sizeof(struct gg_msg_richtext_format);
1071 			byteoffset = g_utf8_offset_to_pointer(message->str, actformat->position + increased_len) - message->str;
1072 
1073 			if(actformat->position == 0 && actformat->font == 0) {
1074 				purple_debug_warning("gg", "ggp_recv_message_handler: bogus formatting (inc: %i)\n", increased_len);
1075 				continue;
1076 			}
1077 			purple_debug_info("gg", "ggp_recv_message_handler: format at pos: %i, image:%i, bold:%i, italic: %i, under:%i (inc: %i)\n",
1078 				actformat->position,
1079 				(actformat->font & GG_FONT_IMAGE) != 0,
1080 				(actformat->font & GG_FONT_BOLD) != 0,
1081 				(actformat->font & GG_FONT_ITALIC) != 0,
1082 				(actformat->font & GG_FONT_UNDERLINE) != 0,
1083 				increased_len);
1084 
1085 			if (actformat->font & GG_FONT_IMAGE) {
1086 				got_image = TRUE;
1087 				actimage = (struct gg_msg_richtext_image*)(cformats);
1088 				cformats += sizeof(struct gg_msg_richtext_image);
1089 				purple_debug_info("gg", "ggp_recv_message_handler: image received, size: %d, crc32: %i\n", actimage->size, actimage->crc32);
1090 
1091 				/* Checking for errors, image size shouldn't be
1092 				 * larger than 255.000 bytes */
1093 				if (actimage->size > 255000) {
1094 					purple_debug_warning("gg", "ggp_recv_message_handler: received image large than 255 kb\n");
1095 					continue;
1096 				}
1097 
1098 				gg_image_request(info->session, ev->event.msg.sender,
1099 					actimage->size, actimage->crc32);
1100 
1101 				handlerid = g_strdup_printf("<IMG ID=\"IMGID_HANDLER-%i\">", actimage->crc32);
1102 				g_string_insert(message, byteoffset, handlerid);
1103 				increased_len += strlen(handlerid);
1104 				g_free(handlerid);
1105 				continue;
1106 			}
1107 
1108 			if (actformat->font & GG_FONT_BOLD) {
1109 				if (bold == FALSE) {
1110 					g_string_insert(message, byteoffset, "<b>");
1111 					increased_len += 3;
1112 					bold = TRUE;
1113 				}
1114 			} else if (bold) {
1115 				g_string_insert(message, byteoffset, "</b>");
1116 				increased_len += 4;
1117 				bold = FALSE;
1118 			}
1119 
1120 			if (actformat->font & GG_FONT_ITALIC) {
1121 				if (italic == FALSE) {
1122 					g_string_insert(message, byteoffset, "<i>");
1123 					increased_len += 3;
1124 					italic = TRUE;
1125 				}
1126 			} else if (italic) {
1127 				g_string_insert(message, byteoffset, "</i>");
1128 				increased_len += 4;
1129 				italic = FALSE;
1130 			}
1131 
1132 			if (actformat->font & GG_FONT_UNDERLINE) {
1133 				if (under == FALSE) {
1134 					g_string_insert(message, byteoffset, "<u>");
1135 					increased_len += 3;
1136 					under = TRUE;
1137 				}
1138 			} else if (under) {
1139 				g_string_insert(message, byteoffset, "</u>");
1140 				increased_len += 4;
1141 				under = FALSE;
1142 			}
1143 
1144 			if (actformat->font & GG_FONT_COLOR) {
1145 				cformats += sizeof(struct gg_msg_richtext_color);
1146 			}
1147 		}
1148 
1149 		msg = message->str;
1150 		g_string_free(message, FALSE);
1151 
1152 		if (got_image) {
1153 			info->pending_richtext_messages = g_list_append(info->pending_richtext_messages, msg);
1154 			return;
1155 		}
1156 	}
1157 
1158 	purple_debug_info("gg", "ggp_recv_message_handler: msg from (%s): %s (class = %d; rcpt_count = %d)\n",
1159 			from, msg, ev->event.msg.msgclass,
1160 			ev->event.msg.recipients_count);
1161 
1162 	if (ev->event.msg.recipients_count == 0) {
1163 		serv_got_im(gc, from, msg, 0, ev->event.msg.time);
1164 	} else {
1165 		const char *chat_name;
1166 		int chat_id;
1167 		char *buddy_name;
1168 
1169 		chat_name = ggp_confer_find_by_participants(gc,
1170 				ev->event.msg.recipients,
1171 				ev->event.msg.recipients_count);
1172 
1173 		if (chat_name == NULL) {
1174 			chat_name = ggp_confer_add_new(gc, NULL);
1175 			serv_got_joined_chat(gc, info->chats_count, chat_name);
1176 
1177 			ggp_confer_participants_add_uin(gc, chat_name,
1178 							ev->event.msg.sender);
1179 
1180 			ggp_confer_participants_add(gc, chat_name,
1181 						    ev->event.msg.recipients,
1182 						    ev->event.msg.recipients_count);
1183 		}
1184 		conv = ggp_confer_find_by_name(gc, chat_name);
1185 		chat_id = purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv));
1186 
1187 		buddy_name = ggp_buddy_get_name(gc, ev->event.msg.sender);
1188 		serv_got_chat_in(gc, chat_id, buddy_name,
1189 				 PURPLE_MESSAGE_RECV, msg, ev->event.msg.time);
1190 		g_free(buddy_name);
1191 	}
1192 	g_free(msg);
1193 	g_free(from);
1194 }
1195 
ggp_send_image_handler(PurpleConnection * gc,const struct gg_event * ev)1196 static void ggp_send_image_handler(PurpleConnection *gc, const struct gg_event *ev)
1197 {
1198 	GGPInfo *info = gc->proto_data;
1199 	PurpleStoredImage *image;
1200 	gint imgid = GPOINTER_TO_INT(g_hash_table_lookup(info->pending_images, GINT_TO_POINTER(ev->event.image_request.crc32)));
1201 
1202 	purple_debug_info("gg", "ggp_send_image_handler: image request received, crc32: %u, imgid: %d\n", ev->event.image_request.crc32, imgid);
1203 
1204 	if(imgid)
1205 	{
1206 		if((image = purple_imgstore_find_by_id(imgid))) {
1207 			gint image_size = purple_imgstore_get_size(image);
1208 			gconstpointer image_bin = purple_imgstore_get_data(image);
1209 			const char *image_filename = purple_imgstore_get_filename(image);
1210 
1211 			purple_debug_info("gg", "ggp_send_image_handler: sending image imgid: %i, crc: %u\n", imgid, ev->event.image_request.crc32);
1212 			gg_image_reply(info->session, (unsigned long int)ev->event.image_request.sender, image_filename, image_bin, image_size);
1213 			purple_imgstore_unref(image);
1214 		} else {
1215 			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);
1216 		}
1217 		g_hash_table_remove(info->pending_images, GINT_TO_POINTER(ev->event.image_request.crc32));
1218 	}
1219 }
1220 
ggp_typing_notification_handler(PurpleConnection * gc,uin_t uin,int length)1221 static void ggp_typing_notification_handler(PurpleConnection *gc, uin_t uin, int length) {
1222 	gchar *from;
1223 
1224 	from = g_strdup_printf("%u", uin);
1225 	if (length)
1226 		serv_got_typing(gc, from, 0, PURPLE_TYPING);
1227 	else
1228 		serv_got_typing_stopped(gc, from);
1229 	g_free(from);
1230 }
1231 
1232 /**
1233  * Handling of XML events.
1234  *
1235  * @param gc PurpleConnection.
1236  * @param data Raw XML contents.
1237  *
1238  * @see http://toxygen.net/libgadu/protocol/#ch1.13
1239  */
ggp_xml_event_handler(PurpleConnection * gc,char * data)1240 static void ggp_xml_event_handler(PurpleConnection *gc, char *data)
1241 {
1242 	xmlnode *xml = NULL;
1243 	xmlnode *xmlnode_next_event;
1244 
1245 	xml = xmlnode_from_str(data, -1);
1246 	if (xml == NULL)
1247 		goto out;
1248 
1249 	xmlnode_next_event = xmlnode_get_child(xml, "event");
1250 	while (xmlnode_next_event != NULL)
1251 	{
1252 		xmlnode *xmlnode_current_event = xmlnode_next_event;
1253 
1254 		xmlnode *xmlnode_type;
1255 		char *event_type_raw;
1256 		int event_type = 0;
1257 
1258 		xmlnode *xmlnode_sender;
1259 		char *event_sender_raw;
1260 		uin_t event_sender = 0;
1261 
1262 		xmlnode_next_event = xmlnode_get_next_twin(xmlnode_next_event);
1263 
1264 		xmlnode_type = xmlnode_get_child(xmlnode_current_event, "type");
1265 		if (xmlnode_type == NULL)
1266 			continue;
1267 		event_type_raw = xmlnode_get_data(xmlnode_type);
1268 		if (event_type_raw != NULL)
1269 			event_type = atoi(event_type_raw);
1270 		g_free(event_type_raw);
1271 
1272 		xmlnode_sender = xmlnode_get_child(xmlnode_current_event, "sender");
1273 		if (xmlnode_sender != NULL)
1274 		{
1275 			event_sender_raw = xmlnode_get_data(xmlnode_sender);
1276 			if (event_sender_raw != NULL)
1277 				event_sender = ggp_str_to_uin(event_sender_raw);
1278 			g_free(event_sender_raw);
1279 		}
1280 
1281 		switch (event_type)
1282 		{
1283 			case 28: /* avatar update */
1284 				purple_debug_info("gg",
1285 					"ggp_xml_event_handler: avatar updated (uid: %u)\n",
1286 					event_sender);
1287 				ggp_update_buddy_avatar(gc, event_sender);
1288 				break;
1289 			default:
1290 				purple_debug_error("gg",
1291 					"ggp_xml_event_handler: unsupported event type=%d from=%u\n",
1292 					event_type, event_sender);
1293 		}
1294 	}
1295 
1296 	out:
1297 		if (xml)
1298 			xmlnode_free(xml);
1299 }
1300 
ggp_callback_recv(gpointer _gc,gint fd,PurpleInputCondition cond)1301 static void ggp_callback_recv(gpointer _gc, gint fd, PurpleInputCondition cond)
1302 {
1303 	PurpleConnection *gc = _gc;
1304 	GGPInfo *info = gc->proto_data;
1305 	struct gg_event *ev;
1306 	int i;
1307 
1308 	if (!(ev = gg_watch_fd(info->session))) {
1309 		purple_debug_error("gg",
1310 			"ggp_callback_recv: gg_watch_fd failed -- CRITICAL!\n");
1311 		purple_connection_error_reason (gc,
1312 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1313 			_("Unable to read from socket"));
1314 		return;
1315 	}
1316 
1317 	purple_input_remove(gc->inpa);
1318 	gc->inpa = purple_input_add(info->session->fd,
1319 		ggp_tcpsocket_inputcond_gg_to_purple(info->session->check),
1320 		ggp_callback_recv, gc);
1321 
1322 	switch (ev->type) {
1323 		case GG_EVENT_NONE:
1324 			/* Nothing happened. */
1325 			break;
1326 		case GG_EVENT_MSG:
1327 			ggp_recv_message_handler(gc, ev);
1328 			break;
1329 		case GG_EVENT_ACK:
1330 			/* Changing %u to %i fixes compiler warning */
1331 			purple_debug_info("gg",
1332 				"ggp_callback_recv: message sent to: %i, delivery status=%d, seq=%d\n",
1333 				ev->event.ack.recipient, ev->event.ack.status,
1334 				ev->event.ack.seq);
1335 			break;
1336 		case GG_EVENT_IMAGE_REPLY:
1337 			ggp_recv_image_handler(gc, ev);
1338 			break;
1339 		case GG_EVENT_IMAGE_REQUEST:
1340 			ggp_send_image_handler(gc, ev);
1341 			break;
1342 		case GG_EVENT_NOTIFY:
1343 		case GG_EVENT_NOTIFY_DESCR:
1344 			{
1345 				struct gg_notify_reply *n;
1346 				char *descr;
1347 
1348 				purple_debug_info("gg", "notify_pre: (%d) status: %d\n",
1349 						ev->event.notify->uin,
1350 						GG_S(ev->event.notify->status));
1351 
1352 				n = (ev->type == GG_EVENT_NOTIFY) ? ev->event.notify
1353 								  : ev->event.notify_descr.notify;
1354 
1355 				for (; n->uin; n++) {
1356 					descr = (ev->type == GG_EVENT_NOTIFY) ? NULL
1357 							: ev->event.notify_descr.descr;
1358 
1359 					purple_debug_info("gg",
1360 						"notify: (%d) status: %d; descr: %s\n",
1361 						n->uin, GG_S(n->status), descr ? descr : "(null)");
1362 
1363 					ggp_generic_status_handler(gc,
1364 						n->uin, GG_S(n->status), descr);
1365 				}
1366 			}
1367 			break;
1368 		case GG_EVENT_NOTIFY60:
1369 			for (i = 0; ev->event.notify60[i].uin; i++) {
1370 				purple_debug_info("gg",
1371 					"notify60: (%d) status=%d; version=%d; descr=%s\n",
1372 					ev->event.notify60[i].uin,
1373 					GG_S(ev->event.notify60[i].status),
1374 					ev->event.notify60[i].version,
1375 					ev->event.notify60[i].descr ? ev->event.notify60[i].descr : "(null)");
1376 
1377 				ggp_generic_status_handler(gc, ev->event.notify60[i].uin,
1378 					GG_S(ev->event.notify60[i].status),
1379 					ev->event.notify60[i].descr);
1380 			}
1381 			break;
1382 		case GG_EVENT_STATUS:
1383 			purple_debug_info("gg", "status: (%d) status=%d; descr=%s\n",
1384 					ev->event.status.uin, GG_S(ev->event.status.status),
1385 					ev->event.status.descr ? ev->event.status.descr : "(null)");
1386 
1387 			ggp_generic_status_handler(gc, ev->event.status.uin,
1388 				GG_S(ev->event.status.status), ev->event.status.descr);
1389 			break;
1390 		case GG_EVENT_STATUS60:
1391 			purple_debug_info("gg",
1392 				"status60: (%d) status=%d; version=%d; descr=%s\n",
1393 				ev->event.status60.uin, GG_S(ev->event.status60.status),
1394 				ev->event.status60.version,
1395 				ev->event.status60.descr ? ev->event.status60.descr : "(null)");
1396 
1397 			ggp_generic_status_handler(gc, ev->event.status60.uin,
1398 				GG_S(ev->event.status60.status), ev->event.status60.descr);
1399 			break;
1400 		case GG_EVENT_PUBDIR50_SEARCH_REPLY:
1401 			ggp_pubdir_reply_handler(gc, ev->event.pubdir50);
1402 			break;
1403 		case GG_EVENT_TYPING_NOTIFICATION:
1404 			ggp_typing_notification_handler(gc, ev->event.typing_notification.uin,
1405 				ev->event.typing_notification.length);
1406 			break;
1407 		case GG_EVENT_XML_EVENT:
1408 			purple_debug_info("gg", "GG_EVENT_XML_EVENT\n");
1409 			ggp_xml_event_handler(gc, ev->event.xml_event.data);
1410 			break;
1411 		default:
1412 			purple_debug_error("gg",
1413 				"unsupported event type=%d\n", ev->type);
1414 			break;
1415 	}
1416 
1417 	gg_free_event(ev);
1418 }
1419 
ggp_async_login_handler(gpointer _gc,gint fd,PurpleInputCondition cond)1420 static void ggp_async_login_handler(gpointer _gc, gint fd, PurpleInputCondition cond)
1421 {
1422 	PurpleConnection *gc = _gc;
1423 	GGPInfo *info;
1424 	struct gg_event *ev;
1425 
1426 	g_return_if_fail(PURPLE_CONNECTION_IS_VALID(gc));
1427 
1428 	info = gc->proto_data;
1429 
1430 	purple_debug_info("gg", "login_handler: session: check = %d; state = %d;\n",
1431 			info->session->check, info->session->state);
1432 
1433 	switch (info->session->state) {
1434 		case GG_STATE_RESOLVING:
1435 			purple_debug_info("gg", "GG_STATE_RESOLVING\n");
1436 			break;
1437 		case GG_STATE_RESOLVING_GG:
1438 			purple_debug_info("gg", "GG_STATE_RESOLVING_GG\n");
1439 			break;
1440 		case GG_STATE_CONNECTING_HUB:
1441 			purple_debug_info("gg", "GG_STATE_CONNECTING_HUB\n");
1442 			break;
1443 		case GG_STATE_READING_DATA:
1444 			purple_debug_info("gg", "GG_STATE_READING_DATA\n");
1445 			break;
1446 		case GG_STATE_CONNECTING_GG:
1447 			purple_debug_info("gg", "GG_STATE_CONNECTING_GG\n");
1448 			break;
1449 		case GG_STATE_READING_KEY:
1450 			purple_debug_info("gg", "GG_STATE_READING_KEY\n");
1451 			break;
1452 		case GG_STATE_READING_REPLY:
1453 			purple_debug_info("gg", "GG_STATE_READING_REPLY\n");
1454 			break;
1455 		case GG_STATE_TLS_NEGOTIATION:
1456 			purple_debug_info("gg", "GG_STATE_TLS_NEGOTIATION\n");
1457 			break;
1458 		default:
1459 			purple_debug_error("gg", "unknown state = %d\n",
1460 					 info->session->state);
1461 		break;
1462 	}
1463 
1464 	if (!(ev = gg_watch_fd(info->session))) {
1465 		purple_debug_error("gg", "login_handler: gg_watch_fd failed!\n");
1466 		purple_connection_error_reason (gc,
1467 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1468 			_("Unable to read from socket"));
1469 		return;
1470 	}
1471 	purple_debug_info("gg", "login_handler: session->fd = %d\n", info->session->fd);
1472 	purple_debug_info("gg", "login_handler: session: check = %d; state = %d;\n",
1473 			info->session->check, info->session->state);
1474 
1475 	purple_input_remove(gc->inpa);
1476 
1477 	/** XXX I think that this shouldn't be done if ev->type is GG_EVENT_CONN_FAILED or GG_EVENT_CONN_SUCCESS -datallah */
1478 	if (info->session->fd >= 0)
1479 		gc->inpa = purple_input_add(info->session->fd,
1480 			ggp_tcpsocket_inputcond_gg_to_purple(info->session->check),
1481 			ggp_async_login_handler, gc);
1482 
1483 	switch (ev->type) {
1484 		case GG_EVENT_NONE:
1485 			/* Nothing happened. */
1486 			purple_debug_info("gg", "GG_EVENT_NONE\n");
1487 			break;
1488 		case GG_EVENT_CONN_SUCCESS:
1489 			{
1490 				purple_debug_info("gg", "GG_EVENT_CONN_SUCCESS\n");
1491 				purple_input_remove(gc->inpa);
1492 				gc->inpa = purple_input_add(info->session->fd,
1493 					ggp_tcpsocket_inputcond_gg_to_purple(info->session->check),
1494 					ggp_callback_recv, gc);
1495 
1496 				ggp_buddylist_send(gc);
1497 				purple_connection_update_progress(gc, _("Connected"), 1, 2);
1498 				purple_connection_set_state(gc, PURPLE_CONNECTED);
1499 			}
1500 			break;
1501 		case GG_EVENT_CONN_FAILED:
1502 			purple_input_remove(gc->inpa);
1503 			gc->inpa = 0;
1504 			purple_connection_error_reason (gc,
1505 				PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1506 				_("Connection failed"));
1507 			break;
1508 		case GG_EVENT_MSG:
1509 			if (ev->event.msg.sender == 0)
1510 				/* system messages are mostly ads */
1511 				purple_debug_info("gg", "System message:\n%s\n",
1512 					ev->event.msg.message);
1513 			else
1514 				purple_debug_warning("gg", "GG_EVENT_MSG: message from user %u "
1515 					"unexpected while connecting:\n%s\n",
1516 					ev->event.msg.sender,
1517 					ev->event.msg.message);
1518 			break;
1519 		default:
1520 			purple_debug_error("gg", "strange event: %d\n", ev->type);
1521 			break;
1522 	}
1523 
1524 	gg_free_event(ev);
1525 }
1526 
1527 /* ---------------------------------------------------------------------- */
1528 /* ----- PurplePluginProtocolInfo ----------------------------------------- */
1529 /* ---------------------------------------------------------------------- */
1530 
ggp_list_icon(PurpleAccount * account,PurpleBuddy * buddy)1531 static const char *ggp_list_icon(PurpleAccount *account, PurpleBuddy *buddy)
1532 {
1533 	return "gadu-gadu";
1534 }
1535 
ggp_status_text(PurpleBuddy * b)1536 static char *ggp_status_text(PurpleBuddy *b)
1537 {
1538 	PurpleStatus *status;
1539 	const char *msg;
1540 	char *text;
1541 	char *tmp;
1542 
1543 	status = purple_presence_get_active_status(
1544 		purple_buddy_get_presence(b));
1545 	msg = purple_status_get_attr_string(status, "message");
1546 
1547 	if (msg == NULL)
1548 		return NULL;
1549 
1550 	tmp = purple_markup_strip_html(msg);
1551 	text = g_markup_escape_text(tmp, -1);
1552 	g_free(tmp);
1553 
1554 	return text;
1555 }
1556 
ggp_tooltip_text(PurpleBuddy * b,PurpleNotifyUserInfo * user_info,gboolean full)1557 static void ggp_tooltip_text(PurpleBuddy *b, PurpleNotifyUserInfo *user_info, gboolean full)
1558 {
1559 	PurpleStatus *status;
1560 	char *text, *tmp;
1561 	const char *msg, *name, *alias;
1562 
1563 	g_return_if_fail(b != NULL);
1564 
1565 	status = purple_presence_get_active_status(purple_buddy_get_presence(b));
1566 	msg = purple_status_get_attr_string(status, "message");
1567 	name = purple_status_get_name(status);
1568 	alias = purple_buddy_get_alias(b);
1569 
1570 	purple_notify_user_info_add_pair (user_info, _("Alias"), alias);
1571 
1572 	if (msg != NULL) {
1573 		text = g_markup_escape_text(msg, -1);
1574 		if (PURPLE_BUDDY_IS_ONLINE(b)) {
1575 			tmp = g_strdup_printf("%s: %s", name, text);
1576 			purple_notify_user_info_add_pair(user_info, _("Status"), tmp);
1577 			g_free(tmp);
1578 		} else {
1579 			purple_notify_user_info_add_pair(user_info, _("Message"), text);
1580 		}
1581 		g_free(text);
1582 	/* We don't want to duplicate 'Status: Offline'. */
1583 	} else if (PURPLE_BUDDY_IS_ONLINE(b)) {
1584 		purple_notify_user_info_add_pair(user_info, _("Status"), name);
1585 	}
1586 }
1587 
ggp_status_types(PurpleAccount * account)1588 static GList *ggp_status_types(PurpleAccount *account)
1589 {
1590 	PurpleStatusType *type;
1591 	GList *types = NULL;
1592 
1593 	type = purple_status_type_new_with_attrs(
1594 			PURPLE_STATUS_AVAILABLE, NULL, NULL, TRUE, TRUE, FALSE,
1595 			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1596 			NULL);
1597 	types = g_list_append(types, type);
1598 
1599 	/*
1600 	 * Without this selecting Invisible as own status doesn't
1601 	 * work. It's not used and not needed to show status of buddies.
1602 	 */
1603 	type = purple_status_type_new_with_attrs(
1604 			PURPLE_STATUS_INVISIBLE, NULL, NULL, TRUE, TRUE, FALSE,
1605 			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1606 			NULL);
1607 	types = g_list_append(types, type);
1608 
1609 	type = purple_status_type_new_with_attrs(
1610 			PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1611 			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1612 			NULL);
1613 	types = g_list_append(types, type);
1614 
1615  	/*
1616 	 * New statuses for GG 8.0 like PoGGadaj ze mna (not yet because
1617 	 * libpurple can't support Chatty status) and Nie przeszkadzac
1618 	 */
1619 	type = purple_status_type_new_with_attrs(
1620 			PURPLE_STATUS_UNAVAILABLE, NULL, NULL, TRUE, TRUE, FALSE,
1621 			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1622 			NULL);
1623 	types = g_list_append(types, type);
1624 
1625 	/*
1626 	 * This status is necessary to display guys who are blocking *us*.
1627 	 */
1628 	type = purple_status_type_new_with_attrs(
1629 			PURPLE_STATUS_INVISIBLE, "blocked", _("Blocked"), TRUE, FALSE, FALSE,
1630 			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING), NULL);
1631 	types = g_list_append(types, type);
1632 
1633 	type = purple_status_type_new_with_attrs(
1634 			PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE,
1635 			"message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1636 			NULL);
1637 	types = g_list_append(types, type);
1638 
1639 	return types;
1640 }
1641 
ggp_blist_node_menu(PurpleBlistNode * node)1642 static GList *ggp_blist_node_menu(PurpleBlistNode *node)
1643 {
1644 	PurpleMenuAction *act;
1645 	GList *m = NULL;
1646 	PurpleAccount *account;
1647 	GGPInfo *info;
1648 
1649 	if (!PURPLE_BLIST_NODE_IS_BUDDY(node))
1650 		return NULL;
1651 
1652 	account = purple_buddy_get_account((PurpleBuddy *) node);
1653 	info = purple_account_get_connection(account)->proto_data;
1654 	if (info->chats) {
1655 		act = purple_menu_action_new(_("Add to chat"),
1656 			PURPLE_CALLBACK(ggp_bmenu_add_to_chat),
1657 			NULL, NULL);
1658 		m = g_list_append(m, act);
1659 	}
1660 
1661 	return m;
1662 }
1663 
ggp_chat_info(PurpleConnection * gc)1664 static GList *ggp_chat_info(PurpleConnection *gc)
1665 {
1666 	GList *m = NULL;
1667 	struct proto_chat_entry *pce;
1668 
1669 	pce = g_new0(struct proto_chat_entry, 1);
1670 	pce->label = _("Chat _name:");
1671 	pce->identifier = "name";
1672 	pce->required = TRUE;
1673 	m = g_list_append(m, pce);
1674 
1675 	return m;
1676 }
1677 
ggp_login_to(PurpleAccount * account,uint32_t server)1678 static void ggp_login_to(PurpleAccount *account, uint32_t server)
1679 {
1680 	PurpleConnection *gc;
1681 	PurplePresence *presence;
1682 	PurpleStatus *status;
1683 	struct gg_login_params *glp;
1684 	GGPInfo *info;
1685 	const gchar *encryption_type;
1686 
1687 	if (ggp_setup_proxy(account) == -1)
1688 		return;
1689 
1690 	gc = purple_account_get_connection(account);
1691 	glp = g_new0(struct gg_login_params, 1);
1692 	info = gc->proto_data;
1693 	g_return_if_fail(info);
1694 
1695 	/* Probably this should be moved to *_new() function. */
1696 	info->session = NULL;
1697 	info->chats = NULL;
1698 	info->chats_count = 0;
1699 	info->token = NULL;
1700 	info->searches = ggp_search_new();
1701 	info->pending_richtext_messages = NULL;
1702 	info->pending_images = g_hash_table_new(g_direct_hash, g_direct_equal);
1703 	info->status_broadcasting = purple_account_get_bool(account, "status_broadcasting", TRUE);
1704 
1705 	glp->uin = ggp_get_uin(account);
1706 	glp->password = (char *)purple_account_get_password(account);
1707 	glp->image_size = 255;
1708 
1709 	presence = purple_account_get_presence(account);
1710 	status = purple_presence_get_active_status(presence);
1711 
1712 	glp->encoding = GG_ENCODING_UTF8;
1713 	glp->protocol_features = (GG_FEATURE_STATUS80|GG_FEATURE_DND_FFC
1714 		|GG_FEATURE_TYPING_NOTIFICATION);
1715 
1716 	glp->async = 1;
1717 	glp->status = ggp_to_gg_status(status, &glp->status_descr);
1718 
1719 	encryption_type = purple_account_get_string(account, "encryption", "none");
1720 	purple_debug_info("gg", "Requested encryption type: %s\n", encryption_type);
1721 	if (purple_strequal(encryption_type, "opportunistic_tls"))
1722 		glp->tls = 1;
1723 	else
1724 		glp->tls = 0;
1725 	purple_debug_info("gg", "TLS enabled: %d\n", glp->tls);
1726 
1727 	if (!info->status_broadcasting)
1728 		glp->status = glp->status|GG_STATUS_FRIENDS_MASK;
1729 	glp->server_addr = server;
1730 
1731 	info->session = gg_login(glp);
1732 	g_free(glp);
1733 
1734 	purple_connection_update_progress(gc, _("Connecting"), 0, 2);
1735 	if (info->session == NULL) {
1736 		purple_connection_error_reason (gc,
1737 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1738 			_("Connection failed"));
1739 		return;
1740 	}
1741 	gc->inpa = purple_input_add(info->session->fd,
1742 		ggp_tcpsocket_inputcond_gg_to_purple(info->session->check),
1743 		ggp_async_login_handler, gc);
1744 }
1745 
1746 static void
ggp_login_resolved(GSList * hosts,gpointer _account,const char * error_message)1747 ggp_login_resolved(GSList *hosts, gpointer _account, const char *error_message)
1748 {
1749 	PurpleAccount *account = _account;
1750 	PurpleConnection *gc;
1751 	GGPInfo *info;
1752 	uint32_t server_addr = 0;
1753 
1754 	gc = purple_account_get_connection(account);
1755 	info = gc->proto_data;
1756 	g_return_if_fail(info);
1757 	info->dns_query = NULL;
1758 
1759 	while (hosts && (hosts = g_slist_delete_link(hosts, hosts))) {
1760 		struct sockaddr *addr = hosts->data;
1761 
1762 		if (addr->sa_family == AF_INET && server_addr == 0) {
1763 			struct sockaddr_in *addrv4 = (struct sockaddr_in *)addr;
1764 
1765 			server_addr = addrv4->sin_addr.s_addr;
1766 		}
1767 
1768 		g_free(hosts->data);
1769 		hosts = g_slist_delete_link(hosts, hosts);
1770 	}
1771 
1772 	if (server_addr == 0) {
1773 		gchar *tmp = g_strdup_printf(
1774 			_("Unable to resolve hostname: %s"), error_message);
1775 		purple_connection_error_reason(gc,
1776 			/* should this be a settings error? */
1777 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
1778 		g_free(tmp);
1779 		return;
1780 	}
1781 
1782 	ggp_login_to(account, server_addr);
1783 }
1784 
1785 static void
ggp_login(PurpleAccount * account)1786 ggp_login(PurpleAccount *account)
1787 {
1788 	PurpleConnection *gc;
1789 	GGPInfo *info;
1790 	const char *address;
1791 
1792 	gc = purple_account_get_connection(account);
1793 	info = g_new0(GGPInfo, 1);
1794 	gc->proto_data = info;
1795 
1796 	address = purple_account_get_string(account, "gg_server", "");
1797 	if (address == NULL || address[0] == '\0') {
1798 		purple_debug_info("gg", "Trying to retrieve address from gg appmsg service\n");
1799 		ggp_login_to(account, 0);
1800 		return;
1801 	}
1802 
1803 	purple_debug_info("gg", "Using gg server given by user (%s)\n", address);
1804 	info->dns_query = purple_dnsquery_a_account(account, address, 8074,
1805 		ggp_login_resolved, account);
1806 }
1807 
ggp_close(PurpleConnection * gc)1808 static void ggp_close(PurpleConnection *gc)
1809 {
1810 	if (gc == NULL) {
1811 		purple_debug_info("gg", "gc == NULL\n");
1812 		return;
1813 	}
1814 
1815 	if (gc->proto_data) {
1816 		PurpleAccount *account = purple_connection_get_account(gc);
1817 		PurpleStatus *status;
1818 		GGPInfo *info = gc->proto_data;
1819 
1820 		if (info->dns_query)
1821 			purple_dnsquery_destroy(info->dns_query);
1822 
1823 		status = purple_account_get_active_status(account);
1824 
1825 		if (info->session != NULL) {
1826 			ggp_set_status(account, status);
1827 			gg_logoff(info->session);
1828 			gg_free_session(info->session);
1829 		}
1830 
1831 		purple_account_set_bool(account, "status_broadcasting", info->status_broadcasting);
1832 
1833 		/* Immediately close any notifications on this handle since that process depends
1834 		 * upon the contents of info->searches, which we are about to destroy.
1835 		 */
1836 		purple_notify_close_with_handle(gc);
1837 
1838 		ggp_search_destroy(info->searches);
1839 		g_list_free(info->pending_richtext_messages);
1840 		g_hash_table_destroy(info->pending_images);
1841 		g_free(info);
1842 		gc->proto_data = NULL;
1843 	}
1844 
1845 	if (gc->inpa > 0)
1846 		purple_input_remove(gc->inpa);
1847 
1848 	purple_debug_info("gg", "Connection closed.\n");
1849 }
1850 
ggp_send_im(PurpleConnection * gc,const char * who,const char * msg,PurpleMessageFlags flags)1851 static int ggp_send_im(PurpleConnection *gc, const char *who, const char *msg,
1852 		       PurpleMessageFlags flags)
1853 {
1854 	GGPInfo *info = gc->proto_data;
1855 	char *tmp, *plain;
1856 	int ret = 1;
1857 	unsigned char format[1024];
1858 	unsigned int format_length = sizeof(struct gg_msg_richtext);
1859 	gint pos = 0;
1860 	GData *attribs;
1861 	const char *start, *end = NULL, *last;
1862 
1863 	if (msg == NULL || *msg == '\0') {
1864 		return 0;
1865 	}
1866 
1867 	last = msg;
1868 
1869 	/* Check if the message is richtext */
1870 	/* TODO: Check formatting, too */
1871 	if(purple_markup_find_tag("img", last, &start, &end, &attribs)) {
1872 
1873 		GString *string_buffer = g_string_new(NULL);
1874 		struct gg_msg_richtext fmt;
1875 
1876 		do {
1877 			PurpleStoredImage *image;
1878 			const char *id;
1879 
1880 			/* Add text before the image */
1881 			if(start - last) {
1882 				pos = pos + g_utf8_strlen(last, start - last);
1883 				g_string_append_len(string_buffer, last, start - last);
1884 			}
1885 
1886 			if((id = g_datalist_get_data(&attribs, "id")) && (image = purple_imgstore_find_by_id(atoi(id)))) {
1887 				struct gg_msg_richtext_format actformat;
1888 				struct gg_msg_richtext_image actimage;
1889 				gint image_size = purple_imgstore_get_size(image);
1890 				gconstpointer image_bin = purple_imgstore_get_data(image);
1891 				const char *image_filename = purple_imgstore_get_filename(image);
1892 				uint32_t crc32 = gg_crc32(0, image_bin, image_size);
1893 
1894 				g_hash_table_insert(info->pending_images, GINT_TO_POINTER(crc32), GINT_TO_POINTER(atoi(id)));
1895 				purple_imgstore_ref(image);
1896 				purple_debug_info("gg", "ggp_send_im_richtext: got crc: %u for imgid: %i\n", crc32, atoi(id));
1897 
1898 				actformat.font = GG_FONT_IMAGE;
1899 				actformat.position = pos;
1900 
1901 				actimage.unknown1 = 0x0109;
1902 				actimage.size = gg_fix32(image_size);
1903 				actimage.crc32 = gg_fix32(crc32);
1904 
1905 				if (actimage.size > 255000) {
1906 					purple_debug_warning("gg", "ggp_send_im_richtext: image over 255kb!\n");
1907 				} else {
1908 					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);
1909 
1910 					memcpy(format + format_length, &actformat, sizeof(actformat));
1911 					format_length += sizeof(actformat);
1912 					memcpy(format + format_length, &actimage, sizeof(actimage));
1913 					format_length += sizeof(actimage);
1914 				}
1915 			} else {
1916 				purple_debug_error("gg", "ggp_send_im_richtext: image not found in the image store!");
1917 			}
1918 
1919 			last = end + 1;
1920 			g_datalist_clear(&attribs);
1921 
1922 		} while(purple_markup_find_tag("img", last, &start, &end, &attribs));
1923 
1924 		/* Add text after the images */
1925 		if(last && *last) {
1926 			/* this is currently not used, but might be useful later? */
1927 			/* pos = pos + g_utf8_strlen(last, -1); */
1928 			g_string_append(string_buffer, last);
1929 		}
1930 
1931 		fmt.flag = 2;
1932 		fmt.length = format_length - sizeof(fmt);
1933 		memcpy(format, &fmt, sizeof(fmt));
1934 
1935 		purple_debug_info("gg", "ggp_send_im: richtext msg = %s\n", string_buffer->str);
1936 		plain = purple_unescape_html(string_buffer->str);
1937 		g_string_free(string_buffer, TRUE);
1938 	} else {
1939 		purple_debug_info("gg", "ggp_send_im: msg = %s\n", msg);
1940 		plain = purple_unescape_html(msg);
1941 	}
1942 
1943 	/*
1944 	tmp = charset_convert(plain, "UTF-8", "CP1250");
1945 	*/
1946 	tmp = g_strdup_printf("%s", plain);
1947 
1948 	if (tmp && (format_length - sizeof(struct gg_msg_richtext))) {
1949 		if(gg_send_message_richtext(info->session, GG_CLASS_CHAT, ggp_str_to_uin(who), (unsigned char *)tmp, format, format_length) < 0) {
1950 			ret = -1;
1951 		} else {
1952 			ret = 1;
1953 		}
1954 	} else if (NULL == tmp || *tmp == 0) {
1955 		ret = 0;
1956 	} else if (strlen(tmp) > GG_MSG_MAXSIZE) {
1957 		ret = -E2BIG;
1958 	} else if (gg_send_message(info->session, GG_CLASS_CHAT,
1959 				ggp_str_to_uin(who), (unsigned char *)tmp) < 0) {
1960 		ret = -1;
1961 	} else {
1962 		ret = 1;
1963 	}
1964 
1965 	g_free(plain);
1966 	g_free(tmp);
1967 
1968 	return ret;
1969 }
1970 
ggp_send_typing(PurpleConnection * gc,const char * name,PurpleTypingState state)1971 static unsigned int ggp_send_typing(PurpleConnection *gc, const char *name, PurpleTypingState state)
1972 {
1973 	int dummy_length; // we don't send real length of typed message
1974 
1975 	if (state == PURPLE_TYPED) // not supported
1976 		return 1;
1977 
1978 	if (state == PURPLE_TYPING)
1979 		dummy_length = (int)g_random_int();
1980 	else // PURPLE_NOT_TYPING
1981 		dummy_length = 0;
1982 
1983 	gg_typing_notification(
1984 		((GGPInfo*)gc->proto_data)->session,
1985 		ggp_str_to_uin(name),
1986 		dummy_length);
1987 
1988 	return 1; // wait 1 second before another notification
1989 }
1990 
ggp_get_info(PurpleConnection * gc,const char * name)1991 static void ggp_get_info(PurpleConnection *gc, const char *name)
1992 {
1993 	GGPInfo *info = gc->proto_data;
1994 	GGPSearchForm *form;
1995 	guint32 seq;
1996 
1997 	form = ggp_search_form_new(GGP_SEARCH_TYPE_INFO);
1998 
1999 	form->user_data = info;
2000 	form->uin = g_strdup(name);
2001 
2002 	seq = ggp_search_start(gc, form);
2003 	ggp_search_add(info->searches, seq, form);
2004 	purple_debug_info("gg", "ggp_get_info(): Added seq %u", seq);
2005 }
2006 
ggp_to_gg_status(PurpleStatus * status,char ** msg)2007 static int ggp_to_gg_status(PurpleStatus *status, char **msg)
2008 {
2009 	const char *status_id = purple_status_get_id(status);
2010 	int new_status, new_status_descr;
2011 	const char *new_msg;
2012 
2013 	g_return_val_if_fail(msg != NULL, 0);
2014 
2015 	purple_debug_info("gg", "ggp_to_gg_status: Requested status = %s\n",
2016 			status_id);
2017 
2018 	if (purple_strequal(status_id, "available")) {
2019 		new_status = GG_STATUS_AVAIL;
2020 		new_status_descr = GG_STATUS_AVAIL_DESCR;
2021 	} else if (purple_strequal(status_id, "away")) {
2022 		new_status = GG_STATUS_BUSY;
2023 		new_status_descr = GG_STATUS_BUSY_DESCR;
2024 	} else if (purple_strequal(status_id, "unavailable")) {
2025 		new_status = GG_STATUS_DND;
2026 		new_status_descr = GG_STATUS_DND_DESCR;
2027 	} else if (purple_strequal(status_id, "invisible")) {
2028 		new_status = GG_STATUS_INVISIBLE;
2029 		new_status_descr = GG_STATUS_INVISIBLE_DESCR;
2030 	} else if (purple_strequal(status_id, "offline")) {
2031 		new_status = GG_STATUS_NOT_AVAIL;
2032 		new_status_descr = GG_STATUS_NOT_AVAIL_DESCR;
2033 	} else {
2034 		new_status = GG_STATUS_AVAIL;
2035 		new_status_descr = GG_STATUS_AVAIL_DESCR;
2036 		purple_debug_info("gg",
2037 			"ggp_set_status: unknown status requested (status_id=%s)\n",
2038 			status_id);
2039 	}
2040 
2041 	new_msg = purple_status_get_attr_string(status, "message");
2042 
2043 	if(new_msg) {
2044 		/*
2045 		char *tmp = purple_markup_strip_html(new_msg);
2046 		*msg = charset_convert(tmp, "UTF-8", "CP1250");
2047 		g_free(tmp);
2048 		*/
2049 		*msg = purple_markup_strip_html(new_msg);
2050 
2051 		return new_status_descr;
2052 	} else {
2053 		*msg = NULL;
2054 		return new_status;
2055 	}
2056 }
2057 
ggp_set_status(PurpleAccount * account,PurpleStatus * status)2058 static void ggp_set_status(PurpleAccount *account, PurpleStatus *status)
2059 {
2060 	PurpleConnection *gc;
2061 	GGPInfo *info;
2062 	int new_status;
2063 	char *new_msg = NULL;
2064 
2065 	if (!purple_status_is_active(status))
2066 		return;
2067 
2068 	gc = purple_account_get_connection(account);
2069 	info = gc->proto_data;
2070 
2071 	new_status = ggp_to_gg_status(status, &new_msg);
2072 
2073 	if (!info->status_broadcasting)
2074 		new_status = new_status|GG_STATUS_FRIENDS_MASK;
2075 
2076 	if (new_msg == NULL) {
2077 		gg_change_status(info->session, new_status);
2078 	} else {
2079 		gg_change_status_descr(info->session, new_status, new_msg);
2080 		g_free(new_msg);
2081 	}
2082 
2083 	ggp_status_fake_to_self(account);
2084 
2085 }
2086 
ggp_add_buddy(PurpleConnection * gc,PurpleBuddy * buddy,PurpleGroup * group)2087 static void ggp_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2088 {
2089 	PurpleAccount *account;
2090 	GGPInfo *info = gc->proto_data;
2091 	const gchar *name = purple_buddy_get_name(buddy);
2092 
2093 	gg_add_notify(info->session, ggp_str_to_uin(name));
2094 
2095 	account = purple_connection_get_account(gc);
2096 	if (purple_strequal(purple_account_get_username(account), name)) {
2097 		ggp_status_fake_to_self(account);
2098 	}
2099 }
2100 
ggp_remove_buddy(PurpleConnection * gc,PurpleBuddy * buddy,PurpleGroup * group)2101 static void ggp_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy,
2102 						 PurpleGroup *group)
2103 {
2104 	GGPInfo *info = gc->proto_data;
2105 
2106 	gg_remove_notify(info->session, ggp_str_to_uin(purple_buddy_get_name(buddy)));
2107 }
2108 
ggp_join_chat(PurpleConnection * gc,GHashTable * data)2109 static void ggp_join_chat(PurpleConnection *gc, GHashTable *data)
2110 {
2111 	GGPInfo *info = gc->proto_data;
2112 	GGPChat *chat;
2113 	char *chat_name;
2114 	GList *l;
2115 	PurpleConversation *conv;
2116 	PurpleAccount *account = purple_connection_get_account(gc);
2117 
2118 	chat_name = g_hash_table_lookup(data, "name");
2119 
2120 	if (chat_name == NULL)
2121 		return;
2122 
2123 	purple_debug_info("gg", "joined %s chat\n", chat_name);
2124 
2125 	for (l = info->chats; l != NULL; l = l->next) {
2126 		 chat = l->data;
2127 
2128 		 if (chat != NULL && g_utf8_collate(chat->name, chat_name) == 0) {
2129 			 purple_notify_error(gc, _("Chat error"),
2130 				 _("This chat name is already in use"), NULL);
2131 			 return;
2132 		 }
2133 	}
2134 
2135 	ggp_confer_add_new(gc, chat_name);
2136 	conv = serv_got_joined_chat(gc, info->chats_count, chat_name);
2137 	purple_conv_chat_add_user(PURPLE_CONV_CHAT(conv),
2138 				purple_account_get_username(account), NULL,
2139 				PURPLE_CBFLAGS_NONE, TRUE);
2140 }
2141 
ggp_get_chat_name(GHashTable * data)2142 static char *ggp_get_chat_name(GHashTable *data) {
2143 	return g_strdup(g_hash_table_lookup(data, "name"));
2144 }
2145 
ggp_chat_send(PurpleConnection * gc,int id,const char * message,PurpleMessageFlags flags)2146 static int ggp_chat_send(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags)
2147 {
2148 	PurpleConversation *conv;
2149 	GGPInfo *info = gc->proto_data;
2150 	GGPChat *chat = NULL;
2151 	GList *l;
2152 	/* char *msg, *plain; */
2153 	gchar *msg;
2154 	uin_t *uins;
2155 	int count = 0;
2156 
2157 	if ((conv = purple_find_chat(gc, id)) == NULL)
2158 		return -EINVAL;
2159 
2160 	for (l = info->chats; l != NULL; l = l->next) {
2161 		chat = l->data;
2162 
2163 		if (g_utf8_collate(chat->name, conv->name) == 0) {
2164 			break;
2165 		}
2166 
2167 		chat = NULL;
2168 	}
2169 
2170 	if (chat == NULL) {
2171 		purple_debug_error("gg",
2172 			"ggp_chat_send: Hm... that's strange. No such chat?\n");
2173 		return -EINVAL;
2174 	}
2175 
2176 	uins = g_new0(uin_t, g_list_length(chat->participants));
2177 
2178 	for (l = chat->participants; l != NULL; l = l->next) {
2179 		uin_t uin = GPOINTER_TO_INT(l->data);
2180 
2181 		uins[count++] = uin;
2182 	}
2183 
2184 	/*
2185 	plain = purple_unescape_html(message);
2186 	msg = charset_convert(plain, "UTF-8", "CP1250");
2187 	g_free(plain);
2188 	*/
2189 	msg = purple_unescape_html(message);
2190 	gg_send_message_confer(info->session, GG_CLASS_CHAT, count, uins,
2191 				(unsigned char *)msg);
2192 	g_free(msg);
2193 	g_free(uins);
2194 
2195 	serv_got_chat_in(gc, id,
2196 			 purple_account_get_username(purple_connection_get_account(gc)),
2197 			 flags, message, time(NULL));
2198 
2199 	return 0;
2200 }
2201 
ggp_keepalive(PurpleConnection * gc)2202 static void ggp_keepalive(PurpleConnection *gc)
2203 {
2204 	GGPInfo *info = gc->proto_data;
2205 
2206 	/* purple_debug_info("gg", "Keeping connection alive....\n"); */
2207 
2208 	if (gg_ping(info->session) < 0) {
2209 		purple_debug_info("gg", "Not connected to the server "
2210 				"or gg_session is not correct\n");
2211 		purple_connection_error_reason (gc,
2212 			PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
2213 			_("Not connected to the server"));
2214 	}
2215 }
2216 
ggp_actions(PurplePlugin * plugin,gpointer context)2217 static GList *ggp_actions(PurplePlugin *plugin, gpointer context)
2218 {
2219 	GList *m = NULL;
2220 	PurplePluginAction *act;
2221 
2222 	act = purple_plugin_action_new(_("Find buddies..."),
2223 				     ggp_find_buddies);
2224 	m = g_list_append(m, act);
2225 
2226 	act = purple_plugin_action_new(_("Change status broadcasting"),
2227 				     ggp_action_change_status_broadcasting);
2228 	m = g_list_append(m, act);
2229 
2230 	m = g_list_append(m, NULL);
2231 
2232 	act = purple_plugin_action_new(_("Save buddylist to file..."),
2233 				     ggp_action_buddylist_save);
2234 	m = g_list_append(m, act);
2235 
2236 	act = purple_plugin_action_new(_("Load buddylist from file..."),
2237 				     ggp_action_buddylist_load);
2238 	m = g_list_append(m, act);
2239 
2240 	return m;
2241 }
2242 
ggp_offline_message(const PurpleBuddy * buddy)2243 static gboolean ggp_offline_message(const PurpleBuddy *buddy)
2244 {
2245 	return TRUE;
2246 }
2247 
ggp_load(PurplePlugin * plugin)2248 static gboolean ggp_load(PurplePlugin *plugin)
2249 {
2250 	purple_debug_info("gg", "Loading Gadu-Gadu protocol plugin with "
2251 		"libgadu %s...\n", gg_libgadu_version());
2252 
2253 	gg_is_gpl_compliant();
2254 
2255 	return TRUE;
2256 }
2257 
2258 static PurplePluginProtocolInfo prpl_info =
2259 {
2260 	OPT_PROTO_IM_IMAGE,
2261 	NULL,				/* user_splits */
2262 	NULL,				/* protocol_options */
2263 	{"png", 32, 32, 96, 96, 0, PURPLE_ICON_SCALE_DISPLAY},	/* icon_spec */
2264 	ggp_list_icon,			/* list_icon */
2265 	NULL,				/* list_emblem */
2266 	ggp_status_text,		/* status_text */
2267 	ggp_tooltip_text,		/* tooltip_text */
2268 	ggp_status_types,		/* status_types */
2269 	ggp_blist_node_menu,		/* blist_node_menu */
2270 	ggp_chat_info,			/* chat_info */
2271 	NULL,				/* chat_info_defaults */
2272 	ggp_login,			/* login */
2273 	ggp_close,			/* close */
2274 	ggp_send_im,			/* send_im */
2275 	NULL,				/* set_info */
2276 	ggp_send_typing,		/* send_typing */
2277 	ggp_get_info,			/* get_info */
2278 	ggp_set_status,			/* set_away */
2279 	NULL,				/* set_idle */
2280 	NULL,				/* change_passwd */
2281 	ggp_add_buddy,			/* add_buddy */
2282 	NULL,				/* add_buddies */
2283 	ggp_remove_buddy,		/* remove_buddy */
2284 	NULL,				/* remove_buddies */
2285 	NULL,				/* add_permit */
2286 	ggp_add_deny,			/* add_deny */
2287 	NULL,				/* rem_permit */
2288 	ggp_rem_deny,			/* rem_deny */
2289 	NULL,				/* set_permit_deny */
2290 	ggp_join_chat,			/* join_chat */
2291 	NULL,				/* reject_chat */
2292 	ggp_get_chat_name,		/* get_chat_name */
2293 	NULL,				/* chat_invite */
2294 	NULL,				/* chat_leave */
2295 	NULL,				/* chat_whisper */
2296 	ggp_chat_send,			/* chat_send */
2297 	ggp_keepalive,			/* keepalive */
2298 	NULL,				/* register_user */
2299 	NULL,				/* get_cb_info */
2300 	NULL,				/* get_cb_away */
2301 	NULL,				/* alias_buddy */
2302 	NULL,				/* group_buddy */
2303 	NULL,				/* rename_group */
2304 	NULL,				/* buddy_free */
2305 	NULL,				/* convo_closed */
2306 	NULL,				/* normalize */
2307 	NULL,				/* set_buddy_icon */
2308 	NULL,				/* remove_group */
2309 	NULL,				/* get_cb_real_name */
2310 	NULL,				/* set_chat_topic */
2311 	NULL,				/* find_blist_chat */
2312 	NULL,				/* roomlist_get_list */
2313 	NULL,				/* roomlist_cancel */
2314 	NULL,				/* roomlist_expand_category */
2315 	NULL,				/* can_receive_file */
2316 	NULL,				/* send_file */
2317 	NULL,				/* new_xfer */
2318 	ggp_offline_message,		/* offline_message */
2319 	NULL,				/* whiteboard_prpl_ops */
2320 	NULL,				/* send_raw */
2321 	NULL,				/* roomlist_room_serialize */
2322 	NULL,				/* unregister_user */
2323 	NULL,				/* send_attention */
2324 	NULL,				/* get_attention_types */
2325 	sizeof(PurplePluginProtocolInfo),       /* struct_size */
2326 	NULL,                           /* get_account_text_table */
2327 	NULL,                           /* initiate_media */
2328 	NULL,                            /* can_do_media */
2329 	NULL,				/* get_moods */
2330 	NULL,				/* set_public_alias */
2331 	NULL,				/* get_public_alias */
2332 	NULL,				/* add_buddy_with_invite */
2333 	NULL,				/* add_buddies_with_invite */
2334 	NULL,				/* get_cb_alias */
2335 	NULL,				/* chat_can_receive_file */
2336 	NULL,				/* chat_send_file */
2337 };
2338 
2339 static PurplePluginInfo info = {
2340 	PURPLE_PLUGIN_MAGIC,			/* magic */
2341 	PURPLE_MAJOR_VERSION,			/* major_version */
2342 	PURPLE_MINOR_VERSION,			/* minor_version */
2343 	PURPLE_PLUGIN_PROTOCOL,			/* plugin type */
2344 	NULL,					/* ui_requirement */
2345 	0,					/* flags */
2346 	NULL,					/* dependencies */
2347 	PURPLE_PRIORITY_DEFAULT,		/* priority */
2348 
2349 	"prpl-gg",				/* id */
2350 	"Gadu-Gadu",				/* name */
2351 	DISPLAY_VERSION,			/* version */
2352 
2353 	N_("Gadu-Gadu Protocol Plugin"),	/* summary */
2354 	N_("Polish popular IM"),		/* description */
2355 	"boler@sourceforge.net",		/* author */
2356 	PURPLE_WEBSITE,				/* homepage */
2357 
2358 	ggp_load,				/* load */
2359 	NULL,					/* unload */
2360 	NULL,					/* destroy */
2361 
2362 	NULL,					/* ui_info */
2363 	&prpl_info,				/* extra_info */
2364 	NULL,					/* prefs_info */
2365 	ggp_actions,				/* actions */
2366 
2367 	/* padding */
2368 	NULL,
2369 	NULL,
2370 	NULL,
2371 	NULL
2372 };
2373 
2374 static void
purple_gg_debug_handler(int level,const char * format,va_list args)2375 purple_gg_debug_handler(int level, const char * format, va_list args)
2376 {
2377 	PurpleDebugLevel purple_level;
2378 	char msgbuff[1000];
2379 	int ret;
2380 
2381 	/* Don't use glib's printf family, since it might not support
2382 	 * system-specific formatting modifiers (like %Iu for size on win32). */
2383 	ret = vsnprintf(msgbuff, sizeof(msgbuff) / sizeof(char), format, args);
2384 
2385 	if (ret <= 0) {
2386 		purple_debug_fatal("gg",
2387 			"failed to printf the following message: %s",
2388 			format ? format : "(null)\n");
2389 
2390 		return;
2391 	}
2392 
2393 	/* This is pretty pointless since the GG_DEBUG levels don't correspond to
2394 	 * the purple ones */
2395 	switch (level) {
2396 		case GG_DEBUG_FUNCTION:
2397 			purple_level = PURPLE_DEBUG_INFO;
2398 			break;
2399 		case GG_DEBUG_MISC:
2400 		case GG_DEBUG_NET:
2401 		case GG_DEBUG_DUMP:
2402 		case GG_DEBUG_TRAFFIC:
2403 		default:
2404 			purple_level = PURPLE_DEBUG_MISC;
2405 			break;
2406 	}
2407 
2408 	purple_debug(purple_level, "gg", "%s", msgbuff);
2409 }
2410 
init_plugin(PurplePlugin * plugin)2411 static void init_plugin(PurplePlugin *plugin)
2412 {
2413 	PurpleAccountOption *option;
2414 	GList *encryption_options = NULL;
2415 
2416 	option = purple_account_option_string_new(_("Nickname"),
2417 			"nick", _("Gadu-Gadu User"));
2418 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
2419 						   option);
2420 
2421 	option = purple_account_option_string_new(_("GG server"),
2422 			"gg_server", "");
2423 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
2424 			option);
2425 
2426 #define ADD_VALUE(list, desc, v) { \
2427 	PurpleKeyValuePair *kvp = g_new0(PurpleKeyValuePair, 1); \
2428 	kvp->key = g_strdup((desc)); \
2429 	kvp->value = g_strdup((v)); \
2430 	list = g_list_append(list, kvp); \
2431 }
2432 
2433 	ADD_VALUE(encryption_options, _("Don't use encryption"), "none");
2434 	ADD_VALUE(encryption_options, _("Use encryption if available"),
2435 		"opportunistic_tls");
2436 #if 0
2437 	/* TODO */
2438 	ADD_VALUE(encryption_options, _("Require encryption"), "require_tls");
2439 #endif
2440 
2441 	option = purple_account_option_list_new(_("Connection security"),
2442 		"encryption", encryption_options);
2443 	prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
2444 		option);
2445 
2446 	my_protocol = plugin;
2447 
2448 	gg_debug_handler = purple_gg_debug_handler;
2449 }
2450 
2451 PURPLE_INIT_PLUGIN(gg, init_plugin, info);
2452 
2453 /* vim: set ts=8 sts=0 sw=8 noet: */
2454