# HG changeset patch # User Luke Schierer # Date 1087053209 0 # Node ID 54fb1f466953754d0af3f845074c6aae807a634c # Parent 3e8619644a8abde17451ba61540e3563ef314248 [gaim-migrate @ 10069] " - 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 committer: Tailor Script diff -r 3e8619644a8a -r 54fb1f466953 ChangeLog --- a/ChangeLog Sat Jun 12 00:00:01 2004 +0000 +++ b/ChangeLog Sat Jun 12 15:13:29 2004 +0000 @@ -23,6 +23,8 @@ defaults for the next pounce. Bug Fixes: + * Better handling of character sets in RTF for Novell (Mike Stoddard of + Novell) * Non-looping animated icons no longer cause Gaim to freeze * Fixed a crash in SILC that sometimes happened when resolving the buddy list (Pekka Riikonen) diff -r 3e8619644a8a -r 54fb1f466953 src/protocols/novell/Makefile.am --- a/src/protocols/novell/Makefile.am Sat Jun 12 00:00:01 2004 +0000 +++ b/src/protocols/novell/Makefile.am Sat Jun 12 15:13:29 2004 +0000 @@ -18,6 +18,8 @@ nmmessage.c \ nmrequest.h \ nmrequest.c \ + nmrtf.h \ + nmrtf.c \ nmuser.h \ nmuser.c \ nmuserrecord.h \ diff -r 3e8619644a8a -r 54fb1f466953 src/protocols/novell/Makefile.mingw --- a/src/protocols/novell/Makefile.mingw Sat Jun 12 00:00:01 2004 +0000 +++ b/src/protocols/novell/Makefile.mingw Sat Jun 12 15:13:29 2004 +0000 @@ -75,6 +75,7 @@ nmevent.c \ nmmessage.c \ nmrequest.c \ + nmrtf.c \ nmuser.c \ nmuserrecord.c \ novell.c diff -r 3e8619644a8a -r 54fb1f466953 src/protocols/novell/nmconn.c --- a/src/protocols/novell/nmconn.c Sat Jun 12 00:00:01 2004 +0000 +++ b/src/protocols/novell/nmconn.c Sat Jun 12 15:13:29 2004 +0000 @@ -383,6 +383,7 @@ 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 @@ /* 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 */ diff -r 3e8619644a8a -r 54fb1f466953 src/protocols/novell/nmevent.c --- a/src/protocols/novell/nmevent.c Sat Jun 12 00:00:01 2004 +0000 +++ b/src/protocols/novell/nmevent.c Sat Jun 12 15:13:29 2004 +0000 @@ -25,6 +25,7 @@ #include "nmfield.h" #include "nmconn.h" #include "nmuserrecord.h" +#include "nmrtf.h" struct _NMEvent { @@ -52,66 +53,6 @@ }; -/* 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 @@ /* 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); diff -r 3e8619644a8a -r 54fb1f466953 src/protocols/novell/nmrtf.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/novell/nmrtf.c Sat Jun 12 15:13:29 2004 +0000 @@ -0,0 +1,829 @@ +/* + * 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 +#include +#include +#include +#include +#include +#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; +} diff -r 3e8619644a8a -r 54fb1f466953 src/protocols/novell/nmrtf.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/protocols/novell/nmrtf.h Sat Jun 12 15:13:29 2004 +0000 @@ -0,0 +1,30 @@ +/* + * 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 diff -r 3e8619644a8a -r 54fb1f466953 src/protocols/novell/nmuser.c --- a/src/protocols/novell/nmuser.c Sat Jun 12 00:00:01 2004 +0000 +++ b/src/protocols/novell/nmuser.c Sat Jun 12 15:13:29 2004 +0000 @@ -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 @@ 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 @@ 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_conn_add_request_item(user->conn, req); } - if (rc == NM_OK) { - } - if (fields) nm_free_fields(&fields); @@ -1155,6 +1152,25 @@ } 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 @@ 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 @@ 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 @@ if (list != NULL) { done = FALSE; + nm_request_set_user_define(request, list); nm_request_add_ref(request); - nm_request_set_user_define(request, list); for (node = list; node; node = node->next) { nm_send_get_details(user, (const char *) node->data, @@ -1909,7 +1927,6 @@ cb(user, ret_code, nm_request_get_data(request), nm_request_get_user_define(request)); - } return rc; @@ -1935,7 +1952,9 @@ 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 @@ 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; +} diff -r 3e8619644a8a -r 54fb1f466953 src/protocols/novell/nmuser.h --- a/src/protocols/novell/nmuser.h Sat Jun 12 00:00:01 2004 +0000 +++ b/src/protocols/novell/nmuser.h Sat Jun 12 15:13:29 2004 +0000 @@ -124,6 +124,9 @@ */ gpointer client_data; + /* Have the privacy lists been synched yet */ + gboolean privacy_synched; + }; #define NM_STATUS_UNKNOWN 0 @@ -559,6 +562,18 @@ 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 diff -r 3e8619644a8a -r 54fb1f466953 src/protocols/novell/novell.c --- a/src/protocols/novell/novell.c Sat Jun 12 00:00:01 2004 +0000 +++ b/src/protocols/novell/novell.c Sat Jun 12 15:13:29 2004 +0000 @@ -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 @@ } _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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 */