view libpurple/protocols/oscar/util.c @ 30818:9d386bf63eab

Stop using custom encodings (and LATIN-1, for that matter) for sending OSCAR messages (ICBM, chat, Direct IM). Now, we use ASCII if a message contains ASCII characters only, and UTF-16 in all other cases. That fixes #10833 (offline messages now will be sent as UTF-16) and also a whole bunch of potential problems we can get with charset 0x3. Different clients tend to interpret this charset differently; for instance, the official client always interprets it as LATIN-1, while alternative clients may decode it as some other user-specified 8-bit encoding. On the other hand, ASCII messages (charset 0x0) and UTF-16 messages (charset 0x2) are understood uniformly by all clients. I also cleaned-up the code a little (got rid of code paths that were never executed, flags that were always set, unused struct members, etc.)
author ivan.komarov@soc.pidgin.im
date Tue, 27 Jul 2010 21:17:01 +0000
parents 2d4dd38c5db5
children 88689cda97d8
line wrap: on
line source

/*
 * Purple's oscar protocol plugin
 * This file is the legal property of its developers.
 * Please see the AUTHORS file distributed alongside this file.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
*/

/*
 * A little bit of this
 * A little bit of that
 * It started with a kiss
 * Now we're up to bat
 */

#include "oscar.h"

#include "core.h"

#include <ctype.h>

#ifdef _WIN32
#include "win32dep.h"
#endif

static const char * const msgerrreason[] = {
	N_("Invalid error"),
	N_("Invalid SNAC"),
	N_("Server rate limit exceeded"),
	N_("Client rate limit exceeded"),
	N_("Not logged in"),
	N_("Service unavailable"),
	N_("Service not defined"),
	N_("Obsolete SNAC"),
	N_("Not supported by host"),
	N_("Not supported by client"),
	N_("Refused by client"),
	N_("Reply too big"),
	N_("Responses lost"),
	N_("Request denied"),
	N_("Busted SNAC payload"),
	N_("Insufficient rights"),
	N_("In local permit/deny"),
	N_("Warning level too high (sender)"),
	N_("Warning level too high (receiver)"),
	N_("User temporarily unavailable"),
	N_("No match"),
	N_("List overflow"),
	N_("Request ambiguous"),
	N_("Queue full"),
	N_("Not while on AOL")
};
static const int msgerrreasonlen = G_N_ELEMENTS(msgerrreason);

const char *oscar_get_msgerr_reason(size_t reason)
{
	return (reason < msgerrreasonlen) ? _(msgerrreason[reason]) : _("Unknown reason");
}

int oscar_get_ui_info_int(const char *str, int default_value)
{
	GHashTable *ui_info;

	ui_info = purple_core_get_ui_info();
	if (ui_info != NULL) {
		gpointer value;
		if (g_hash_table_lookup_extended(ui_info, str, NULL, &value))
			return GPOINTER_TO_INT(value);
	}

	return default_value;
}

const char *oscar_get_ui_info_string(const char *str, const char *default_value)
{
	GHashTable *ui_info;
	const char *value = NULL;

	ui_info = purple_core_get_ui_info();
	if (ui_info != NULL)
		value = g_hash_table_lookup(ui_info, str);
	if (value == NULL)
		value = default_value;

	return value;
}

gchar *oscar_get_clientstring(void)
{
	const char *name, *version;

	name = oscar_get_ui_info_string("name", "Purple");
	version = oscar_get_ui_info_string("version", VERSION);

	return g_strdup_printf("%s/%s", name, version);;
}

/*
 * Tokenizing functions.  Used to portably replace strtok/sep.
 *   -- DMP.
 *
 */
/* TODO: Get rid of this and use glib functions */
int
aimutil_tokslen(char *toSearch, int theindex, char dl)
{
	int curCount = 1;
	char *next;
	char *last;
	int toReturn;

	last = toSearch;
	next = strchr(toSearch, dl);

	while(curCount < theindex && next != NULL) {
		curCount++;
		last = next + 1;
		next = strchr(last, dl);
	}

	if ((curCount < theindex) || (next == NULL))
		toReturn = strlen(toSearch) - (curCount - 1);
	else
		toReturn = next - toSearch - (curCount - 1);

	return toReturn;
}

int
aimutil_itemcnt(char *toSearch, char dl)
{
	int curCount;
	char *next;

	curCount = 1;

	next = strchr(toSearch, dl);

	while(next != NULL) {
		curCount++;
		next = strchr(next + 1, dl);
	}

	return curCount;
}

char *
aimutil_itemindex(char *toSearch, int theindex, char dl)
{
	int curCount;
	char *next;
	char *last;
	char *toReturn;

	curCount = 0;

	last = toSearch;
	next = strchr(toSearch, dl);

	while (curCount < theindex && next != NULL) {
		curCount++;
		last = next + 1;
		next = strchr(last, dl);
	}
	next = strchr(last, dl);

	if (curCount < theindex) {
		toReturn = g_malloc(sizeof(char));
		*toReturn = '\0';
	} else {
		if (next == NULL) {
			toReturn = g_malloc((strlen(last) + 1) * sizeof(char));
			strcpy(toReturn, last);
		} else {
			toReturn = g_malloc((next - last + 1) * sizeof(char));
			memcpy(toReturn, last, (next - last));
			toReturn[next - last] = '\0';
		}
	}
	return toReturn;
}

/**
 * Calculate the checksum of a given icon.
 */
guint16
aimutil_iconsum(const guint8 *buf, int buflen)
{
	guint32 sum;
	int i;

	for (i=0, sum=0; i+1<buflen; i+=2)
		sum += (buf[i+1] << 8) + buf[i];
	if (i < buflen)
		sum += buf[i];
	sum = ((sum & 0xffff0000) >> 16) + (sum & 0x0000ffff);

	return sum;
}

/**
 * Check if the given name is a valid AIM username.
 * Example: BobDole
 * Example: Henry_Ford@mac.com
 * Example: 1KrazyKat@example.com
 *
 * @return TRUE if the name is valid, FALSE if not.
 */
static gboolean
oscar_util_valid_name_aim(const char *name)
{
	int i;

	if (purple_email_is_valid(name))
		return TRUE;

	/* Normal AIM usernames can't start with a number */
	if (isdigit(name[0]))
		return FALSE;

	for (i = 0; name[i] != '\0'; i++) {
		if (!isalnum(name[i]) && (name[i] != ' '))
			return FALSE;
	}

	return TRUE;
}

/**
 * Check if the given name is a valid ICQ username.
 * Example: 1234567
 *
 * @return TRUE if the name is valid, FALSE if not.
 */
gboolean
oscar_util_valid_name_icq(const char *name)
{
	int i;

	for (i = 0; name[i] != '\0'; i++) {
		if (!isdigit(name[i]))
			return FALSE;
	}

	return TRUE;
}

/**
 * Check if the given name is a valid SMS username.
 * Example: +19195551234
 *
 * @return TRUE if the name is valid, FALSE if not.
 */
gboolean
oscar_util_valid_name_sms(const char *name)
{
	int i;

	if (name[0] != '+')
		return FALSE;

	for (i = 1; name[i] != '\0'; i++) {
		if (!isdigit(name[i]))
			return FALSE;
	}

	return TRUE;
}

/**
 * Check if the given name is a valid oscar username.
 *
 * @return TRUE if the name is valid, FALSE if not.
 */
gboolean
oscar_util_valid_name(const char *name)
{
	if ((name == NULL) || (*name == '\0'))
		return FALSE;

	return oscar_util_valid_name_icq(name)
			|| oscar_util_valid_name_sms(name)
			|| oscar_util_valid_name_aim(name);
}

/**
 * This takes two names and compares them using the rules
 * on usernames for AIM/AOL.  Mainly, this means case and space
 * insensitivity (all case differences and spacing differences are
 * ignored, with the exception that usernames can not start with
 * a space).
 *
 * @return 0 if equal, non-0 if different
 */
/* TODO: Do something different for email addresses. */
int
oscar_util_name_compare(const char *name1, const char *name2)
{

	if ((name1 == NULL) || (name2 == NULL))
		return -1;

	do {
		while (*name2 == ' ')
			name2++;
		while (*name1 == ' ')
			name1++;
		if (toupper(*name1) != toupper(*name2))
			return 1;
	} while ((*name1 != '\0') && name1++ && name2++);

	return 0;
}

/**
 * Looks for %n, %d, or %t in a string, and replaces them with the
 * specified name, date, and time, respectively.
 *
 * @param str  The string that may contain the special variables.
 * @param name The sender name.
 *
 * @return A newly allocated string where the special variables are
 *         expanded.  This should be g_free'd by the caller.
 */
gchar *
oscar_util_format_string(const char *str, const char *name)
{
	char *c;
	GString *cpy;
	time_t t;
	struct tm *tme;

	g_return_val_if_fail(str  != NULL, NULL);
	g_return_val_if_fail(name != NULL, NULL);

	/* Create an empty GString that is hopefully big enough for most messages */
	cpy = g_string_sized_new(1024);

	t = time(NULL);
	tme = localtime(&t);

	c = (char *)str;
	while (*c) {
		switch (*c) {
		case '%':
			if (*(c + 1)) {
				switch (*(c + 1)) {
				case 'n':
					/* append name */
					g_string_append(cpy, name);
					c++;
					break;
				case 'd':
					/* append date */
					g_string_append(cpy, purple_date_format_short(tme));
					c++;
					break;
				case 't':
					/* append time */
					g_string_append(cpy, purple_time_format(tme));
					c++;
					break;
				default:
					g_string_append_c(cpy, *c);
				}
			} else {
				g_string_append_c(cpy, *c);
			}
			break;
		default:
			g_string_append_c(cpy, *c);
		}
		c++;
	}

	return g_string_free(cpy, FALSE);
}

gchar *
oscar_format_buddies(GSList *buddies, const gchar *no_buddies_message)
{
	GSList *cur;
	GString *result;
	if (!buddies) {
		return g_strdup_printf("<i>%s</i>", no_buddies_message);
	}
	result = g_string_new("");
	for (cur = buddies; cur != NULL; cur = cur->next) {
		PurpleBuddy *buddy = cur->data;
		const gchar *bname = purple_buddy_get_name(buddy);
		const gchar *alias = purple_buddy_get_alias_only(buddy);
		g_string_append(result, bname);
		if (alias) {
			g_string_append_printf(result, " (%s)", alias);
		}
		g_string_append(result, "<br>");
	}
	return g_string_free(result, FALSE);
}