Codebase list telepathy-idle / debian/0.1.14-1 src / idle-handles.c
debian/0.1.14-1

Tree @debian/0.1.14-1 (Download .tar.gz)

idle-handles.c @debian/0.1.14-1raw · history · blame

/*
 * This file is part of telepathy-idle
 *
 * Copyright (C) 2006-2007 Collabora Limited
 * Copyright (C) 2006-2007 Nokia Corporation
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * This 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 St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "idle-handles.h"

#include <glib.h>
#include <ctype.h>
#include <string.h>

#include <telepathy-glib/errors.h>
#include <telepathy-glib/handle-repo-dynamic.h>

#define IDLE_DEBUG_FLAG IDLE_DEBUG_PARSER
#include "idle-debug.h"
#include "idle-muc-channel.h"

/* When strict_mode is true, we validate the nick strictly against the IRC
 * RFCs (e.g. only ascii characters, no leading '-'.  When strict_mode is
 * false, we releax the requirements slightly.  This is because we don't want
 * to allow contribute to invalid nicks on IRC, but we want to be able to
 * handle them if another client allows people to use invalid nicks */
gboolean idle_nickname_is_valid(const gchar *nickname, gboolean strict_mode) {
	const gchar *char_pos;

	IDLE_DEBUG("Validating nickname '%s' with strict mode %d", nickname, strict_mode);

	/* FIXME: also check for max length? */
	if (!nickname || *nickname == '\0')
		return FALSE;

	for (char_pos = nickname; *char_pos; char_pos = g_utf8_find_next_char(char_pos, NULL)) {
		/* only used for non-strict checks */
		gunichar ucs4char = g_utf8_get_char_validated(char_pos, -1);

		switch (*char_pos) {
			case '[':
			case ']':
			case '\\':
			case '`':
			case '_':
			case '^':
			case '{':
			case '|':
			case '}':
				break;

			/* '-' is technically not allowed as first char in a nickname */
			case '-':
				if (strict_mode) {
						if (char_pos == nickname)
							return FALSE;
				}
				break;

			default:
				if (strict_mode) {
						/* only accept ascii characters in strict mode */
						if (!(g_ascii_isalpha(*char_pos) || ((char_pos != nickname) && g_ascii_isdigit(*char_pos)))) {
								IDLE_DEBUG("invalid character '%c'", *char_pos);
								return FALSE;
						}
				} else {
						/* allow unicode and digits as first char in non-strict mode */
						if (!(g_unichar_isalpha(ucs4char) || g_unichar_isdigit(ucs4char))) {
								IDLE_DEBUG("invalid character %d", ucs4char);
								return FALSE;
						}
				}
				break;
		}
	}

	return TRUE;
}

static gboolean _channelname_is_valid(const gchar *channel) {
	static const gchar not_allowed_chars[] = {' ', '\007', ',', '\r', '\n', ':', '\0'};
	gsize len;
	const gchar *tmp;

	if (!idle_muc_channel_is_typechar(channel[0]))
		return FALSE;

	len = strlen(channel);
	if ((len < 2) || (len > 50))
		return FALSE;

	if (channel[0] == '!') {
		for (gsize i = 0; i < 5 && i + 1 < len; i++) {
			if (!g_ascii_isupper(channel[i + 1]) && !isdigit(channel[i + 1]))
				return FALSE;
		}
	}

	tmp = strchr(channel + 1, ':');
	if (tmp != NULL) {
		for (const gchar *tmp2 = channel + 1; tmp2 != tmp; tmp2++) {
			if (strchr(not_allowed_chars, *tmp2))
				return FALSE;
		}

		for (const gchar *tmp2 = tmp + 1; tmp2 != channel + len; tmp2++) {
			if (strchr(not_allowed_chars, *tmp2))
				return FALSE;
		}
	} else {
		for (const gchar *tmp2 = channel + 1; tmp2 != channel + len; tmp2++) {
			if (strchr(not_allowed_chars, *tmp2))
				return FALSE;
		}
	}

	return TRUE;
}

gchar *idle_normalize_nickname (const gchar *id, GError **error) {
	gchar *normalized;

	if (!idle_nickname_is_valid(id, FALSE)) {
		g_set_error(error, TP_ERROR, TP_ERROR_INVALID_HANDLE, "invalid nickname");
		return NULL;
	}

	normalized = g_utf8_strdown(id, -1);

	return normalized;
}

static gchar *_nick_normalize_func(TpHandleRepoIface *repo, const gchar *id, gpointer ctx, GError **error) {
	return idle_normalize_nickname (id, error);
}

static gchar *_channel_normalize_func(TpHandleRepoIface *repo, const gchar *id, gpointer ctx, GError **error) {
	gchar *normalized;

	if (!_channelname_is_valid(id)) {
		g_set_error(error, TP_ERROR, TP_ERROR_INVALID_HANDLE, "invalid channel ID");
		return NULL;
	}

	normalized = g_utf8_strdown(id, -1);

	return normalized;
}

void idle_handle_repos_init(TpHandleRepoIface **handles) {
	g_assert(handles != NULL);

	handles[TP_HANDLE_TYPE_CONTACT] = (TpHandleRepoIface *) g_object_new(TP_TYPE_DYNAMIC_HANDLE_REPO,
			"handle-type", TP_HANDLE_TYPE_CONTACT,
			"normalize-function", _nick_normalize_func,
			"default-normalize-context", NULL,
			NULL);

	handles[TP_HANDLE_TYPE_ROOM] = (TpHandleRepoIface *) g_object_new(TP_TYPE_DYNAMIC_HANDLE_REPO,
			"handle-type", TP_HANDLE_TYPE_ROOM,
			"normalize-function", _channel_normalize_func,
			"default-normalize-context", NULL,
			NULL);
}