Update of /cvsroot/gaim/gaim/src/protocols/novell In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv4641/src/protocols/novell Modified Files: Makefile.am Makefile.mingw nmconn.c nmevent.c nmuser.c nmuser.h novell.c Added Files: nmrtf.c nmrtf.h Log Message: " - Added keepalive support - Improved handling of character sets in RTF (improves intl interoperability between Gaim and the Windows/XPLAT Messenger clients)." --Mike Stoddard of Novell --- NEW FILE: nmrtf.c --- /* * nmrtf.c * * Copyright (c) 2004 Novell, Inc. All Rights Reserved. * * 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; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ /* This code was adapted from the sample RTF reader found here: * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnrtfspec/html/rtfspec.asp */ #include <glib.h> #include <stdlib.h> #include <stdio.h> #include <stddef.h> #include <ctype.h> #include <string.h> #include "nmrtf.h" #include "debug.h" /* Internal RTF parser error codes */ #define NMRTF_OK 0 /* Everything's fine! */ #define NMRTF_STACK_UNDERFLOW 1 /* Unmatched '}' */ #define NMRTF_STACK_OVERFLOW 2 /* Too many '{' -- memory exhausted */ #define NMRTF_UNMATCHED_BRACE 3 /* RTF ended during an open group. */ #define NMRTF_INVALID_HEX 4 /* invalid hex character found in data */ #define NMRTF_BAD_TABLE 5 /* RTF table (sym or prop) invalid */ #define NMRTF_ASSERTION 6 /* Assertion failure */ #define NMRTF_EOF 7 /* End of file reached while reading RTF */ #define NMRTF_CONVERT_ERROR 8 /* Error converting text */ #define NMRTF_MAX_DEPTH 256 typedef enum { NMRTF_STATE_NORMAL, NMRTF_STATE_SKIP, NMRTF_STATE_FONTTABLE, NMRTF_STATE_BIN, NMRTF_STATE_HEX } NMRtfState; /* Rtf State */ /* Property types that we care about */ typedef enum { NMRTF_PROP_FONT_IDX, NMRTF_PROP_FONT_CHARSET, NMRTF_PROP_MAX } NMRtfProperty; typedef enum { NMRTF_SPECIAL_BIN, NMRTF_SPECIAL_HEX, NMRTF_SPECIAL_UNICODE, NMRTF_SPECIAL_SKIP } NMRtfSpecialKwd; typedef enum { NMRTF_DEST_FONTTABLE, NMRTF_DEST_SKIP } NMRtfDestinationType; typedef enum { NMRTF_KWD_CHAR, NMRTF_KWD_DEST, NMRTF_KWD_PROP, NMRTF_KWD_SPEC } NMRtfKeywordType; typedef struct _NMRTFCharProp { /* All we care about for now is the font. * bold, italic, underline, etc. should be * added here */ int font_idx; int font_charset; } NMRtfCharProp; typedef struct _NMRtfStateSave { NMRtfCharProp chp; NMRtfState rds; NMRtfState ris; } NMRtfStateSave; typedef struct _NMRtfSymbol { char *keyword; /* RTF keyword */ int default_val; /* default value to use */ gboolean pass_default; /* true to use default value from this table */ NMRtfKeywordType kwd_type; /* the type of the keyword */ int action; /* property type if the keyword represents a property */ /* destination type if the keyword represents a destination */ /* character to print if the keyword represents a character */ } NMRtfSymbol; typedef struct _NMRtfFont { int number; char *name; int charset; } NMRtfFont; /* RTF Context */ struct _NMRtfContext { NMRtfState rds; /* destination state */ NMRtfState ris; /* internal state */ NMRtfCharProp chp; /* current character properties (ie. font, bold, italic, etc.) */ GSList *font_table; /* the font table */ GSList *saved; /* saved state stack */ int param; /* numeric parameter for the current keyword */ long bytes_to_skip; /* number of bytes to skip (after encountering \bin) */ int depth; /* how many groups deep are we */ gboolean skip_unknown; /* if true, skip any unknown destinations (this is set after encountering '\*') */ char *input; /* input string */ char nextch; /* next char in input */ GString *ansi; /* Temporary ansi text, will be convert/flushed to the output string */ GString *output; /* The plain text UTF8 string */ }; static int rtf_parse(NMRtfContext *ctx); static int rtf_push_state(NMRtfContext *ctx); static int rtf_pop_state(NMRtfContext *ctx); static NMRtfFont *rtf_get_font(NMRtfContext *ctx, int index); static int rtf_get_char(NMRtfContext *ctx, guchar *ch); static int rtf_unget_char(NMRtfContext *ctx, guchar ch); static int rtf_flush_data(NMRtfContext *ctx); static int rtf_parse_keyword(NMRtfContext *ctx); static int rtf_dispatch_control(NMRtfContext *ctx, char *keyword, int param, gboolean param_set); static int rtf_dispatch_char(NMRtfContext *ctx, guchar ch); static int rtf_dispatch_unicode_char(NMRtfContext *ctx, gunichar ch); static int rtf_print_char(NMRtfContext *ctx, guchar ch); static int rtf_print_unicode_char(NMRtfContext *ctx, gunichar ch); static int rtf_change_destination(NMRtfContext *ctx, NMRtfDestinationType dest); static int rtf_dispatch_special(NMRtfContext *ctx, NMRtfSpecialKwd special); static int rtf_apply_property(NMRtfContext *ctx, NMRtfProperty prop, int val); /* RTF parser tables */ /* Keyword descriptions */ NMRtfSymbol rtf_symbols[] = { /* keyword, default, pass_default, keyword_type, action */ {"fonttbl", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_FONTTABLE}, {"f", 0, FALSE, NMRTF_KWD_PROP, NMRTF_PROP_FONT_IDX}, {"fcharset", 0, FALSE, NMRTF_KWD_PROP, NMRTF_PROP_FONT_CHARSET}, {"par", 0, FALSE, NMRTF_KWD_CHAR, 0x0a}, {"line", 0, FALSE, NMRTF_KWD_CHAR, 0x0a}, {"\0x0a", 0, FALSE, NMRTF_KWD_CHAR, 0x0a}, {"\0x0d", 0, FALSE, NMRTF_KWD_CHAR, 0x0a}, {"tab", 0, FALSE, NMRTF_KWD_CHAR, 0x09}, {"\r", 0, FALSE, NMRTF_KWD_CHAR, '\r'}, {"\n", 0, FALSE, NMRTF_KWD_CHAR, '\n'}, {"ldblquote",0, FALSE, NMRTF_KWD_CHAR, '"'}, {"rdblquote",0, FALSE, NMRTF_KWD_CHAR, '"'}, {"{", 0, FALSE, NMRTF_KWD_CHAR, '{'}, {"}", 0, FALSE, NMRTF_KWD_CHAR, '}'}, {"\\", 0, FALSE, NMRTF_KWD_CHAR, '\\'}, {"bin", 0, FALSE, NMRTF_KWD_SPEC, NMRTF_SPECIAL_BIN}, {"*", 0, FALSE, NMRTF_KWD_SPEC, NMRTF_SPECIAL_SKIP}, {"'", 0, FALSE, NMRTF_KWD_SPEC, NMRTF_SPECIAL_HEX}, {"u", 0, FALSE, NMRTF_KWD_SPEC, NMRTF_SPECIAL_UNICODE}, {"colortbl", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"author", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"buptim", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"comment", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"creatim", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"doccomm", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"footer", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"footerf", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"footerl", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"footerr", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"footnote", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"ftncn", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"ftnsep", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"ftnsepc", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"header", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"headerf", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"headerl", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"headerr", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"info", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"keywords", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"operator", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"pict", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"printim", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"private1", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"revtim", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"rxe", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"stylesheet", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"subject", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"tc", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"title", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"txe", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP}, {"xe", 0, FALSE, NMRTF_KWD_DEST, NMRTF_DEST_SKIP} }; int table_size = sizeof(rtf_symbols) / sizeof(NMRtfSymbol); NMRtfContext * nm_rtf_init() { NMRtfContext *ctx = g_new0(NMRtfContext, 1); ctx->nextch = -1; ctx->ansi = g_string_new(""); ctx->output = g_string_new(""); return ctx; } char * nm_rtf_strip_formatting(NMRtfContext *ctx, const char *input) { int status; ctx->input = (char *)input; status = rtf_parse(ctx); if (status == NMRTF_OK) return g_strdup(ctx->output->str); gaim_debug_info("novell", "RTF parser failed with error code %d", status); return NULL; } void nm_rtf_deinit(NMRtfContext *ctx) { GSList *node; NMRtfFont *font; NMRtfStateSave *save; if (ctx) { for (node = ctx->font_table; node; node = node->next) { font = node->data; g_free(font->name); g_free(font); node->data = NULL; } g_slist_free(ctx->font_table); for (node = ctx->saved; node; node = node->next) { save = node->data; g_free(save); node->data = NULL; } g_slist_free(ctx->saved); g_string_free(ctx->ansi, TRUE); g_string_free(ctx->output, TRUE); g_free(ctx); } } static const char * get_current_encoding(NMRtfContext *ctx) { NMRtfFont *font; font = rtf_get_font(ctx, ctx->chp.font_idx); switch (font->charset) { case 0: return "CP1252"; case 77: return "MACINTOSH"; case 78: return "SJIS"; case 128: return "CP932"; case 129: return "CP949"; case 130: return "CP1361"; case 134: return "CP936"; case 136: return "CP950"; case 161: return "CP1253"; case 162: return "CP1254"; case 163: return "CP1258"; case 181: case 177: return "CP1255"; case 178: case 179: case 180: return "CP1256"; case 186: return "CP1257"; case 204: return "CP1251"; case 222: return "CP874"; case 238: return "CP1250"; case 254: return "CP437"; default: gaim_debug_info("novell", "Unhandled font charset %d\n", font->charset); return "CP1252"; } return "CP1252"; } /* * Add an entry to the font table */ static int rtf_add_font_entry(NMRtfContext *ctx, int number, const char *name, int charset) { NMRtfFont *font = g_new0(NMRtfFont, 1); font->number = number; font->name = g_strdup(name); font->charset = charset; gaim_debug_info("novell", "Adding font to table: #%d\t%s\t%d\n", font->number, font->name, font->charset); ctx->font_table = g_slist_append(ctx->font_table, font); return NMRTF_OK; } /* * Return the nth entry in the font table */ static NMRtfFont * rtf_get_font(NMRtfContext *ctx, int nth) { NMRtfFont *font; font = g_slist_nth_data(ctx->font_table, nth); return font; } /* * Step 1: * Isolate RTF keywords and send them to rtf_parse_keyword; * Push and pop state at the start and end of RTF groups; * Send text to rtf_dispatch_char for further processing. */ static int rtf_parse(NMRtfContext *ctx) { int status; guchar ch; guchar hex_byte = 0; int hex_count = 2; int len; if (ctx->input == NULL) return NMRTF_OK; while (rtf_get_char(ctx, &ch) == NMRTF_OK) { if (ctx->depth < 0) return NMRTF_STACK_UNDERFLOW; /* if we're parsing binary data, handle it directly */ if (ctx->ris == NMRTF_STATE_BIN) { if ((status = rtf_dispatch_char(ctx, ch)) != NMRTF_OK) return status; } else { switch (ch) { case '{': if (ctx->depth > NMRTF_MAX_DEPTH) return NMRTF_STACK_OVERFLOW; rtf_flush_data(ctx); if ((status = rtf_push_state(ctx)) != NMRTF_OK) return status; break; case '}': rtf_flush_data(ctx); /* for some reason there is always an unwanted '\par' at the end */ if (ctx->rds == NMRTF_STATE_NORMAL) { len = ctx->output->len; if (ctx->output->str[len-1] == '\n') ctx->output = g_string_truncate(ctx->output, len-1); } if ((status = rtf_pop_state(ctx)) != NMRTF_OK) return status; if (ctx->depth < 0) return NMRTF_STACK_OVERFLOW; break; case '\\': if ((status = rtf_parse_keyword(ctx)) != NMRTF_OK) return status; break; case 0x0d: case 0x0a: /* cr and lf are noise characters... */ break; default: if (ctx->ris == NMRTF_STATE_NORMAL) { if ((status = rtf_dispatch_char(ctx, ch)) != NMRTF_OK) return status; } else { /* parsing a hex encoded character */ if (ctx->ris != NMRTF_STATE_HEX) return NMRTF_ASSERTION; hex_byte = hex_byte << 4; if (isdigit(ch)) hex_byte += (char) ch - '0'; else { if (islower(ch)) { if (ch < 'a' || ch > 'f') return NMRTF_INVALID_HEX; hex_byte += (char) ch - 'a' + 10; } else { if (ch < 'A' || ch > 'F') return NMRTF_INVALID_HEX; hex_byte += (char) ch - 'A' + 10; } } hex_count--; if (hex_count == 0) { if ((status = rtf_dispatch_char(ctx, hex_byte)) != NMRTF_OK) return status; hex_count = 2; hex_byte = 0; ctx->ris = NMRTF_STATE_NORMAL; } } break; } } } if (ctx->depth < 0) return NMRTF_STACK_OVERFLOW; if (ctx->depth > 0) return NMRTF_UNMATCHED_BRACE; return NMRTF_OK; } /* * Push the current state onto stack */ static int rtf_push_state(NMRtfContext *ctx) { NMRtfStateSave *save = g_new0(NMRtfStateSave, 1); save->chp = ctx->chp; save->rds = ctx->rds; save->ris = ctx->ris; ctx->saved = g_slist_prepend(ctx->saved, save); ctx->ris = NMRTF_STATE_NORMAL; (ctx->depth)++; return NMRTF_OK; } /* * Restore the state at the top of the stack */ static int rtf_pop_state(NMRtfContext *ctx) { NMRtfStateSave *save_old; GSList *link_old; if (ctx->saved == NULL) return NMRTF_STACK_UNDERFLOW; save_old = ctx->saved->data; ctx->chp = save_old->chp; ctx->rds = save_old->rds; ctx->ris = save_old->ris; (ctx->depth)--; g_free(save_old); link_old = ctx->saved; ctx->saved = g_slist_remove_link(ctx->saved, link_old); g_slist_free_1(link_old); return NMRTF_OK; } /* * Step 2: * Get a control word (and its associated value) and * dispatch the control. */ static int rtf_parse_keyword(NMRtfContext *ctx) { int status = NMRTF_OK; guchar ch; gboolean param_set = FALSE; gboolean is_neg = FALSE; int param = 0; char *pch; char keyword[30]; char parameter[20]; keyword[0] = '\0'; parameter[0] = '\0'; if ((status = rtf_get_char(ctx, &ch)) != NMRTF_OK) return status; if (!isalpha(ch)) { /* a control symbol; no delimiter. */ keyword[0] = (char) ch; keyword[1] = '\0'; return rtf_dispatch_control(ctx, keyword, 0, param_set); } /* parse keyword */ for (pch = keyword; isalpha(ch); rtf_get_char(ctx, &ch)) { *pch = (char) ch; pch++; } *pch = '\0'; /* check for '-' indicated a negative parameter value */ if (ch == '-') { is_neg = TRUE; if ((status = rtf_get_char(ctx, &ch)) != NMRTF_OK) return status; } /* check for numerical param */ if (isdigit(ch)) { param_set = TRUE; for (pch = parameter; isdigit(ch); rtf_get_char(ctx, &ch)) { *pch = (char) ch; pch++; } *pch = '\0'; ctx->param = param = atoi(parameter); if (is_neg) ctx->param = param = -param; } /* space after control is optional, put character back if it is not a space */ if (ch != ' ') rtf_unget_char(ctx, ch); return rtf_dispatch_control(ctx, keyword, param, param_set); } /* * Route the character to the appropriate destination */ static int rtf_dispatch_char(NMRtfContext *ctx, guchar ch) { if (ctx->ris == NMRTF_STATE_BIN && --(ctx->bytes_to_skip) <= 0) ctx->ris = NMRTF_STATE_NORMAL; switch (ctx->rds) { case NMRTF_STATE_SKIP: return NMRTF_OK; case NMRTF_STATE_NORMAL: return rtf_print_char(ctx, ch); case NMRTF_STATE_FONTTABLE: if (ch == ';') { rtf_add_font_entry(ctx, ctx->chp.font_idx, ctx->ansi->str, ctx->chp.font_charset); g_string_truncate(ctx->ansi, 0); } else { return rtf_print_char(ctx, ch); } return NMRTF_OK; default: return NMRTF_OK; } } /* Handle a unicode character */ static int rtf_dispatch_unicode_char(NMRtfContext *ctx, gunichar ch) { switch (ctx->rds) { case NMRTF_STATE_SKIP: return NMRTF_OK; case NMRTF_STATE_NORMAL: case NMRTF_STATE_FONTTABLE: return rtf_print_unicode_char(ctx, ch); default: return NMRTF_OK; } } /* * Output a character */ static int rtf_print_char(NMRtfContext *ctx, guchar ch) { ctx->ansi = g_string_append_c(ctx->ansi, ch); return NMRTF_OK; } /* * Output a unicode character */ static int rtf_print_unicode_char(NMRtfContext *ctx, gunichar ch) { char buf[7]; int num; /* convert and flush the ansi buffer to the utf8 buffer */ rtf_flush_data(ctx); /* convert the unicode character to utf8 and add directly to the output buffer */ num = g_unichar_to_utf8((gunichar) ch, buf); buf[num] = 0; gaim_debug_info("novell", "converted unichar 0x%X to utf8 char %s\n", ch, buf); ctx->output = g_string_append(ctx->output, buf); return NMRTF_OK; } /* * Flush the output text */ static int rtf_flush_data(NMRtfContext *ctx) { int status = NMRTF_OK; char *conv_data = NULL; const char *enc = NULL; GError *gerror = NULL; if (ctx->rds == NMRTF_STATE_NORMAL && ctx->ansi->len > 0) { enc = get_current_encoding(ctx); conv_data = g_convert(ctx->ansi->str, ctx->ansi->len, "UTF-8", enc, NULL, NULL, &gerror); if (conv_data) { ctx->output = g_string_append(ctx->output, conv_data); g_free(conv_data); ctx->ansi = g_string_truncate(ctx->ansi, 0); } else { status = NMRTF_CONVERT_ERROR; gaim_debug_info("novell", "failed to convert data! error code = %d msg = %s\n", gerror->code, gerror->message); g_free(gerror); } } return status; } /* * Handle a property change */ static int rtf_apply_property(NMRtfContext *ctx, NMRtfProperty prop, int val) { if (ctx->rds == NMRTF_STATE_SKIP) /* If we're skipping text, */ return NMRTF_OK; /* don't do anything. */ /* Need to flush any temporary data before a property change*/ rtf_flush_data(ctx); switch (prop) { case NMRTF_PROP_FONT_IDX: ctx->chp.font_idx = val; break; case NMRTF_PROP_FONT_CHARSET: ctx->chp.font_charset = val; break; default: return NMRTF_BAD_TABLE; } return NMRTF_OK; } /* * Step 3. * Search the table for keyword and evaluate it appropriately. * * Inputs: * keyword: The RTF control to evaluate. * param: The parameter of the RTF control. * param_set: TRUE if the control had a parameter; (that is, if param is valid) * FALSE if it did not. */ static int rtf_dispatch_control(NMRtfContext *ctx, char *keyword, int param, gboolean param_set) { int idx; for (idx = 0; idx < table_size; idx++) { if (strcmp(keyword, rtf_symbols[idx].keyword) == 0) break; } if (idx == table_size) { if (ctx->skip_unknown) ctx->rds = NMRTF_STATE_SKIP; ctx->skip_unknown = FALSE; return NMRTF_OK; } /* found it! use kwd_type and action to determine what to do with it. */ ctx->skip_unknown = FALSE; switch (rtf_symbols[idx].kwd_type) { case NMRTF_KWD_PROP: if (rtf_symbols[idx].pass_default || !param_set) param = rtf_symbols[idx].default_val; return rtf_apply_property(ctx, rtf_symbols[idx].action, param); case NMRTF_KWD_CHAR: return rtf_dispatch_char(ctx, rtf_symbols[idx].action); case NMRTF_KWD_DEST: return rtf_change_destination(ctx, rtf_symbols[idx].action); case NMRTF_KWD_SPEC: return rtf_dispatch_special(ctx, rtf_symbols[idx].action); default: return NMRTF_BAD_TABLE; } return NMRTF_BAD_TABLE; } /* * Change to the destination specified. */ static int rtf_change_destination(NMRtfContext *ctx, NMRtfDestinationType type) { /* if we're skipping text, don't do anything */ if (ctx->rds == NMRTF_STATE_SKIP) return NMRTF_OK; switch (type) { case NMRTF_DEST_FONTTABLE: ctx->rds = NMRTF_STATE_FONTTABLE; g_string_truncate(ctx->ansi, 0); break; default: ctx->rds = NMRTF_STATE_SKIP; /* when in doubt, skip it... */ break; } return NMRTF_OK; } /* * Dispatch an RTF control that needs special processing */ static int rtf_dispatch_special(NMRtfContext *ctx, NMRtfSpecialKwd type) { int status = NMRTF_OK; guchar ch; if (ctx->rds == NMRTF_STATE_SKIP && type != NMRTF_SPECIAL_BIN) /* if we're skipping, and it's not */ return NMRTF_OK; /* the \bin keyword, ignore it. */ switch (type) { case NMRTF_SPECIAL_BIN: ctx->ris = NMRTF_STATE_BIN; ctx->bytes_to_skip = ctx->param; break; case NMRTF_SPECIAL_SKIP: ctx->skip_unknown = TRUE; break; case NMRTF_SPECIAL_HEX: ctx->ris = NMRTF_STATE_HEX; break; case NMRTF_SPECIAL_UNICODE: gaim_debug_info("novell", "parsing unichar\n"); status = rtf_dispatch_unicode_char(ctx, ctx->param); /* Skip next char */ if (status == NMRTF_OK) status = rtf_get_char(ctx, &ch); break; default: status = NMRTF_BAD_TABLE; break; } return status; } /* * Get the next character from the input stream */ static int rtf_get_char(NMRtfContext *ctx, guchar *ch) { if (ctx->nextch >= 0) { *ch = ctx->nextch; ctx->nextch = -1; } else { *ch = *(ctx->input); ctx->input++; } if (*ch) return NMRTF_OK; else return NMRTF_EOF; } /* * Move a character back into the input stream */ static int rtf_unget_char(NMRtfContext *ctx, guchar ch) { ctx->nextch = ch; return NMRTF_OK; } --- NEW FILE: nmrtf.h --- /* * nmrtf.h * * Copyright (c) 2004 Novell, Inc. All Rights Reserved. * * 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; version 2 of the License. * * 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 * */ #ifndef __NMRTF_H__ #define __NMRTF_H__ typedef struct _NMRtfContext NMRtfContext; NMRtfContext *nm_rtf_init(); char *nm_rtf_strip_formatting(NMRtfContext *ctx, const char *input); void nm_rtf_deinit(NMRtfContext *ctx); #endif Index: Makefile.am =================================================================== RCS file: /cvsroot/gaim/gaim/src/protocols/novell/Makefile.am,v retrieving revision 1.2 retrieving revision 1.3 diff -u -d -p -r1.2 -r1.3 --- Makefile.am 17 Apr 2004 18:28:30 -0000 1.2 +++ Makefile.am 12 Jun 2004 15:13:29 -0000 1.3 @@ -18,6 +18,8 @@ NOVELLSOURCES = \ nmmessage.c \ nmrequest.h \ nmrequest.c \ + nmrtf.h \ + nmrtf.c \ nmuser.h \ nmuser.c \ nmuserrecord.h \ Index: Makefile.mingw =================================================================== RCS file: /cvsroot/gaim/gaim/src/protocols/novell/Makefile.mingw,v retrieving revision 1.1 retrieving revision 1.2 diff -u -d -p -r1.1 -r1.2 --- Makefile.mingw 17 Apr 2004 13:55:28 -0000 1.1 +++ Makefile.mingw 12 Jun 2004 15:13:29 -0000 1.2 @@ -75,6 +75,7 @@ C_SRC = nmfield.c \ nmevent.c \ nmmessage.c \ nmrequest.c \ + nmrtf.c \ nmuser.c \ nmuserrecord.c \ novell.c Index: nmconn.c =================================================================== RCS file: /cvsroot/gaim/gaim/src/protocols/novell/nmconn.c,v retrieving revision 1.5 retrieving revision 1.6 diff -u -d -p -r1.5 -r1.6 --- nmconn.c 15 May 2004 14:00:30 -0000 1.5 +++ nmconn.c 12 Jun 2004 15:13:29 -0000 1.6 @@ -383,6 +383,7 @@ nm_send_request(NMConn * conn, char *cmd int bytes_to_send; int ret; NMField *request = NULL; + char *str = NULL; if (conn == NULL || cmd == NULL) return NMERR_BAD_PARM; @@ -415,14 +416,13 @@ nm_send_request(NMConn * conn, char *cmd /* Add the transaction id to the request fields */ if (rc == NM_OK) { - request = nm_copy_field_array(fields); - if (request) { - char *str = g_strdup_printf("%d", ++(conn->trans_id)); + if (fields) + request = nm_copy_field_array(fields); - request = nm_field_add_pointer(request, NM_A_SZ_TRANSACTION_ID, 0, - NMFIELD_METHOD_VALID, 0, - str, NMFIELD_TYPE_UTF8); - } + str = g_strdup_printf("%d", ++(conn->trans_id)); + request = nm_field_add_pointer(request, NM_A_SZ_TRANSACTION_ID, 0, + NMFIELD_METHOD_VALID, 0, + str, NMFIELD_TYPE_UTF8); } /* Send the request to the server */ Index: nmevent.c =================================================================== RCS file: /cvsroot/gaim/gaim/src/protocols/novell/nmevent.c,v retrieving revision 1.4 retrieving revision 1.5 diff -u -d -p -r1.4 -r1.5 --- nmevent.c 15 May 2004 14:00:31 -0000 1.4 +++ nmevent.c 12 Jun 2004 15:13:29 -0000 1.5 @@ -25,6 +25,7 @@ #include "nmfield.h" #include "nmconn.h" #include "nmuserrecord.h" +#include "nmrtf.h" struct _NMEvent { @@ -52,66 +53,6 @@ struct _NMEvent }; -/* Return a copy of src minus the RTF */ -static char * -_strip_rtf(const char *src, int len) -{ - const char *p = src; - char *q; - char *dest = g_new0(char, len + 1); - int level = 0; - - /* Make sure we are dealing with rtf */ - if (strncmp("{\\rtf1", src, strlen("{\\rtf1")) != 0) { - strncpy(dest, src, len); - return dest; - } - p += strlen("{\\rtf1"); - - q = dest; - while (*p != '\0') { - if (*p == '\\') { - if (level == 0) { - if (*(p + 1) == '\\' || *(p + 1) == '{' || *(p + 1) == '}') { - *q++ = *(p + 1); - p++; - } else if (*(p + 1) == 't' && *(p + 2) == 'a' && *(p + 3) == 'b') { - *q++ = '\t'; - p++; - } - } - p++; - } else if (*p == '{') { - level++; - p++; - } else if (*p == '}') { - level--; - p++; - } else if (level == 0) { - if ((*p == ' ' || *p == '\r') && (*(p + 1) != '\\')) { - p++; - - if (*p == '\n' && - (*(p + 1) == '{' || *(p + 1) == '}' || *(p + 1) == '\\')) { - p++; - } else { - /* We found some text */ - while (*p != '\0' && *p != '\\' && *p != '{' && *p != '}') { - *q++ = *p; - p++; - } - } - } else { - p++; - } - } else { - p++; - } - } - - return dest; -} - /* Handle getdetails response and set the new user record into the event */ static void _got_user_for_event(NMUser * user, NMERR_T ret_val, @@ -228,8 +169,11 @@ handle_receive_message(NMUser * user, NM /* Auto replies are not in RTF format! */ if (!autoreply) { + NMRtfContext *ctx; - nortf = _strip_rtf((const char *) msg, size); + ctx = nm_rtf_init(); + nortf = nm_rtf_strip_formatting(ctx, msg); + nm_rtf_deinit(ctx); gaim_debug(GAIM_DEBUG_INFO, "novell", "Message without RTF is %s\n", nortf); Index: nmuser.c =================================================================== RCS file: /cvsroot/gaim/gaim/src/protocols/novell/nmuser.c,v retrieving revision 1.6 retrieving revision 1.7 diff -u -d -p -r1.6 -r1.7 --- nmuser.c 15 May 2004 14:00:31 -0000 1.6 +++ nmuser.c 12 Jun 2004 15:13:29 -0000 1.7 @@ -31,20 +31,17 @@ /* This is the template that we wrap outgoing messages in, since the other * GW Messenger clients expect messages to be in RTF. */ -#define RTF_TEMPLATE "{\\rtf1\\fbidis\\ansi\\ansicpg1252\\deff0\\deflang1033"\ - "{\\fonttbl{\\f0\\fswiss\\fprq2\\fcharset0 "\ - "Microsoft Sans Serif;}}\n{\\colortbl ;\\red0"\ - "\\green0\\blue0;}\n\\viewkind4\\uc1\\pard\\ltrpar"\ - "\\li50\\ri50\\cf1\\f0\\fs20 %s\\par\n}" +#define RTF_TEMPLATE "{\\rtf1\\ansi\n"\ + "{\\fonttbl{\\f0\\fnil Unknown;}}\n"\ + "{\\colortbl ;\\red0\\green0\\blue0;}\n"\ + "\\uc1\\cf1\\f0\\fs24 %s\\par\n}" #define NM_MAX_MESSAGE_SIZE 2048 static NMERR_T nm_process_response(NMUser * user); - static void _update_contact_list(NMUser * user, NMField * fields); - -static void -_handle_multiple_get_details_login_cb(NMUser * user, NMERR_T ret_code, - gpointer resp_data, gpointer user_data); +static void _handle_multiple_get_details_login_cb(NMUser * user, NMERR_T ret_code, + gpointer resp_data, gpointer user_data); +static char * nm_rtfize_text(char *text); /** * See header for comments on on "public" functions @@ -540,7 +537,7 @@ NMERR_T nm_send_message(NMUser * user, NMMessage * message, nm_response_cb callback) { NMERR_T rc = NM_OK; - char *text; + char *text, *rtfized; NMField *fields = NULL, *tmp = NULL; NMRequest *req = NULL; NMConference *conf; @@ -572,21 +569,24 @@ nm_send_message(NMUser * user, NMMessage if (strlen(text) > NM_MAX_MESSAGE_SIZE) text[NM_MAX_MESSAGE_SIZE] = 0; + rtfized = nm_rtfize_text(text); + + gaim_debug_info("novell", "message text is: %s\n", text); + gaim_debug_info("novell", "message rtf is: %s\n", rtfized); + tmp = nm_field_add_pointer(tmp, NM_A_SZ_MESSAGE_BODY, 0, NMFIELD_METHOD_VALID, 0, - g_strdup_printf(RTF_TEMPLATE, text), NMFIELD_TYPE_UTF8); + rtfized, NMFIELD_TYPE_UTF8); tmp = nm_field_add_number(tmp, NM_A_UD_MESSAGE_TYPE, 0, NMFIELD_METHOD_VALID, 0, 0, NMFIELD_TYPE_UDWORD); tmp = nm_field_add_pointer(tmp, NM_A_SZ_MESSAGE_TEXT, 0, NMFIELD_METHOD_VALID, 0, - g_strdup(text), NMFIELD_TYPE_UTF8); + text, NMFIELD_TYPE_UTF8); fields = nm_field_add_pointer(fields, NM_A_FA_MESSAGE, 0, NMFIELD_METHOD_VALID, 0, tmp, NMFIELD_TYPE_ARRAY); tmp = NULL; - g_free(text); - /* Add participants */ count = nm_conference_get_participant_count(conf); for (i = 0; i < count; i++) { @@ -1116,9 +1116,6 @@ nm_send_remove_privacy_item(NMUser *user nm_conn_add_request_item(user->conn, req); } - if (rc == NM_OK) { - } - if (fields) nm_free_fields(&fields); @@ -1155,6 +1152,25 @@ nm_send_set_privacy_default(NMUser *user } NMERR_T +nm_send_keepalive(NMUser *user, nm_response_cb callback, gpointer data) +{ + NMERR_T rc = NM_OK; + NMRequest *req = NULL; + + if (user == NULL) + return NMERR_BAD_PARM; + + rc = nm_send_request(user->conn, "ping", NULL, &req); + if (rc == NM_OK && req) { + nm_request_set_callback(req, callback); + nm_request_set_user_define(req, data); + nm_conn_add_request_item(user->conn, req); + } + + return rc; +} + +NMERR_T nm_process_new_data(NMUser * user) { NMConn *conn; @@ -1449,6 +1465,7 @@ _create_privacy_list(NMUser * user, NMRe if (need_details) { + nm_request_add_ref(request); nm_send_multiple_get_details(user, need_details, _handle_multiple_get_details_login_cb, request); @@ -1648,6 +1665,7 @@ _handle_multiple_get_details_login_cb(NM if ((cb = nm_request_get_callback(request))) { cb(user, ret_code, nm_request_get_data(request), nm_request_get_user_define(request)); + nm_release_request(request); } } @@ -1793,8 +1811,8 @@ nm_call_handler(NMUser * user, NMRequest if (list != NULL) { done = FALSE; - nm_request_add_ref(request); nm_request_set_user_define(request, list); + nm_request_add_ref(request); for (node = list; node; node = node->next) { nm_send_get_details(user, (const char *) node->data, @@ -1909,7 +1927,6 @@ nm_call_handler(NMUser * user, NMRequest cb(user, ret_code, nm_request_get_data(request), nm_request_get_user_define(request)); - } return rc; @@ -1935,7 +1952,9 @@ nm_process_response(NMUser * user) req = nm_conn_find_request(conn, atoi((char *) field->ptr_value)); if (req != NULL) { rc = nm_call_handler(user, req, fields); + nm_conn_remove_request_item(conn, req); } + } } @@ -2220,3 +2239,82 @@ _update_contact_list(NMUser * user, NMFi cursor++; } } + +static char * +nm_rtfize_text(char *text) +{ + GString *gstr = NULL; + unsigned char *pch; + char *uni_str = NULL, *rtf = NULL; + int bytes; + gunichar uc; + + gstr = g_string_sized_new(strlen(text)*2); + pch = text; + while (*pch) { + if ((*pch) <= 0x7F) { + switch (*pch) { + case '{': + case '}': + case '\\': + gstr = g_string_append_c(gstr, '\\'); + gstr = g_string_append_c(gstr, *pch); + break; + case '\n': + gstr = g_string_append(gstr, "\\par "); + break; + default: + gstr = g_string_append_c(gstr, *pch); + break; + } + pch++; + } else { + /* convert the utf-8 character to ucs-4 for rtf encoding */ + if(*pch <= 0xDF) { + uc = ((((gunichar)pch[0]) & 0x001F) << 6) | + (((gunichar)pch[1]) & 0x003F); + bytes = 2; + } else if(*pch <= 0xEF) { + uc = ((((gunichar)pch[0]) & 0x000F) << 12) | + ((((gunichar)pch[1]) & 0x003F) << 6) | + (((gunichar)pch[2]) & 0x003F); + bytes = 3; + } else if (*pch <= 0xF7) { + uc = ((((gunichar)pch[0]) & 0x0007) << 18) | + ((((gunichar)pch[1]) & 0x003F) << 12) | + ((((gunichar)pch[2]) & 0x003F) << 6) | + (((gunichar)pch[3]) & 0x003F); + bytes = 4; + } else if (*pch <= 0xFB) { + uc = ((((gunichar)pch[0]) & 0x0003) << 24) | + ((((gunichar)pch[1]) & 0x003F) << 18) | + ((((gunichar)pch[2]) & 0x003F) << 12) | + ((((gunichar)pch[3]) & 0x003F) << 6) | + (((gunichar)pch[4]) & 0x003F); + bytes = 5; + } else if (*pch <= 0xFD) { + uc = ((((gunichar)pch[0]) & 0x0001) << 30) | + ((((gunichar)pch[1]) & 0x003F) << 24) | + ((((gunichar)pch[2]) & 0x003F) << 18) | + ((((gunichar)pch[3]) & 0x003F) << 12) | + ((((gunichar)pch[4]) & 0x003F) << 6) | + (((gunichar)pch[5]) & 0x003F); + bytes = 6; + } else { + /* should never happen ... bogus utf-8! */ + gaim_debug_info("novell", "bogus utf-8 lead byte: 0x%X\n", pch[0]); + uc = 0x003F; + bytes = 1; + } + uni_str = g_strdup_printf("\\u%d?", uc); + gaim_debug_info("novell", "unicode escaped char %s\n", uni_str); + gstr = g_string_append(gstr, uni_str); + pch += bytes; + g_free(uni_str); + } + } + + rtf = g_strdup_printf(RTF_TEMPLATE, gstr->str); + g_string_free(gstr, TRUE); + return rtf; +} Index: nmuser.h =================================================================== RCS file: /cvsroot/gaim/gaim/src/protocols/novell/nmuser.h,v retrieving revision 1.4 retrieving revision 1.5 diff -u -d -p -r1.4 -r1.5 --- nmuser.h 15 May 2004 14:00:31 -0000 1.4 +++ nmuser.h 12 Jun 2004 15:13:29 -0000 1.5 @@ -124,6 +124,9 @@ struct _NMUser */ gpointer client_data; + /* Have the privacy lists been synched yet */ + gboolean privacy_synched; + }; #define NM_STATUS_UNKNOWN 0 @@ -559,6 +562,18 @@ nm_send_set_privacy_default(NMUser *user nm_response_cb callback, gpointer data); /** + * Send a ping to the server + * + * @param user The logged in User + * @param callback Function to call when we get the response from the server + * @param data User defined data + * + * @return NM_OK if successfully sent, error otherwise + */ +NMERR_T +nm_send_keepalive(NMUser *user, nm_response_cb callback, gpointer data); + +/** * Reads a response/event from the server and processes it. * * @param user The logged in User Index: novell.c =================================================================== RCS file: /cvsroot/gaim/gaim/src/protocols/novell/novell.c,v retrieving revision 1.17 retrieving revision 1.18 diff -u -d -p -r1.17 -r1.18 --- novell.c 23 May 2004 17:27:43 -0000 1.17 +++ novell.c 12 Jun 2004 15:13:29 -0000 1.18 @@ -37,8 +37,6 @@ static GaimPlugin *my_protocol = NULL; -static gboolean set_permit = FALSE; - static gboolean _is_disconnect_error(NMERR_T err); @@ -104,7 +102,6 @@ _login_resp_cb(NMUser * user, NMERR_T re } _sync_contact_list(user); - _sync_privacy_lists(user); /* Tell Gaim that we are connected */ gaim_connection_set_state(gc, GAIM_CONNECTED); @@ -1685,6 +1682,9 @@ _evt_receive_message(NMUser * user, NMEv GaimConversation *gconv; NMConference *conference; GaimConvImFlags imflags; + char *text = NULL; + + text = g_markup_escape_text(nm_event_get_text(event), -1); conference = nm_event_get_conference(event); if (conference) { @@ -1703,7 +1703,7 @@ _evt_receive_message(NMUser * user, NMEv serv_got_im(gaim_account_get_connection(user->client_data), nm_user_record_get_display_id(user_record), - nm_event_get_text(event), imflags, + text, imflags, nm_event_get_gmt(event)); gconv = gaim_find_conversation_with_account( @@ -1715,8 +1715,7 @@ _evt_receive_message(NMUser * user, NMEv if (contact) { gaim_conversation_set_title( - gconv, - nm_contact_get_display_name(contact)); + gconv, nm_contact_get_display_name(contact)); } else { @@ -1759,12 +1758,12 @@ _evt_receive_message(NMUser * user, NMEv serv_got_chat_in(gaim_account_get_connection(user->client_data), gaim_conv_chat_get_id(GAIM_CONV_CHAT(chat)), - name, - 0, nm_event_get_text(event), - nm_event_get_gmt(event)); + name, 0, text, nm_event_get_gmt(event)); } } } + + g_free(text); } static void @@ -2161,7 +2160,7 @@ novell_send_im(GaimConnection * gc, cons return 0; /* Create a new message */ - message = nm_create_message(gaim_markup_strip_html(message_body)); + message = nm_create_message(message_body); /* Need to get the DN for the buddy so we can look up the convo */ dn = nm_lookup_dn(user, name); @@ -2371,7 +2370,7 @@ novell_chat_send(GaimConnection * gc, in if (user == NULL) return -1; - message = nm_create_message(gaim_markup_strip_html(text)); + message = nm_create_message(text); for (cnode = user->conferences; cnode != NULL; cnode = cnode->next) { conference = cnode->data; @@ -3083,8 +3082,9 @@ novell_set_permit_deny(GaimConnection *g if (user == NULL) return; - if (set_permit == FALSE) { - set_permit = TRUE; + if (user->privacy_synched == FALSE) { + _sync_privacy_lists(user); + user->privacy_synched = TRUE; return; } @@ -3285,7 +3285,7 @@ static GList * novell_blist_node_menu(GaimBlistNode *node) { GList *list = NULL; - GaimBlistNodeAction *act; + GaimBlistNodeAction *act; if(GAIM_BLIST_NODE_IS_BUDDY(node)) { act = gaim_blist_node_action_new(_("Initiate _Chat"), @@ -3296,6 +3296,23 @@ novell_blist_node_menu(GaimBlistNode *no return list; } +static void +novell_keepalive(GaimConnection *gc) +{ + NMUser *user; + NMERR_T rc = NM_OK; + + if (gc == NULL) + return; + + user = gc->proto_data; + if (user == NULL) + return; + + rc = nm_send_keepalive(user, NULL, NULL); + _check_for_disconnect(user, rc); +} + static GaimPluginProtocolInfo prpl_info = { GAIM_PRPL_API_VERSION, 0, @@ -3328,12 +3345,12 @@ static GaimPluginProtocolInfo prpl_info novell_set_permit_deny, NULL, /* warn */ NULL, /* join_chat */ - NULL, /* reject_chat ?? */ + NULL, /* reject_chat */ novell_chat_invite, /* chat_invite */ novell_chat_leave, NULL, /* chat_whisper */ novell_chat_send, - NULL, /* keepalive */ + novell_keepalive, NULL, /* register_user */ NULL, /* get_cb_info */ NULL, /* get_cb_away_msg */ |