gaim/gaim

Logging fixes backport
v0_74-branch
2003-11-25, Ethan Blanton
e2a1b928cf16
Parents de41d95d1aae
Children bea195b020a6
Logging fixes backport
  • +865 -0
    src/log.c
  • +2153 -0
    src/util.c
  • +548 -0
    src/util.h
  • --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/src/log.c Tue Nov 25 21:53:14 2003 -0500
    @@ -0,0 +1,865 @@
    +/**
    + * @file log.c Logging API
    + * @ingroup core
    + *
    + * gaim
    + *
    + * Copyright (C) 2003 Buzz Lightyear
    + *
    + * 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
    + */
    +
    +#include "account.h"
    +#include "debug.h"
    +#include "internal.h"
    +#include "log.h"
    +#include "prefs.h"
    +#include "util.h"
    +
    +static GaimLogLogger html_logger;
    +static GaimLogLogger txt_logger;
    +static GaimLogLogger old_logger;
    +
    +/**************************************************************************
    + * PUBLIC LOGGING FUNCTIONS ***********************************************
    + **************************************************************************/
    +
    +GaimLog *gaim_log_new(GaimLogType type, const char *name, GaimAccount *account, time_t time)
    +{
    + GaimLog *log = g_new0(GaimLog, 1);
    + log->name = g_strdup(name);
    + log->account = account;
    + log->time = time;
    + log->logger = gaim_log_logger_get();
    + if (log->logger && log->logger->create)
    + log->logger->create(log);
    + return log;
    +}
    +
    +void gaim_log_free(GaimLog *log)
    +{
    + g_return_if_fail(log);
    + if (log->logger && log->logger->finalize)
    + log->logger->finalize(log);
    + g_free(log->name);
    + g_free(log);
    +}
    +
    +
    +void gaim_log_write(GaimLog *log, GaimMessageFlags type,
    + const char *from, time_t time, const char *message)
    +{
    + g_return_if_fail(log);
    + g_return_if_fail(log->logger);
    + g_return_if_fail(log->logger->write);
    +
    + if ((log->type == GAIM_LOG_IM && gaim_prefs_get_bool("/core/logging/log_ims")) ||
    + (log->type == GAIM_LOG_CHAT && gaim_prefs_get_bool("/core/logging/log_chats")))
    + (log->logger->write)(log, type, from, time, message);
    +}
    +
    +char *gaim_log_read(GaimLog *log, GaimLogReadFlags *flags)
    +{
    + GaimLogReadFlags mflags;
    + g_return_val_if_fail(log && log->logger, NULL);
    + if (log->logger->read) {
    + char *ret = (log->logger->read)(log, flags ? flags : &mflags);
    + gaim_str_strip_cr(ret);
    + return ret;
    + }
    + return (_("<b><font color=\"red\">The logger has no read function</font></b>"));
    +}
    +
    +int gaim_log_get_size(GaimLog *log)
    +{
    + g_return_val_if_fail(log && log->logger, 0);
    + if (log->logger->size)
    + return log->logger->size(log);
    + return 0;
    +}
    +
    +int gaim_log_get_total_size(const char *name, GaimAccount *account)
    +{
    + GList *logs = gaim_log_get_logs(name, account);
    + int size = 0;
    +
    +
    + while (logs) {
    + GList *logs2 = logs->next;
    + GaimLog *log = (GaimLog*)(logs->data);
    + size += gaim_log_get_size(log);
    + g_free(log->name);
    + g_free(log);
    + g_list_free_1(logs);
    + logs = logs2;
    + }
    +
    + return size;
    +}
    +
    +/****************************************************************************
    + * LOGGER FUNCTIONS *********************************************************
    + ****************************************************************************/
    +
    +static GaimLogLogger *current_logger = NULL;
    +static GSList *loggers = NULL;
    +
    +static void logger_pref_cb(const char *name, GaimPrefType type,
    + gpointer value, gpointer data)
    +{
    + GaimLogLogger *logger;
    + GSList *l = loggers;
    + while (l) {
    + logger = l->data;
    + if (!strcmp(logger->id, value)) {
    + gaim_log_logger_set(logger);
    + return;
    + }
    + l = l->next;
    + }
    + gaim_log_logger_set(&txt_logger);
    +}
    +
    +
    +GaimLogLogger *gaim_log_logger_new(void(*create)(GaimLog *),
    + void(*write)(GaimLog *, GaimMessageFlags, const char *,
    + time_t, const char *),
    + void(*finalize)(GaimLog *), GList*(*list)(const char*, GaimAccount*),
    + char*(*read)(GaimLog*, GaimLogReadFlags*),
    + int(*size)(GaimLog*))
    +{
    + GaimLogLogger *logger = g_new0(GaimLogLogger, 1);
    + logger->create = create;
    + logger->write = write;
    + logger->finalize = finalize;
    + logger->list = list;
    + logger->read = read;
    + logger->size = size;
    + return logger;
    +}
    +
    +void gaim_log_logger_free(GaimLogLogger *logger)
    +{
    + g_free(logger);
    +}
    +
    +void gaim_log_logger_add (GaimLogLogger *logger)
    +{
    + g_return_if_fail(logger);
    + if (g_slist_find(loggers, logger))
    + return;
    + loggers = g_slist_append(loggers, logger);
    +}
    +
    +void gaim_log_logger_remove (GaimLogLogger *logger)
    +{
    + g_return_if_fail(logger);
    + g_slist_remove(loggers, logger);
    +}
    +
    +void gaim_log_logger_set (GaimLogLogger *logger)
    +{
    + g_return_if_fail(logger);
    + current_logger = logger;
    +}
    +
    +GaimLogLogger *gaim_log_logger_get()
    +{
    + return current_logger;
    +}
    +
    +GList *gaim_log_logger_get_options(void)
    +{
    + GSList *n;
    + GList *list = NULL;
    + GaimLogLogger *data;
    +
    + for (n = loggers; n; n = n->next) {
    + data = n->data;
    + if (!data->write)
    + continue;
    + list = g_list_append(list, _(data->name));
    + list = g_list_append(list, data->id);
    + }
    +
    + return list;
    +}
    +
    +static gint log_compare(gconstpointer y, gconstpointer z)
    +{
    + const GaimLog *a = y;
    + const GaimLog *b = z;
    +
    + return b->time - a->time;
    +}
    +
    +GList *gaim_log_get_logs(const char *name, GaimAccount *account)
    +{
    + GList *logs = NULL;
    + GSList *n;
    + for (n = loggers; n; n = n->next) {
    + GaimLogLogger *logger = n->data;
    + if (!logger->list)
    + continue;
    + logs = g_list_concat(logs, logger->list(name, account));
    + }
    +
    + return g_list_sort(logs, log_compare);
    +}
    +
    +void gaim_log_init(void)
    +{
    + gaim_prefs_add_none("/core/logging");
    + gaim_prefs_add_bool("/core/logging/log_ims", FALSE);
    + gaim_prefs_add_bool("/core/logging/log_chats", FALSE);
    + gaim_prefs_add_string("/core/logging/format", "txt");
    + gaim_log_logger_add(&html_logger);
    + gaim_log_logger_add(&txt_logger);
    + gaim_log_logger_add(&old_logger);
    + gaim_prefs_connect_callback("/core/logging/format",
    + logger_pref_cb, NULL);
    + gaim_prefs_trigger_callback("/core/logging/format");
    +}
    +
    +/****************************************************************************
    + * LOGGERS ******************************************************************
    + ****************************************************************************/
    +
    +struct generic_logger_data {
    + char *path;
    + FILE *file;
    +};
    +
    +static GList *log_lister_common(const char *screenname, GaimAccount *account, const char *ext, GaimLogLogger *logger)
    +{
    + GDir *dir;
    + GList *list = NULL;
    + const char *filename;
    + char *me = g_strdup(gaim_normalize(account, gaim_account_get_username(account)));
    +
    + const char *prpl = GAIM_PLUGIN_PROTOCOL_INFO
    + (gaim_find_prpl(gaim_account_get_protocol(account)))->list_icon(account, NULL);
    + char *path = g_build_filename(gaim_user_dir(), "logs", prpl, me, gaim_normalize(account, screenname), NULL);
    +
    + g_free(me);
    +
    + if (!(dir = g_dir_open(path, 0, NULL))) {
    + g_free(path);
    + return NULL;
    + }
    + while ((filename = g_dir_read_name(dir))) {
    + if (gaim_str_has_suffix(filename, ext)) {
    + const char *l = filename;
    + struct tm time;
    + GaimLog *log;
    + struct generic_logger_data *data;
    + char d[5];
    +
    + strncpy(d, l, 4);
    + d[4] = '\0';
    + time.tm_year = atoi(d) - 1900;
    + l = l + 5;
    +
    + strncpy(d, l, 2);
    + d[2] = '\0';
    + time.tm_mon = atoi(d) - 1;
    + l = l + 3;
    +
    + strncpy(d, l, 2);
    + time.tm_mday = atoi(d);
    + l = l + 3;
    +
    + strncpy(d, l, 2);
    + time.tm_hour = atoi(d);
    + l = l + 2;
    +
    + strncpy(d, l, 2);
    + time.tm_min = atoi(d);
    + l = l + 2;
    +
    + strncpy(d, l, 2);
    + time.tm_sec = atoi(d);
    + l = l + 2;
    + log = gaim_log_new(GAIM_LOG_IM, screenname, account, mktime(&time));
    + log->logger = logger;
    + log->logger_data = data = g_new0(struct generic_logger_data, 1);
    + data->path = g_build_filename(path, filename, NULL);
    + list = g_list_append(list, log);
    + }
    + }
    + g_dir_close(dir);
    + g_free(path);
    + return list;
    +}
    +
    +/* Only to be used with logs listed from log_lister_common */
    +int log_sizer_common(GaimLog *log)
    +{
    + struct stat st;
    + struct generic_logger_data *data = log->logger_data;
    +
    + if (!data->path || stat(data->path, &st))
    + st.st_size = 0;
    +
    + return st.st_size;
    +}
    +
    +#if 0 /* Maybe some other time. */
    +/****************
    + ** XML LOGGER **
    + ****************/
    +
    +static const char *str_from_msg_type (GaimMessageFlags type)
    +{
    +
    + return "";
    +
    +}
    +
    +static void xml_logger_write(GaimLog *log,
    + GaimMessageFlags type,
    + const char *from, time_t time, const char *message)
    +{
    + char date[64];
    + char *xhtml = NULL;
    + if (!log->logger_data) {
    + /* This log is new. We could use the loggers 'new' function, but
    + * creating a new file there would result in empty files in the case
    + * that you open a convo with someone, but don't say anything.
    + */
    + char *ud = gaim_user_dir();
    + char *guy = g_strdup(gaim_normalize(log->account, gaim_account_get_username(log->account)));
    + const char *prpl = GAIM_PLUGIN_PROTOCOL_INFO
    + (gaim_find_prpl(gaim_account_get_protocol(log->account)))->list_icon(log->account, NULL);
    + char *dir;
    + FILE *file;
    +
    + strftime(date, sizeof(date), "%Y-%m-%d.%H%M%S.xml", localtime(&log->time));
    +
    + dir = g_build_filename(ud, "logs", NULL);
    + mkdir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
    + g_free(dir);
    + dir = g_build_filename(ud, "logs",
    + prpl, NULL);
    + mkdir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
    + g_free(dir);
    + dir = g_build_filename(ud, "logs",
    + prpl, guy, NULL);
    + mkdir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
    + g_free(dir);
    + dir = g_build_filename(ud, "logs",
    + prpl, guy, gaim_normalize(log->account, log->name), NULL);
    + mkdir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
    + g_free(guy);
    +
    + char *filename = g_build_filename(dir, date, NULL);
    + g_free(dir);
    +
    + log->logger_data = fopen(filename, "a");
    + if (!log->logger_data) {
    + gaim_debug(GAIM_DEBUG_ERROR, "log", "Could not create log file %s\n", filename);
    + g_free(filename);
    + return;
    + }
    + g_free(filename);
    + fprintf(log->logger_data, "<?xml version='1.0' encoding='UTF-8' ?>\n"
    + "<?xml-stylesheet href='file:///usr/src/web/htdocs/log-stylesheet.xsl' type='text/xml' ?>\n");
    +
    + strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", localtime(&log->time));
    + fprintf(log->logger_data, "<conversation time='%s' screenname='%s' protocol='%s'>\n",
    + date, log->name, prpl);
    + }
    +
    + strftime(date, sizeof(date), "%H:%M:%S", localtime(&time));
    + gaim_markup_html_to_xhtml(message, &xhtml, NULL);
    + if (from)
    + fprintf(log->logger_data, "<message %s %s from='%s' time='%s'>%s</message>\n",
    + str_from_msg_type(type),
    + type & GAIM_MESSAGE_SEND ? "direction='sent'" :
    + type & GAIM_MESSAGE_RECV ? "direction='received'" : "",
    + from, date, xhtml);
    + else
    + fprintf(log->logger_data, "<message %s %s time='%s'>%s</message>\n",
    + str_from_msg_type(type),
    + type & GAIM_MESSAGE_SEND ? "direction='sent'" :
    + type & GAIM_MESSAGE_RECV ? "direction='received'" : "",
    + date, xhtml):
    + fflush(log->logger_data);
    + g_free(xhtml);
    +}
    +
    + static void xml_logger_finalize(GaimLog *log)
    +{
    + if (log->logger_data) {
    + fprintf(log->logger_data, "</conversation>\n");
    + fclose(log->logger_data);
    + log->logger_data = NULL;
    + }
    +}
    +
    +static GList *xml_logger_list(const char *sn, GaimAccount *account)
    +{
    + return log_lister_common(sn, account, ".xml", &xml_logger);
    +}
    +
    +static GaimLogLogger xml_logger = {
    + N_("XML"), "xml",
    + NULL,
    + xml_logger_write,
    + xml_logger_finalize,
    + xml_logger_list,
    + NULL
    +};
    +#endif
    +
    +/****************************
    + ** HTML LOGGER *************
    + ****************************/
    +
    +static void html_logger_write(GaimLog *log, GaimMessageFlags type,
    + const char *from, time_t time, const char *message)
    +{
    + GaimConnection *gc = gaim_account_get_connection(log->account);
    + char date[64];
    + struct generic_logger_data *data = log->logger_data;
    + if(!data) {
    + /* This log is new */
    + char *ud = gaim_user_dir();
    + char *guy = g_strdup(gaim_normalize(log->account, gaim_account_get_username(log->account)));
    + char *chat;
    + const char *prpl = GAIM_PLUGIN_PROTOCOL_INFO
    + (gaim_find_prpl(gaim_account_get_protocol(log->account)))->list_icon(log->account, NULL);
    + char *dir;
    + char *filename;
    +
    + if (log->type == GAIM_LOG_CHAT) {
    + chat = g_strdup_printf("%s.chat", guy);
    + g_free(guy);
    + guy = chat;
    + }
    +
    + strftime(date, sizeof(date), "%Y-%m-%d.%H%M%S.html", localtime(&log->time));
    +
    + dir = g_build_filename(ud, "logs", NULL);
    + mkdir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
    + g_free(dir);
    + dir = g_build_filename(ud, "logs",
    + prpl, NULL);
    + mkdir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
    + g_free(dir);
    + dir = g_build_filename(ud, "logs",
    + prpl, guy, NULL);
    + mkdir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
    + g_free(dir);
    + dir = g_build_filename(ud, "logs",
    + prpl, guy, gaim_normalize(log->account, log->name), NULL);
    + mkdir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
    + g_free(guy);
    +
    + filename = g_build_filename(dir, date, NULL);
    + g_free(dir);
    +
    + log->logger_data = data = g_new0(struct generic_logger_data, 1);
    +
    + data->file = fopen(filename, "a");
    + if (!data->file) {
    + gaim_debug(GAIM_DEBUG_ERROR, "log",
    + "Could not create log file %s\n", filename);
    + g_free(filename);
    + return;
    + }
    + g_free(filename);
    + strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", localtime(&log->time));
    + fprintf(data->file, "<html><head><title>");
    + fprintf(data->file, "Conversation with %s at %s on %s (%s)",
    + log->name, date, gaim_account_get_username(log->account), prpl);
    + fprintf(data->file, "</title></head><body>");
    + fprintf(data->file,
    + "<h3>Conversation with %s at %s on %s (%s)</h3>\n",
    + log->name, date, gaim_account_get_username(log->account), prpl);
    + }
    +
    + /* if we can't write to the file, give up before we hurt ourselves */
    + if(!data->file)
    + return;
    +
    + strftime(date, sizeof(date), "%H:%M:%S", localtime(&time));
    + if (type & GAIM_MESSAGE_SYSTEM)
    + fprintf(data->file, "(%s)<b> %s</b><br/>\n", date, message);
    + else if (type & GAIM_MESSAGE_WHISPER)
    + fprintf(data->file, "<font color=\"#6C2585\">(%s)<b> %s:</b></font> %s<br/>\n",
    + date, from, message);
    + else if (type & GAIM_MESSAGE_AUTO_RESP) {
    + if (type & GAIM_MESSAGE_SEND)
    + fprintf(data->file, _("<font color=\"#16569E\">(%s) <b>%s <AUTO-REPLY>:</b></font> %s<br/>\n"), date, from, message);
    + else if (type & GAIM_MESSAGE_RECV)
    + fprintf(data->file, _("<font color=\"#A82F2F\">(%s) <b>%s <AUTO-REPLY>:</b></font> %s<br/>\n"), date, from, message);
    + } else if (type & GAIM_MESSAGE_RECV) {
    + char *msg = g_strdup(message);
    + if(gaim_message_meify(msg, -1))
    + fprintf(data->file, "<font color=\"#6C2585\">(%s) <b>***%s</b></font> <font sml=\"%s\">%s</font><br/>\n",
    + date, from, gc->prpl->info->name, msg);
    + else
    + fprintf(data->file, "<font color=\"#A82F2F\">(%s) <b>%s:</b></font> <font sml=\"%s\">%s</font><br/>\n",
    + date, from, gc->prpl->info->name, msg);
    + g_free(msg);
    + } else if (type & GAIM_MESSAGE_SEND) {
    + char *msg = g_strdup(message);
    + if(gaim_message_meify(msg, -1))
    + fprintf(data->file, "<font color=\"#6C2585\">(%s) <b>***%s</b></font> <font sml=\"%s\">%s</font><br/>\n",
    + date, from, gc->prpl->info->name, msg);
    + else
    + fprintf(data->file, "<font color=\"#16569E\">(%s) <b>%s:</b></font> <font sml=\"%s\">%s</font><br/>\n",
    + date, from, gc->prpl->info->name, msg);
    + g_free(msg);
    + }
    + fflush(data->file);
    +}
    +
    +static void html_logger_finalize(GaimLog *log)
    +{
    + struct generic_logger_data *data = log->logger_data;
    + if (data) {
    + if(data->file) {
    + fprintf(data->file, "</body></html>");
    + fclose(data->file);
    + }
    + g_free(data->path);
    + }
    +}
    +
    +static GList *html_logger_list(const char *sn, GaimAccount *account)
    +{
    + return log_lister_common(sn, account, ".html", &html_logger);
    +}
    +
    +static char *html_logger_read(GaimLog *log, GaimLogReadFlags *flags)
    +{
    + char *read, *minus_header;
    + struct generic_logger_data *data = log->logger_data;
    + *flags = GAIM_LOG_READ_NO_NEWLINE;
    + if (!data || !data->path)
    + return g_strdup(_("<font color=\"red\"><b>Unable to find log path!</b></font>"));
    + if (g_file_get_contents(data->path, &read, NULL, NULL)) {
    + minus_header = strchr(read, '\n');
    + if (!minus_header)
    + minus_header = g_strdup(read);
    + else
    + minus_header = g_strdup(minus_header + 1);
    + g_free(read);
    + return minus_header;
    + }
    + return g_strdup(_("<font color=\"red\"><b>Could not read file: %s</b></font>"));
    +}
    +
    +static GaimLogLogger html_logger = {
    + N_("HTML"), "html",
    + NULL,
    + html_logger_write,
    + html_logger_finalize,
    + html_logger_list,
    + html_logger_read,
    + log_sizer_common
    +};
    +
    +
    +
    +
    +/****************************
    + ** PLAIN TEXT LOGGER *******
    + ****************************/
    +
    +static void txt_logger_write(GaimLog *log,
    + GaimMessageFlags type,
    + const char *from, time_t time, const char *message)
    +{
    + char date[64];
    + char *stripped = NULL;
    + struct generic_logger_data *data = log->logger_data;
    + if (!data) {
    + /* This log is new. We could use the loggers 'new' function, but
    + * creating a new file there would result in empty files in the case
    + * that you open a convo with someone, but don't say anything.
    + */
    + char *ud = gaim_user_dir();
    + char *filename;
    + char *guy = g_strdup(gaim_normalize(log->account, gaim_account_get_username(log->account)));
    + char *chat;
    + const char *prpl = GAIM_PLUGIN_PROTOCOL_INFO
    + (gaim_find_prpl(gaim_account_get_protocol(log->account)))->list_icon(log->account, NULL);
    + char *dir;
    +
    + if (log->type == GAIM_LOG_CHAT) {
    + chat = g_strdup_printf("%s.chat", guy);
    + g_free(guy);
    + guy = chat;
    + }
    + strftime(date, sizeof(date), "%Y-%m-%d.%H%M%S.txt", localtime(&log->time));
    +
    + dir = g_build_filename(ud, "logs", NULL);
    + mkdir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
    + g_free(dir);
    + dir = g_build_filename(ud, "logs",
    + prpl, NULL);
    + mkdir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
    + g_free(dir);
    + dir = g_build_filename(ud, "logs",
    + prpl, guy, NULL);
    + mkdir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
    + g_free(dir);
    + dir = g_build_filename(ud, "logs",
    + prpl, guy, gaim_normalize(log->account, log->name), NULL);
    + mkdir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
    + g_free(guy);
    +
    + filename = g_build_filename(dir, date, NULL);
    + g_free(dir);
    +
    + log->logger_data = data = g_new0(struct generic_logger_data, 1);
    +
    + data->file = fopen(filename, "a");
    + if (!data->file) {
    + gaim_debug(GAIM_DEBUG_ERROR, "log", "Could not create log file %s\n", filename);
    + g_free(filename);
    + return;
    + }
    + g_free(filename);
    + strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", localtime(&log->time));
    + fprintf(data->file, "Conversation with %s at %s on %s (%s)\n",
    + log->name, date, gaim_account_get_username(log->account), prpl);
    + }
    +
    + /* if we can't write to the file, give up before we hurt ourselves */
    + if(!data->file)
    + return;
    +
    + strftime(date, sizeof(date), "%H:%M:%S", localtime(&time));
    + stripped = gaim_markup_strip_html(message);
    + if (type & GAIM_MESSAGE_SEND ||
    + type & GAIM_MESSAGE_RECV) {
    + if (type & GAIM_MESSAGE_AUTO_RESP) {
    + fprintf(data->file, _("(%s) %s <AUTO-REPLY>: %s\n"), date, from, stripped);
    + } else {
    + if(gaim_message_meify(stripped, -1))
    + fprintf(data->file, "(%s) ***%s %s\n", date, from,
    + stripped);
    + else
    + fprintf(data->file, "(%s) %s: %s\n", date, from,
    + stripped);
    + }
    + } else if (type & GAIM_MESSAGE_SYSTEM)
    + fprintf(data->file, "(%s) %s\n", date, stripped);
    + else if (type & GAIM_MESSAGE_NO_LOG) {
    + /* This shouldn't happen */
    + g_free(stripped);
    + return;
    + } else if (type & GAIM_MESSAGE_WHISPER)
    + fprintf(data->file, "(%s) *%s* %s", date, from, stripped);
    + else
    + fprintf(data->file, "(%s) %s%s %s\n", date, from ? from : "", from ? ":" : "", stripped);
    +
    + fflush(data->file);
    + g_free(stripped);
    +}
    +
    +static void txt_logger_finalize(GaimLog *log)
    +{
    + struct generic_logger_data *data = log->logger_data;
    + if (data) {
    + if(data->file)
    + fclose(data->file);
    + if(data->path)
    + g_free(data->path);
    + }
    +}
    +
    +static GList *txt_logger_list(const char *sn, GaimAccount *account)
    +{
    + return log_lister_common(sn, account, ".txt", &txt_logger);
    +}
    +
    +static char *txt_logger_read(GaimLog *log, GaimLogReadFlags *flags)
    +{
    + char *read, *minus_header;
    + struct generic_logger_data *data = log->logger_data;
    + *flags = 0;
    + if (!data || !data->path)
    + return g_strdup(_("<font color=\"red\"><b>Unable to find log path!</b></font>"));
    + if (g_file_get_contents(data->path, &read, NULL, NULL)) {
    + minus_header = strchr(read, '\n');
    + if (!minus_header)
    + minus_header = g_strdup(read);
    + else
    + minus_header = g_strdup(minus_header + 1);
    + g_free(read);
    + return minus_header;
    + }
    + return g_strdup(_("<font color=\"red\"><b>Could not read file: %s</b></font>"));
    +}
    +
    +static GaimLogLogger txt_logger = {
    + N_("Plain text"), "txt",
    + NULL,
    + txt_logger_write,
    + txt_logger_finalize,
    + txt_logger_list,
    + txt_logger_read,
    + log_sizer_common
    +};
    +
    +/****************
    + * OLD LOGGER ***
    + ****************/
    +
    +/* The old logger doesn't write logs, only reads them. This is to include
    + * old logs in the log viewer transparently.
    + */
    +
    +struct old_logger_data {
    + char *path;
    + int offset;
    + int length;
    +};
    +
    +static GList *old_logger_list(const char *sn, GaimAccount *account)
    +{
    + FILE *file;
    + char buf[BUF_LONG];
    + struct tm tm;
    + struct old_logger_data *data = NULL;
    + char day[4], month[4], year[5];
    + char *logfile = g_strdup_printf("%s.log", gaim_normalize(account, sn));
    + char *date;
    + char *path = g_build_filename(gaim_user_dir(), "logs", logfile, NULL);
    + char *newlog;
    +
    + GaimLog *log = NULL;
    + GList *list = NULL;
    +
    + g_free(logfile);
    +
    + if (!(file = fopen(path, "rb"))) {
    + g_free(path);
    + return NULL;
    + }
    +
    + while (fgets(buf, BUF_LONG, file)) {
    + if ((newlog = strstr(buf, "---- New C"))) {
    + int length;
    + int offset;
    + GDate gdate;
    + char convostart[32];
    + char *temp = strchr(buf, '@');
    +
    + if (temp == NULL || strlen(temp) < 2)
    + continue;
    +
    + temp++;
    + length = strcspn(temp, "-");
    + if (length > 31) length = 31;
    +
    + offset = ftell(file);
    +
    + if (data) {
    + data->length = offset - data->offset - length;
    + if(strstr(buf, "----</H3><BR>")) {
    + data->length -=
    + strlen("<HR><BR><H3 Align=Center> ---- New Conversation @ ") +
    + strlen("----</H3><BR>");
    + } else {
    + data->length -=
    + strlen("---- New Conversation @ ") + strlen("----");
    + }
    +
    + if(strchr(buf, '\r'))
    + data->length--;
    +
    + if (data->length != 0)
    + list = g_list_append(list, log);
    + else
    + gaim_log_free(log);
    + }
    +
    + log = gaim_log_new(GAIM_LOG_IM, sn, account, -1);
    + log->logger = &old_logger;
    +
    + data = g_new0(struct old_logger_data, 1);
    + data->offset = offset;
    + data->path = g_strdup(path);
    + log->logger_data = data;
    +
    +
    + g_snprintf(convostart, length, "%s", temp);
    + sscanf(convostart, "%*s %s %s %d:%d:%d %s",
    + month, day, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, year);
    + date = g_strdup_printf("%s %s %s", month, day, year);
    + g_date_set_parse(&gdate, date);
    + tm.tm_mday = g_date_get_day(&gdate);
    + tm.tm_mon = g_date_get_month(&gdate) - 1;
    + tm.tm_year = g_date_get_year(&gdate) - 1900;
    + log->time = mktime(&tm);
    + g_free(date);
    +
    + }
    + }
    +
    + if (data) {
    + data->length = ftell(file) - data->offset;
    + if (data->length != 0)
    + list = g_list_append(list, log);
    + else
    + gaim_log_free(log);
    + }
    +
    + g_free(path);
    + fclose(file);
    + return list;
    +}
    +
    +static char * old_logger_read (GaimLog *log, GaimLogReadFlags *flags)
    +{
    + struct old_logger_data *data = log->logger_data;
    + FILE *file = fopen(data->path, "rb");
    + char *read = g_malloc(data->length + 1);
    + fseek(file, data->offset, SEEK_SET);
    + fread(read, data->length, 1, file);
    + read[data->length] = '\0';
    + *flags = 0;
    + if(strstr(read, "<BR>"))
    + *flags |= GAIM_LOG_READ_NO_NEWLINE;
    + return read;
    +}
    +
    +static int old_logger_size (GaimLog *log)
    +{
    + struct old_logger_data *data = log->logger_data;
    + return data ? data->length : 0;
    +}
    +
    +static void old_logger_finalize(GaimLog *log)
    +{
    + struct old_logger_data *data = log->logger_data;
    + g_free(data->path);
    + g_free(data);
    +}
    +
    +static GaimLogLogger old_logger = {
    + "old logger", "old",
    + NULL, NULL,
    + old_logger_finalize,
    + old_logger_list,
    + old_logger_read,
    + old_logger_size
    +};
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/src/util.c Tue Nov 25 21:53:14 2003 -0500
    @@ -0,0 +1,2153 @@
    +/*
    + * @file util.h Utility Functions
    + * @ingroup core
    + *
    + * Copyright (C) 1998-1999 Mark Spencer <markster@marko.net>
    + * 2003 Christian Hammond <chipx86@gnupdate.org>
    + * 2003 Nathan Walp <faceprint@faceprint.com>
    + *
    + * 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
    + */
    +#include "internal.h"
    +
    +#include "conversation.h"
    +#include "debug.h"
    +#include "prpl.h"
    +#include "prefs.h"
    +#include "util.h"
    +
    +typedef struct
    +{
    + void (*callback)(void *, const char *, size_t);
    + void *user_data;
    +
    + struct
    + {
    + char *address;
    + int port;
    + char *page;
    +
    + } website;
    +
    + char *url;
    + gboolean full;
    + char *user_agent;
    + gboolean http11;
    +
    + int inpa;
    +
    + gboolean sentreq;
    + gboolean newline;
    + gboolean startsaving;
    + char *webdata;
    + unsigned long len;
    + unsigned long data_len;
    +
    +} GaimFetchUrlData;
    +
    +
    +static char home_dir[MAXPATHLEN];
    +
    +
    +/**************************************************************************
    + * Base16 Functions
    + **************************************************************************/
    +unsigned char *
    +gaim_base16_encode(const unsigned char *data, int length)
    +{
    + int i;
    + unsigned char *ascii = NULL;
    +
    + g_return_val_if_fail(data != NULL, NULL);
    + g_return_val_if_fail(length > 0, NULL);
    +
    + ascii = g_malloc(length * 2 + 1);
    +
    + for (i = 0; i < length; i++)
    + snprintf(&ascii[i * 2], 3, "%02hhx", data[i]);
    +
    + return ascii;
    +}
    +
    +int
    +gaim_base16_decode(const char *ascii, unsigned char **raw)
    +{
    + int len, i, accumulator = 0;
    + unsigned char *data;
    +
    + g_return_val_if_fail(ascii != NULL, 0);
    +
    + len = strlen(ascii);
    +
    + g_return_val_if_fail(strlen(ascii) > 0, 0);
    + g_return_val_if_fail(len % 2 > 0, 0);
    +
    + data = g_malloc(len / 2);
    +
    + for (i = 0; i < len; i++)
    + {
    + if ((i % 2) == 0)
    + accumulator = 0;
    + else
    + accumulator <<= 4;
    +
    + if (isdigit(ascii[i]))
    + accumulator |= ascii[i] - 48;
    + else
    + {
    + switch(ascii[i])
    + {
    + case 'a': case 'A': accumulator |= 10; break;
    + case 'b': case 'B': accumulator |= 11; break;
    + case 'c': case 'C': accumulator |= 12; break;
    + case 'd': case 'D': accumulator |= 13; break;
    + case 'e': case 'E': accumulator |= 14; break;
    + case 'f': case 'F': accumulator |= 15; break;
    + }
    + }
    +
    + if (i % 2)
    + data[(i - 1) / 2] = accumulator;
    + }
    +
    + *raw = data;
    +
    + return (len / 2);
    +}
    +
    +/**************************************************************************
    + * Base64 Functions
    + **************************************************************************/
    +static const char alphabet[] =
    + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    + "0123456789+/";
    +
    +unsigned char *
    +gaim_base64_encode(const unsigned char *in, size_t inlen)
    +{
    + char *out, *rv;
    +
    + g_return_val_if_fail(in != NULL, NULL);
    + g_return_val_if_fail(inlen > 0, NULL);
    +
    + rv = out = g_malloc(((inlen/3)+1)*4 + 1);
    +
    + for (; inlen >= 3; inlen -= 3)
    + {
    + *out++ = alphabet[in[0] >> 2];
    + *out++ = alphabet[((in[0] << 4) & 0x30) | (in[1] >> 4)];
    + *out++ = alphabet[((in[1] << 2) & 0x3c) | (in[2] >> 6)];
    + *out++ = alphabet[in[2] & 0x3f];
    + in += 3;
    + }
    +
    + if (inlen > 0)
    + {
    + unsigned char fragment;
    +
    + *out++ = alphabet[in[0] >> 2];
    + fragment = (in[0] << 4) & 0x30;
    +
    + if (inlen > 1)
    + fragment |= in[1] >> 4;
    +
    + *out++ = alphabet[fragment];
    + *out++ = (inlen < 2) ? '=' : alphabet[(in[1] << 2) & 0x3c];
    + *out++ = '=';
    + }
    +
    + *out = '\0';
    +
    + return rv;
    +}
    +
    +void
    +gaim_base64_decode(const char *text, char **data, int *size)
    +{
    + char *out = NULL;
    + char tmp = 0;
    + const char *c;
    + gint32 tmp2 = 0;
    + int len = 0, n = 0;
    +
    + g_return_if_fail(text != NULL);
    + g_return_if_fail(data != NULL);
    +
    + c = text;
    +
    + while (*c) {
    + if (*c >= 'A' && *c <= 'Z') {
    + tmp = *c - 'A';
    + } else if (*c >= 'a' && *c <= 'z') {
    + tmp = 26 + (*c - 'a');
    + } else if (*c >= '0' && *c <= 57) {
    + tmp = 52 + (*c - '0');
    + } else if (*c == '+') {
    + tmp = 62;
    + } else if (*c == '/') {
    + tmp = 63;
    + } else if (*c == '\r' || *c == '\n') {
    + c++;
    + continue;
    + } else if (*c == '=') {
    + if (n == 3) {
    + out = g_realloc(out, len + 2);
    + out[len] = (char)(tmp2 >> 10) & 0xff;
    + len++;
    + out[len] = (char)(tmp2 >> 2) & 0xff;
    + len++;
    + } else if (n == 2) {
    + out = g_realloc(out, len + 1);
    + out[len] = (char)(tmp2 >> 4) & 0xff;
    + len++;
    + }
    + break;
    + }
    + tmp2 = ((tmp2 << 6) | (tmp & 0xff));
    + n++;
    + if (n == 4) {
    + out = g_realloc(out, len + 3);
    + out[len] = (char)((tmp2 >> 16) & 0xff);
    + len++;
    + out[len] = (char)((tmp2 >> 8) & 0xff);
    + len++;
    + out[len] = (char)(tmp2 & 0xff);
    + len++;
    + tmp2 = 0;
    + n = 0;
    + }
    + c++;
    + }
    +
    + out = g_realloc(out, len + 1);
    + out[len] = 0;
    +
    + *data = out;
    +
    + if (size)
    + *size = len;
    +}
    +
    +
    +/**************************************************************************
    + * Date/Time Functions
    + **************************************************************************/
    +const char *
    +gaim_date(void)
    +{
    + static char date[80];
    + time_t tme;
    +
    + time(&tme);
    + strftime(date, sizeof(date), "%H:%M:%S", localtime(&tme));
    +
    + return date;
    +}
    +
    +const char *
    +gaim_date_full(void)
    +{
    + char *date;
    + time_t tme;
    +
    + time(&tme);
    + date = ctime(&tme);
    + date[strlen(date) - 1] = '\0';
    +
    + return date;
    +}
    +
    +time_t
    +gaim_time_build(int year, int month, int day, int hour, int min, int sec)
    +{
    + struct tm tm;
    +
    + tm.tm_year = year - 1900;
    + tm.tm_mon = month - 1;
    + tm.tm_mday = day;
    + tm.tm_hour = hour;
    + tm.tm_min = min;
    + tm.tm_sec = sec >= 0 ? sec : time(NULL) % 60;
    +
    + return mktime(&tm);
    +}
    +
    +
    +/**************************************************************************
    + * Markup Functions
    + **************************************************************************/
    +gboolean
    +gaim_markup_find_tag(const char *needle, const char *haystack,
    + const char **start, const char **end, GData **attributes)
    +{
    + GData *attribs;
    + const char *cur = haystack;
    + char *name = NULL;
    + gboolean found = FALSE;
    + gboolean in_tag = FALSE;
    + gboolean in_attr = FALSE;
    + const char *in_quotes = NULL;
    + size_t needlelen = strlen(needle);
    +
    + g_datalist_init(&attribs);
    +
    + while (*cur && !found) {
    + if (in_tag) {
    + if (in_quotes) {
    + const char *close = cur;
    +
    + while (*close && *close != *in_quotes)
    + close++;
    +
    + /* if we got the close quote, store the value and carry on from *
    + * after it. if we ran to the end of the string, point to the NULL *
    + * and we're outta here */
    + if (*close) {
    + /* only store a value if we have an attribute name */
    + if (name) {
    + size_t len = close - cur;
    + char *val = g_strndup(cur, len);
    +
    + g_datalist_set_data_full(&attribs, name, val, g_free);
    + g_free(name);
    + name = NULL;
    + }
    +
    + in_quotes = NULL;
    + cur = close + 1;
    + } else {
    + cur = close;
    + }
    + } else if (in_attr) {
    + const char *close = cur;
    +
    + while (*close && *close != '>' && *close != '"' &&
    + *close != '\'' && *close != ' ' && *close != '=')
    + close++;
    +
    + /* if we got the equals, store the name of the attribute. if we got
    + * the quote, save the attribute and go straight to quote mode.
    + * otherwise the tag closed or we reached the end of the string,
    + * so we can get outta here */
    + switch (*close) {
    + case '"':
    + case '\'':
    + in_quotes = close;
    + case '=':
    + {
    + size_t len = close - cur;
    +
    + /* don't store a blank attribute name */
    + if (len) {
    + if (name)
    + g_free(name);
    + name = g_ascii_strdown(cur, len);
    + }
    +
    + in_attr = FALSE;
    + cur = close + 1;
    + break;
    + }
    + case ' ':
    + case '>':
    + in_attr = FALSE;
    + default:
    + cur = close;
    + break;
    + }
    + } else {
    + switch (*cur) {
    + case ' ':
    + /* swallow extra spaces inside tag */
    + while (*cur && *cur == ' ') cur++;
    + in_attr = TRUE;
    + break;
    + case '>':
    + found = TRUE;
    + *end = cur;
    + break;
    + case '"':
    + case '\'':
    + in_quotes = cur;
    + default:
    + cur++;
    + break;
    + }
    + }
    + } else {
    + /* if we hit a < followed by the name of our tag... */
    + if (*cur == '<' && !g_ascii_strncasecmp(cur + 1, needle, needlelen)) {
    + *start = cur;
    + cur = cur + needlelen + 1;
    +
    + /* if we're pointing at a space or a >, we found the right tag. if *
    + * we're not, we've found a longer tag, so we need to skip to the *
    + * >, but not being distracted by >s inside quotes. */
    + if (*cur == ' ' || *cur == '>') {
    + in_tag = TRUE;
    + } else {
    + while (*cur && *cur != '"' && *cur != '>') {
    + if (*cur == '"') {
    + cur++;
    + while (*cur && *cur != '"')
    + cur++;
    + } else {
    + cur++;
    + }
    + }
    + }
    + } else {
    + cur++;
    + }
    + }
    + }
    +
    + /* clean up any attribute name from a premature termination */
    + if (name)
    + g_free(name);
    +
    + if (found) {
    + *attributes = attribs;
    + } else {
    + *start = NULL;
    + *end = NULL;
    + *attributes = NULL;
    + }
    +
    + return found;
    +}
    +
    +gboolean
    +gaim_markup_extract_info_field(const char *str, char *dest_buffer,
    + const char *start_token, int skip,
    + const char *end_token, char check_value,
    + const char *no_value_token,
    + const char *display_name, gboolean is_link,
    + const char *link_prefix)
    +{
    + const char *p, *q;
    + char buf[1024];
    +
    + g_return_val_if_fail(str != NULL, FALSE);
    + g_return_val_if_fail(dest_buffer != NULL, FALSE);
    + g_return_val_if_fail(start_token != NULL, FALSE);
    + g_return_val_if_fail(end_token != NULL, FALSE);
    + g_return_val_if_fail(display_name != NULL, FALSE);
    +
    + p = strstr(str, start_token);
    +
    + if (p == NULL)
    + return FALSE;
    +
    + p += strlen(start_token) + skip;
    +
    + if (check_value != '\0' && *p == check_value)
    + return FALSE;
    +
    + q = strstr(p, end_token);
    +
    + if (q != NULL && (!no_value_token ||
    + (no_value_token && strncmp(p, no_value_token,
    + strlen(no_value_token)))))
    + {
    + strcat(dest_buffer, "<b>");
    + strcat(dest_buffer, display_name);
    + strcat(dest_buffer, ":</b> ");
    +
    + if (is_link)
    + {
    + strcat(dest_buffer, "<br><a href=\"");
    + memcpy(buf, p, q - p);
    + buf[q - p] = '\0';
    +
    + if (link_prefix)
    + strcat(dest_buffer, link_prefix);
    +
    + strcat(dest_buffer, buf);
    + strcat(dest_buffer, "\">");
    +
    + if (link_prefix)
    + strcat(dest_buffer, link_prefix);
    +
    + strcat(dest_buffer, buf);
    + strcat(dest_buffer, "</a>");
    + }
    + else
    + {
    + memcpy(buf, p, q - p);
    + buf[q - p] = '\0';
    + strcat(dest_buffer, buf);
    + }
    +
    + strcat(dest_buffer, "<br>\n");
    +
    + return TRUE;
    + }
    +
    + return FALSE;
    +}
    +
    +struct gaim_parse_tag {
    + char *src_tag;
    + char *dest_tag;
    +};
    +
    +#define ALLOW_TAG_ALT(x, y) if(!g_ascii_strncasecmp(c, "<" x " ", strlen("<" x " "))) { \
    + const char *o = c + strlen("<" x); \
    + const char *p = NULL, *q = NULL, *r = NULL; \
    + GString *innards = g_string_new(""); \
    + while(o && *o) { \
    + if(!q && (*o == '\"' || *o == '\'') ) { \
    + q = o; \
    + } else if(q) { \
    + if(*o == *q) { \
    + char *unescaped = g_strndup(q+1, o-q-1); \
    + char *escaped = g_markup_escape_text(unescaped, -1); \
    + g_string_append_printf(innards, "%c%s%c", *q, escaped, *q); \
    + g_free(unescaped); \
    + g_free(escaped); \
    + q = NULL; \
    + } else if(*c == '\\') { \
    + o++; \
    + } \
    + } else if(*o == '<') { \
    + r = o; \
    + } else if(*o == '>') { \
    + p = o; \
    + break; \
    + } else { \
    + innards = g_string_append_c(innards, *o); \
    + } \
    + o++; \
    + } \
    + if(p && !r) { \
    + if(*(p-1) != '/') { \
    + struct gaim_parse_tag *pt = g_new0(struct gaim_parse_tag, 1); \
    + pt->src_tag = x; \
    + pt->dest_tag = y; \
    + tags = g_list_prepend(tags, pt); \
    + } \
    + xhtml = g_string_append(xhtml, "<" y); \
    + c += strlen("<" x ); \
    + xhtml = g_string_append(xhtml, innards->str); \
    + xhtml = g_string_append_c(xhtml, '>'); \
    + c = p + 1; \
    + } else { \
    + xhtml = g_string_append(xhtml, "&lt;"); \
    + plain = g_string_append_c(plain, '<'); \
    + c++; \
    + } \
    + g_string_free(innards, TRUE); \
    + continue; \
    + } \
    + if(!g_ascii_strncasecmp(c, "<" x, strlen("<" x)) && \
    + (*(c+strlen("<" x)) == '>' || \
    + !g_ascii_strncasecmp(c+strlen("<" x), "/>", 2))) { \
    + xhtml = g_string_append(xhtml, "<" y); \
    + c += strlen("<" x); \
    + if(*c != '/') { \
    + struct gaim_parse_tag *pt = g_new0(struct gaim_parse_tag, 1); \
    + pt->src_tag = x; \
    + pt->dest_tag = y; \
    + tags = g_list_prepend(tags, pt); \
    + xhtml = g_string_append_c(xhtml, '>'); \
    + } else { \
    + xhtml = g_string_append(xhtml, "/>");\
    + } \
    + c = strchr(c, '>') + 1; \
    + continue; \
    + }
    +#define ALLOW_TAG(x) ALLOW_TAG_ALT(x, x)
    +void
    +gaim_markup_html_to_xhtml(const char *html, char **xhtml_out,
    + char **plain_out)
    +{
    + GString *xhtml = g_string_new("");
    + GString *plain = g_string_new("");
    + GList *tags = NULL, *tag;
    + const char *c = html;
    +
    + while(c && *c) {
    + if(*c == '<') {
    + if(*(c+1) == '/') { /* closing tag */
    + tag = tags;
    + while(tag) {
    + struct gaim_parse_tag *pt = tag->data;
    + if(!g_ascii_strncasecmp((c+2), pt->src_tag, strlen(pt->src_tag)) && *(c+strlen(pt->src_tag)+2) == '>') {
    + c += strlen(pt->src_tag) + 3;
    + break;
    + }
    + tag = tag->next;
    + }
    + if(tag) {
    + while(tags) {
    + struct gaim_parse_tag *pt = tags->data;
    + g_string_append_printf(xhtml, "</%s>", pt->dest_tag);
    + if(tags == tag)
    + break;
    + tags = g_list_remove(tags, pt);
    + g_free(pt);
    + }
    + g_free(tag->data);
    + tags = g_list_remove(tags, tag->data);
    + } else {
    + /* we tried to close a tag we never opened! escape it
    + * and move on */
    + xhtml = g_string_append(xhtml, "&lt;");
    + plain = g_string_append_c(plain, '<');
    + c++;
    + }
    + } else { /* opening tag */
    + ALLOW_TAG("a");
    + ALLOW_TAG_ALT("b", "strong");
    + ALLOW_TAG("blockquote");
    + ALLOW_TAG_ALT("bold", "strong");
    + ALLOW_TAG("cite");
    + ALLOW_TAG("div");
    + ALLOW_TAG("em");
    + ALLOW_TAG("h1");
    + ALLOW_TAG("h2");
    + ALLOW_TAG("h3");
    + ALLOW_TAG("h4");
    + ALLOW_TAG("h5");
    + ALLOW_TAG("h6");
    + /* we only allow html to start the message */
    + if(c == html)
    + ALLOW_TAG("html");
    + ALLOW_TAG_ALT("i", "em");
    + ALLOW_TAG_ALT("italic", "em");
    + ALLOW_TAG("li");
    + ALLOW_TAG("ol");
    + ALLOW_TAG("p");
    + ALLOW_TAG("pre");
    + ALLOW_TAG("q");
    + ALLOW_TAG("span");
    + ALLOW_TAG("strong");
    + ALLOW_TAG("ul");
    +
    + /* we skip <HR> because it's not legal in XHTML-IM. However,
    + * we still want to send something sensible, so we put a
    + * linebreak in its place. <BR> also needs special handling
    + * because putting a </BR> to close it would just be dumb. */
    + if((!g_ascii_strncasecmp(c, "<br", 3)
    + || !g_ascii_strncasecmp(c, "<hr", 3))
    + && (*(c+3) == '>' ||
    + !g_ascii_strncasecmp(c+3, "/>", 2) ||
    + !g_ascii_strncasecmp(c+3, " />", 3))) {
    + c = strchr(c, '>') + 1;
    + xhtml = g_string_append(xhtml, "<br/>");
    + if(*c != '\n')
    + plain = g_string_append_c(plain, '\n');
    + continue;
    + }
    + if(!g_ascii_strncasecmp(c, "<u>", 3) || !g_ascii_strncasecmp(c, "<underline>", strlen("<underline>"))) {
    + struct gaim_parse_tag *pt = g_new0(struct gaim_parse_tag, 1);
    + pt->src_tag = *(c+2) == '>' ? "u" : "underline";
    + pt->dest_tag = "span";
    + tags = g_list_prepend(tags, pt);
    + c = strchr(c, '>') + 1;
    + xhtml = g_string_append(xhtml, "<span style='text-decoration: underline;'>");
    + continue;
    + }
    + if(!g_ascii_strncasecmp(c, "<s>", 3) || !g_ascii_strncasecmp(c, "<strike>", strlen("<strike>"))) {
    + struct gaim_parse_tag *pt = g_new0(struct gaim_parse_tag, 1);
    + pt->src_tag = *(c+2) == '>' ? "s" : "strike";
    + pt->dest_tag = "span";
    + tags = g_list_prepend(tags, pt);
    + c = strchr(c, '>') + 1;
    + xhtml = g_string_append(xhtml, "<span style='text-decoration: line-through;'>");
    + continue;
    + }
    + if(!g_ascii_strncasecmp(c, "<sub>", 5)) {
    + struct gaim_parse_tag *pt = g_new0(struct gaim_parse_tag, 1);
    + pt->src_tag = "sub";
    + pt->dest_tag = "span";
    + tags = g_list_prepend(tags, pt);
    + c = strchr(c, '>') + 1;
    + xhtml = g_string_append(xhtml, "<span style='vertical-align:sub;'>");
    + continue;
    + }
    + if(!g_ascii_strncasecmp(c, "<sup>", 5)) {
    + struct gaim_parse_tag *pt = g_new0(struct gaim_parse_tag, 1);
    + pt->src_tag = "sup";
    + pt->dest_tag = "span";
    + tags = g_list_prepend(tags, pt);
    + c = strchr(c, '>') + 1;
    + xhtml = g_string_append(xhtml, "<span style='vertical-align:super;'>");
    + continue;
    + }
    + if(!g_ascii_strncasecmp(c, "<font", 5) && (*(c+5) == '>' || *(c+5) == ' ')) {
    + const char *p = c;
    + GString *style = g_string_new("");
    + struct gaim_parse_tag *pt;
    + while(*p && *p != '>') {
    + if(!g_ascii_strncasecmp(p, "color=", strlen("color="))) {
    + const char *q = p + strlen("color=");
    + GString *color = g_string_new("");
    + if(*q == '\'' || *q == '\"')
    + q++;
    + while(*q && *q != '\"' && *q != '\'' && *q != ' ') {
    + color = g_string_append_c(color, *q);
    + q++;
    + }
    + g_string_append_printf(style, "color: %s; ", color->str);
    + g_string_free(color, TRUE);
    + p = q;
    + } else if(!g_ascii_strncasecmp(p, "face=", strlen("face="))) {
    + const char *q = p + strlen("face=");
    + gboolean space_allowed = FALSE;
    + GString *face = g_string_new("");
    + if(*q == '\'' || *q == '\"') {
    + space_allowed = TRUE;
    + q++;
    + }
    + while(*q && *q != '\"' && *q != '\'' && (space_allowed || *q != ' ')) {
    + face = g_string_append_c(face, *q);
    + q++;
    + }
    + g_string_append_printf(style, "font-family: %s; ", face->str);
    + g_string_free(face, TRUE);
    + p = q;
    + } else if(!g_ascii_strncasecmp(p, "size=", strlen("size="))) {
    + const char *q = p + strlen("size=");
    + int sz;
    + const char *size = "medium";
    + if(*q == '\'' || *q == '\"')
    + q++;
    + sz = atoi(q);
    + if(sz < 3)
    + size = "smaller";
    + else if(sz > 3)
    + size = "larger";
    + g_string_append_printf(style, "font-size: %s; ", size);
    + p = q;
    + }
    + p++;
    + }
    + c = strchr(c, '>') + 1;
    + pt = g_new0(struct gaim_parse_tag, 1);
    + pt->src_tag = "font";
    + pt->dest_tag = "span";
    + tags = g_list_prepend(tags, pt);
    + xhtml = g_string_append(xhtml, "<span");
    + if(style->len)
    + g_string_append_printf(xhtml, " style='%s'", style->str);
    + xhtml = g_string_append_c(xhtml, '>');
    + g_string_free(style, TRUE);
    + continue;
    + }
    + if(!g_ascii_strncasecmp(c, "<body ", 6)) {
    + const char *p = c;
    + gboolean did_something = FALSE;
    + while(*p && *p != '>') {
    + if(!g_ascii_strncasecmp(p, "bgcolor=", strlen("bgcolor="))) {
    + const char *q = p + strlen("bgcolor=");
    + struct gaim_parse_tag *pt = g_new0(struct gaim_parse_tag, 1);
    + GString *color = g_string_new("");
    + if(*q == '\'' || *q == '\"')
    + q++;
    + while(*q && *q != '\"' && *q != '\'' && *q != ' ') {
    + color = g_string_append_c(color, *q);
    + q++;
    + }
    + g_string_append_printf(xhtml, "<span style='background: %s;'>", color->str);
    + g_string_free(color, TRUE);
    + c = strchr(c, '>') + 1;
    + pt->src_tag = "body";
    + pt->dest_tag = "span";
    + tags = g_list_prepend(tags, pt);
    + did_something = TRUE;
    + break;
    + }
    + p++;
    + }
    + if(did_something) continue;
    + }
    + /* this has to come after the special case for bgcolor */
    + ALLOW_TAG("body");
    + if(!g_ascii_strncasecmp(c, "<!--", strlen("<!--"))) {
    + char *p = strstr(c + strlen("<!--"), "-->");
    + if(p) {
    + xhtml = g_string_append(xhtml, "<!--");
    + c += strlen("<!--");
    + continue;
    + }
    + }
    +
    + xhtml = g_string_append(xhtml, "&lt;");
    + plain = g_string_append_c(plain, '<');
    + c++;
    + }
    + } else if(*c == '&') {
    + char buf[7];
    + char *pln;
    + int len = 1;
    + guint pound;
    + if(!g_ascii_strncasecmp(c, "&amp;", 5)) {
    + pln = "&";
    + len = 5;
    + } else if(!g_ascii_strncasecmp(c, "&lt;", 4)) {
    + pln = "<";
    + len = 4;
    + } else if(!g_ascii_strncasecmp(c, "&gt;", 4)) {
    + pln = ">";
    + len = 4;
    + } else if(!g_ascii_strncasecmp(c, "&nbsp;", 6)) {
    + pln = " ";
    + len = 6;
    + } else if(!g_ascii_strncasecmp(c, "&copy;", 6)) {
    + pln = "©";
    + len = 6;
    + } else if(!g_ascii_strncasecmp(c, "&quot;", 6)) {
    + pln = "\"";
    + len = 6;
    + } else if(!g_ascii_strncasecmp(c, "&reg;", 5)) {
    + pln = "®";
    + len = 5;
    + } else if(!g_ascii_strncasecmp(c, "&apos;", 6)) {
    + pln = "\'";
    + len = 6;
    + } else if(*(c+1) == '#' && (sscanf(c, "&#%u;", &pound) == 1) &&
    + pound != 0 && *(c+3+(gint)log10(pound)) == ';') {
    + int buflen = g_unichar_to_utf8((gunichar)pound, buf);
    + buf[buflen] = '\0';
    + pln = buf;
    +
    +
    + len = 2;
    + while(isdigit((gint) c [len])) len++;
    + if(c [len] == ';') len++;
    + } else {
    + len = 1;
    + g_snprintf(buf, sizeof(buf), "%c", *c);
    + pln = buf;
    + }
    + xhtml = g_string_append_len(xhtml, c, len);
    + plain = g_string_append(plain, pln);
    + c += len;
    + } else {
    + xhtml = g_string_append_c(xhtml, *c);
    + plain = g_string_append_c(plain, *c);
    + c++;
    + }
    + }
    + tag = tags;
    + while(tag) {
    + g_string_append_printf(xhtml, "</%s>", (char *)tag->data);
    + tag = tag->next;
    + }
    + g_list_free(tags);
    + if(xhtml_out)
    + *xhtml_out = g_strdup(xhtml->str);
    + if(plain_out)
    + *plain_out = g_strdup(plain->str);
    + g_string_free(xhtml, TRUE);
    + g_string_free(plain, TRUE);
    +}
    +
    +char *
    +gaim_markup_strip_html(const char *str)
    +{
    + int i, j, k;
    + gboolean visible = TRUE;
    + gchar *str2;
    +
    + if(!str)
    + return NULL;
    +
    + str2 = g_strdup(str);
    +
    + for (i = 0, j = 0; str2[i]; i++)
    + {
    + if (str2[i] == '<')
    + {
    + k = i + 1;
    +
    + if(g_ascii_isspace(str2[k]))
    + visible = TRUE;
    + else
    + {
    + while (str2[k])
    + {
    + if (str2[k] == '<')
    + {
    + visible = TRUE;
    + break;
    + }
    +
    + if (str2[k] == '>')
    + {
    + visible = FALSE;
    + break;
    + }
    +
    + k++;
    + }
    + }
    + }
    + else if (str2[i] == '>' && !visible)
    + {
    + visible = TRUE;
    + continue;
    + }
    +
    + if (str2[i] == '&' && strncasecmp(str2 + i, "&quot;", 6) == 0)
    + {
    + str2[j++] = '\"';
    + i = i + 5;
    + continue;
    + }
    +
    + if (visible)
    + str2[j++] = str2[i];
    + }
    +
    + str2[j] = '\0';
    +
    + return str2;
    +}
    +
    +static gint
    +badchar(char c)
    +{
    + switch (c) {
    + case ' ':
    + case ',':
    + case '(':
    + case ')':
    + case '\0':
    + case '\n':
    + case '<':
    + case '>':
    + case '"':
    + case '\'':
    + return 1;
    + default:
    + return 0;
    + }
    +}
    +
    +char *
    +gaim_markup_linkify(const char *text)
    +{
    + const char *c, *t, *q = NULL;
    + char *tmp;
    + char url_buf[BUF_LEN * 4];
    + GString *ret = g_string_new("");
    + /* Assumes you have a buffer able to cary at least BUF_LEN * 2 bytes */
    +
    + c = text;
    + while (*c) {
    + if(!q && (*c == '\"' || *c == '\'')) {
    + q = c;
    + } else if(q) {
    + if(*c == *q)
    + q = NULL;
    + } else if (!g_ascii_strncasecmp(c, "<A", 2)) {
    + while (1) {
    + if (!g_ascii_strncasecmp(c, "/A>", 3)) {
    + break;
    + }
    + ret = g_string_append_c(ret, *c);
    + c++;
    + if (!(*c))
    + break;
    + }
    + } else if ((*c=='h') && (!g_ascii_strncasecmp(c, "http://", 7) ||
    + (!g_ascii_strncasecmp(c, "https://", 8)))) {
    + t = c;
    + while (1) {
    + if (badchar(*t)) {
    +
    + if (*(t) == ',' && (*(t + 1) != ' ')) {
    + t++;
    + continue;
    + }
    +
    + if (*(t - 1) == '.')
    + t--;
    + strncpy(url_buf, c, t - c);
    + url_buf[t - c] = 0;
    + g_string_append_printf(ret, "<A HREF=\"%s\">%s</A>",
    + url_buf, url_buf);
    + c = t;
    + break;
    + }
    + if (!t)
    + break;
    + t++;
    +
    + }
    + } else if (!g_ascii_strncasecmp(c, "www.", 4)) {
    + if (c[4] != '.') {
    + t = c;
    + while (1) {
    + if (badchar(*t)) {
    + if (t - c == 4) {
    + break;
    + }
    +
    + if (*(t) == ',' && (*(t + 1) != ' ')) {
    + t++;
    + continue;
    + }
    +
    + if (*(t - 1) == '.')
    + t--;
    + strncpy(url_buf, c, t - c);
    + url_buf[t - c] = 0;
    + g_string_append_printf(ret,
    + "<A HREF=\"http://%s\">%s</A>", url_buf,
    + url_buf);
    + c = t;
    + break;
    + }
    + if (!t)
    + break;
    + t++;
    + }
    + }
    + } else if (!g_ascii_strncasecmp(c, "ftp://", 6)) {
    + t = c;
    + while (1) {
    + if (badchar(*t)) {
    + if (*(t - 1) == '.')
    + t--;
    + strncpy(url_buf, c, t - c);
    + url_buf[t - c] = 0;
    + g_string_append_printf(ret, "<A HREF=\"%s\">%s</A>",
    + url_buf, url_buf);
    + c = t;
    + break;
    + }
    + if (!t)
    + break;
    + t++;
    +
    + }
    + } else if (!g_ascii_strncasecmp(c, "ftp.", 4)) {
    + if (c[4] != '.') {
    + t = c;
    + while (1) {
    + if (badchar(*t)) {
    + if (t - c == 4) {
    + break;
    + }
    + if (*(t - 1) == '.')
    + t--;
    + strncpy(url_buf, c, t - c);
    + url_buf[t - c] = 0;
    + g_string_append_printf(ret,
    + "<A HREF=\"ftp://%s\">%s</A>", url_buf,
    + url_buf);
    + c = t;
    + break;
    + }
    + if (!t)
    + break;
    + t++;
    + }
    + }
    + } else if (!g_ascii_strncasecmp(c, "mailto:", 7)) {
    + t = c;
    + while (1) {
    + if (badchar(*t)) {
    + if (*(t - 1) == '.')
    + t--;
    + strncpy(url_buf, c, t - c);
    + url_buf[t - c] = 0;
    + g_string_append_printf(ret, "<A HREF=\"%s\">%s</A>",
    + url_buf, url_buf);
    + c = t;
    + break;
    + }
    + if (!t)
    + break;
    + t++;
    +
    + }
    + } else if (c != text && (*c == '@')) {
    + char *tmp;
    + int flag;
    + int len = 0;
    + const char illegal_chars[] = "!@#$%^&*()[]{}/|\\<>\":;\r\n \0";
    + url_buf[0] = 0;
    +
    + if (strchr(illegal_chars,*(c - 1)) || strchr(illegal_chars, *(c + 1)))
    + flag = 0;
    + else
    + flag = 1;
    +
    + t = c;
    + while (flag) {
    + if (badchar(*t)) {
    + ret = g_string_truncate(ret, ret->len - (len - 1));
    + break;
    + } else {
    + len++;
    + tmp = g_malloc(len + 1);
    + tmp[len] = 0;
    + tmp[0] = *t;
    + strncpy(tmp + 1, url_buf, len - 1);
    + strcpy(url_buf, tmp);
    + url_buf[len] = 0;
    + g_free(tmp);
    + t--;
    + if (t < text) {
    + ret = g_string_assign(ret, "");
    + break;
    + }
    + }
    + }
    +
    + t = c + 1;
    +
    + while (flag) {
    + if (badchar(*t)) {
    + char *d;
    +
    + for (d = url_buf + strlen(url_buf) - 1; *d == '.'; d--, t--)
    + *d = '\0';
    +
    + g_string_append_printf(ret, "<A HREF=\"mailto:%s\">%s</A>",
    + url_buf, url_buf);
    + c = t;
    +
    + break;
    + } else {
    + strncat(url_buf, t, 1);
    + len++;
    + url_buf[len] = 0;
    + }
    +
    + t++;
    + }
    + }
    +
    + if (*c == 0)
    + break;
    +
    + ret = g_string_append_c(ret, *c);
    + c++;
    +
    + }
    + tmp = ret->str;
    + g_string_free(ret, FALSE);
    + return tmp;
    +}
    +
    +
    +/**************************************************************************
    + * Path/Filename Functions
    + **************************************************************************/
    +const char *
    +gaim_home_dir(void)
    +{
    +#ifndef _WIN32
    + if(g_get_home_dir())
    + return g_get_home_dir();
    + else
    + return NULL;
    +#else
    + return wgaim_data_dir();
    +#endif
    +}
    +
    +/* returns a string of the form ~/.gaim, where ~ is replaced by the user's home
    + * dir. Note that there is no trailing slash after .gaim. */
    +char *
    +gaim_user_dir(void)
    +{
    + const gchar *hd = gaim_home_dir();
    +
    + if(hd)
    + {
    + strcpy( (char*)&home_dir, hd );
    + strcat( (char*)&home_dir, G_DIR_SEPARATOR_S ".gaim" );
    +
    + return (gchar*)&home_dir;
    + }
    +
    + return NULL;
    +}
    +
    +/*
    + * Like mkstemp() but returns a file pointer, uses a pre-set template,
    + * uses the semantics of tempnam() for the directory to use and allocates
    + * the space for the filepath.
    + *
    + * Caller is responsible for closing the file and removing it when done,
    + * as well as freeing the space pointed-to by "path" with g_free().
    + *
    + * Returns NULL on failure and cleans up after itself if so.
    + */
    +static const char *gaim_mkstemp_templ = {"gaimXXXXXX"};
    +
    +FILE *
    +gaim_mkstemp(char **fpath)
    +{
    + const gchar *tmpdir;
    +#ifndef _WIN32
    + int fd;
    +#endif
    + FILE *fp = NULL;
    +
    + g_return_val_if_fail(fpath != NULL, NULL);
    +
    + if((tmpdir = (gchar*)g_get_tmp_dir()) != NULL) {
    + if((*fpath = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", tmpdir, gaim_mkstemp_templ)) != NULL) {
    +#ifdef _WIN32
    + char* result = _mktemp( *fpath );
    + if( result == NULL )
    + gaim_debug(GAIM_DEBUG_ERROR, "gaim_mkstemp",
    + "Problem creating the template\n");
    + else
    + {
    + if( (fp = fopen( result, "w+" )) == NULL ) {
    + gaim_debug(GAIM_DEBUG_ERROR, "gaim_mkstemp",
    + "Couldn't fopen() %s\n", result);
    + }
    + }
    +#else
    + if((fd = mkstemp(*fpath)) == -1) {
    + gaim_debug(GAIM_DEBUG_ERROR, "gaim_mkstemp",
    + "Couldn't make \"%s\", error: %d\n",
    + *fpath, errno);
    + } else {
    + if((fp = fdopen(fd, "r+")) == NULL) {
    + close(fd);
    + gaim_debug(GAIM_DEBUG_ERROR, "gaim_mkstemp",
    + "Couldn't fdopen(), error: %d\n", errno);
    + }
    + }
    +#endif
    + if(!fp) {
    + g_free(*fpath);
    + *fpath = NULL;
    + }
    + }
    + } else {
    + gaim_debug(GAIM_DEBUG_ERROR, "gaim_mkstemp",
    + "g_get_tmp_dir() failed!");
    + }
    +
    + return fp;
    +}
    +
    +gboolean
    +gaim_program_is_valid(const char *program)
    +{
    + GError *error = NULL;
    + char **argv;
    + gchar *progname;
    + gboolean is_valid = FALSE;
    +
    + g_return_val_if_fail(program != NULL, FALSE);
    + g_return_val_if_fail(*program != '\0', FALSE);
    +
    + if (!g_shell_parse_argv(program, NULL, &argv, &error)) {
    + gaim_debug(GAIM_DEBUG_ERROR, "program_is_valid",
    + "Could not parse program '%s': %s\n",
    + program, error->message);
    + g_error_free(error);
    + return FALSE;
    + }
    +
    + if (argv == NULL) {
    + return FALSE;
    + }
    +
    + progname = g_find_program_in_path(argv[0]);
    + is_valid = (progname != NULL);
    +
    + g_strfreev(argv);
    + g_free(progname);
    +
    + return is_valid;
    +}
    +
    +char *
    +gaim_fd_get_ip(int fd)
    +{
    + struct sockaddr addr;
    + socklen_t namelen = sizeof(addr);
    +
    + g_return_val_if_fail(fd != 0, NULL);
    +
    + if (getsockname(fd, &addr, &namelen))
    + return NULL;
    +
    + return g_strdup(inet_ntoa(((struct sockaddr_in *)&addr)->sin_addr));
    +}
    +
    +
    +/**************************************************************************
    + * String Functions
    + **************************************************************************/
    +const char *
    +gaim_normalize(const GaimAccount *account, const char *s)
    +{
    + GaimPlugin *prpl = NULL;
    + GaimPluginProtocolInfo *prpl_info = NULL;
    + const char *ret = NULL;
    +
    + if(account)
    + prpl = gaim_find_prpl(gaim_account_get_protocol(account));
    +
    + if(prpl)
    + prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl);
    +
    + if(prpl_info && prpl_info->normalize)
    + ret = prpl_info->normalize(account, s);
    +
    + if(!ret) {
    + static char buf[BUF_LEN];
    + char *tmp;
    + int i, j;
    +
    + g_return_val_if_fail(s != NULL, NULL);
    +
    + strncpy(buf, s, BUF_LEN);
    + for (i=0, j=0; buf[j]; i++, j++) {
    + while (buf[j] == ' ')
    + j++;
    + buf[i] = buf[j];
    + }
    + buf[i] = '\0';
    +
    + tmp = g_utf8_strdown(buf, -1);
    + g_snprintf(buf, sizeof(buf), "%s", tmp);
    + g_free(tmp);
    + tmp = g_utf8_normalize(buf, -1, G_NORMALIZE_DEFAULT);
    + g_snprintf(buf, sizeof(buf), "%s", tmp);
    + g_free(tmp);
    +
    + ret = buf;
    + }
    + return ret;
    +}
    +
    +/* Look for %n, %d, or %t in msg, and replace with the sender's name, date,
    + or time */
    +const char *
    +gaim_str_sub_away_formatters(const char *msg, const char *name)
    +{
    + char *c;
    + static char cpy[BUF_LONG];
    + int cnt = 0;
    + time_t t;
    + struct tm *tme;
    + char tmp[20];
    +
    + g_return_val_if_fail(msg != NULL, NULL);
    + g_return_val_if_fail(name != NULL, NULL);
    +
    + t = time(NULL);
    + tme = localtime(&t);
    +
    + cpy[0] = '\0';
    + c = (char *)msg;
    + while (*c) {
    + switch (*c) {
    + case '%':
    + if (*(c + 1)) {
    + switch (*(c + 1)) {
    + case 'n':
    + /* append name */
    + strcpy(cpy + cnt, name);
    + cnt += strlen(name);
    + c++;
    + break;
    + case 'd':
    + /* append date */
    + strftime(tmp, 20, "%m/%d/%Y", tme);
    + strcpy(cpy + cnt, tmp);
    + cnt += strlen(tmp);
    + c++;
    + break;
    + case 't':
    + /* append time */
    + strftime(tmp, 20, "%I:%M:%S %p", tme);
    + strcpy(cpy + cnt, tmp);
    + cnt += strlen(tmp);
    + c++;
    + break;
    + default:
    + cpy[cnt++] = *c;
    + }
    + }
    + break;
    + default:
    + cpy[cnt++] = *c;
    + }
    + c++;
    + }
    + cpy[cnt] = '\0';
    + return (cpy);
    +}
    +
    +/*
    + * rcg10312000 This could be more robust, but it works for my current
    + * goal: to remove those annoying <BR> tags. :)
    + * dtf12162000 made the loop more readable. i am a neat freak. ;) */
    +void
    +gaim_strncpy_nohtml(char *dest, const char *src, size_t destsize)
    +{
    + char *ptr;
    +
    + g_return_if_fail(dest != NULL);
    + g_return_if_fail(src != NULL);
    + g_return_if_fail(destsize > 0);
    +
    + g_snprintf(dest, destsize, "%s", src);
    +
    + while ((ptr = strstr(dest, "<BR>")) != NULL) {
    + /* replace <BR> with a newline. */
    + *ptr = '\n';
    + memmove(ptr + 1, ptr + 4, strlen(ptr + 4) + 1);
    + }
    +}
    +
    +void
    +gaim_strncpy_withhtml(gchar *dest, const gchar *src, size_t destsize)
    +{
    + gchar *end;
    +
    + g_return_if_fail(dest != NULL);
    + g_return_if_fail(src != NULL);
    + g_return_if_fail(destsize > 0);
    +
    + end = dest + destsize;
    +
    + while (dest < end) {
    + if (*src == '\n' && dest < end - 5) {
    + strcpy(dest, "<BR>");
    + src++;
    + dest += 4;
    + } else if(*src == '\r') {
    + src++;
    + } else {
    + *dest++ = *src;
    + if (*src == '\0')
    + return;
    + else
    + src++;
    + }
    + }
    +}
    +
    +/*
    + * Like strncpy_withhtml (above), but malloc()'s the necessary space
    + *
    + * The caller is responsible for freeing the space pointed to by the
    + * return value.
    + */
    +char *
    +gaim_strdup_withhtml(const char *src)
    +{
    + char *sp, *dest;
    + gulong destsize;
    +
    + g_return_val_if_fail(src != NULL, NULL);
    +
    + /*
    + * All we need do is multiply the number of newlines by 3 (the
    + * additional length of "<BR>" over "\n"), account for the
    + * terminator, malloc the space and call strncpy_withhtml.
    + */
    + for(destsize = 0, sp = (gchar *)src;
    + (sp = strchr(sp, '\n')) != NULL;
    + ++sp, ++destsize)
    + ;
    +
    + destsize *= 3;
    + destsize += strlen(src) + 1;
    + dest = g_malloc(destsize);
    + gaim_strncpy_withhtml(dest, src, destsize);
    +
    + return dest;
    +}
    +
    +gboolean
    +gaim_str_has_prefix(const char *s, const char *p)
    +{
    + if (!strncmp(s, p, strlen(p)))
    + return TRUE;
    +
    + return FALSE;
    +}
    +
    +gboolean
    +gaim_str_has_suffix(const char *s, const char *x)
    +{
    + int off = strlen(s) - strlen(x);
    +
    + if (off >= 0 && !strcmp(s + off, x))
    + return TRUE;
    +
    + return FALSE;
    +}
    +
    +char *
    +gaim_str_add_cr(const char *text)
    +{
    + char *ret = NULL;
    + int count = 0, i, j;
    +
    + g_return_val_if_fail(text != NULL, NULL);
    +
    + if (text[0] == '\n')
    + count++;
    + for (i = 1; i < strlen(text); i++)
    + if (text[i] == '\n' && text[i - 1] != '\r')
    + count++;
    +
    + if (count == 0)
    + return g_strdup(text);
    +
    + ret = g_malloc0(strlen(text) + count + 1);
    +
    + i = 0; j = 0;
    + if (text[i] == '\n')
    + ret[j++] = '\r';
    + ret[j++] = text[i++];
    + for (; i < strlen(text); i++) {
    + if (text[i] == '\n' && text[i - 1] != '\r')
    + ret[j++] = '\r';
    + ret[j++] = text[i];
    + }
    +
    + gaim_debug_misc("gaim_str_add_cr", "got: %s, leaving with %s\n",
    + text, ret);
    +
    + return ret;
    +}
    +
    +void
    +gaim_str_strip_cr(char *text)
    +{
    + int i, j;
    + char *text2;
    +
    + g_return_if_fail(text != NULL);
    +
    + text2 = g_malloc(strlen(text) + 1);
    +
    + for (i = 0, j = 0; text[i]; i++)
    + if (text[i] != '\r')
    + text2[j++] = text[i];
    + text2[j] = '\0';
    +
    + strcpy(text, text2);
    + g_free(text2);
    +}
    +
    +char *
    +gaim_strreplace(const char *string, const char *delimiter,
    + const char *replacement)
    +{
    + gchar **split;
    + gchar *ret;
    +
    + g_return_val_if_fail(string != NULL, NULL);
    + g_return_val_if_fail(delimiter != NULL, NULL);
    + g_return_val_if_fail(replacement != NULL, NULL);
    +
    + split = g_strsplit(string, delimiter, 0);
    + ret = g_strjoinv(replacement, split);
    + g_strfreev(split);
    +
    + return ret;
    +}
    +
    +const char *
    +gaim_strcasestr(const char *haystack, const char *needle)
    +{
    + size_t hlen, nlen;
    + const char *tmp, *ret;
    +
    + g_return_val_if_fail(haystack != NULL, NULL);
    + g_return_val_if_fail(needle != NULL, NULL);
    +
    + hlen = strlen(haystack);
    + nlen = strlen(needle);
    + tmp = haystack,
    + ret = NULL;
    +
    + g_return_val_if_fail(hlen > 0, NULL);
    + g_return_val_if_fail(nlen > 0, NULL);
    +
    + while (*tmp && !ret) {
    + if (!g_ascii_strncasecmp(needle, tmp, nlen))
    + ret = tmp;
    + else
    + tmp++;
    + }
    +
    + return ret;
    +}
    +
    +char *
    +gaim_str_size_to_units(size_t size)
    +{
    + static const char *size_str[4] = { "bytes", "KB", "MB", "GB" };
    + float size_mag;
    + int size_index = 0;
    +
    + if (size == -1) {
    + return g_strdup(_("Calculating..."));
    + }
    + else if (size == 0) {
    + return g_strdup(_("Unknown."));
    + }
    + else {
    + size_mag = (float)size;
    +
    + while ((size_index < 4) && (size_mag > 1024)) {
    + size_mag /= 1024;
    + size_index++;
    + }
    +
    + return g_strdup_printf("%.2f %s", size_mag, size_str[size_index]);
    + }
    +}
    +
    +char *
    +gaim_str_seconds_to_string(guint sec)
    +{
    + guint daze, hrs, min;
    + char *ret = NULL;
    +
    + daze = sec / (60 * 60 * 24);
    + hrs = (sec % (60 * 60 * 24)) / (60 * 60);
    + min = (sec % (60 * 60)) / 60;
    + sec = min % 60;
    +
    + if (daze) {
    + if (hrs || min) {
    + if (hrs) {
    + if (min) {
    + ret = g_strdup_printf(
    + "%d %s, %d %s, %d %s.",
    + daze, ngettext("day","days",daze),
    + hrs, ngettext("hour","hours",hrs), min, ngettext("minute","minutes",min));
    + } else {
    + ret = g_strdup_printf(
    + "%d %s, %d %s.",
    + daze, ngettext("day","days",daze), hrs, ngettext("hour","hours",hrs));
    + }
    + } else {
    + ret = g_strdup_printf(
    + "%d %s, %d %s.",
    + daze, ngettext("day","days",daze), min, ngettext("minute","minutes",min));
    + }
    + } else
    + ret = g_strdup_printf("%d %s.", daze, ngettext("day","days",daze));
    + } else {
    + if (hrs) {
    + if (min) {
    + ret = g_strdup_printf(
    + "%d %s, %d %s.",
    + hrs, ngettext("hour","hours",hrs), min, ngettext("minute","minutes",min));
    + } else {
    + ret = g_strdup_printf("%d %s.", hrs, ngettext("hour","hours",hrs));
    + }
    + } else {
    + ret = g_strdup_printf("%d %s.", min, ngettext("minute","minutes",min));
    + }
    + }
    +
    + return ret;
    +}
    +
    +/**************************************************************************
    + * URI/URL Functions
    + **************************************************************************/
    +gboolean
    +gaim_url_parse(const char *url, char **ret_host, int *ret_port,
    + char **ret_path)
    +{
    + char scan_info[255];
    + char port_str[5];
    + int f;
    + const char *turl;
    + char host[256], path[256];
    + int port = 0;
    + /* hyphen at end includes it in control set */
    + static char addr_ctrl[] = "A-Za-z0-9.-";
    + static char port_ctrl[] = "0-9";
    + static char page_ctrl[] = "A-Za-z0-9.~_/:*!@&%%?=+^-";
    +
    + g_return_val_if_fail(url != NULL, FALSE);
    +
    + if ((turl = strstr(url, "http://")) != NULL ||
    + (turl = strstr(url, "HTTP://")) != NULL)
    + {
    + turl += 7;
    + url = turl;
    + }
    +
    + g_snprintf(scan_info, sizeof(scan_info),
    + "%%[%s]:%%[%s]/%%[%s]", addr_ctrl, port_ctrl, page_ctrl);
    +
    + f = sscanf(url, scan_info, host, port_str, path);
    +
    + if (f == 1)
    + {
    + g_snprintf(scan_info, sizeof(scan_info),
    + "%%[%s]/%%[%s]",
    + addr_ctrl, page_ctrl);
    + f = sscanf(url, scan_info, host, path);
    + g_snprintf(port_str, sizeof(port_str), "80");
    + }
    +
    + if (f == 1)
    + *path = '\0';
    +
    + sscanf(port_str, "%d", &port);
    +
    + if (ret_host != NULL) *ret_host = g_strdup(host);
    + if (ret_port != NULL) *ret_port = port;
    + if (ret_path != NULL) *ret_path = g_strdup(path);
    +
    + return TRUE;
    +}
    +
    +static void
    +destroy_fetch_url_data(GaimFetchUrlData *gfud)
    +{
    + if (gfud->webdata != NULL) g_free(gfud->webdata);
    + if (gfud->url != NULL) g_free(gfud->url);
    + if (gfud->user_agent != NULL) g_free(gfud->user_agent);
    + if (gfud->website.address != NULL) g_free(gfud->website.address);
    + if (gfud->website.page != NULL) g_free(gfud->website.page);
    +
    + g_free(gfud);
    +}
    +
    +static gboolean
    +parse_redirect(const char *data, size_t data_len, gint sock,
    + GaimFetchUrlData *gfud)
    +{
    + gchar *s;
    +
    + if ((s = g_strstr_len(data, data_len, "Location: ")) != NULL)
    + {
    + gchar *new_url, *temp_url, *end;
    + gboolean full;
    + int len;
    +
    + s += strlen("Location: ");
    + end = strchr(s, '\r');
    +
    + /* Just in case :) */
    + if (end == NULL)
    + end = strchr(s, '\n');
    +
    + len = end - s;
    +
    + new_url = g_malloc(len + 1);
    + strncpy(new_url, s, len);
    + new_url[len] = '\0';
    +
    + full = gfud->full;
    +
    + if (*new_url == '/' || g_strstr_len(new_url, len, "://") == NULL)
    + {
    + temp_url = new_url;
    +
    + new_url = g_strdup_printf("%s:%d%s", gfud->website.address,
    + gfud->website.port, temp_url);
    +
    + g_free(temp_url);
    +
    + full = FALSE;
    + }
    +
    + /* Close the existing stuff. */
    + gaim_input_remove(gfud->inpa);
    + close(sock);
    +
    + gaim_debug_info("gaim_url_fetch", "Redirecting to %s\n", new_url);
    +
    + /* Try again, with this new location. */
    + gaim_url_fetch(new_url, full, gfud->user_agent, gfud->http11,
    + gfud->callback, gfud->user_data);
    +
    + /* Free up. */
    + g_free(new_url);
    + destroy_fetch_url_data(gfud);
    +
    + return TRUE;
    + }
    +
    + return FALSE;
    +}
    +
    +static size_t
    +parse_content_len(const char *data, size_t data_len)
    +{
    + size_t content_len = 0;
    +
    + sscanf(data, "Content-Length: %d", (int *)&content_len);
    +
    + return content_len;
    +}
    +
    +static void
    +url_fetched_cb(gpointer url_data, gint sock, GaimInputCondition cond)
    +{
    + GaimFetchUrlData *gfud = url_data;
    + char data;
    +
    + if (sock == -1)
    + {
    + gfud->callback(gfud->user_data, NULL, 0);
    +
    + destroy_fetch_url_data(gfud);
    +
    + return;
    + }
    +
    + if (!gfud->sentreq)
    + {
    + char buf[1024];
    +
    + if (gfud->user_agent)
    + {
    + if (gfud->http11)
    + {
    + g_snprintf(buf, sizeof(buf),
    + "GET %s%s HTTP/1.1\r\n"
    + "User-Agent: \"%s\"\r\n"
    + "Host: %s\r\n\r\n",
    + (gfud->full ? "" : "/"),
    + (gfud->full ? gfud->url : gfud->website.page),
    + gfud->user_agent, gfud->website.address);
    + }
    + else
    + {
    + g_snprintf(buf, sizeof(buf),
    + "GET %s%s HTTP/1.0\r\n"
    + "User-Agent: \"%s\"\r\n\r\n",
    + (gfud->full ? "" : "/"),
    + (gfud->full ? gfud->url : gfud->website.page),
    + gfud->user_agent);
    + }
    + }
    + else
    + {
    + if (gfud->http11)
    + {
    + g_snprintf(buf, sizeof(buf),
    + "GET %s%s HTTP/1.1\r\n"
    + "Host: %s\r\n\r\n",
    + (gfud->full ? "" : "/"),
    + (gfud->full ? gfud->url : gfud->website.page),
    + gfud->website.address);
    + }
    + else
    + {
    + g_snprintf(buf, sizeof(buf),
    + "GET %s%s HTTP/1.0\r\n\r\n",
    + (gfud->full ? "" : "/"),
    + (gfud->full ? gfud->url : gfud->website.page));
    + }
    + }
    +
    + gaim_debug_misc("gaim_url_fetch", "Request: %s\n", buf);
    +
    + write(sock, buf, strlen(buf));
    + fcntl(sock, F_SETFL, O_NONBLOCK);
    + gfud->sentreq = TRUE;
    + gfud->inpa = gaim_input_add(sock, GAIM_INPUT_READ,
    + url_fetched_cb, url_data);
    + gfud->data_len = 4096;
    + gfud->webdata = g_malloc(gfud->data_len);
    +
    + return;
    + }
    +
    + if (read(sock, &data, 1) > 0 || errno == EWOULDBLOCK)
    + {
    + if (errno == EWOULDBLOCK)
    + {
    + errno = 0;
    +
    + return;
    + }
    +
    + gfud->len++;
    +
    + if (gfud->len == gfud->data_len + 1)
    + {
    + gfud->data_len += (gfud->data_len) / 2;
    +
    + gfud->webdata = g_realloc(gfud->webdata, gfud->data_len);
    + }
    +
    + gfud->webdata[gfud->len - 1] = data;
    +
    + if (!gfud->startsaving)
    + {
    + if (data == '\r')
    + return;
    +
    + if (data == '\n')
    + {
    + if (gfud->newline)
    + {
    + size_t content_len;
    + gfud->startsaving = TRUE;
    +
    + /* See if we can find a redirect. */
    + if (parse_redirect(gfud->webdata, gfud->len, sock, gfud))
    + return;
    +
    + /* No redirect. See if we can find a content length. */
    + content_len = parse_content_len(gfud->webdata, gfud->len);
    +
    + if (content_len == 0)
    + {
    + /* We'll stick with an initial 8192 */
    + content_len = 8192;
    + }
    +
    + /* Out with the old... */
    + gfud->len = 0;
    + g_free(gfud->webdata);
    + gfud->webdata = NULL;
    +
    + /* In with the new. */
    + gfud->data_len = content_len;
    + gfud->webdata = g_malloc(gfud->data_len);
    + }
    + else
    + gfud->newline = TRUE;
    +
    + return;
    + }
    +
    + gfud->newline = FALSE;
    + }
    + }
    + else if (errno != ETIMEDOUT)
    + {
    + gfud->webdata = g_realloc(gfud->webdata, gfud->len + 1);
    + gfud->webdata[gfud->len] = 0;
    +
    + gaim_debug_misc("gaim_url_fetch", "Received: '%s'\n", gfud->webdata);
    +
    + gaim_input_remove(gfud->inpa);
    + close(sock);
    + gfud->callback(gfud->user_data, gfud->webdata, gfud->len);
    +
    + destroy_fetch_url_data(gfud);
    + }
    + else
    + {
    + gaim_input_remove(gfud->inpa);
    + close(sock);
    +
    + gfud->callback(gfud->user_data, NULL, 0);
    +
    + destroy_fetch_url_data(gfud);
    + }
    +}
    +
    +void
    +gaim_url_fetch(const char *url, gboolean full,
    + const char *user_agent, gboolean http11,
    + void (*cb)(gpointer, const char *, size_t),
    + void *user_data)
    +{
    + int sock;
    + GaimFetchUrlData *gfud;
    +
    + g_return_if_fail(url != NULL);
    + g_return_if_fail(cb != NULL);
    +
    + gfud = g_new0(GaimFetchUrlData, 1);
    +
    + gfud->callback = cb;
    + gfud->user_data = user_data;
    + gfud->url = g_strdup(url);
    + gfud->user_agent = (user_agent != NULL ? g_strdup(user_agent) : NULL);
    + gfud->http11 = http11;
    + gfud->full = full;
    +
    + gaim_url_parse(url, &gfud->website.address, &gfud->website.port,
    + &gfud->website.page);
    +
    + if ((sock = gaim_proxy_connect(NULL, gfud->website.address,
    + gfud->website.port, url_fetched_cb,
    + gfud)) < 0)
    + {
    + destroy_fetch_url_data(gfud);
    +
    + cb(user_data, g_strdup(_("g003: Error opening connection.\n")), 0);
    + }
    +}
    +
    +const char *
    +gaim_url_decode(const char *str)
    +{
    + static char buf[BUF_LEN];
    + int i, j = 0;
    + char *bum;
    +
    + g_return_val_if_fail(str != NULL, NULL);
    +
    + for (i = 0; i < strlen(str); i++) {
    + char hex[3];
    +
    + if (str[i] != '%')
    + buf[j++] = str[i];
    + else {
    + strncpy(hex, str + ++i, 2);
    + hex[2] = '\0';
    +
    + /* i is pointing to the start of the number */
    + i++;
    +
    + /*
    + * Now it's at the end and at the start of the for loop
    + * will be at the next character.
    + */
    + buf[j++] = strtol(hex, NULL, 16);
    + }
    + }
    +
    + buf[j] = '\0';
    +
    + if (!g_utf8_validate(buf, -1, (const char **)&bum))
    + *bum = '\0';
    +
    + return buf;
    +}
    +
    +const char *
    +gaim_url_encode(const char *str)
    +{
    + static char buf[BUF_LEN];
    + int i, j = 0;
    +
    + g_return_val_if_fail(str != NULL, NULL);
    +
    + for (i = 0; i < strlen(str); i++) {
    + if (isalnum(str[i]))
    + buf[j++] = str[i];
    + else {
    + sprintf(buf + j, "%%%02x", (unsigned char)str[i]);
    + j += 3;
    + }
    + }
    +
    + buf[j] = '\0';
    +
    + return buf;
    +}
    +
    +/**************************************************************************
    + * UTF8 String Functions
    + **************************************************************************/
    +char *
    +gaim_utf8_try_convert(const char *str)
    +{
    + gsize converted;
    + char *utf8;
    +
    + g_return_val_if_fail(str != NULL, NULL);
    +
    + if (g_utf8_validate(str, -1, NULL)) {
    + return g_strdup(str);
    + }
    +
    + utf8 = g_locale_to_utf8(str, -1, &converted, NULL, NULL);
    + if (utf8)
    + return(utf8);
    +
    + g_free(utf8);
    +
    + utf8 = g_convert(str, -1, "UTF-8", "ISO-8859-15", &converted, NULL, NULL);
    + if (utf8 && converted == strlen (str)) {
    + return(utf8);
    + } else if (utf8) {
    + g_free(utf8);
    + }
    +
    + return(NULL);
    +}
    +
    +int
    +gaim_utf8_strcasecmp(const char *a, const char *b)
    +{
    + char *a_norm = NULL;
    + char *b_norm = NULL;
    + int ret = -1;
    +
    + if(!a && b)
    + return -1;
    + else if(!b && a)
    + return 1;
    + else if(!a && !b)
    + return 0;
    +
    + if(!g_utf8_validate(a, -1, NULL) || !g_utf8_validate(b, -1, NULL))
    + {
    + gaim_debug_error("gaim_utf8_strcasecmp",
    + "One or both parameters are invalid UTF8\n");
    + return ret;
    + }
    +
    + a_norm = g_utf8_casefold(a, -1);
    + b_norm = g_utf8_casefold(b, -1);
    + ret = g_utf8_collate(a_norm, b_norm);
    + g_free(a_norm);
    + g_free(b_norm);
    +
    + return ret;
    +}
    +
    +gboolean gaim_message_meify(char *message, size_t len)
    +{
    + char *c;
    + gboolean inside_html = FALSE;
    +
    + g_return_val_if_fail(message != NULL, FALSE);
    +
    + if(len == -1)
    + len = strlen(message);
    +
    + for (c = message; *c; c++, len--) {
    + if(inside_html) {
    + if(*c == '>')
    + inside_html = FALSE;
    + } else {
    + if(*c == '<')
    + inside_html = TRUE;
    + else
    + break;
    + }
    + }
    +
    + if(*c && !g_ascii_strncasecmp(c, "/me ", 4)) {
    + memmove(c, c+4, len-3);
    + return TRUE;
    + }
    +
    + return FALSE;
    +}
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/src/util.h Tue Nov 25 21:53:14 2003 -0500
    @@ -0,0 +1,548 @@
    +/**
    + * @file util.h Utility Functions
    + * @ingroup core
    + *
    + * gaim
    + *
    + * Copyright (C) 2002-2003, Christian Hammond <chipx86@gnupdate.org>
    + *
    + * 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
    + *
    + * @todo Rename the functions so that they live somewhere in the gaim
    + * namespace.
    + */
    +#ifndef _GAIM_UTIL_H_
    +#define _GAIM_UTIL_H_
    +
    +#include <stdio.h>
    +
    +#include "account.h"
    +
    +#ifdef __cplusplus
    +extern "C" {
    +#endif
    +
    +/**************************************************************************/
    +/** @name Base16 Functions */
    +/**************************************************************************/
    +/*@{*/
    +
    +/**
    + * Converts a string to its base-16 equivalent.
    + *
    + * @param str The string to convert.
    + * @param len The length of the string.
    + *
    + * @return The base-16 string.
    + *
    + * @see gaim_base16_decode()
    + */
    +unsigned char *gaim_base16_encode(const unsigned char *str, int len);
    +
    +/**
    + * Converts a string back from its base-16 equivalent.
    + *
    + * @param str The string to convert back.
    + * @param ret_str The returned, non-base-16 string.
    + *
    + * @return The length of the returned string.
    + *
    + * @see gaim_base16_encode()
    + */
    +int gaim_base16_decode(const char *str, unsigned char **ret_str);
    +
    +/*@}*/
    +
    +
    +/**************************************************************************/
    +/** @name Base64 Functions */
    +/**************************************************************************/
    +/*@{*/
    +
    +/**
    + * Converts a string to its base-64 equivalent.
    + *
    + * @param buf The data to convert.
    + * @param len The length of the data.
    + *
    + * @return The base-64 version of @a str.
    + *
    + * @see gaim_base64_decode()
    + */
    +unsigned char *gaim_base64_encode(const unsigned char *buf, size_t len);
    +
    +/**
    + * Converts a string back from its base-64 equivalent.
    + *
    + * @param str The string to convert back.
    + * @param ret_str The returned, non-base-64 string.
    + * @param ret_len The returned string length.
    + *
    + * @see gaim_base64_encode()
    + */
    +void gaim_base64_decode(const char *str, char **ret_str, int *ret_len);
    +
    +/*@}*/
    +
    +
    +/**************************************************************************/
    +/** @name Date/Time Functions */
    +/**************************************************************************/
    +/*@{*/
    +
    +/**
    + * Returns the current local time in hour:minute:second form.
    + *
    + * The returned string is stored in a static buffer, so the result
    + * should be g_strdup()'d if it's intended to be used for long.
    + *
    + * @return The current local time.
    + *
    + * @see gaim_date_full()
    + */
    +const char *gaim_date(void);
    +
    +/**
    + * Returns the date and time in human-readable form.
    + *
    + * The returned string is stored in a static buffer, so the result
    + * should be g_strdup()'d if it's intended to be used for long.
    + *
    + * @return The date and time in human-readable form.
    + *
    + * @see gaim_date()
    + */
    +const char *gaim_date_full(void);
    +
    +/**
    + * Builds a time_t from the supplied information.
    + *
    + * @param year The year.
    + * @param month The month.
    + * @param day The day.
    + * @param hour The hour.
    + * @param min The minute.
    + * @param sec The second.
    + *
    + * @return A time_t.
    + */
    +time_t gaim_time_build(int year, int month, int day, int hour,
    + int min, int sec);
    +
    +/*@}*/
    +
    +
    +/**************************************************************************/
    +/** @name Markup Functions */
    +/**************************************************************************/
    +/*@{*/
    +
    +/**
    + * Finds a HTML tag matching the given name.
    + *
    + * This locates an HTML tag's start and end, and stores its attributes
    + * in a GData hash table. The names of the attributes are lower-cased
    + * in the hash table, and the name of the tag is case insensitive.
    + *
    + * @param needle the name of the tag
    + * @param haystack the null-delimited string to search in
    + * @param start a pointer to the start of the tag if found
    + * @param end a pointer to the end of the tag if found
    + * @param attributes the attributes, if the tag was found
    + * @return TRUE if the tag was found
    + */
    +gboolean gaim_markup_find_tag(const char *needle, const char *haystack,
    + const char **start, const char **end,
    + GData **attributes);
    +
    +/**
    + * Extracts a field of data from HTML.
    + *
    + * This is a scary function. See protocols/msn/msn.c and
    + * protocols/yahoo/yahoo.c for example usage.
    + *
    + * @param str The string to parse.
    + * @param dest_buffer The destination buffer to append the new
    + * field info to.
    + * @param start_token The beginning token.
    + * @param skip The number of characters to skip after the
    + * start token.
    + * @param end_token The ending token.
    + * @param check_value The value that the last character must meet.
    + * @param no_value_token The token indicating no value is given.
    + * @param display_name The short descriptive name to display for this token.
    + * @param is_link TRUE if this should be a link, or FALSE otherwise.
    + * @param link_prefix The prefix for the link.
    + *
    + * @return TRUE if successful, or FALSE otherwise.
    + */
    +gboolean gaim_markup_extract_info_field(const char *str, char *dest_buffer,
    + const char *start_token, int skip,
    + const char *end_token,
    + char check_value,
    + const char *no_value_token,
    + const char *display_name,
    + gboolean is_link,
    + const char *link_prefix);
    +
    +/**
    + * Converts HTML markup to XHTML.
    + *
    + * @param html The HTML markup.
    + * @param dest_xhtml The destination XHTML output.
    + * @param dest_plain The destination plain-text output.
    + */
    +void gaim_markup_html_to_xhtml(const char *html, char **dest_xhtml,
    + char **dest_plain);
    +
    +/**
    + * Strips HTML tags from a string.
    + *
    + * @param str The string to strip HTML from.
    + *
    + * @return The new string without HTML. This must be freed.
    + */
    +char *gaim_markup_strip_html(const char *str);
    +
    +/**
    + * Adds the necessary HTML code to turn URIs into HTML links in a string.
    + *
    + * @param str The string to linkify.
    + *
    + * @return The linkified text.
    + */
    +char *gaim_markup_linkify(const char *str);
    +
    +/*@}*/
    +
    +
    +/**************************************************************************/
    +/** @name Path/Filename Functions */
    +/**************************************************************************/
    +/*@{*/
    +
    +/**
    + * Returns the user's home directory.
    + *
    + * @return The user's home directory.
    + *
    + * @see gaim_user_dir()
    + */
    +const gchar *gaim_home_dir(void);
    +
    +/**
    + * Returns the gaim settings directory in the user's home directory.
    + *
    + * @return The gaim settings directory.
    + *
    + * @see gaim_home_dir()
    + */
    +char *gaim_user_dir(void);
    +
    +/**
    + * Creates a temporary file and returns a file pointer to it.
    + *
    + * This is like mkstemp(), but returns a file pointer and uses a
    + * pre-set template. It uses the semantics of tempnam() for the
    + * directory to use and allocates the space for the file path.
    + *
    + * The caller is responsible for closing the file and removing it when
    + * done, as well as freeing the space pointed to by @a path with
    + * g_free().
    + *
    + * @param path The returned path to the temp file.
    + *
    + * @return A file pointer to the temporary file, or @c NULL on failure.
    + */
    +FILE *gaim_mkstemp(char **path);
    +
    +/**
    + * Checks if the given program name is valid and executable.
    + *
    + * @param program The file name of the application.
    + *
    + * @return True if the program is runable.
    + */
    +gboolean gaim_program_is_valid(const char *program);
    +
    +/**
    + * Returns the IP address from a socket file descriptor.
    + *
    + * @param fd The socket file descriptor.
    + *
    + * @return The IP address, or @c NULL on error.
    + */
    +char *gaim_fd_get_ip(int fd);
    +
    +/*@}*/
    +
    +
    +/**************************************************************************/
    +/** @name String Functions */
    +/**************************************************************************/
    +/*@{*/
    +
    +/**
    + * Normalizes a string, so that it is suitable for comparison.
    + *
    + * The returned string will point to a static buffer, so if the
    + * string is intended to be kept long-term, you <i>must</i>
    + * g_strdup() it. Also, calling normalize() twice in the same line
    + * will lead to problems.
    + *
    + * @param account The account the string belongs to.
    + * @param str The string to normalize.
    + *
    + * @return A pointer to the normalized version stored in a static buffer.
    + */
    +const char *gaim_normalize(const GaimAccount *account, const char *str);
    +
    +/**
    + * Compares two strings to see if the first contains the second as
    + * a proper prefix.
    + *
    + * @param s The string to check.
    + * @param p The prefix in question.
    + *
    + * @return TRUE if p is a prefix of s, otherwise FALSE.
    + */
    +gboolean gaim_str_has_prefix(const char *s, const char *p);
    +
    +/**
    + * Compares two strings to see if the second is a proper suffix
    + * of the first.
    + *
    + * @param s The string to check.
    + * @param x The suffix in question.
    + *
    + * @return TRUE if x is a a suffix of s, otherwise FALSE.
    + */
    +gboolean gaim_str_has_suffix(const char *s, const char *x);
    +
    +/**
    + * Looks for %n, %d, or %t in a string, and replaces them with the
    + * specified name, date, and time, respectively.
    + *
    + * The returned string is stored in a static buffer, so the result
    + * should be g_strdup()'d if it's intended to be used for long.
    + *
    + * @param str The string that may contain the special variables.
    + * @param name The sender name.
    + *
    + * @return A new string where the special variables are expanded.
    + */
    +const char *gaim_str_sub_away_formatters(const char *str, const char *name);
    +
    +/**
    + * Copies a string and replaces all HTML linebreaks with newline characters.
    + *
    + * @param dest The destination string.
    + * @param src The source string.
    + * @param dest_len The destination string length.
    + *
    + * @see gaim_strncpy_withhtml()
    + * @see gaim_strdup_withhtml()
    + */
    +void gaim_strncpy_nohtml(char *dest, const char *src, size_t dest_len);
    +
    +/**
    + * Copies a string and replaces all newline characters with HTML linebreaks.
    + *
    + * @param dest The destination string.
    + * @param src The source string.
    + * @param dest_len The destination string length.
    + *
    + * @see gaim_strncpy_nohtml()
    + * @see gaim_strdup_withhtml()
    + */
    +void gaim_strncpy_withhtml(gchar *dest, const gchar *src, size_t dest_len);
    +
    +/**
    + * Duplicates a string and replaces all newline characters from the
    + * source string with HTML linebreaks.
    + *
    + * @param src The source string.
    + *
    + * @return The new string.
    + *
    + * @see gaim_strncpy_nohtml()
    + * @see gaim_strncpy_withhtml()
    + */
    +char *gaim_strdup_withhtml(const char *src);
    +
    +/**
    + * Ensures that all linefeeds have a matching carriage return.
    + *
    + * @param str The source string.
    + *
    + * @return The string with carriage returns.
    + */
    +char *gaim_str_add_cr(const char *str);
    +
    +/**
    + * Strips all carriage returns from a string.
    + *
    + * @param str The string to strip carriage returns from.
    + */
    +void gaim_str_strip_cr(char *str);
    +
    +/**
    + * Given a string, this replaces one substring with another
    + * and returns a newly allocated string.
    + *
    + * @param string The string from which to replace stuff.
    + * @param delimiter The substring you want replaced.
    + * @param replacement The substring you want inserted in place
    + * of the delimiting substring.
    + */
    +char *gaim_strreplace(const char *string, const char *delimiter,
    + const char *replacement);
    +
    +/**
    + * This is like strstr, except that it ignores ASCII case in
    + * searching for the substring.
    + *
    + * @param haystack The string to search in.
    + * @param needle The substring to find.
    + *
    + * @return the location of the substring if found, or NULL if not
    + */
    +const char *gaim_strcasestr(const char *haystack, const char *needle);
    +
    +/**
    + * Returns a string representing a filesize in the appropriate
    + * units (MB, KB, GB, etc.)
    + *
    + * @param size The size
    + *
    + * @return The string in units form. This must be freed.
    + */
    +char *gaim_str_size_to_units(size_t size);
    +
    +/**
    + * Converts seconds into a human-readable form.
    + *
    + * @param sec The seconds.
    + *
    + * @return A human-readable form, containing days, hours, minutes, and
    + * seconds.
    + */
    +char *gaim_str_seconds_to_string(guint sec);
    +
    +/*@}*/
    +
    +
    +/**************************************************************************/
    +/** @name URI/URL Functions */
    +/**************************************************************************/
    +/*@{*/
    +
    +/**
    + * Parses a URL, returning its host, port, and file path.
    + *
    + * The returned data must be freed.
    + *
    + * @param url The URL to parse.
    + * @param ret_host The returned host.
    + * @param ret_port The returned port.
    + * @param ret_path The returned path.
    + */
    +gboolean gaim_url_parse(const char *url, char **ret_host, int *ret_port,
    + char **ret_path);
    +
    +/**
    + * Fetches the data from a URL, and passes it to a callback function.
    + *
    + * @param url The URL.
    + * @param full TRUE if this is the full URL, or FALSE if it's a
    + * partial URL.
    + * @param cb The callback function.
    + * @param data The user data to pass to the callback function.
    + * @param user_agent The user agent field to use, or NULL.
    + * @param http11 TRUE if HTTP/1.1 should be used to download the file.
    + */
    +void gaim_url_fetch(const char *url, gboolean full,
    + const char *user_agent, gboolean http11,
    + void (*cb)(void *, const char *, size_t),
    + void *data);
    +/**
    + * Decodes a URL into a plain string.
    + *
    + * This will change hex codes and such to their ascii equivalents.
    + *
    + * @param str The string to translate.
    + *
    + * @return The resulting string.
    + */
    +const char *gaim_url_decode(const char *str);
    +
    +/**
    + * Encodes a URL into an escaped string.
    + *
    + * This will change non-alphanumeric characters to hex codes.
    + *
    + * @param str The string to translate.
    + *
    + * @return The resulting string.
    + */
    +const char *gaim_url_encode(const char *str);
    +
    +/*@}*/
    +
    +/**************************************************************************
    + * UTF8 String Functions
    + **************************************************************************/
    +/*@{*/
    +
    +/**
    + * Attempts to convert a string to UTF-8 from an unknown encoding.
    + *
    + * This function checks the locale and tries sane defaults.
    + *
    + * @param str The source string.
    + *
    + * @return The UTF-8 string, or @c NULL if it could not be converted.
    + */
    +char *gaim_utf8_try_convert(const char *str);
    +
    +/**
    + * Compares two UTF-8 strings.
    + *
    + * @param a The first string.
    + * @param b The second string.
    + *
    + * @return -1 if @a is less than @a b.
    + * 0 if @a is equal to @a b.
    + * 1 if @a is greater than @a b.
    + */
    +int gaim_utf8_strcasecmp(const char *a, const char *b);
    +
    +/**
    + * Checks for messages starting with "/me "
    + *
    + * @param message The message to check
    + * @param len The message length, or -1
    + *
    + * @return TRUE if it starts with /me, and it has been removed, otherwise FALSE
    + */
    +gboolean gaim_message_meify(char *message, size_t len);
    +
    +/*@}*/
    +
    +#ifdef __cplusplus
    +}
    +#endif
    +
    +#endif /* _GAIM_UTIL_H_ */