diff libpurple/protocols/gg/lib/libgadu.c @ 32072:3a90a59ddea2

Update libgadu to 0.11.0 plus local changes; thanks to Tomasz Wasilczyk. Fixes 14248
author Ethan Blanton <elb@pidgin.im>
date Sun, 05 Jun 2011 01:28:53 +0000
parents 93b08d43f684
children 15c227a35207
line wrap: on
line diff
--- a/libpurple/protocols/gg/lib/libgadu.c	Tue May 24 01:48:26 2011 +0000
+++ b/libpurple/protocols/gg/lib/libgadu.c	Sun Jun 05 01:28:53 2011 +0000
@@ -1,4 +1,4 @@
-/* $Id: libgadu.c 1062 2011-03-13 18:10:24Z wojtekka $ */
+/* $Id: libgadu.c 1102 2011-05-05 21:17:57Z wojtekka $ */
 
 /*
  *  (C) Copyright 2001-2010 Wojtek Kaniewski <wojtekka@irc.pl>
@@ -28,13 +28,7 @@
  * \brief Główny moduł biblioteki
  */
 
-#include "libgadu.h"
-#include "libgadu-config.h"
-#include "libgadu-internal.h"
-#include "libgadu-debug.h"
-
 #include <sys/types.h>
-
 #ifdef _WIN32
 #  include <io.h>
 #  include <fcntl.h>
@@ -50,16 +44,21 @@
 #endif
 
 #include "compat.h"
+#include "libgadu.h"
+#include "libgadu-config.h"
 #include "protocol.h"
 #include "resolver.h"
+#include "libgadu-internal.h"
 #include "encoding.h"
+#include "libgadu-debug.h"
 #include "session.h"
+#include "message.h"
+#include "deflate.h"
 
 #ifndef _WIN32
 #  include <errno.h> /* on Win32 this is included above */
 #  include <netdb.h>
 #endif
-
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -75,11 +74,11 @@
 #  include <openssl/rand.h>
 #endif
 
-#define GG_LIBGADU_VERSION "1.10.1"
+#define GG_LIBGADU_VERSION "1.11.0"
 
 /**
  * Port gniazda nasłuchującego dla połączeń bezpośrednich.
- *
+ * 
  * \ingroup ip
  */
 int gg_dcc_port = 0;
@@ -147,7 +146,7 @@
 #ifdef __GNUC__
 __attribute__ ((unused))
 #endif
-= "$Id: libgadu.c 1062 2011-03-13 18:10:24Z wojtekka $";
+= "$Id: libgadu.c 1102 2011-05-05 21:17:57Z wojtekka $";
 #endif
 
 #endif /* DOXYGEN */
@@ -290,11 +289,11 @@
  */
 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) {
@@ -317,7 +316,7 @@
 #ifdef GG_CONFIG_HAVE_OPENSSL
 	if (sess->ssl != NULL) {
 		for (;;) {
-			int res, err;
+			int err;
 
 			res = SSL_read(sess->ssl, buf, length);
 
@@ -340,7 +339,14 @@
 	}
 #endif
 
-	return read(sess->fd, buf, length);
+	for (;;) {
+		res = read(sess->fd, buf, length);
+
+		if (res == -1 && errno == EINTR)
+			continue;
+
+		return res;
+	}
 }
 
 /**
@@ -361,11 +367,11 @@
  */
 static int gg_write_common(struct gg_session *sess, const char *buf, int length)
 {
+	int res;
+
 #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) {
@@ -388,7 +394,7 @@
 #ifdef GG_CONFIG_HAVE_OPENSSL
 	if (sess->ssl != NULL) {
 		for (;;) {
-			int res, err;
+			int err;
 
 			res = SSL_write(sess->ssl, buf, length);
 
@@ -411,7 +417,14 @@
 	}
 #endif
 
-	return write(sess->fd, buf, length);
+	for (;;) {
+		res = write(sess->fd, buf, length);
+
+		if (res == -1 && errno == EINTR)
+			continue;
+
+		return res;
+	}
 }
 
 
@@ -489,7 +502,7 @@
 void *gg_recv_packet(struct gg_session *sess)
 {
 	struct gg_header h;
-	char *buf = NULL;
+	char *packet;
 	int ret = 0;
 	unsigned int offset, size = 0;
 
@@ -542,7 +555,6 @@
 			}
 
 			sess->header_done += ret;
-
 		}
 
 		h.type = gg_fix32(h.type);
@@ -561,26 +573,25 @@
 		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() resuming last gg_recv_packet()\n");
 		size = sess->recv_left;
 		offset = sess->recv_done;
-		buf = sess->recv_buf;
 	} else {
-		if (!(buf = malloc(sizeof(h) + h.length + 1))) {
+		if (!(sess->recv_buf = malloc(sizeof(h) + h.length + 1))) {
 			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() not enough memory for packet data\n");
 			return NULL;
 		}
 
-		memcpy(buf, &h, sizeof(h));
+		memcpy(sess->recv_buf, &h, sizeof(h));
 
 		offset = 0;
 		size = h.length;
 	}
 
 	while (size > 0) {
-		ret = gg_read(sess, buf + sizeof(h) + offset, size);
-		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() body recv(%d,%p,%d) = %d\n", sess->fd, buf + sizeof(h) + offset, size, ret);
+		ret = gg_read(sess, sess->recv_buf + sizeof(h) + offset, size);
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() body recv(%d,%p,%d) = %d\n", sess->fd, sess->recv_buf + sizeof(h) + offset, size, ret);
 		if (!ret) {
 			gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed: connection broken\n");
 			errno = ECONNRESET;
-			return NULL;
+			goto fail;
 		}
 		if (ret > -1 && ret <= size) {
 			offset += ret;
@@ -590,23 +601,30 @@
 
 			if (errno == EAGAIN) {
 				gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() %d bytes received, %d left\n", offset, size);
-				sess->recv_buf = buf;
 				sess->recv_left = size;
 				sess->recv_done = offset;
 				return NULL;
 			}
 
-			free(buf);
-			return NULL;
+			goto fail;
 		}
 	}
 
+	packet = sess->recv_buf;
+	sess->recv_buf = NULL;
 	sess->recv_left = 0;
 
 	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);
+	gg_debug_dump(sess, GG_DEBUG_DUMP, packet, sizeof(h) + h.length);
+
+	return packet;
 
-	return buf;
+fail:
+	free(sess->recv_buf);
+	sess->recv_buf = NULL;
+	sess->recv_left = 0;
+
+	return NULL;
 }
 
 /**
@@ -735,7 +753,10 @@
  * serwera -- z tego powodu program musi poprawnie obsłużyć sygnał SIGCHLD.
  *
  * \note Po nawiązaniu połączenia z serwerem należy wysłać listę kontaktów
- * za pomocą funkcji \c gg_notify() lub \c gg_notify_ex().
+ *       za pomocą funkcji \c gg_notify() lub \c gg_notify_ex().
+ *
+ * \note Funkcja zwróci błąd ENOSYS jeśli połączenie SSL było wymagane, ale
+ *       obsługa SSL nie jest wkompilowana.
  *
  * \param p Struktura opisująca parametry połączenia. Wymagane pola: uin,
  *          password, async.
@@ -796,6 +817,7 @@
 	sess->server_addr = p->server_addr;
 	sess->external_port = p->external_port;
 	sess->external_addr = p->external_addr;
+	sess->client_addr = p->client_addr;
 	sess->client_port = p->client_port;
 
 	if (p->protocol_features == 0) {
@@ -848,14 +870,14 @@
 			gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for status\n");
 			goto fail;
 		}
-
+		
 		// XXX pamiętać, żeby nie ciąć w środku znaku utf-8
-
+		
 		if (strlen(sess->initial_descr) > max_length)
 			sess->initial_descr[max_length] = 0;
 	}
 
-	if (p->tls == 1) {
+	if (p->tls != GG_SSL_DISABLED) {
 #ifdef GG_CONFIG_HAVE_GNUTLS
 		gg_session_gnutls_t *tmp;
 
@@ -912,6 +934,11 @@
 		}
 #else
 		gg_debug(GG_DEBUG_MISC, "// gg_login() client requested TLS but no support compiled in\n");
+
+		if (p->tls == GG_SSL_REQUIRED) {
+			errno = ENOSYS;
+			goto fail;
+		}
 #endif
 	}
 
@@ -1141,6 +1168,7 @@
 	free(sess->password);
 	free(sess->initial_descr);
 	free(sess->client_version);
+	free(sess->recv_buf);
 	free(sess->header_buf);
 
 #ifdef GG_CONFIG_HAVE_OPENSSL
@@ -1277,8 +1305,10 @@
 
 	free(new_descr);
 
-	if (GG_S_NA(status))
+	if (GG_S_NA(status)) {
 		sess->state = GG_STATE_DISCONNECTING;
+		sess->timeout = GG_TIMEOUT_DISCONNECT;
+	}
 
 	return res;
 }
@@ -1436,208 +1466,6 @@
 }
 
 /**
- * \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).
- */
-static int gg_convert_to_html(char *dst, const char *src, const unsigned char *format, int format_len)
-{
-	const char span_fmt[] = "<span style=\"color:#%02x%02x%02x; font-family:'MS Shell Dlg 2'; font-size:9pt; \">";
-	const int span_len = 75;
-	const char img_fmt[] = "<img name=\"%02x%02x%02x%02x%02x%02x%02x%02x\">";
-	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;
-
-	len = 0;
-
-	/* Nie mamy atrybutów dla pierwsze znaku, a tekst nie jest pusty, więc
-	 * tak czy inaczej trzeba otworzyć <span>. */
-
-	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, "</u>", 4);
-
-					if ((old_attr & GG_FONT_ITALIC) != 0)
-						gg_append(dst, &len, "</i>", 4);
-
-					if ((old_attr & GG_FONT_BOLD) != 0)
-						gg_append(dst, &len, "</b>", 4);
-
-					if (src[i] != 0)
-						gg_append(dst, &len, "</span>", 7);
-				}
-
-				if (((attr & GG_FONT_COLOR) != 0) && (format_idx + 3 <= format_len)) {
-					color = &format[format_idx];
-					format_idx += 3;
-				} else {
-					color = (const 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, "<b>", 3);
-
-			if ((attr & GG_FONT_ITALIC) != 0)
-				gg_append(dst, &len, "<i>", 3);
-
-			if ((attr & GG_FONT_UNDERLINE) != 0)
-				gg_append(dst, &len, "<u>", 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, "&amp;", 5);
-				break;
-			case '<':
-				gg_append(dst, &len, "&lt;", 4);
-				break;
-			case '>':
-				gg_append(dst, &len, "&gt;", 4);
-				break;
-			case '\'':
-				gg_append(dst, &len, "&apos;", 6);
-				break;
-			case '\"':
-				gg_append(dst, &len, "&quot;", 6);
-				break;
-			case '\n':
-				gg_append(dst, &len, "<br>", 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, "</u>", 4);
-
-	if ((old_attr & GG_FONT_ITALIC) != 0)
-		gg_append(dst, &len, "</i>", 4);
-
-	if ((old_attr & GG_FONT_BOLD) != 0)
-		gg_append(dst, &len, "</b>", 4);
-
-	if (src[0] != 0)
-		gg_append(dst, &len, "</span>", 7);
-
-	if (dst != NULL)
-		dst[len] = 0;
-
-	return len;
-}
-
-/**
  * Wysyła wiadomość formatowaną w ramach konferencji.
  *
  * Zwraca losowy numer sekwencyjny, który można zignorować albo wykorzystać
@@ -1652,7 +1480,7 @@
  * \param formatlen Długość informacji o formatowaniu
  *
  * \return Numer sekwencyjny wiadomości lub -1 w przypadku błędu.
- *
+ * 
  * \ingroup messages
  */
 int gg_send_message_confer_richtext(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message, const unsigned char *format, int formatlen)
@@ -1708,7 +1536,7 @@
 		s.seq = gg_fix32(seq_no);
 	} else {
 		int len;
-
+		
 		// Drobne odchylenie od protokołu. Jeśli wysyłamy kilka
 		// wiadomości w ciągu jednej sekundy, zwiększamy poprzednią
 		// wartość, żeby każda wiadomość miała unikalny numer.
@@ -1725,7 +1553,7 @@
 			formatlen = 9;
 		}
 
-		len = gg_convert_to_html(NULL, utf_msg, format + 3, formatlen - 3);
+		len = gg_message_text_to_html(NULL, utf_msg, (char*) format + 3, formatlen - 3);
 
 		html_msg = malloc(len + 1);
 
@@ -1734,7 +1562,7 @@
 			goto cleanup;
 		}
 
-		gg_convert_to_html(html_msg, utf_msg, format + 3, formatlen - 3);
+		gg_message_text_to_html(html_msg, utf_msg, (char*) format + 3, formatlen - 3);
 
 		s80.seq = gg_fix32(seq_no);
 		s80.msgclass = gg_fix32(msgclass);
@@ -2335,6 +2163,70 @@
 }
 
 /**
+ * Wysyła do serwera zapytanie dotyczące listy kontaktów (10.0).
+ *
+ * Funkcja służy do importu lub eksportu listy kontaktów do serwera.
+ * W odróżnieniu od funkcji \c gg_notify(), ta lista kontaktów jest przez
+ * serwer jedynie przechowywana i nie ma wpływu na połączenie. Format
+ * listy kontaktów jest jednak weryfikowany przez serwer, który stara się
+ * synchronizować listę kontaktów zapisaną w formatach GG 7.0 oraz GG 10.0.
+ * Serwer przyjmuje listy kontaktów przysłane w formacie niezgodnym z podanym
+ * jako \c format_type, ale nie zachowuje ich, a przesłanie takiej listy jest
+ * równoznaczne z usunięciem listy kontaktów.
+ *
+ * Program nie musi się przejmować kompresją listy kontaktów zgodną
+ * z protokołem -- wysyła i odbiera kompletną listę zapisaną czystym tekstem.
+ *
+ * \param sess Struktura sesji
+ * \param type Rodzaj zapytania
+ * \param version Numer ostatniej znanej programowi wersji listy kontaktów lub 0
+ * \param format_type Typ formatu listy kontaktów
+ * \param request Treść zapytania (może być równe NULL)
+ *
+ * \return 0 jeśli się powiodło, -1 w przypadku błędu
+ *
+ * \ingroup importexport
+ */
+int gg_userlist100_request(struct gg_session *sess, char type, unsigned int version, char format_type, const char *request)
+{
+	struct gg_userlist100_request pkt;
+	unsigned char *zrequest;
+	size_t zrequest_len;
+	int ret;
+
+	if (!sess) {
+		errno = EFAULT;
+		return -1;
+	}
+
+	if (sess->state != GG_STATE_CONNECTED) {
+		errno = ENOTCONN;
+		return -1;
+	}
+
+	pkt.type = type;
+	pkt.version = gg_fix32(version);
+	pkt.format_type = format_type;
+	pkt.unknown1 = 0x01;
+
+	if (request == NULL)
+		return gg_send_packet(sess, GG_USERLIST100_REQUEST, &pkt, sizeof(pkt), NULL);
+
+	zrequest = gg_deflate(request, &zrequest_len);
+
+	if (zrequest == NULL) {
+		gg_debug_session(sess, GG_DEBUG_MISC, "// gg_userlist100_request() gg_deflate() failed");
+		return -1;
+	}
+
+	ret = gg_send_packet(sess, GG_USERLIST100_REQUEST, &pkt, sizeof(pkt), zrequest, zrequest_len, NULL);
+
+	free(zrequest);
+
+	return ret;
+}
+
+/**
  * Informuje rozmówcę o pisaniu wiadomości.
  *
  * \param sess Struktura sesji
@@ -2377,6 +2269,47 @@
 
 /* @} */
 
+/**
+ * Sprawdza czy biblioteka obsługuje daną funkcję.
+ *
+ * \param feature Identyfikator funkcji.
+ *
+ * \return Wartość niezerowa jeśli funkcja jest obsłgiwana.
+ *
+ * \ingroup version
+ */
+int gg_libgadu_check_feature(gg_libgadu_feature_t feature)
+{
+	switch (feature)
+	{
+	case GG_LIBGADU_FEATURE_SSL:
+#if defined(GG_CONFIG_HAVE_OPENSSL) || defined(GG_CONFIG_HAVE_GNUTLS)
+		return 1;
+#else
+		return 0;
+#endif
+
+	case GG_LIBGADU_FEATURE_PTHREAD:
+#ifdef GG_CONFIG_HAVE_PTHREAD
+		return 1;
+#else
+		return 0;
+#endif
+
+	case GG_LIBGADU_FEATURE_USERLIST100:
+#ifdef GG_CONFIG_HAVE_ZLIB
+		return 1;
+#else
+		return 0;
+#endif
+
+	/* Celowo nie ma default, żeby kompilator wyłapał brakujące funkcje */
+
+	}
+
+	return 0;
+}
+
 /*
  * Local variables:
  * c-indentation-style: k&r