view src/protocols/qq/ip_location.c @ 13967:99b9b58b19dd

[gaim-migrate @ 16523] Fix a crazy MSN crash. Basically it's possible to have more than one slplink associated with a given switchboard, but our code did not allow for that. I think it happens when you're in a multi-user chat and you do stuff with multiple users that involves slplinks. Like maybe file transfer and buddy icon related stuff. Tracking this down took an ungodly amount of time, but thanks to Meebo for letting me do it :-) committer: Tailor Script <tailor@pidgin.im>
author Mark Doliner <mark@kingant.net>
date Thu, 20 Jul 2006 07:31:15 +0000
parents 983fd420e86b
children ef8490f9e823
line wrap: on
line source

/**
 * The QQ2003C protocol plugin
 *
 * for gaim
 *
 * Copyright (C) 2004 Puzzlebird
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * This code is based on
 *  * qqwry.c, by lvxiang <srm2003@163.com>
 *  * IpChecker.m, by Jeff_Ye
 */

// START OF FILE
/*****************************************************************************/
#include "internal.h"
#include <string.h>		// memset
#include "debug.h"
#include "prefs.h"		// gaim_prefs_get_string

#include "utils.h"
#include "ip_location.h"

#define DEFAULT_IP_LOCATION_FILE  "gaim/QQWry.dat"

typedef struct _ip_finder ip_finder;

// all offset is based the begining of the file
struct _ip_finder {
	guint32 offset_first_start_ip;	// first abs offset of start ip
	guint32 offset_last_start_ip;	// last abs offset of start ip
	guint32 cur_start_ip;	// start ip of current search range
	guint32 cur_end_ip;	// end ip of current search range
	guint32 offset_cur_end_ip;	// where is the current end ip saved
	GIOChannel *io;		// IO Channel to read file
};				// struct _ip_finder

/*****************************************************************************/
// convert 1-4 bytes array to integer.
// Small endian (higher bytes in lower place)
static guint32 _byte_array_to_int(guint8 * ip, gint count)
{
	guint32 ret, i;
	g_return_val_if_fail(count >= 1 && count <= 4, 0);
	ret = ip[0];
	for (i = 0; i < count; i++)
		ret |= ((guint32) ip[i]) << (8 * i);
	return ret;
}				// _byte_array_to_int

/*****************************************************************************/
// read len of bytes to buf, from io at offset
static void _read_from(GIOChannel * io, guint32 offset, guint8 * buf, gint len)
{
	GError *err;
	GIOStatus status;

	err = NULL;
	status = g_io_channel_seek_position(io, offset, G_SEEK_SET, &err);
	if (err != NULL) {
		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail seek file @offset[%d]: %s", offset, err->message);
		g_error_free(err);
		memset(buf, 0, len);
		return;
	}			// if err

	status = g_io_channel_read_chars(io, buf, len, NULL, &err);
	if (err != NULL) {
		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail read %d bytes from file: %s", len, err->message);
		g_error_free(err);
		memset(buf, 0, len);
		return;
	}			// if err
}				// _read_from

/*****************************************************************************/
// read len of bytes to buf, from io at offset
static gsize _read_line_from(GIOChannel * io, guint32 offset, gchar ** ret_str)
{
	gsize bytes_read;
	GError *err;
	GIOStatus status;

	err = NULL;
	status = g_io_channel_seek_position(io, offset, G_SEEK_SET, &err);
	if (err != NULL) {
		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail seek file @offset[%d]: %s", offset, err->message);
		g_error_free(err);
		ret_str = NULL;
		return -1;
	}			// if err

	status = g_io_channel_read_line(io, ret_str, &bytes_read, NULL, &err);
	if (err != NULL) {
		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Fail read from file: %s", err->message);
		g_error_free(err);
		ret_str = NULL;
		return -1;
	}			// if err

	return bytes_read;
}				// _read_from

/*****************************************************************************/
// read the string from io, at offset, it may jump several times
// returns the offset of next readable string for area
static guint32 _get_string(GIOChannel * io, guint32 offset, gchar ** ret)
{
	guint8 *buf;
	g_return_val_if_fail(io != NULL, 0);

	buf = g_new0(guint8, 3);
	_read_from(io, offset, buf, 1);

	switch (buf[0]) {	/* fixed by bluestar11 at gmail dot com, 04/12/20 */
	case 0x01:		/* jump together */
	  _read_from(io, offset + 1, buf, 3);
	  return _get_string(io, _byte_array_to_int(buf, 3), ret);
	case 0x02:		/* jump separately */
	   _read_from(io, offset + 1, buf, 3);
	  _get_string(io, _byte_array_to_int(buf, 3), ret);
	  return offset + 4;
	default:
	  _read_line_from(io, offset, ret);
	  return offset + strlen(*ret) + 1;
	} /* switch */
}				// _get_string

/*****************************************************************************/
// extract country and area starting from offset
static void _get_country_city(GIOChannel * io, guint32 offset, gchar ** country, gchar ** area)
{
	guint32 next_offset;
	g_return_if_fail(io != NULL);

	next_offset = _get_string(io, offset, country);
	if (next_offset == 0)
		*area = g_strdup("");
	else
		_get_string(io, next_offset, area);
}				// _get_country_city

/*****************************************************************************/
// set start_ip and end_ip of current range
static void _set_ip_range(gint rec_no, ip_finder * f)
{
	guint8 *buf;
	guint32 offset;

	g_return_if_fail(f != NULL);

	buf = g_newa(guint8, 7);
	offset = f->offset_first_start_ip + rec_no * 7;

	_read_from(f->io, offset, buf, 7);
	f->cur_start_ip = _byte_array_to_int(buf, 4);
	f->offset_cur_end_ip = _byte_array_to_int(buf + 4, 3);

	_read_from(f->io, f->offset_cur_end_ip, buf, 4);
	f->cur_end_ip = _byte_array_to_int(buf, 4);

}				// _set_ip_range

/*****************************************************************************/
// set the country and area for given IP.
// country and area needs to be freed later
gboolean qq_ip_get_location(guint32 ip, gchar ** country, gchar ** area)
{
	gint rec, record_count, B, E;
	guint8 *buf;
	gchar *addr_file;
	ip_finder *f;
	GError *err;
	const char *ip_fn;

	if (ip == 0)
		return FALSE;

	f = g_new0(ip_finder, 1);
	err = NULL;
	ip_fn = gaim_prefs_get_string("/plugins/prpl/qq/ipfile");
	if (ip_fn == NULL || strlen(ip_fn) == 0 || strncmp(ip_fn, "(null)", strlen("(null)")) == 0) {
		addr_file = g_build_filename(DATADIR, DEFAULT_IP_LOCATION_FILE, NULL);
	} else {
		addr_file = g_build_filename(ip_fn, NULL);
	}

	f->io = g_io_channel_new_file(addr_file, "r", &err);
	g_free(addr_file);
	if (err != NULL) {
		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Unable to open (%s): %s\n", addr_file, err->message);
		g_error_free(err);
		return FALSE;
	} else
		g_io_channel_set_encoding(f->io, NULL, NULL);	// set binary

	buf = g_newa(guint8, 4);

	_read_from(f->io, 0, buf, 4);
	f->offset_first_start_ip = _byte_array_to_int(buf, 4);
	_read_from(f->io, 4, buf, 4);
	f->offset_last_start_ip = _byte_array_to_int(buf, 4);

	record_count = (f->offset_last_start_ip - f->offset_first_start_ip) / 7;
	if (record_count <= 1) {
		gaim_debug(GAIM_DEBUG_ERROR, "QQ", "File data error, no records found\n");
		g_io_channel_shutdown(f->io, FALSE, NULL);
		return FALSE;;
	}
	// search for right range
	B = 0;
	E = record_count;
	while (B < E - 1) {
		rec = (B + E) / 2;
		_set_ip_range(rec, f);
		if (ip == f->cur_start_ip) {
			B = rec;
			break;
		}
		if (ip > f->cur_start_ip)
			B = rec;
		else
			E = rec;
	}			// while
	_set_ip_range(B, f);

	if (f->cur_start_ip <= ip && ip <= f->cur_end_ip) {
		_get_country_city(f->io, f->offset_cur_end_ip + 4, country, area);
	} else {		// not in this range... miss
		*country = g_strdup("unkown");
		*area = g_strdup(" ");
	}			// if ip_start<=ip<=ip_end

	g_io_channel_shutdown(f->io, FALSE, NULL);
	return TRUE;

}				// qq_ip_get_location

/*****************************************************************************/
// END OF FILE