This is patch05 to PennMUSH 1.8.3. After applying this patch, you will have version 1.8.4p5 To apply this patch, save it to a file in your top-level 1.8.3p4 MUSH directory, and do the following: patch -p0 < 1.8.3-patch05 make update make install If you use GNU patch 2.2, you probably want the above to be 'patch -b -p0', not just 'patch -p0'. Unix (or cygwin) users need not worry about failed hunks in src/switchinc.c, hdrs/switches.h, hdrs/cmds.h, or hdrs/funs.h. These files are automatically rebuilt on compile. On the off chance they appear not to be, simply rm them and re-run make. Then @shutdown and restart your MUSH. - Shawn/Raevnos In this patch: Major changes: * Significant rewrite of ansi parsing and better ansi support for many string-handling functions. Patch by Sketch. * Rewrite of the softcode regression testing framework, and addition of more tests. [SW] Minor changes: * Store a pointer to the start of a player's mailbox in objdata instead of the connection struct. * Experimental rewrite of hash tables to use the cuckoo hashing algorithm, with constant-time lookups even in the worst case. (And appears to have generally faster lookup even in normal usage.) * Regular expression @sitelocks save the compiled regexp instead of recompiling every time the rule is tested. * Added %4 to @pageformat, which is the default page message. Commands: * Added @message, which makes it easy to use @chatformat or @pageformat via @hooks, or to create your own *format. Functions: * Added message(), the function version of @message. Fixes: * decode64() does better validation of its input. [SW] * Various compile fixes reported by Interevis and Kimiko. Win32 patched by Intrevis. * @sitelock does better error reporting. [SW] * Crash bug related to regeditall fixed. * @decompile didn't handle attribute trees correctly. * Compile failure in funstr.c on some systems. Fixed by Boris. * '@set =foo' failed silently. Reported by Talvo. * Fixes from 1.8.2p7 Prereq: 1.8.3p4 =================================================================== --- Patchlevel (.../p4) (revision 1119) +++ Patchlevel (.../p5) (revision 1119) @@ -1,2 +1,2 @@ Do not edit this file. It is maintained by the official PennMUSH patches. -This is PennMUSH 1.8.3p4 +This is PennMUSH 1.8.3p5 Index: configure =================================================================== --- configure (.../p4) (revision 1119) +++ configure (.../p5) (revision 1119) @@ -17943,6 +17943,8 @@ ac_config_files="$ac_config_files game/txt/compose.sh" +ac_config_files="$ac_config_files test/alltests.sh" + cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure @@ -18487,6 +18489,7 @@ "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; "src/Makefile") CONFIG_FILES="$CONFIG_FILES src/Makefile" ;; "game/txt/compose.sh") CONFIG_FILES="$CONFIG_FILES game/txt/compose.sh" ;; + "test/alltests.sh") CONFIG_FILES="$CONFIG_FILES test/alltests.sh" ;; *) { { echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5 echo "$as_me: error: invalid argument: $ac_config_target" >&2;} @@ -19033,6 +19036,7 @@ case $ac_file$ac_mode in "game/txt/compose.sh":F) chmod +x game/txt/compose.sh ;; + "test/alltests.sh":F) chmod +x test/alltests.sh ;; esac done # for ac_tag Index: Makefile.in =================================================================== --- Makefile.in (.../p4) (revision 1119) +++ Makefile.in (.../p5) (revision 1119) @@ -3,7 +3,7 @@ # - System configuration - # VERSION=1.8.3 -PATCHLEVEL=3 +PATCHLEVEL=5 # # This section of the file should be automatically configured by @@ -80,6 +80,7 @@ netmud: (cd src; make netmud "CC=$(CC)" "CCFLAGS=$(CCFLAGS)" \ + "SQL_CFLAGS=$(SQL_CFLAGS)" "SQL_LDFLAGS=$(SQL_LDFLAGS)" \ "LDFLAGS=$(LDFLAGS)" "CLIBS=$(CLIBS)" ) access: @@ -162,6 +163,7 @@ update-hdr: -@@TOUCH@ options.h.dist + -@sleep 2 -@@PERL@ utils/update.pl options.h options.h.dist test: netmud Index: options.h.dist =================================================================== --- options.h.dist (.../p4) (revision 1119) +++ options.h.dist (.../p5) (revision 1119) @@ -58,7 +58,7 @@ * when decompressing, and considerably slower when compressing. * (But you decompress a lot more often). Compression ratio * is worse than Huffman for small dbs (<1.5Mb of text), but - * better for larger dbs. Win32 systems must use this. + * better for larger dbs. * 4 - Raevnos's almost 8-bit clean version of the word-based algorithm. * Prefer 3 unless you need extended characters. This algorithm * can encode all characters except 0x06. Index: src/sql.c =================================================================== --- src/sql.c (.../p4) (revision 1119) +++ src/sql.c (.../p5) (revision 1119) @@ -427,23 +427,14 @@ break; } if (cell && *cell) { - if (strchr(cell, ESC_CHAR)) { - /* Old style ANSI string */ + if (strchr(cell, TAG_START) || strchr(cell, ESC_CHAR)) { + /* Either old or new style ANSI string. */ tbp = tbuf; - as = parse_ansi_string_real(cell, 1); + as = parse_ansi_string(cell); safe_ansi_string(as, 0, as->len, tbuf, &tbp); *tbp = '\0'; free_ansi_string(as); cell = tbuf; - } else if (strchr(cell, TAG_START)) { - /* Either old or new style ANSI string, - * We assume new style. */ - tbp = tbuf; - as = parse_ansi_string_real(cell, 2); - safe_ansi_string(as, 0, as->len, tbuf, &tbp); - *tbp = '\0'; - free_ansi_string(as); - cell = tbuf; } } notify_format(player, "Row %d, Field %s: %s", @@ -478,7 +469,6 @@ #ifdef HAVE_MYSQL MYSQL_FIELD *fields = NULL; #endif - if (sql_platform() == SQL_PLATFORM_DISABLED) { safe_str(T(e_disabled), buff, bp); return; @@ -490,7 +480,6 @@ if (!fetch_ufun_attrib(args[0], executor, &ufun, 1)) return; - if (nargs > 2) { /* we have an output separator in args[2]. */ osep = args[2]; @@ -504,10 +493,8 @@ for (i = 0; i < 10; i++) wenv[i] = NULL; - qres = sql_query(args[1], &affected_rows); sql_test_result(qres); - /* Get results. A silent query (INSERT, UPDATE, etc.) will return NULL */ switch (sql_platform()) { #ifdef HAVE_MYSQL @@ -608,24 +595,15 @@ default: break; } - if (cell) { - if (strchr(cell, ESC_CHAR)) { - /* Old style ANSI string */ + if (cell && *cell) { + if (strchr(cell, TAG_START) || strchr(cell, ESC_CHAR)) { + /* Either old or new style ANSI string. */ tbp = buffs[i]; - as = parse_ansi_string_real(cell, 1); + as = parse_ansi_string(cell); safe_ansi_string(as, 0, as->len, buffs[i], &tbp); *tbp = '\0'; free_ansi_string(as); cell = buffs[i]; - } else if (strchr(cell, TAG_START)) { - /* Either old or new style ANSI string, - * We assume new style. */ - tbp = buffs[i]; - as = parse_ansi_string_real(cell, 2); - safe_ansi_string(as, 0, as->len, buffs[i], &tbp); - *tbp = '\0'; - free_ansi_string(as); - cell = buffs[i]; } } wenv[i + 1] = cell; @@ -635,7 +613,8 @@ /* Now call the ufun. */ if (call_ufun(&ufun, wenv, i + 1, rbuff, executor, enactor, pe_info)) goto finished; - if (safe_str(rbuff, buff, bp) && funccount == pe_info->fun_invocations) + if (safe_str(rbuff, buff, bp) + && funccount == pe_info->fun_invocations) goto finished; funccount = pe_info->fun_invocations; } @@ -655,7 +634,6 @@ int i; int numfields, numrows; ansi_string *as; - if (sql_platform() == SQL_PLATFORM_DISABLED) { safe_str(T(e_disabled), buff, bp); return; @@ -676,9 +654,7 @@ } qres = sql_query(args[0], &affected_rows); - sql_test_result(qres); - /* Get results. A silent query (INSERT, UPDATE, etc.) will return NULL */ switch (sql_platform()) { #ifdef HAVE_MYSQL @@ -748,23 +724,14 @@ break; } if (cell && *cell) { - if (strchr(cell, ESC_CHAR)) { - /* Old style ANSI string */ + if (strchr(cell, TAG_START) || strchr(cell, ESC_CHAR)) { + /* Either old or new style ANSI string. */ tbp = tbuf; - as = parse_ansi_string_real(cell, 1); + as = parse_ansi_string(cell); safe_ansi_string(as, 0, as->len, tbuf, &tbp); *tbp = '\0'; free_ansi_string(as); cell = tbuf; - } else if (strchr(cell, TAG_START)) { - /* Either old or new style ANSI string, - * We assume new style. */ - tbp = tbuf; - as = parse_ansi_string_real(cell, 2); - safe_ansi_string(as, 0, as->len, tbuf, &tbp); - *tbp = '\0'; - free_ansi_string(as); - cell = tbuf; } if (safe_str(cell, buff, bp)) goto finished; /* We filled the buffer, best stop */ Index: src/extchat.c =================================================================== --- src/extchat.c (.../p4) (revision 1119) +++ src/extchat.c (.../p5) (revision 1119) @@ -3368,9 +3368,9 @@ if (!(((flags & 1) && Chanuser_Quiet(u)) || Chanuser_Gag(u) || (IsPlayer(current) && !Connected(current)))) { - if (!messageformat(current, "CHATFORMAT", player, - na_flags | ((flags & CB_NOSPOOF) ? 0 : NA_SPOOF), - ctype, cname, message, name, title, tbuf1)) { + if (!vmessageformat(current, "CHATFORMAT", player, + na_flags | ((flags & CB_NOSPOOF) ? 0 : NA_SPOOF), + 6, ctype, cname, message, name, title, tbuf1)) { notify_anything(player, na_one, ¤t, ns_esnotify, na_flags | ((flags & CB_NOSPOOF) ? 0 : NA_SPOOF), tbuf1); Index: src/utils.c =================================================================== --- src/utils.c (.../p4) (revision 1119) +++ src/utils.c (.../p5) (revision 1119) @@ -231,13 +231,9 @@ int pe_ret; char const *ap; - int old_re_subpatterns; - int *old_re_offsets; - ansi_string *old_re_from; + struct re_save rsave; - old_re_subpatterns = global_eval_context.re_subpatterns; - old_re_offsets = global_eval_context.re_offsets; - old_re_from = global_eval_context.re_from; + save_regexp_context(&rsave); /* Make sure we have a ufun first */ if (!ufun) @@ -261,6 +257,7 @@ /* Set all the regexp patterns to NULL so they are not * propogated */ + global_eval_context.re_code = NULL; global_eval_context.re_subpatterns = -1; global_eval_context.re_offsets = NULL; global_eval_context.re_from = NULL; @@ -286,9 +283,7 @@ } /* Restore regexp patterns */ - global_eval_context.re_offsets = old_re_offsets; - global_eval_context.re_subpatterns = old_re_subpatterns; - global_eval_context.re_from = old_re_from; + restore_regexp_context(&rsave); return pe_ret; } @@ -323,9 +318,7 @@ ATTR *attrib; char *saver[NUMQ]; - int old_re_subpatterns; - int *old_re_offsets; - ansi_string *old_re_from; + struct re_save rsave; /* Make sure we have a valid object to call first */ if (!GoodObject(thing) || IsGarbage(thing)) @@ -350,9 +343,7 @@ save_global_regs("localize", saver); /* Store regepx info */ - old_re_subpatterns = global_eval_context.re_subpatterns; - old_re_offsets = global_eval_context.re_offsets; - old_re_from = global_eval_context.re_from; + save_regexp_context(&rsave); /* If the user doesn't care about the return of the expression, * then use our own rbuff. @@ -377,6 +368,7 @@ /* Set all the regexp patterns to NULL so they are not * propogated */ + global_eval_context.re_code = NULL; global_eval_context.re_subpatterns = -1; global_eval_context.re_offsets = NULL; global_eval_context.re_from = NULL; @@ -402,10 +394,7 @@ } /* Restore regexp patterns */ - global_eval_context.re_offsets = old_re_offsets; - global_eval_context.re_subpatterns = old_re_subpatterns; - global_eval_context.re_from = old_re_from; - + restore_regexp_context(&rsave); restore_global_regs("localize", saver); return pe_ret; Index: src/malias.c =================================================================== --- src/malias.c (.../p4) (revision 1119) +++ src/malias.c (.../p5) (revision 1119) @@ -142,7 +142,7 @@ return; } if (!alias || !*alias || !tolist || !*tolist) { - notify(player, T("MAIL: What alias do you want to create?.")); + notify(player, T("MAIL: What alias do you want to create?")); return; } if (*alias != MALIAS_TOKEN) { Index: src/wiz.c =================================================================== --- src/wiz.c (.../p4) (revision 1119) +++ src/wiz.c (.../p5) (revision 1119) @@ -1028,7 +1028,6 @@ void do_debug_examine(dbref player, const char *name) { - MAIL *mp; dbref thing; if (!Hasprivs(player)) { @@ -1052,8 +1051,7 @@ switch (Typeof(thing)) { case TYPE_PLAYER: - mp = desc_mail(thing); - notify_format(player, T("First mail sender: %d"), mp ? mp->from : NOTHING); + break; case TYPE_THING: notify_format(player, "Location: %d", Location(thing)); notify_format(player, "Home: %d", Home(thing)); @@ -1447,7 +1445,7 @@ return; } if (opts && *opts) { - int can, cant; + uint32_t can, cant; dbref whod = AMBIGUOUS; /* Options form of the command. */ if (!site || !*site) { @@ -1467,20 +1465,23 @@ } } - add_access_sitelock(player, site, whod, can, cant); - write_access_file(); - if (whod != AMBIGUOUS) { - notify_format(player, - T("Site %s access options for %s(%s) set to %s"), - site, Name(whod), unparse_dbref(whod), opts); - do_log(LT_WIZ, player, NOTHING, - T("*** SITELOCK *** %s for %s(%s) --> %s"), site, - Name(whod), unparse_dbref(whod), opts); - } else { - notify_format(player, T("Site %s access options set to %s"), site, opts); - do_log(LT_WIZ, player, NOTHING, "*** SITELOCK *** %s --> %s", site, opts); + if (add_access_sitelock(player, site, whod, can, cant)) { + write_access_file(); + if (whod != AMBIGUOUS) { + notify_format(player, + T("Site %s access options for %s(%s) set to %s"), + site, Name(whod), unparse_dbref(whod), opts); + do_log(LT_WIZ, player, NOTHING, + T("*** SITELOCK *** %s for %s(%s) --> %s"), site, + Name(whod), unparse_dbref(whod), opts); + } else { + notify_format(player, T("Site %s access options set to %s"), site, + opts); + do_log(LT_WIZ, player, NOTHING, "*** SITELOCK *** %s --> %s", site, + opts); + } + return; } - return; } else { /* Backward-compatible non-options form of the command, * or @sitelock/name @@ -1491,16 +1492,18 @@ do_list_access(player); return; case SITELOCK_ADD: - add_access_sitelock(player, site, AMBIGUOUS, 0, ACS_CREATE); - write_access_file(); - notify_format(player, T("Site %s locked"), site); - do_log(LT_WIZ, player, NOTHING, "*** SITELOCK *** %s", site); + if (add_access_sitelock(player, site, AMBIGUOUS, 0, ACS_CREATE)) { + write_access_file(); + notify_format(player, T("Site %s locked"), site); + do_log(LT_WIZ, player, NOTHING, "*** SITELOCK *** %s", site); + } break; case SITELOCK_BAN: - add_access_sitelock(player, site, AMBIGUOUS, 0, ACS_DEFAULT); - write_access_file(); - notify_format(player, T("Site %s banned"), site); - do_log(LT_WIZ, player, NOTHING, "*** SITELOCK *** %s", site); + if (add_access_sitelock(player, site, AMBIGUOUS, 0, ACS_DEFAULT)) { + write_access_file(); + notify_format(player, T("Site %s banned"), site); + do_log(LT_WIZ, player, NOTHING, "*** SITELOCK *** %s", site); + } break; case SITELOCK_CHECK:{ struct access *ap; Index: src/services.c =================================================================== --- src/services.c (.../p4) (revision 1119) +++ src/services.c (.../p5) (revision 1119) @@ -30,7 +30,7 @@ #define THIS_SERVICE_DISPLAY "PennMUSH for Win32" int WIN32_CDECL main(int argc, char **argv); -void mainthread(int argc, char **argv); +int mainthread(int argc, char **argv); SERVICE_STATUS ssStatus; /* current status of the service */ @@ -431,7 +431,7 @@ /* start up the main MUSH code */ - mainthread(argc, argv); + exit(mainthread(argc, argv)); } /* end of worker_thread */ Index: src/conf.c =================================================================== --- src/conf.c (.../p4) (revision 1119) +++ src/conf.c (.../p5) (revision 1119) @@ -679,7 +679,7 @@ maxval); } } - if (from_cmd && ((!GoodObject(n) && n != NOTHING) || IsGarbage(n))) { + if (from_cmd && ((!GoodObject(n) && n != NOTHING) || (n > 0 && IsGarbage(n)))) { do_rawlog(LT_ERR, T("CONFIG: attempt to set option %s to a bad dbref (#%d)"), opt, n); Index: src/db.c =================================================================== --- src/db.c (.../p4) (revision 1119) +++ src/db.c (.../p5) (revision 1119) @@ -1219,7 +1219,7 @@ */ tb2 = getstring_noalloc(f); if (strchr(tb2, TAG_START) || strchr(tb2, ESC_CHAR)) { - as = parse_ansi_string_real(tb2, 1); + as = parse_ansi_string(tb2); tb2 = tbuf2; safe_ansi_string(as, 0, as->len, tbuf2, &tb2); *(tb2) = '\0'; @@ -1306,7 +1306,7 @@ if (!(globals.indb_flags & DBF_SPIFFY_AF_ANSI)) { if (strchr(value, ESC_CHAR) || strchr(value, TAG_START)) { char *vp = value; - as = parse_ansi_string_real(value, 1); + as = parse_ansi_string(value); safe_ansi_string(as, 0, as->len, value, &vp); *vp = '\0'; free_ansi_string(as); @@ -1801,10 +1801,10 @@ static void init_objdata_htab(int size, void (*free_data) (void *)) { - if (size < 128) - size = 128; - hash_init(&htab_objdata, size, 4, free_data); - hashinit(&htab_objdata_keys, 8, 32); + if (size < 10) + size = 10; + hash_init(&htab_objdata, size, free_data); + hashinit(&htab_objdata_keys, 8); } /** Add data to the object data hashtable. @@ -1828,7 +1828,7 @@ mush_strncpy(keyname, tprintf("%s_#%d", keybase, thing), BUFFER_LEN); hashdelete(keyname, &htab_objdata); if (data) { - if (hashadd(keyname, data, &htab_objdata) < 0) + if (!hashadd(keyname, data, &htab_objdata)) return NULL; if (hash_find(&htab_objdata_keys, keybase) == NULL) { char *newkey = mush_strdup(keyname, "objdata.key"); Index: src/function.c =================================================================== --- src/function.c (.../p4) (revision 1119) +++ src/function.c (.../p5) (revision 1119) @@ -45,6 +45,25 @@ * Utilities. */ +/* Save and restore regexp data */ +void +save_regexp_context(struct re_save *save) +{ + save->re_code = global_eval_context.re_code; + save->re_from = global_eval_context.re_from; + save->re_subpatterns = global_eval_context.re_subpatterns; + save->re_offsets = global_eval_context.re_offsets; +} + +void +restore_regexp_context(struct re_save *save) +{ + global_eval_context.re_code = save->re_code; + global_eval_context.re_from = save->re_from; + global_eval_context.re_subpatterns = save->re_subpatterns; + global_eval_context.re_offsets = save->re_offsets; +} + /** Save a single q-register */ void @@ -513,6 +532,7 @@ {"MEDIAN", fun_median, 1, INT_MAX, FN_REG}, {"MEMBER", fun_member, 2, 3, FN_REG}, {"MERGE", fun_merge, 3, 3, FN_REG}, + {"MESSAGE", fun_message, 3, 13, FN_REG}, {"MID", fun_mid, 3, 3, FN_REG}, {"MIN", fun_min, 1, INT_MAX, FN_REG}, {"MIX", fun_mix, 3, 12, FN_REG}, @@ -563,7 +583,7 @@ {"NVEXITS", fun_dbwalker, 1, 1, FN_REG}, {"NVPLAYERS", fun_dbwalker, 1, 1, FN_REG}, {"NVTHINGS", fun_dbwalker, 1, 1, FN_REG}, - {"NWHO", fun_nwho, 0, 0, FN_REG}, + {"NWHO", fun_nwho, 0, 1, FN_REG}, {"OBJ", fun_obj, 1, 1, FN_REG}, {"OBJEVAL", fun_objeval, 2, -2, FN_NOPARSE}, {"OBJID", fun_objid, 1, 1, FN_REG}, @@ -889,8 +909,8 @@ { FUNTAB *ftp; - hashinit(&htab_function, 512, sizeof(FUN)); - hash_init(&htab_user_function, 32, sizeof(FUN), delete_function); + hashinit(&htab_function, 512); + hash_init(&htab_user_function, 32, delete_function); function_slab = slab_create("functions", sizeof(FUN)); for (ftp = flist; ftp->name; ftp++) { function_add(ftp->name, ftp->fun, ftp->minargs, ftp->maxargs, ftp->flags); Index: src/cmds.c =================================================================== --- src/cmds.c (.../p4) (revision 1119) +++ src/cmds.c (.../p5) (revision 1119) @@ -665,6 +665,44 @@ do_dolist(player, arg_left, arg_right, cause, flags); } +COMMAND(cmd_message) +{ + char *message; + char *attrib; + unsigned int flags = 0; + int numargs, i; + char *args[10]; + + if (!(SW_ISSET(sw, SWITCH_SPOOF) && (controls(player, cause) + || Can_Nspemit(player)))) { + cause = player; + } + + for (numargs = 1; args_right[numargs] && numargs < 13; numargs++) ; + + switch (numargs) { + case 1: + notify(player, T("@message them with what?")); + return; + case 2: + notify(player, T("Use what attribute for the @message?")); + return; + } + if (!*arg_left) { + notify(player, T("@message who?")); + return; + } + + message = args_right[1]; + attrib = args_right[2]; + + for (i = 0; (i + 3) < numargs; i++) { + args[i] = args_right[i + 3]; + } + + do_message_list(player, cause, arg_left, attrib, message, flags, i, args); +} + COMMAND(cmd_motd) { if (SW_ISSET(sw, SWITCH_CONNECT)) Index: src/markup.c =================================================================== --- src/markup.c (.../p4) (revision 1119) +++ src/markup.c (.../p5) (revision 1119) @@ -1,7 +1,7 @@ /** - * \file strutil.c + * \file markup.c * - * \brief String utilities for PennMUSH. + * \brief Markup handling in PennMUSH strings. * * */ @@ -26,27 +26,17 @@ #include "game.h" #include "confmagic.h" -#define ANSI_BLACK_V (30) -#define ANSI_RED_V (31) -#define ANSI_GREEN_V (32) -#define ANSI_YELLOW_V (33) -#define ANSI_BLUE_V (34) -#define ANSI_MAGENTA_V (35) -#define ANSI_CYAN_V (36) -#define ANSI_WHITE_V (37) - #define ANSI_BEGIN "\x1B[" #define ANSI_FINISH "m" -#define COL_NORMAL "\x1B[0m" +/* COL_* defines */ -/* COL_* and VAL_* defines */ - -#define CBIT_FLASH (1) /**< ANSI flash attribute bit */ -#define CBIT_HILITE (2) /**< ANSI hilite attribute bit */ -#define CBIT_INVERT (4) /**< ANSI inverse attribute bit */ +#define CBIT_HILITE (1) /**< ANSI hilite attribute bit */ +#define CBIT_INVERT (2) /**< ANSI inverse attribute bit */ +#define CBIT_FLASH (4) /**< ANSI flash attribute bit */ #define CBIT_UNDERSCORE (8) /**< ANSI underscore attribute bit */ +#define COL_NORMAL (0) /**< ANSI normal */ #define COL_HILITE (1) /**< ANSI hilite attribute value */ #define COL_UNDERSCORE (4) /**< ANSI underscore attribute value */ #define COL_FLASH (5) /**< ANSI flag attribute value */ @@ -64,25 +54,21 @@ /* Now the code */ static int write_ansi_close(char *buff, char **bp); -static int is_ansi_oldstyle(const char *str); +static int write_ansi_letters(ansi_data cur, char *buff, char **bp); static int safe_markup(char const *a_tag, char *buf, char **bp, char type); static int safe_markup_cancel(char const *a_tag, char *buf, char **bp, char type); -static int - compare_starts(const void *a, const void *b); +static int compare_starts(const void *a, const void *b); +static int escape_marked_str(char **str, char *buff, char **bp); const char *is_allowed_tag(const char *s, unsigned int len); +static const ansi_data ansi_null = { 0, 0, 0, 0 }; /* ARGSUSED */ FUNCTION(fun_stripansi) { - /* Strips ANSI codes away from a given string of text. Starts by - * finding the '\x' character and stripping until it hits an 'm'. - */ - char *cp; - cp = remove_markup(args[0], NULL); safe_str(cp, buff, bp); } @@ -95,27 +81,31 @@ char *ptr; ansi_string *as; char choice; - if (nargs < 2 || !args[1] || !*args[1]) + int strnum; + if (nargs < 2 || !args[0] || !*args[0]) { choice = 'r'; - else - choice = *args[1]; + strnum = 0; + } else { + choice = *args[0]; + strnum = 1; + } switch (choice) { case 'i': - as = parse_ansi_string(args[0]); + as = parse_ansi_string(args[strnum]); inspect_ansi_string(as, executor); free_ansi_string(as); break; case 'r': - for (ptr = args[0]; *ptr; ptr++) { + for (ptr = args[strnum]; *ptr; ptr++) { if (*ptr == TAG_START) *ptr = '<'; if (*ptr == TAG_END) *ptr = '>'; } - safe_str(args[0], buff, bp); + safe_str(args[strnum], buff, bp); break; case 'l': - safe_integer(arglens[0], buff, bp); + safe_integer(arglens[strnum], buff, bp); break; default: safe_str("i: inspect r: raw l: length", buff, bp); @@ -127,7 +117,7 @@ /* ARGSUSED */ FUNCTION(fun_ansi) { - struct ansi_data colors; + ansi_data colors; char *save = *bp; char *p; int i; @@ -136,8 +126,8 @@ define_ansi_data(&colors, args[0]); if (!(colors.bits || colors.offbits || colors.fore || colors.back)) { if (!safe_strl(args[1], arglens[1], buff, bp)) - write_ansi_close(buff, bp); - return; + /* write_ansi_close(buff, bp); */ + return; } /* Write the colors to buff */ @@ -230,12 +220,12 @@ return 0; while (*p) { - if (*p == ESC_CHAR) { + if (*p == TAG_START) { + while ((*p) && (*p != TAG_END)) + p++; + } else if (*p == ESC_CHAR) { while ((*p) && (*p != 'm')) p++; - } else if (*p == TAG_START) { - while ((*p) && (*p != TAG_END)) - p++; } else { i++; } @@ -244,33 +234,7 @@ return i; } -/** Compare two strings, ignoring all ansi and html markup from a string. - * Is *NOT* locale safe (a la strcoll) - * \param a string to compare to - * \param b Other string - * \return int - 0 is identical, -1 or 1 for difference. - */ -int -ansi_strcmp(const char *astr, const char *bstr) -{ - const char *a, *b; - - for (a = astr, b = bstr; *a && *b;) { - a = skip_leading_ansi(a); - b = skip_leading_ansi(b); - if (*a != *b) - return (*a - *b); - b++; - a++; - } - if (*a) - a = skip_leading_ansi(a); - if (*b) - b = skip_leading_ansi(b); - return (*a - *b); -} - -/** Returns the apparent length of a string, up to numchars visible +/** Returns the apparent length of a string, up to numchars visible * characters. The apparent length skips over nonprinting ansi and * tags. * \param p string. @@ -303,7 +267,55 @@ return i; } -/** Strip all ansi and html markup from a string. As a side effect, +/** Compare two strings, ignoring all ansi and html markup from a string. + * Is *NOT* locale safe (a la strcoll) + * \param a string to compare to + * \param b Other string + * \return int - 0 is identical, -1 or 1 for difference. + */ +int +ansi_strcmp(const char *astr, const char *bstr) +{ + const char *a, *b; + + for (a = astr, b = bstr; *a && *b;) { + a = skip_leading_ansi(a); + b = skip_leading_ansi(b); + if (*a != *b) + return (*a - *b); + b++; + a++; + } + if (*a) + a = skip_leading_ansi(a); + if (*b) + b = skip_leading_ansi(b); + return (*a - *b); +} + +/** Compare ansi_data for exact equality. + * \param a ansi_data to compare + * \param b other ansi_data + * \return int - 1 is identical, 0 is different + */ +int +ansi_equal(const ansi_data a, const ansi_data b) +{ + return ((a.bits == b.bits) && (a.offbits == b.offbits) && + (a.fore == b.fore) && (a.back == b.back)); +} + +/** Return true if ansi_data contains no ansi values. + * \param a ansi_data to check + * \return int 1 on ansi_null, 0 otherwise + */ +int +ansi_isnull(const ansi_data a) +{ + return ((a.bits == 0) && (a.offbits == 0) && (a.fore == 0) && (a.back == 0)); +} + +/** Strip all ANSI and HTML markup from a string. As a side effect, * stores the length of the stripped string in a provided address. * NOTE! Length returned is length *including* the terminating NULL, * because we usually memcpy the result. @@ -347,108 +359,55 @@ } static char ansi_chars[50]; - static int ansi_codes[255]; -struct { - char flag; - int num; -} build_ansi_codes[] = { - { - 'n', 0}, { - 'f', COL_FLASH}, { - 'h', COL_HILITE}, { - 'i', COL_INVERT}, { - 'u', COL_UNDERSCORE}, { - 'x', COL_BLACK}, { - 'X', COL_BLACK + 10}, { - 'r', COL_RED}, { - 'R', COL_RED + 10}, { - 'g', COL_GREEN}, { - 'G', COL_GREEN + 10}, { - 'y', COL_YELLOW}, { - 'Y', COL_YELLOW + 10}, { - 'b', COL_BLUE}, { - 'B', COL_BLUE + 10}, { - 'm', COL_MAGENTA}, { - 'M', COL_MAGENTA + 10}, { - 'c', COL_CYAN}, { - 'C', COL_CYAN + 10}, { - 'w', COL_WHITE}, { - 'W', COL_WHITE + 10}, { - '\0', 0} -}; +#define BUILD_ANSI(letter,ESCcode) \ +do { \ + ansi_chars[ESCcode] = letter; \ + ansi_codes[(unsigned char) letter] = ESCcode; \ +} while (0) void init_ansi_codes(void) { - int i; - memset(ansi_chars, 0, sizeof(ansi_chars)); memset(ansi_codes, 0, sizeof(ansi_codes)); - - for (i = 0; build_ansi_codes[i].flag; i++) { - ansi_chars[build_ansi_codes[i].num] = build_ansi_codes[i].flag; - ansi_codes[(unsigned char) build_ansi_codes[i].flag] = - build_ansi_codes[i].num; - } +/* + BUILD_ANSI('n', COL_NORMAL); + BUILD_ANSI('f', COL_FLASH); + BUILD_ANSI('h', COL_HILITE); + BUILD_ANSI('i', COL_INVERT); + BUILD_ANSI('u', COL_UNDERSCORE); +*/ + BUILD_ANSI('x', COL_BLACK); + BUILD_ANSI('X', COL_BLACK + 10); + BUILD_ANSI('r', COL_RED); + BUILD_ANSI('R', COL_RED + 10); + BUILD_ANSI('g', COL_GREEN); + BUILD_ANSI('G', COL_GREEN + 10); + BUILD_ANSI('y', COL_YELLOW); + BUILD_ANSI('Y', COL_YELLOW + 10); + BUILD_ANSI('b', COL_BLUE); + BUILD_ANSI('B', COL_BLUE + 10); + BUILD_ANSI('m', COL_MAGENTA); + BUILD_ANSI('M', COL_MAGENTA + 10); + BUILD_ANSI('c', COL_CYAN); + BUILD_ANSI('C', COL_CYAN + 10); + BUILD_ANSI('w', COL_WHITE); + BUILD_ANSI('W', COL_WHITE + 10); } +#undef BUILD_ANSI + int -read_raw_ansi_data(struct ansi_data *store, const char *codes) +write_ansi_data(ansi_data * cur, char *buff, char **bp) { - int curnum; - if (codes == NULL || store == NULL) - return 0; - store->bits = 0; - store->offbits = 0; - store->fore = 0; - store->back = 0; - - /* codes can point at either the ESC_CHAR or one - * following after. */ - - /* Skip to the first ansi number */ - while (*codes && !isdigit((unsigned char) *codes) && *codes != 'm') - codes++; - - memset(store, 0, sizeof(struct ansi_data)); - - while (*codes && (*codes != 'm')) { - curnum = atoi(codes); - if (curnum < 10) { - switch (curnum) { - case COL_HILITE: - store->bits ^= CBIT_HILITE; - break; - case COL_UNDERSCORE: - store->bits ^= CBIT_UNDERSCORE; - break; - case COL_FLASH: - store->bits ^= CBIT_FLASH; - break; - case COL_INVERT: - store->bits ^= CBIT_INVERT; - break; - case 0: - store->bits = 0; - store->offbits = 0; - store->fore = 'n'; - store->back = 'n'; - break; - } - } else if (curnum < 40) { - store->fore = ansi_chars[curnum]; - } else if (curnum < 50) { - store->back = ansi_chars[curnum]; - } - /* Skip current and find the nxt ansi number */ - while (*codes && isdigit((unsigned char) *codes)) - codes++; - while (*codes && !isdigit((unsigned char) *codes) && (*codes != 'm')) - codes++; - } - return 1; + int retval = 0; + retval += safe_chr(TAG_START, buff, bp); + retval += safe_chr(MARKUP_COLOR, buff, bp); + retval += write_ansi_letters(*cur, buff, bp); + retval += safe_chr(TAG_END, buff, bp); + return retval; } static int @@ -462,17 +421,16 @@ return retval; } - -int -write_ansi_data(struct ansi_data *cur, char *buff, char **bp) +static int +write_ansi_letters(const ansi_data cur, char *buff, char **bp) { int retval = 0; - retval += safe_chr(TAG_START, buff, bp); - retval += safe_chr(MARKUP_COLOR, buff, bp); - if (cur->fore == 'n') { - retval += safe_chr(cur->fore, buff, bp); + char *save; + save = *bp; + if (cur.fore == 'n') { + retval += safe_chr(cur.fore, buff, bp); } else { -#define CBIT_SET(x,y) (x->bits & y) +#define CBIT_SET(x,y) (x.bits & y) if (CBIT_SET(cur, CBIT_FLASH)) retval += safe_chr('f', buff, bp); if (CBIT_SET(cur, CBIT_HILITE)) @@ -482,7 +440,7 @@ if (CBIT_SET(cur, CBIT_UNDERSCORE)) retval += safe_chr('u', buff, bp); #undef CBIT_SET -#define CBIT_SET(x,y) (x->offbits & y) +#define CBIT_SET(x,y) (x.offbits & y) if (CBIT_SET(cur, CBIT_FLASH)) retval += safe_chr('F', buff, bp); if (CBIT_SET(cur, CBIT_HILITE)) @@ -493,25 +451,20 @@ retval += safe_chr('U', buff, bp); #undef CBIT_SET - if (cur->fore) - retval += safe_chr(cur->fore, buff, bp); - if (cur->back) - retval += safe_chr(cur->back, buff, bp); + if (cur.fore) + retval += safe_chr(cur.fore, buff, bp); + if (cur.back) + retval += safe_chr(cur.back, buff, bp); } - retval += safe_chr(TAG_END, buff, bp); + if (retval) + *bp = save; return retval; } -/* We need EDGE_UP to return 1 if: - * x has bit set and y's offbit does. - */ -#define EDGE_UP(x,y,z) ((x->bits & z) != (y->bits & z)) -static struct ansi_data ansi_normal = { 0, 0xFF, 'n', 0 }; - void -nest_ansi_data(struct ansi_data *old, struct ansi_data *cur) +nest_ansi_data(ansi_data * old, ansi_data * cur) { if (cur->fore != 'n') { cur->bits |= old->bits; @@ -521,127 +474,127 @@ if (!cur->back) cur->back = old->back; } else { - cur->fore = 0; + cur->bits = 0; + cur->offbits = 0; cur->back = 0; - cur->bits = 0; - cur->offbits = ~0; } } +/* We need EDGE_UP to return 1 if x has bit set and y doesn't. */ +#define EDGE_UP(x,y,z) ((x.bits & z) != (y->bits & z)) int -write_raw_ansi_data(struct ansi_data *old, struct ansi_data *cur, - char *buff, char **bp) +write_raw_ansi_data(ansi_data * old, ansi_data * cur, char *buff, char **bp) { int f = 0; - + ansi_data past = *old; + ansi_data pres = *cur; /* This shouldn't happen */ - if (cur->fore == 'n') { - if (old->bits || (old->fore != 'n') || old->back) { - return safe_str(COL_NORMAL, buff, bp); + if (pres.fore == 'n') { + if (past.bits || (past.fore != 'n') || past.back) { + return safe_str(ANSI_RAW_NORMAL, buff, bp); } } - if (cur->fore == 'd') - cur->fore = 0; - if (cur->back == 'D') - cur->back = 0; + if (pres.fore == 'd') + pres.fore = 0; + if (pres.back == 'D') + pres.back = 0; /* Do we *unset* anything in cur? */ - if ((old->bits & ~(cur->bits)) || - (old->fore && !cur->fore) || (old->back && !cur->back)) { - safe_str(COL_NORMAL, buff, bp); - old = &ansi_normal; + if ((past.bits & ~(pres.bits)) || + (past.fore && !pres.fore) || (past.back && !pres.back)) { + safe_str(ANSI_RAW_NORMAL, buff, bp); + past = ansi_null; } - cur->bits |= old->bits; - cur->bits &= ~cur->offbits; - - if (old->fore == cur->fore && - old->back == cur->back && old->bits == cur->bits) + if (past.fore == pres.fore && past.back == pres.back && + past.bits == pres.bits) return 0; - if (!(cur->fore || cur->back || cur->bits)) { - if (old != &ansi_normal) - return safe_str(COL_NORMAL, buff, bp); + if (!(pres.fore || pres.back || pres.bits)) { + if (past.fore != 'n') + return safe_str(ANSI_RAW_NORMAL, buff, bp); return 0; } safe_str(ANSI_BEGIN, buff, bp); - if (EDGE_UP(old, cur, CBIT_FLASH)) { + if (EDGE_UP(past, cur, CBIT_HILITE)) { if (f++) safe_chr(';', buff, bp); - safe_integer(ansi_codes['f'], buff, bp); + safe_integer(COL_HILITE, buff, bp); } - if (EDGE_UP(old, cur, CBIT_HILITE)) { + if (EDGE_UP(past, cur, CBIT_INVERT)) { if (f++) safe_chr(';', buff, bp); - safe_integer(ansi_codes['h'], buff, bp); + safe_integer(COL_INVERT, buff, bp); } - if (EDGE_UP(old, cur, CBIT_INVERT)) { + if (EDGE_UP(past, cur, CBIT_FLASH)) { if (f++) safe_chr(';', buff, bp); - safe_integer(ansi_codes['i'], buff, bp); + safe_integer(COL_FLASH, buff, bp); } - if (EDGE_UP(old, cur, CBIT_UNDERSCORE)) { + if (EDGE_UP(past, cur, CBIT_UNDERSCORE)) { if (f++) safe_chr(';', buff, bp); - safe_integer(ansi_codes['u'], buff, bp); + safe_integer(COL_UNDERSCORE, buff, bp); } - if (cur->fore && cur->fore != old->fore) { + if (pres.fore && pres.fore != past.fore) { if (f++) safe_chr(';', buff, bp); - safe_integer(ansi_codes[(unsigned char) cur->fore], buff, bp); + safe_integer(ansi_codes[(unsigned char) pres.fore], buff, bp); } - if (cur->back && cur->back != old->back) { + if (pres.back && pres.back != past.back) { if (f++) safe_chr(';', buff, bp); - safe_integer(ansi_codes[(unsigned char) cur->back], buff, bp); + safe_integer(ansi_codes[(unsigned char) pres.back], buff, bp); } return safe_str(ANSI_FINISH, buff, bp); } +#undef EDGE_UP + void -define_ansi_data(struct ansi_data *cur, const char *str) +define_ansi_data(ansi_data * store, const char *str) { - cur->bits = 0; - cur->offbits = 0; - cur->fore = 0; - cur->back = 0; + *store = ansi_null; for (; str && *str && (*str != TAG_END); str++) { switch (*str) { case 'n': /* normal */ - /* This is explicitly normal, it'll never be - * clored */ - cur->bits = 0; - cur->fore = 0; - cur->back = 0; - cur->offbits = ~0; + /* This is explicitly normal, it'll never be colored */ + store->bits = 0; + store->offbits = ~0; + store->fore = 'n'; + store->back = 0; break; case 'f': /* flash */ - cur->bits |= CBIT_FLASH; + store->bits |= CBIT_FLASH; + store->offbits &= ~CBIT_FLASH; break; case 'h': /* hilite */ - cur->bits |= CBIT_HILITE; + store->bits |= CBIT_HILITE; + store->offbits &= ~CBIT_HILITE; break; case 'i': /* inverse */ - cur->bits |= CBIT_INVERT; + store->bits |= CBIT_INVERT; + store->offbits &= ~CBIT_INVERT; break; case 'u': /* underscore */ - cur->bits |= CBIT_UNDERSCORE; + store->bits |= CBIT_UNDERSCORE; + store->offbits &= ~CBIT_UNDERSCORE; break; case 'F': /* flash */ - cur->offbits |= CBIT_FLASH; + store->offbits |= CBIT_FLASH; break; case 'H': /* hilite */ - cur->offbits |= CBIT_HILITE; + store->offbits |= CBIT_HILITE; break; case 'I': /* inverse */ - cur->offbits |= CBIT_INVERT; + store->offbits |= CBIT_INVERT; break; case 'U': /* underscore */ - cur->offbits |= CBIT_UNDERSCORE; + store->offbits |= CBIT_UNDERSCORE; break; case 'b': /* blue fg */ case 'c': /* cyan fg */ @@ -652,7 +605,7 @@ case 'x': /* black fg */ case 'y': /* yellow fg */ case 'd': /* default fg */ - cur->fore = *str; + store->fore = *str; break; case 'B': /* blue bg */ case 'C': /* cyan bg */ @@ -663,12 +616,72 @@ case 'X': /* black bg */ case 'Y': /* yellow bg */ case 'D': /* default fg */ - cur->back = *str; + store->back = *str; break; } } + store->bits &= ~(store->offbits); } +int +read_raw_ansi_data(ansi_data * store, const char *codes) +{ + int curnum; + if (!codes || !store) + return 0; + store->bits = 0; + store->offbits = 0; + store->fore = 0; + store->back = 0; + + /* 'codes' can point at either the ESC_CHAR, + * the '[', or the following byte. */ + + /* Skip to the first ansi number */ + while (*codes && !isdigit((unsigned char) *codes) && *codes != 'm') + codes++; + + while (*codes && (*codes != 'm')) { + curnum = atoi(codes); + if (curnum < 10) { + switch (curnum) { + case COL_HILITE: + store->bits |= CBIT_HILITE; + store->offbits &= ~CBIT_HILITE; + break; + case COL_UNDERSCORE: + store->bits |= CBIT_UNDERSCORE; + store->offbits &= ~CBIT_UNDERSCORE; + break; + case COL_FLASH: + store->bits |= CBIT_FLASH; + store->offbits &= ~CBIT_FLASH; + break; + case COL_INVERT: + store->bits |= CBIT_INVERT; + store->offbits &= ~CBIT_INVERT; + break; + case COL_NORMAL: + store->bits = 0; + store->offbits = ~0; + store->fore = 'n'; + store->back = 0; + break; + } + } else if (curnum < 40) { + store->fore = ansi_chars[curnum]; + } else if (curnum < 50) { + store->back = ansi_chars[curnum]; + } + /* Skip current and find the next ansi number */ + while (*codes && isdigit((unsigned char) *codes)) + codes++; + while (*codes && !isdigit((unsigned char) *codes) && (*codes != 'm')) + codes++; + } + return 1; +} + /** Return a string pointer past any ansi/html markup at the start. * \param p a string. * \return pointer to string after any initial ansi/html markup. @@ -723,37 +736,6 @@ } } -/* Is this string an old style format? */ -static int -is_ansi_oldstyle(const char *source) -{ - const char *ptr; - /* First test: ESC_CHAR. If there's one this is old style. */ - if (strchr(source, ESC_CHAR) != NULL) - return 1; - /* This usually means a TAG_START appears, but no ESC_CHAR. */ - if (strstr(source, MARKUP_START MARKUP_HTML_STR "/") || - strstr(source, MARKUP_START MARKUP_COLOR_STR "/")) { - /* There's a

*/ - if (strncasecmp(ptr + 1, "PRE", 3) == 0) - return 1; - /* And

or

*/ - if (!isalnum((unsigned char) *(ptr + 2))) - return 1; - } - return 0; -} - /** Convert a string into an ansi_string. * This takes a string that may contain ansi/html markup codes and * converts it to an ansi_string structure that separately stores @@ -764,42 +746,44 @@ ansi_string * parse_ansi_string(const char *source) { - return parse_ansi_string_real(source, 0); + return real_parse_ansi_string(source); } -/** Convert a string into an ansi_string. - * This takes a string that may contain ansi/html markup codes and - * converts it to an ansi_string structure that separately stores - * the plain string and the markup codes for each character. - * \param source string to parse. - * \param oldstyle If true, treats it as an old style ansi string. - * \return pointer to an ansi_string structure representing the src string. - */ +/* This does the actual work of parse_ansi_string. */ ansi_string * -parse_ansi_string_real(const char *source, int oldstyle) +real_parse_ansi_string(const char *source) { ansi_string *data = NULL; char src[BUFFER_LEN], *sptr; char tagbuff[BUFFER_LEN]; char *ptr, *txt; + ansi_data *col; char *tmp; char type; - int i, j; + + int pos = 0; + int num = 0; + int priority = 0; markup_information *info; + ansi_data ansistack[BUFFER_LEN]; + ansistack[0] = ansi_null; + int stacktop = 0; + + ansi_data tmpansi; + int oldcodes = 0; + + if (!source) return NULL; - if (oldstyle == 2) - oldstyle = is_ansi_oldstyle(source); info = NULL; sptr = src; safe_str(source, src, &sptr); *sptr = '\0'; - data = mush_malloc(sizeof(ansi_string), "ansi_string"); if (!data) return NULL; @@ -808,23 +792,45 @@ memset(data, 0, sizeof(ansi_string)); txt = data->text; - i = 0; + col = data->ansi; + for (ptr = src; *ptr;) { - /* Is this an ansi sequence? */ switch (*ptr) { case TAG_START: /* In modern Penn, this is both Pueblo/HTML and color defining code */ - /* Find the end. */ + for (tmp = ptr; *tmp && *tmp != TAG_END; tmp++) ; - if (*tmp) { - *(tmp) = '\0'; - } else { - /* Point tmp at the end */ + if (*tmp) + *tmp = '\0'; + else tmp--; - } ptr++; - type = oldstyle ? MARKUP_HTML : *(ptr++); + /* Now ptr is at TAG_START and tmp is at TAG_END (nulled) */ + + type = *(ptr++); switch (type) { + case MARKUP_COLOR: + if (!*ptr) + break; + if (oldcodes == 1) { + oldcodes = 0; + stacktop--; + } + /* Start or end tag? */ + if (*ptr != '/') { + define_ansi_data(&tmpansi, ptr); + nest_ansi_data(&(ansistack[stacktop]), &tmpansi); + stacktop++; + ansistack[stacktop] = tmpansi; + } else { + if (*(ptr + 1) == 'a') { + stacktop = 0; /* Endall tag */ + } else { + if (stacktop > 0) + stacktop--; + } + } + break; case MARKUP_HTML: if (*ptr && *ptr != '/') { /* We're at the start tag. */ @@ -833,19 +839,19 @@ snprintf(tagbuff, BUFFER_LEN, "/%s", parse_tagname(ptr)); info->stop_code = mush_strdup(tagbuff, "markup_code"); info->type = MARKUP_HTML; - info->start = i; + info->start = pos; info->end = -1; info->priority = priority++; } else if (*ptr) { /* Closing tag */ - for (j = data->nmarkups - 1; j >= 0; j--) { - if (data->markup[j].end < 0 && data->markup[j].stop_code && - strcasecmp(data->markup[j].stop_code, ptr) == 0) { + for (num = data->nmarkups - 1; num >= 0; num--) { + if (data->markup[num].end < 0 && data->markup[num].stop_code && + strcasecmp(data->markup[num].stop_code, ptr) == 0) { break; } } - if (j >= 0) { - data->markup[j].end = i; + if (num >= 0) { + data->markup[num].end = pos; } else { /* This is greviously wrong, we can't find the begin tag? * Consider this a standalone tag with no close tag. */ @@ -855,35 +861,10 @@ info->type = MARKUP_HTML; info->priority = priority++; info->start = -1; - info->end = i; + info->end = pos; } } break; - case MARKUP_COLOR: - if (*ptr && *ptr != '/') { - /* We're at the start tag. */ - j = data->nmarkups++; - data->markup[j].start_code = NULL; - data->markup[j].stop_code = NULL; - data->markup[j].type = MARKUP_COLOR; - data->markup[j].priority = priority++; - define_ansi_data(&(data->markup[j].ansi), ptr); - data->markup[j].start = i; - data->markup[j].end = -1; - } else if (*ptr) { - int endall = (*(ptr + 1) == 'a'); - /* Closing tag. For markup color, this means we - * close the last opened tag. */ - for (j = (data->nmarkups) - 1; j >= 0; j--) { - if (data->markup[j].end < 0 && data->markup[j].type == MARKUP_COLOR) { - data->markup[j].end = i; - /* If it's not ENDALL, break */ - if (!endall) - break; - } - } - } - break; default: /* This is a broken string. Are we near or at buffer_len? */ if (ptr - source < BUFFER_LEN - 4) { @@ -896,90 +877,52 @@ ptr++; break; case ESC_CHAR: - /* I'm getting rid of ESC_CHAR. Still, pretend it's - * a proper "opening" one, unless it's normal, - * in which case we ether close all extant, or - * open a 'normal'. - * - * If we open a new one, find all old open ones that - * it overwrites (rather than modifies) */ + /* ESC_CHAR tags shouldn't be used anymore, so hopefully + * we won't get here. + * To parse these, we assume they can't have the new tag-style + * ANSI codes in them, as this should always be true when loading + * from attributes. Assuming that, this code is separate from the + * "actual" tags, creating a single temporary holding space on the + * top of the ansi-stack and playing with the colors there. + */ for (tmp = ptr; *tmp && *tmp != 'm'; tmp++) ; - if (strcmp(ptr, COL_NORMAL) != 0) { - struct ansi_data cur; - read_raw_ansi_data(&cur, ptr); - /* Close any we can */ - for (j = (data->nmarkups) - 1; j >= 0; j--) { - if (data->markup[j].type == MARKUP_COLOR_OLD) { - cur.bits |= data->markup[j].ansi.bits; - data->markup[j].type = MARKUP_COLOR; - data->markup[j].end = i; - } - } - /* We're at a start tag. */ - j = data->nmarkups++; - data->markup[j].start_code = NULL; - data->markup[j].stop_code = NULL; - data->markup[j].type = MARKUP_COLOR_OLD; - data->markup[j].priority = priority++; - data->markup[j].ansi = cur; - data->markup[j].start = i; - data->markup[j].end = -1; - } else { - int found = 0; - /* Closing tag. For markup color, this means we - * close the last opened tag. */ - for (j = (data->nmarkups) - 1; j >= 0; j--) { - if (data->markup[j].type == MARKUP_COLOR_OLD) { - data->markup[j].type = MARKUP_COLOR; - data->markup[j].end = i; - found = 1; - } - } - /* Is it an "opening" ansi_normal tag? */ - if (!found) { - j = data->nmarkups++; - data->markup[j].start_code = NULL; - data->markup[j].stop_code = NULL; - data->markup[j].type = MARKUP_COLOR_OLD; - data->markup[j].priority = priority++; - data->markup[j].ansi.bits = 0; - data->markup[j].ansi.offbits = 0; - data->markup[j].ansi.fore = 'n'; - data->markup[j].ansi.back = 0; - data->markup[j].start = i; - data->markup[j].end = -1; - } + + /* Store the "background" colors */ + tmpansi = ansistack[stacktop]; + if (oldcodes == 0) { + oldcodes = 1; + stacktop++; + ansistack[stacktop] = tmpansi; + ansistack[stacktop].offbits = 0; } + + read_raw_ansi_data(&tmpansi, ptr); + ansistack[stacktop].bits |= tmpansi.bits; + ansistack[stacktop].bits &= ~(tmpansi.offbits); /* ANSI_RAW_NORMAL */ + if (tmpansi.fore) + ansistack[stacktop].fore = tmpansi.fore; + if (tmpansi.back) + ansistack[stacktop].back = tmpansi.back; + ptr = tmp; if (*tmp) ptr++; break; default: - txt[i++] = *(ptr++); + col[pos] = ansistack[stacktop]; + txt[pos++] = *(ptr++); } } - txt[i] = '\0'; - data->len = i; + txt[pos] = '\0'; + data->len = pos; /* For everything left on the stack: - * If it's an ANSI code, close it with COL_NORMAL and i. * If it's an HTML code, assume it's a standalone, and leave * its stop point where it is. */ - for (j = 0; j < data->nmarkups; j++) { - info = &(data->markup[j]); + for (num = 0; num < data->nmarkups; num++) { + info = &(data->markup[num]); switch (info->type) { - case MARKUP_COLOR_OLD: - info->type = MARKUP_COLOR; - case MARKUP_COLOR: - /* If it's ANSI, we assume it affects the whole string */ - /* Sucks, but ... */ - if (info->end < 0) - info->end = i; - if (info->end == info->start) { - info->end = info->start = -1; - } - break; case MARKUP_HTML: /* If it's HTML, we treat it as standalone (,
, etc) * This is ugly - it's not a "start" but a "stop" */ @@ -995,13 +938,13 @@ } return data; broken_string: - /* This stinks. We treat this as if it's not ansi safe */ + /* This stinks. We treat this as if it's not pueblo-safe */ if (data == NULL) return NULL; strncpy(data->text, source, BUFFER_LEN); data->len = strlen(data->text); - for (i = data->nmarkups - 1; i >= 0; i--) { - free_markup_info(&(data->markup[i])); + for (num = data->nmarkups - 1; num >= 0; num--) { + free_markup_info(&(data->markup[num])); } data->nmarkups = 0; return data; @@ -1016,19 +959,24 @@ { int i, j; markup_information *info; - char tmp; + char tmptext; + ansi_data tmpansi; int mid; int len = as->len; /* Reverse the text */ mid = len / 2; /* Midpoint */ for (i = len - 1, j = 0; i >= mid; j++, i--) { - tmp = as->text[i]; + tmptext = as->text[i]; as->text[i] = as->text[j]; - as->text[j] = tmp; + as->text[j] = tmptext; + + tmpansi = as->ansi[i]; + as->ansi[i] = as->ansi[j]; + as->ansi[j] = tmpansi; } - /* Now reverse the markup. */ + /* Now reverse the html-markup. */ for (i = as->nmarkups - 1; i >= 0; i--) { int start, end; info = &(as->markup[i]); @@ -1061,10 +1009,6 @@ mush_free(as, "ansi_string"); } -/* Compress the markup information in an ansi_string. - * - * This combines adjacent identical markup. - */ static int compare_starts(const void *a, const void *b) @@ -1077,111 +1021,117 @@ return ai->start - bi->start; } + + void optimize_ansi_string(ansi_string *as) { int i, j; - /* Nothing to optimize if we've only got 1 or none. */ - if (as->nmarkups > 1) { + if (!as) + return; + + /* If we've only got 1 or 0, or we've already optimized, do nothing. */ + if (as->nmarkups > 1 && as->optimized == 0) { /* Sort the markup codes by their start position */ qsort(as->markup, as->nmarkups, sizeof(markup_information), compare_starts); for (i = 0; i < as->nmarkups; i++) { - /* If start and end are negative, it's a standalone (img) */ - if (as->markup[i].start == -1 && as->markup[i].end == -1) + + /* If end is negative, it's removed or broken. Either way... */ + if (as->markup[i].end < 0) continue; + for (j = i + 1; j < as->nmarkups; j++) { + /* Already removed? */ - if (as->markup[j].start == -1 && as->markup[j].end == -1) + if (as->markup[j].end < 0 && as->markup[j].start < 0) continue; - if (as->markup[j].start > as->markup[i].end) - break; /* Far apart */ - if (as->markup[i].type == MARKUP_COLOR && - as->markup[j].type == MARKUP_COLOR && - (as->markup[i].ansi.bits == as->markup[j].ansi.bits && - as->markup[i].ansi.offbits == as->markup[j].ansi.offbits && - as->markup[i].ansi.fore == as->markup[j].ansi.fore && - as->markup[i].ansi.back == as->markup[j].ansi.back) + + /* End if we can't stretch markup[i] any farther */ + if (as->markup[i].end < as->markup[j].start) + break; + + /* If there's an identical code within our bounds, merge it. */ + if (as->markup[i].start_code && as->markup[j].start_code && + (strcmp(as->markup[j].start_code, as->markup[i].start_code) == 0) ) { if (as->markup[j].end > as->markup[i].end) as->markup[i].end = as->markup[j].end; - if (as->markup[j].start < as->markup[i].start) - as->markup[i].start = as->markup[j].start; - if (as->markup[j].end > as->markup[i].end) - as->markup[j].start = -1; - as->markup[j].end = -1; - } else if ((as->markup[i].start_code && as->markup[j].start_code) && - strcmp(as->markup[j].start_code, - as->markup[i].start_code) == 0) { - /* i and j are adjacent and identical */ - if (as->markup[j].end > as->markup[i].end) - as->markup[i].end = as->markup[j].end; - if (as->markup[j].start < as->markup[i].start) - as->markup[i].start = as->markup[j].start; as->markup[j].start = -1; as->markup[j].end = -1; - } - } - } + } /* end if_indentical */ + } /* end inner loop */ + } /* end outer loop */ } + /* end if_optimized */ + int target = -1; + int len = 0; + j = 0; - /* Get rid of all removed markups */ - for (i = 0, j = 0; i < as->nmarkups; i++) { - if ((as->markup[i].end >= 0) && (as->markup[i].start != as->markup[i].end)) { - if (i != j) { - memmove(&(as->markup[j]), &(as->markup[i]), sizeof(markup_information)); - } + /* Get rid of all removed markups + * "target" is non-negative when we've pegged a destination + * "len" begins counting when we have a target set and we hit a + * block of non-removed markup + * If len is non-zero and we hit a removed markup, shift the block left. + * The end of the removed string is our new target (it's removable anyway) + */ + + for (i = 0; i < as->nmarkups; i++) { + /* Valid tag? */ + if (as->markup[i].end >= 0 && (as->markup[i].start < as->markup[i].end)) { + if (target != -1) + len++; j++; } else { free_markup_info(&(as->markup[i])); + if (len > 0 && target != -1) + memmove(&(as->markup[target]), &(as->markup[i - len]), + len * sizeof(markup_information)); + if (len > 0 || target == -1) { + target = j; + len = 0; + } } } + if (len > 0) + memmove(&(as->markup[target]), &(as->markup[i - len]), + len * sizeof(markup_information)); as->nmarkups = j; + as->optimized = 1; } -/* Copy the start code for a particular markup_info - * For HTML/Pueblo, this inserts TAG_START and TAG_END - * Otherwise it's just a plain copy */ +/* Copy the start code for a particular markup_info */ static int copy_start_code(markup_information *info, char *buff, char **bp) { int retval = 0; char *save; save = *bp; - if (info->start_code) { + if (info && info->start_code) { retval += safe_chr(TAG_START, buff, bp); retval += safe_chr(info->type, buff, bp); retval += safe_str(info->start_code, buff, bp); retval += safe_chr(TAG_END, buff, bp); - } else if (info->type == MARKUP_COLOR) { - retval += write_ansi_data(&(info->ansi), buff, bp); } if (retval) *bp = save; return retval; } -/* Copy the stop code for a particular markup_info - * For HTML/Pueblo, this inserts TAG_START and TAG_END - * Otherwise it's just a plain copy */ +/* Copy the stop code for a particular markup_info */ static int copy_stop_code(markup_information *info, char *buff, char **bp) { int retval = 0; char *save; save = *bp; - if (info->type == MARKUP_HTML && (info->stop_code != NULL)) { + if (info && info->stop_code) { retval += safe_chr(TAG_START, buff, bp); retval += safe_chr(MARKUP_HTML, buff, bp); retval += safe_str(info->stop_code, buff, bp); retval += safe_chr(TAG_END, buff, bp); - } else if (info->type == MARKUP_COLOR) { - retval += safe_chr(TAG_START, buff, bp); - retval += safe_chr(MARKUP_COLOR, buff, bp); - retval += safe_chr('/', buff, bp); - retval += safe_chr(TAG_END, buff, bp); } if (retval) *bp = save; @@ -1205,13 +1155,6 @@ " %d (%s): (start: %d end: %d) start_code: %s stop_code: %s", count++, (info->type == MARKUP_HTML ? "html" : "ansi"), info->start, info->end, info->start_code, info->stop_code); - } else { - notify_format(who, - " %d (%s): (start: %d end: %d) bits: %d fore: %c back: %c", - count++, (info->type == MARKUP_HTML ? "html" : "ansi"), - info->start, info->end, info->ansi.bits, - (info->ansi.fore) ? info->ansi.fore : '-', - (info->ansi.back) ? info->ansi.back : '-'); } } notify_format(who, "Inspecting ansi string complete"); @@ -1233,20 +1176,30 @@ int end; markup_information *dm; - if (start >= as->len) + /* Nothing to delete */ + if (start >= as->len || count <= 0) return 0; - if (count <= 0) - return 0; + + /* We can't delete from a negative index */ + if (start < 0) { + /* start is negative: this *decreases* count. */ + count += start; + start = 0; + } + + /* We can't delete past the end of our string */ if ((start + count) > as->len) count = as->len - start; + /* Nothing to delete */ + if (count <= 0) + return 0; + end = start + count; - as->optimized = 0; - dm = as->markup; - /* Remove or shrink the markup on dst */ + /* Remove or shrink the Pueblo markup on dst */ for (i = 0; i < as->nmarkups; i++) { if (dm[i].start >= start && dm[i].end <= end) { dm[i].start = -1; @@ -1264,10 +1217,16 @@ } } + if (as->nmarkups > 0) + as->optimized = 0; + /* Shift text over */ memmove(as->text + start, as->text + end, as->len - end); + memmove(as->ansi + start, as->ansi + end, + (as->len - end) * sizeof(ansi_data)); as->len -= count; as->text[as->len] = '\0'; + as->ansi[as->len] = ansi_null; return 0; } @@ -1275,120 +1234,147 @@ do { \ x.type = y.type; \ x.priority = y.priority; \ - x.start_code = NULL; \ - x.stop_code = NULL; \ if (y.start_code) x.start_code = mush_strdup(y.start_code,"markup_code"); \ else (x.start_code = NULL); \ if (y.stop_code) x.stop_code = mush_strdup(y.stop_code,"markup_code"); \ else (x.stop_code = NULL); \ - x.ansi.bits = y.ansi.bits; \ - x.ansi.offbits = y.ansi.offbits; \ - x.ansi.fore = y.ansi.fore; \ - x.ansi.back = y.ansi.back; \ } while (0) /** Insert an ansi string into another ansi_string * with markups kept as straight as possible. * \param dst ansi_string to insert into. - * \param loc Location to insert into, 0-indexed + * \param loc Location to insert into, 0-indexed * \param src ansi_string to insert - * \param start start point in src - * \param size length of string from src * \retval 0 success - * \reval 1 failure. + * \retval 1 failure. */ int -ansi_string_insert(ansi_string *dst, int loc, - ansi_string *src, int start, int count) +ansi_string_insert(ansi_string *dst, int loc, ansi_string *src) { int i, j; int len; - int end, m_end; - int rval = 0; + int retval = 0; + int src_len = src->len; + markup_information *dm, *sm; - if (loc >= dst->len) - loc = dst->len; - if (start >= src->len) + /* If src->len == 0, we might have only markup. Stand-alones. Ew! */ + if (src->len <= 0 && src->nmarkups <= 0) return 0; - if (count <= 0) - return 0; - if ((start + count) > src->len) - count = src->len - start; + if (dst->len >= BUFFER_LEN) + return 1; - dst->optimized = 0; + if (src_len >= BUFFER_LEN) + src_len = BUFFER_LEN - 1; + if (src_len < 0) + src_len = 0; + if (loc > dst->len) + loc = dst->len; + if (loc < 0) + loc = 0; + + if (dst->nmarkups > 0 || src->nmarkups > 0) + dst->optimized = 0; + dm = dst->markup; sm = src->markup; - /* End in src */ - end = start + count; - - /* Starting location */ - if (loc <= 0) - loc = 0; - if (loc >= dst->len) - loc = dst->len; - - /* End of src's insert location in dst */ - m_end = loc + count; - - /* shift or widen the markup on dst */ + /* shift or widen the Pueblo markup on dst */ for (i = 0; i < dst->nmarkups; i++) { - if (dm[i].start >= loc) - dm[i].start += count; - if (dm[i].end > loc) - dm[i].end += count; + if (loc <= dm[i].start) + dm[i].start += src_len; + if (loc < dm[i].end) + dm[i].end += src_len; } - /* Copy markup */ + + /* Copy markup onto the end of dst */ for (j = 0; j < src->nmarkups; j++) { - /* It's possible, but not at all easy, to get this much ansi markup */ + + /* It's possible, but not at all easy, to get this much Pueblo markup */ if (i >= BUFFER_LEN) break; - if (((sm[j].start <= end && sm[j].end >= start) && sm[j].start >= 0) || - (sm[j].end > start && sm[j].end <= end)) { + + /* Is it a valid tag? */ + if (sm[j].end >= 0 && sm[j].start < sm[j].end && + ((sm[j].start < 0 && sm[j].end <= src_len) + || (sm[j].start < src_len && sm[j].start >= 0))) { + copyto(dm[i], sm[j]); - if (sm[j].start < 0) { + + /* If our start is non-negative, start+loc is its position in dm */ + if (sm[j].start >= 0) + dm[i].start = sm[j].start + loc; + else dm[i].start = -1; - } else { - dm[i].start = loc + sm[j].start - start; - if (dm[i].start < loc) - dm[i].start = loc; - } - dm[i].end = loc + sm[j].end - start; - if (dm[i].end >= m_end) - dm[i].end = m_end; + + /* Make sure the end position is within the bounds of its own string */ + if (sm[j].end <= src_len) + dm[i].end = sm[j].end + loc; + else + dm[i].end = src_len + loc; + i++; } } + + for (; i >= BUFFER_LEN; i--) + free_markup_info(&(dm[i])); dst->nmarkups = i; - dst->len += count; + dst->len += src_len; if (dst->len >= BUFFER_LEN) { - rval = 1; + retval = 1; dst->len = BUFFER_LEN - 1; } - len = dst->len - m_end; + len = dst->len - src->len - loc; + + /* Determine what old ansi might stretch across the new text. + * This sets backansi to any ansi values (bits, colors) that + * are continuous across an entire length of text. */ + ansi_data backansi = ansi_null; + if (0 < loc && loc < dst->len) { + backansi.offbits = dst->ansi[loc - 1].offbits & dst->ansi[loc].offbits; + backansi.bits = dst->ansi[loc - 1].bits & dst->ansi[loc].bits; + if (dst->ansi[loc - 1].fore == dst->ansi[loc].fore) + backansi.fore = dst->ansi[loc].fore; + if (dst->ansi[loc - 1].back == dst->ansi[loc].back) + backansi.back = dst->ansi[loc].back; + } + /* Shift text over */ - if (len > 0) { - if (m_end + len >= BUFFER_LEN) { - len = (BUFFER_LEN - m_end - loc - 1); - memmove(dst->text + m_end, dst->text + loc, len); - } else { - memmove(dst->text + m_end, dst->text + loc, len); - } + if (0 < len) { + /* The length beyond our insertion that can *actually* be moved. */ + if (loc + src_len + len >= BUFFER_LEN) + len = BUFFER_LEN - loc - src_len - len - 1; + memmove(dst->text + loc + src_len, dst->text + loc, len); + memmove(dst->ansi + loc + src_len, dst->ansi + loc, + len * sizeof(ansi_data)); } + /* Copy text from src */ - if (loc + count >= BUFFER_LEN) - count = BUFFER_LEN - 1 - loc; - memcpy(dst->text + loc, src->text + start, count); + if (loc + src_len >= BUFFER_LEN) + src_len = BUFFER_LEN - 1 - loc; + if (src_len > 0) { + memcpy(dst->text + loc, src->text, src_len); + for (i = 0; i < src_len; i++) { + j = loc + i; + dst->ansi[j].back = src->ansi[i].back ? src->ansi[i].back : backansi.back; + dst->ansi[j].fore = src->ansi[i].fore ? src->ansi[i].fore : backansi.fore; + dst->ansi[j].offbits = src->ansi[i].offbits | backansi.offbits; + dst->ansi[j].bits = + ~(dst->ansi[j].offbits) & (src->ansi[i].bits | backansi.bits); + } + } dst->text[dst->len] = '\0'; - return rval; + dst->ansi[dst->len] = ansi_null; + return retval; } + /** Replace a portion of an ansi string with * another ansi string, keeping markups as * straight as possible. @@ -1396,116 +1382,123 @@ * \param loc Location to insert into, 0-indexed * \param len Length of string inside dst to replace * \param src ansi_string to insert - * \param start start point in src - * \param size length of string from src * \retval 0 success - * \reval 1 failure. + * \retval 1 failure. */ int -ansi_string_replace(ansi_string *dst, int loc, int len, - ansi_string *src, int start, int count) +ansi_string_replace(ansi_string *dst, int loc, int len, ansi_string *src) { int i, j; - int end, m_end, s_end; + int end, d_end; int diff; - int rval = 0; + int retval = 0; markup_information *dm, *sm; + if (loc < 0) + loc = 0; + /* Is it really an insert? */ - if (loc >= dst->len || len == 0) { - return ansi_string_insert(dst, loc, src, start, count); - } - /* Boundaries */ - if (start <= 0) - start = 0; - if ((start + count) > src->len) - count = src->len - start; - if ((len + loc) > dst->len) - len = dst->len - loc; + if (loc >= dst->len || len <= 0) + return ansi_string_insert(dst, loc, src); + /* Is it really a delete? */ - if ((start >= src->len) || (count <= 0)) { + if (src->len <= 0) return ansi_string_delete(dst, loc, len); - } - /* Starting location */ - if (loc <= 0) - loc = 0; - if (loc >= dst->len) - loc = dst->len; + /* We can't delete past the end of our string. */ + if ((len + loc) > dst->len) + len = dst->len - loc; - end = loc + len; - diff = count - len; - m_end = loc + count; + end = loc + len; /* End of the removed section */ + d_end = loc + src->len; /* End of the new string within the dst string */ + diff = src->len - len; /* Total change in length */ + if (diff > 0 && dst->len >= BUFFER_LEN) + return 1; + dst->optimized = 0; dm = dst->markup; sm = src->markup; - /* Modify, remove, stretch, and mangle markup */ + /* Modify, remove, stretch, and mangle Pueblo markup */ for (i = 0; i < dst->nmarkups; i++) { + /* If it doesn't cross into the replaced part, leave as is */ if (dm[i].end <= loc) continue; - if (dm[i].start == loc && dm[i].end == end) { - /* Debatable: If it surrounds the replaced part exactly, - * keep it, stretching it to wrap around the replacement */ - dm[i].end = m_end; - } else if (dm[i].start <= loc && dm[i].end >= end) { - dm[i].end += diff; - if (dm[i].end > BUFFER_LEN) - dm[i].end = BUFFER_LEN; - } else if (dm[i].start >= loc && dm[i].end <= end) { - /* If it's completely inside the removed area, remove it */ - dm[i].start = -1; - dm[i].end = -1; - } else if (dm[i].start < loc && dm[i].end < end) { - /* If it ends inside, but begins to the left, push end left. */ - if (dm[i].start >= 0) - dm[i].end = loc; - else /* Standalone is inside */ + + /* Debatable: If it surrounds the replaced part exactly, try + * to keep it, stretching it to wrap around the replacement. */ + if (loc <= dm[i].start && dm[i].end <= end) { + /* If the locations match, stretch it, otherwise overwrite it. */ + if (dm[i].start == loc && dm[i].end == end) { + dm[i].end = loc + src->len; + } else { + dm[i].start = -1; dm[i].end = -1; - } else if (dm[i].end > end && dm[i].start < end) { - /* If it begins inside, but ends to the right, push start right. */ - if (dm[i].start >= 0) - dm[i].start = m_end; - dm[i].end += diff; - } else if (dm[i].start > end) { - /* It's to the right */ - dm[i].start += diff; - dm[i].end += diff; - } else { - /* Shift */ - if (dm[i].start > loc) + } + continue; + } + + /* Shift the beginning if necessary */ + if (loc < dm[i].start) { + /* If we're beyond the markup, just shift start. */ + if (end < dm[i].start) { dm[i].start += diff; - dm[i].end += diff; + } else { + /* Otherwise it's inside; push it to the right of the new markup. */ + dm[i].start = loc + src->len; + } + if (dm[i].start > BUFFER_LEN) { + dm[i].start = -1; + dm[i].end = -1; + } } + + /* If this markup ends before the new one, squish it. */ + if (dm[i].end < end) { + dm[i].end = (dm[i].start >= 0) ? loc : -1; /* Or erase stand-alones */ + } else { + dm[i].end += diff; /* Otherwise, shift it. */ + if (dm[i].end > BUFFER_LEN) + dm[i].end = BUFFER_LEN; + } + } - s_end = start + count; - /* Copy markup */ + /* Copy markup. Code taken from ansi_string_insert. */ for (j = 0; j < src->nmarkups; j++) { - /* It's possible, but not at all easy, to get this much ansi markup */ + + /* It's possible, but not at all easy, to get this much pueblo markup */ if (i >= BUFFER_LEN) break; - if (((sm[j].start <= s_end && sm[j].end >= start) && sm[j].start >= 0) || - (sm[j].end > start && sm[j].end <= s_end)) { + + /* Is it a valid tag? */ + if (sm[j].end >= 0 && sm[j].start < sm[j].end && + ((sm[j].start < 0 && sm[j].end <= src->len) + || (sm[j].start < src->len && sm[j].start >= 0))) { + copyto(dm[i], sm[j]); - if (sm[j].start < 0) { + + /* If our start is non-negative, start+loc is its position in dm */ + if (sm[j].start >= 0) + dm[i].start = sm[j].start + loc; + else dm[i].start = -1; - } else { - dm[i].start = loc + sm[j].start - start; - if (dm[i].start < loc) - dm[i].start = loc; - } - dm[i].end = loc + sm[j].end - start; - if (dm[i].end >= m_end) - dm[i].end = m_end; + + /* Make sure the end position is within the bounds of its own string */ + if (sm[j].end <= src->len) + dm[i].end = sm[j].end + loc; + else + dm[i].end = src->len + loc; + i++; } } - if (i >= BUFFER_LEN) - i = BUFFER_LEN - 1; + + for (; i >= BUFFER_LEN; i--) + free_markup_info(&(dm[i])); dst->nmarkups = i; /* length of original string after replace bits */ @@ -1513,30 +1506,131 @@ dst->len += diff; if (dst->len >= BUFFER_LEN) { - rval = 1; + retval = 1; dst->len = BUFFER_LEN - 1; } + /* Determine what old ansi might stretch across the new text. + * This sets backansi to any ansi values (bits, colors) that + * are continuous across an entire length of text. + */ + ansi_data backansi = dst->ansi[loc]; + for (i = loc; i < end && !ansi_isnull(backansi); i++) { + backansi.offbits &= dst->ansi[i].offbits; + backansi.bits &= dst->ansi[i].bits; + if (backansi.fore != dst->ansi[i].fore) + backansi.fore = 0; + if (backansi.back != dst->ansi[i].back) + backansi.back = 0; + } + /* Shift text over */ if (diff != 0) { - if (m_end + len >= BUFFER_LEN) { - len = BUFFER_LEN - (1 + m_end); - if (len > 0) { - memmove(dst->text + m_end, dst->text + end, len); - } - } else { - memmove(dst->text + m_end, dst->text + end, len); + if (d_end + len >= BUFFER_LEN) + len = BUFFER_LEN - (1 + d_end); + if (len > 0) { + memmove(dst->text + d_end, dst->text + end, len); + memmove(dst->ansi + d_end, dst->ansi + end, len * sizeof(ansi_data)); } } /* Copy text from src */ - if (loc + count >= BUFFER_LEN) - count = BUFFER_LEN - (1 + loc); - memcpy(dst->text + loc, src->text + start, count); + len = src->len; + if (loc + len >= BUFFER_LEN) + len = BUFFER_LEN - loc - 1; + if (len > 0) { + memcpy(dst->text + loc, src->text, len); + for (i = 0; i < len; i++) { + j = loc + i; + dst->ansi[j].back = src->ansi[i].back ? src->ansi[i].back : backansi.back; + dst->ansi[j].fore = src->ansi[i].fore ? src->ansi[i].fore : backansi.fore; + dst->ansi[j].offbits = src->ansi[i].offbits | backansi.offbits; + dst->ansi[j].bits = + ~(dst->ansi[j].offbits) & (src->ansi[i].bits | backansi.bits); + } + } dst->text[dst->len] = '\0'; - return rval; + dst->ansi[dst->len] = ansi_null; + return retval; } + +/** Scrambles an ansi_string, returning a pointer to the new string. + * \param as ansi_string to scramble. + */ +ansi_string * +scramble_ansi_string(ansi_string *as) +{ + int i, j, k; + int pos[BUFFER_LEN]; + markup_information *dm; + ansi_string *tmp = NULL; + + if (!as) + return NULL; + + optimize_ansi_string(as); + + tmp = mush_malloc(sizeof(ansi_string), "ansi_string"); + if (!tmp) + return NULL; + + memset(tmp, 0, sizeof(ansi_string)); + + /* Scramble the text */ + tmp->len = as->len; + + for (i = 0; i < as->len; i++) + pos[i] = i; + + for (i = 0; i < as->len; i++) { + j = get_random_long(0, as->len - 1); + k = pos[i]; + pos[i] = pos[j]; + pos[j] = k; + } + + /* The old scramble did new[i] = old[pos[i]], + * but handling markup tags is easier if we do it this way. */ + /* Scramble the text and ansi... */ + for (i = 0; i < as->len; i++) { + tmp->text[pos[i]] = as->text[i]; + tmp->ansi[pos[i]] = as->ansi[i]; + } + + /* Scramble the Pueblo markup */ + dm = tmp->markup; + if (as->nmarkups > 0) + tmp->optimized = 0; + + /* Copy the standalones (tags with -1 for start) */ + for (i = 0; i < as->nmarkups && as->markup[i].start == -1; i++) { + copyto(dm[i], as->markup[i]); + dm[i].end = pos[i]; + } + + /* Copy the rest */ + j = i; + for (; i < as->nmarkups; i++) { + for (k = as->markup[i].start; k < as->markup[i].end; k++) { + copyto(dm[j], as->markup[i]); + dm[j].start = pos[k]; + dm[j].end = dm[j].start + 1; + j++; + if (j >= BUFFER_LEN) { + optimize_ansi_string(tmp); + tmp->optimized = 0; + j = tmp->nmarkups; + if (j >= BUFFER_LEN) + return tmp; + } + } + } + tmp->nmarkups = j; + optimize_ansi_string(tmp); + return tmp; +} + #undef copyto /** Safely append an ansi_string into a buffer as a real string, @@ -1548,25 +1642,26 @@ * \retval 0 success. * \retval 1 failure. */ - int safe_ansi_string(ansi_string *as, int start, int len, char *buff, char **bp) { int i, j; + int cur; markup_information *info; int nextstart, nextend, next; int end = start + len; int retval = 0; + ansi_data curansi; - if (as->optimized == 0) { - optimize_ansi_string(as); - as->optimized = 1; - } + if (!as) + return 0; + optimize_ansi_string(as); + if (len <= 0) return 0; - - i = start; + if (as->len > BUFFER_LEN) + as->len = BUFFER_LEN; if (start >= as->len) return 0; if (end > as->len) @@ -1575,86 +1670,143 @@ /* Standalones (Stop codes with -1 for start) */ for (j = 0; j < as->nmarkups; j++) { info = &(as->markup[j]); - if (info->start == -1 && info->end == i) { - /* This is a standalone tag. YUCK! */ - if (info->stop_code) - retval += safe_str(info->stop_code, buff, bp); - } + if (info->start != -1) + break; /* No more standalone tags to copy */ + if (info->stop_code && info->end == start) + retval += safe_str(info->stop_code, buff, bp); } /* Now, start codes of everything that impacts us. */ - for (j = 0; j < as->nmarkups; j++) { + for (; j < as->nmarkups; j++) { info = &(as->markup[j]); - if (info->start >= 0) { - if (info->start <= i && info->end > i) { - retval += copy_start_code(info, buff, bp); - } - } + if (info->start > start) + break; /* No more tags to copy. */ + if (info->end > start) + retval += copy_start_code(info, buff, bp); } - - /* Find the next changes */ - nextstart = BUFFER_LEN + 1; + /* Find the next changes--new tags, or a prior tag ending. */ + i = start; nextend = BUFFER_LEN + 1; - for (j = 0; j < as->nmarkups; j++) { - info = &(as->markup[j]); - if (info->start > i && info->start < nextstart) - nextstart = info->start; - if (info->end > i && info->end < nextend) - nextend = info->end; + /* If there is another start, it's our next one; we have a sorted list. */ + if (j < as->nmarkups) + nextstart = as->markup[j].start; + else + nextstart = BUFFER_LEN + 1; + + /* To find the next END is harder, since it isn't sorted... */ + /* Scan forward. Stop once we find a tag with a start beyond + * this one's end. Anything beyond that can't be the nextend, + * so we'll backtrack from there. */ + if (as->nmarkups > 0) { + cur = j; + if (cur >= as->nmarkups) + cur--; + info = &(as->markup[cur]); + while (cur < as->nmarkups && as->markup[cur].start < info->end) + cur++; + cur--; + if (info->end > as->markup[cur].start) { + for (; cur >= 0; cur--) { + if (as->markup[cur].end > i && as->markup[cur].end < nextend) + nextend = as->markup[cur].end; + } + } } next = (nextend < nextstart) ? nextend : nextstart; + if (end < next) next = end; - for (; i < next && i < as->len; i++) { - if (as->text[i]) + curansi = as->ansi[start]; + if (!ansi_isnull(curansi)) + write_ansi_data(&curansi, buff, bp); + /* If there's any text/ansi between start and next, print it */ + for (i = start; i < next && i < as->len; i++) { + if (as->text[i]) { + if (!ansi_equal(curansi, as->ansi[i])) { + if (!ansi_isnull(curansi)) + write_ansi_close(buff, bp); + curansi = as->ansi[i]; + if (!ansi_isnull(curansi)) + write_ansi_data(&curansi, buff, bp); + } safe_chr(as->text[i], buff, bp); + } } - i = next; + cur = j; /* Our current markup */ + i = next; /* Our current position */ + + /* Basically the same thing as above, in loop form. */ while (i < end) { + if (i >= nextend) { nextend = BUFFER_LEN + 2; - for (j = 0; j < as->nmarkups; j++) { + j = cur; + /* Find the last markup that could possibly have a relevant end code */ + while (j < as->nmarkups && as->markup[j].start < as->markup[cur].end) + j++; + j--; + /* We MUST have markup if we're here, so no nmarkup > 0 check. */ + /* Print relevant stop codes, and find our nextend */ + for (; j >= 0; j--) { info = &(as->markup[j]); - if (info->end == i) { - retval += copy_stop_code(info, buff, bp); + if (info->end >= i) { + if (info->end == i) + retval += copy_stop_code(info, buff, bp); + else if (info->end < nextend) + nextend = info->end; } - if (info->end > i && info->end < nextend) - nextend = info->end; } } + if (i >= nextstart) { - nextstart = BUFFER_LEN + 2; - for (j = 0; j < as->nmarkups; j++) { - info = &(as->markup[j]); - if (info->start == i) { - retval += copy_start_code(info, buff, bp); - } else if (info->start > i && info->start < nextstart) { - nextstart = info->start; - } - } + /* Print out all the relevant start codes */ + for (; cur < as->nmarkups && as->markup[cur].start == i; cur++) + retval += copy_start_code(&(as->markup[cur]), buff, bp); + if (cur < as->nmarkups) + nextstart = as->markup[cur].start; + else + nextstart = BUFFER_LEN + 2; } + next = (nextend < nextstart) ? nextend : nextstart; if (end < next) next = end; + for (; i < next && i < as->len; i++) { - if (as->text[i]) + if (as->text[i]) { + if (!ansi_equal(curansi, as->ansi[i])) { + if (!ansi_isnull(curansi)) + write_ansi_close(buff, bp); + curansi = as->ansi[i]; + if (!ansi_isnull(curansi)) + write_ansi_data(&curansi, buff, bp); + } safe_chr(as->text[i], buff, bp); + } } } /* Now, find all things that end for us */ - for (j = 0; j < as->nmarkups; j++) { - info = &(as->markup[j]); - if (info->start < i && info->end >= i) - retval += copy_stop_code(info, buff, bp); + if (as->nmarkups > 0) { + j = cur; + while (cur < as->nmarkups && as->markup[j].end > as->markup[cur].start) + cur++; + cur--; + for (; cur >= 0; cur--) { + info = &(as->markup[cur]); + if (info->start < i && info->end >= i) + retval += copy_stop_code(info, buff, bp); + } } + if (!retval && !ansi_isnull(curansi)) + retval += write_ansi_close(buff, bp); return retval; } @@ -1664,272 +1816,278 @@ extern char escaped_chars[UCHAR_MAX + 1]; -static int escape_strn(char *s, int start, int count, char *buff, char **bp); - static int -escape_str(char *in, char *buff, char **bp) +escape_marked_str(char **str, char *buff, char **bp) { - return escape_strn(in, 0, strlen(in), buff, bp); -} - -static int -escape_strn(char *s, int start, int count, char *buff, char **bp) -{ unsigned char *in; int retval = 0; int dospace = 1; int spaces = 0; - int i, j; + int i; - in = (unsigned char *) s; - - if (*in) { - count += start; - for (i = start; in[i] && i < count; i++) { - if (in[i] == ' ') { - spaces++; - } else { - if (spaces) { - if (spaces >= 5) { - retval += safe_str("[space(", buff, bp); - retval += safe_number(spaces, buff, bp); - retval += safe_str(")]", buff, bp); - } else { - if (dospace) { - spaces--; + if (!str || !*str || !**str) + return 0; + in = (unsigned char *) *str; + for (; *in && *in != ESC_CHAR && *in != TAG_START; in++) { + if (*in == ' ') { + spaces++; + } else { + if (spaces) { + if (spaces >= 5) { + retval += safe_str("[space(", buff, bp); + retval += safe_number(spaces, buff, bp); + retval += safe_str(")]", buff, bp); + } else { + if (dospace) { + spaces--; + retval += safe_str("%b", buff, bp); + } + while (spaces) { + retval += safe_chr(' ', buff, bp); + if (--spaces) { + --spaces; retval += safe_str("%b", buff, bp); } - while (spaces) { - retval += safe_chr(' ', buff, bp); - if (--spaces) { - --spaces; - retval += safe_str("%b", buff, bp); - } - } } } - spaces = 0; - dospace = 0; - if (in[i] == '\n') { - retval += safe_str("%r", buff, bp); - } else if (in[i] == '\t') { - retval += safe_str("%t", buff, bp); - } else if (in[i] == BEEP_CHAR) { - for (j = i; in[i + 1] == BEEP_CHAR && (i - j) < 4; i++) ; - retval += safe_format(buff, bp, "[beep(%d)]", (i - j) + 1); - } else if (escaped_chars[in[i]]) { + } + spaces = 0; + dospace = 0; + switch (*in) { + case '\n': + retval += safe_str("%r", buff, bp); + break; + case '\t': + retval += safe_str("%t", buff, bp); + break; + case BEEP_CHAR: + for (i = 1; *(in + 1) == BEEP_CHAR && i < 5; in++, i++) ; + retval += safe_format(buff, bp, "[beep(%d)]", i); + break; + default: + if (escaped_chars[*in]) retval += safe_chr('\\', buff, bp); - retval += safe_chr(in[i], buff, bp); - } else { - retval += safe_chr(in[i], buff, bp); - } + retval += safe_chr(*in, buff, bp); + break; } } - if (spaces) { - if (spaces >= 5) { - retval += safe_str("[space(", buff, bp); - retval += safe_number(spaces, buff, bp); - retval += safe_str(")]", buff, bp); - } else { - spaces--; /* This is for the final %b space */ - if (spaces && dospace) { - spaces--; + } + if (spaces) { + if (spaces >= 5) { + retval += safe_str("[space(", buff, bp); + retval += safe_number(spaces, buff, bp); + retval += safe_str(")]", buff, bp); + } else { + spaces--; /* This is for the final %b space */ + if (spaces && dospace) { + spaces--; + retval += safe_str("%b", buff, bp); + } + while (spaces) { + safe_chr(' ', buff, bp); + if (--spaces) { + --spaces; retval += safe_str("%b", buff, bp); } - while (spaces) { - safe_chr(' ', buff, bp); - if (--spaces) { - --spaces; - retval += safe_str("%b", buff, bp); - } - } - retval += safe_str("%b", buff, bp); } + retval += safe_str("%b", buff, bp); } } + *str = (char *) in; return retval; } - -static int -dump_start_code(markup_information *info, char *buff, char **bp) +/* Does the work of decompose_str, which is found in look.c. + * Even handles ANSI and Pueblo, which is why it's so ugly. + * Code based off of real_parse_ansi_string, not safe_ansi_string. + */ +int +real_decompose_str(char *orig, char *buff, char **bp) { - int retval = 0; - char *save; - save = *bp; - if (info->type == MARKUP_HTML) { - if (info->stop_code != NULL) { - char *ptr; - retval += safe_str("[tagwrap(", buff, bp); - if ((ptr = strchr(info->start_code, ' ')) != NULL) { - *(ptr++) = '\0'; - retval += escape_str(info->start_code, buff, bp); - if (*ptr) { - retval += safe_chr(',', buff, bp); - retval += escape_str(ptr, buff, bp); - } - ptr--; - *ptr = ' '; - } else { - retval += escape_str(info->start_code, buff, bp); - } - retval += safe_chr(',', buff, bp); - } else { - retval += safe_str("[tag(", buff, bp); - retval += escape_str(info->start_code, buff, bp); - retval += safe_str(")]", buff, bp); - } - } else { - /* Find the digits */ - retval += safe_str("[ansi(", buff, bp); - if (info->ansi.fore == 'n') { - retval += safe_chr('n', buff, bp); - } else { -#define CBIT_SET(x,y) (x.bits & y) - if (CBIT_SET(info->ansi, CBIT_FLASH)) - retval += safe_chr('f', buff, bp); - if (CBIT_SET(info->ansi, CBIT_HILITE)) - retval += safe_chr('h', buff, bp); - if (CBIT_SET(info->ansi, CBIT_INVERT)) - retval += safe_chr('i', buff, bp); - if (CBIT_SET(info->ansi, CBIT_UNDERSCORE)) - retval += safe_chr('u', buff, bp); -#undef CBIT_SET -#define CBIT_SET(x,y) (x.offbits & y) - if (CBIT_SET(info->ansi, CBIT_FLASH)) - retval += safe_chr('F', buff, bp); - if (CBIT_SET(info->ansi, CBIT_HILITE)) - retval += safe_chr('H', buff, bp); - if (CBIT_SET(info->ansi, CBIT_INVERT)) - retval += safe_chr('I', buff, bp); - if (CBIT_SET(info->ansi, CBIT_UNDERSCORE)) - retval += safe_chr('U', buff, bp); -#undef CBIT_SET + int i; + char *str = orig; + char *tmp; + char *pstr; + char type; - if (info->ansi.fore) - retval += safe_chr(info->ansi.fore, buff, bp); - if (info->ansi.back) - retval += safe_chr(info->ansi.back, buff, bp); - retval += safe_chr(',', buff, bp); - } - } - if (retval) - *bp = save; - return retval; -} + ansi_data ansistack[BUFFER_LEN]; + ansistack[0] = ansi_null; + ansi_data oldansi; + ansi_data tmpansi; + int ansitop = 0; + int ansiheight = 0; + int howmanyopen = 0; + int oldcodes = 0; -static int -dump_stop_code(markup_information *info - __attribute__ ((__unused__)), char *buff, char **bp) -{ - char *save = *bp; - if (safe_str(")]", buff, bp)) { - *bp = save; - } - return 1; -} + char *pueblostack[BUFFER_LEN]; + char tagbuff[BUFFER_LEN]; + int pueblotop = -1; -int -dump_ansi_string(ansi_string *as, char *buff, char **bp) -{ - int i, j; - markup_information *info; - int nextstart, nextend, next; - int end; - int start = 0; int retval = 0; - end = as->len; + if (!str || !*str) + return 0; - if (as->optimized == 0) { - optimize_ansi_string(as); - as->optimized = 1; - } + retval += escape_marked_str(&str, buff, bp); - i = start; - if (start > as->len) - return 0; - if (end > as->len) - end = as->len; + while (str && *str && *str != '\0') { + oldansi = ansistack[ansitop]; + ansiheight = ansitop; + while (*str == TAG_START || *str == ESC_CHAR) { + switch (*str) { + case TAG_START: + for (tmp = str; *tmp && *tmp != TAG_END; tmp++) ; + if (*tmp) { + *tmp = '\0'; + } else { + tmp--; + } + str++; + type = *(str++); + switch (type) { + case MARKUP_COLOR: + if (!*str) + break; + if (oldcodes == 1) { + ansitop--; + oldcodes = 0; + } + /* Start or end tag? */ + if (*str != '/') { + define_ansi_data(&tmpansi, str); + nest_ansi_data(&(ansistack[ansitop]), &tmpansi); + ansitop++; + ansistack[ansitop] = tmpansi; + } else { + if (*(str + 1) == 'a') { + ansitop = 0; + } else { + if (ansitop > 0) { + ansitop--; + } + } + } + break; + case MARKUP_HTML: + if (!*str) + break; + if (*str != '/') { + pueblotop++; + snprintf(tagbuff, BUFFER_LEN, "%s", parse_tagname(str)); + pueblostack[pueblotop] = mush_strdup(tagbuff, "markup_code"); - /* Standalones (Stop codes with -1 for start) */ - for (j = 0; j < as->nmarkups; j++) { - info = &(as->markup[j]); - if (info->start == -1 && info->end == i) { - /* This is a standalone tag. YUCK! */ - if (info->stop_code) - retval += safe_str(info->stop_code, buff, bp); - } - } + retval += safe_str("[tag(", buff, bp); + retval += safe_str(tagbuff, buff, bp); + str += strlen(tagbuff); + if (str && *str) { + while (str && str != tmp) { + str++; + pstr = strchr(str, '='); + if (pstr) { + *pstr = '\0'; + retval += safe_chr(',', buff, bp); + retval += safe_str(str, buff, bp); + retval += safe_chr('=', buff, bp); + str = pstr + 1; + pstr = strchr(str, '\"'); - /* Now, start codes of everything that impacts us. */ - for (j = 0; j < as->nmarkups; j++) { - info = &(as->markup[j]); - if (info->start >= 0) { - if (info->start <= i && info->end > i) { - retval += dump_start_code(info, buff, bp); - } - } - } + retval += safe_chr('\"', buff, bp); + if (str == pstr) { + str++; + pstr = strchr(str, '\"'); + } else { + pstr = strchr(str, ' '); + } + if (!pstr) + pstr = tmp; - /* Find the next changes */ - nextstart = BUFFER_LEN + 1; - nextend = BUFFER_LEN + 1; + *pstr = '\0'; + retval += safe_str(str, buff, bp); + retval += safe_chr('\"', buff, bp); + str = pstr; + } else { + safe_str(str, buff, bp); + break; + } + } + } + retval += safe_str(")]", buff, bp); - for (j = 0; j < as->nmarkups; j++) { - info = &(as->markup[j]); - if (info->start > i && info->start < nextstart) - nextstart = info->start; - if (info->end > i && info->end < nextend) - nextend = info->end; - } + } else { + if (pueblotop > -1) { + i = (*(str + 1) == 'a') ? 0 : pueblotop; + for (i--; pueblotop > i; pueblotop--) { + retval += safe_str("[endtag(", buff, bp); + retval += safe_str(pueblostack[pueblotop], buff, bp); + retval += safe_str(")]", buff, bp); + mush_free(pueblostack[pueblotop], "markup_code"); + } + } + } + break; + } + tmp++; + str = tmp; + break; + case ESC_CHAR: + /* It SHOULD be impossible to get here... */ + for (tmp = str; *tmp && *tmp != 'm'; tmp++) ; - next = (nextend < nextstart) ? nextend : nextstart; - if (end < next) - next = end; + /* Store the "background" colors */ + tmpansi = ansistack[ansitop]; + if (oldcodes == 0) { + oldcodes = 1; + ansitop++; + ansistack[ansitop] = tmpansi; + ansistack[ansitop].offbits = 0; + } - if (next > as->len) - next = as->len; - escape_strn(as->text, i, next - i, buff, bp); - i = next; + read_raw_ansi_data(&tmpansi, str); + ansistack[ansitop].bits |= tmpansi.bits; + ansistack[ansitop].bits &= ~(tmpansi.offbits); /* ANSI_RAW_NORMAL */ + if (tmpansi.fore) + ansistack[ansitop].fore = tmpansi.fore; + if (tmpansi.back) + ansistack[ansitop].back = tmpansi.back; - while (i < end) { - if (i >= nextend) { - nextend = BUFFER_LEN + 2; - for (j = 0; j < as->nmarkups; j++) { - info = &(as->markup[j]); - if (info->end == i) { - retval += dump_stop_code(info, buff, bp); - } - if (info->end > i && info->end < nextend) - nextend = info->end; + str = tmp; + if (*tmp) + str++; + break; } } - if (i >= nextstart) { - nextstart = BUFFER_LEN + 2; - for (j = 0; j < as->nmarkups; j++) { - info = &(as->markup[j]); - if (info->start == i) { - retval += dump_start_code(info, buff, bp); - } else if (info->start > i && info->start < nextstart) { - nextstart = info->start; + + /* Handle ANSI/Text */ + tmpansi = ansistack[ansitop]; + if (ansitop > 0 || ansiheight > 0) { + /* Close existing tags as necessary to cleanly open the next. */ + /* oldansi = ansistack[ansiheight]; */ + if (!ansi_equal(oldansi, tmpansi)) { + while (ansiheight > 0) { + if (howmanyopen > 0) { + howmanyopen--; + retval += safe_str(")]", buff, bp); + } + ansiheight--; } } + if (!ansi_isnull(tmpansi) && !ansi_equal(oldansi, tmpansi)) { + retval += safe_str("[ansi(", buff, bp); + retval += write_ansi_letters(tmpansi, buff, bp); + retval += safe_chr(',', buff, bp); + howmanyopen++; + } } - next = (nextend < nextstart) ? nextend : nextstart; - if (end < next) - next = end; - - escape_strn(as->text, i, next - i, buff, bp); - i = next; + retval += escape_marked_str(&str, buff, bp); } - /* Now, find all things that end for us */ - for (j = 0; j < as->nmarkups; j++) { - info = &(as->markup[j]); - if (info->start < i && info->end >= i) - retval += dump_stop_code(info, buff, bp); + for (; howmanyopen > 0; howmanyopen--) + retval += safe_str(")]", buff, bp); + for (; pueblotop > -1; pueblotop--) { + retval += safe_str("[endtag(", buff, bp); + retval += safe_str(pueblostack[pueblotop], buff, bp); + retval += safe_str(")]", buff, bp); } return retval; @@ -1945,8 +2103,9 @@ * \return size of subpattern, or -1 if unknown pattern */ int -ansi_pcre_copy_substring(ansi_string *as, int *ovector, int stringcount, - int stringnumber, int nonempty, char *buff, char **bp) +ansi_pcre_copy_substring(ansi_string *as, int *ovector, + int stringcount, int stringnumber, + int nonempty, char *buff, char **bp) { int yield; if (stringnumber < 0 || stringnumber >= stringcount) @@ -1972,8 +2131,9 @@ * \return size of subpattern, or -1 if unknown pattern */ int -ansi_pcre_copy_named_substring(const pcre * code, ansi_string *as, int *ovector, - int stringcount, const char *stringname, int ne, +ansi_pcre_copy_named_substring(const pcre * code, ansi_string *as, + int *ovector, int stringcount, + const char *stringname, int ne, char *buff, char **bp) { int n = pcre_get_stringnumber(code, stringname); @@ -1997,7 +2157,6 @@ { int result = 0; char *save = buf; - result = safe_chr(TAG_START, buf, bp); result = safe_chr(type, buf, bp); result = safe_str(a_tag, buf, bp); @@ -2005,7 +2164,6 @@ /* If it didn't all fit, rewind. */ if (result) *bp = save; - return result; } @@ -2032,7 +2190,6 @@ { int result = 0; char *save = buf; - result = safe_chr(TAG_START, buf, bp); result = safe_chr(type, buf, bp); result = safe_chr('/', buf, bp); @@ -2041,7 +2198,6 @@ /* If it didn't all fit, rewind. */ if (result) *bp = save; - return result; } @@ -2065,12 +2221,11 @@ * \retval 1, tagged text wouldn't fit in buffer. */ int -safe_tag_wrap(char const *a_tag, char const *params, char const *data, - char *buf, char **bp, dbref player) +safe_tag_wrap(char const *a_tag, char const *params, + char const *data, char *buf, char **bp, dbref player) { int result = 0; char *save = buf; - if (SUPPORT_PUEBLO) { result = safe_chr(TAG_START, buf, bp); result = safe_chr(MARKUP_HTML, buf, bp); Index: src/access.c =================================================================== --- src/access.c (.../p4) (revision 1119) +++ src/access.c (.../p5) (revision 1119) @@ -77,6 +77,7 @@ #endif #include "conf.h" #include "externs.h" +#include "pcre.h" #include "access.h" #include "mymalloc.h" #include "match.h" @@ -94,8 +95,8 @@ */ struct a_acsflag { const char *name; /**< Name of the access flag */ - int toggle; /**< Is this a negatable flag? */ - int flag; /**< Bitmask of the flag */ + bool toggle; /**< Is this a negatable flag? */ + uint32_t flag; /**< Bitmask of the flag */ }; static acsflag acslist[] = { {"connect", 1, ACS_CONNECT}, @@ -113,31 +114,62 @@ }; static struct access *access_top; -static int add_access_node - (const char *host, const dbref who, const int can, const int cant, - const char *comment); static void free_access_list(void); -static int -add_access_node(const char *host, const dbref who, const int can, - const int cant, const char *comment) +extern const unsigned char *tables; + +static struct access * +sitelock_alloc(const char *host, dbref who, + uint32_t can, uint32_t cant, + const char *comment, const char **errptr) + __attribute_malloc__; + + static struct access *sitelock_alloc(const char *host, dbref who, + uint32_t can, uint32_t cant, + const char *comment, + const char **errptr) { - struct access *end; struct access *tmp; - - tmp = (struct access *) mush_malloc(sizeof(struct access), "struct_access"); - if (!tmp) - return 0; + tmp = mush_malloc(sizeof(struct access), "sitelock.rule"); + if (!tmp) { + static const char *memerr = "unable to allocate memory"; + if (errptr) + *errptr = memerr; + return NULL; + } tmp->who = who; tmp->can = can; tmp->cant = cant; - strcpy(tmp->host, host); + mush_strncpy(tmp->host, host, BUFFER_LEN); if (comment) - strcpy(tmp->comment, comment); + mush_strncpy(tmp->comment, comment, BUFFER_LEN); else tmp->comment[0] = '\0'; tmp->next = NULL; + if (can & ACS_REGEXP) { + int erroffset = 0; + tmp->re = pcre_compile(host, 0, errptr, &erroffset, tables); + if (!tmp->re) { + mush_free(tmp, "sitelock.rule"); + return NULL; + } + } else + tmp->re = NULL; + + return tmp; +} + +static bool +add_access_node(const char *host, dbref who, uint32_t can, + uint32_t cant, const char *comment, const char **errptr) +{ + struct access *end, *tmp; + + tmp = sitelock_alloc(host, who, can, cant, comment, errptr); + if (!tmp) + return false; + if (!access_top) { /* Add to the beginning */ access_top = tmp; @@ -147,25 +179,25 @@ end = end->next; end->next = tmp; } - - return 1; + return true; } /** Read the access.cnf file. * Initialize the access rules linked list and read in the access.cnf file. - * Return 1 if successful, 0 if not + * \return true if successful, false if not */ -int +bool read_access_file(void) { FILE *fp; char buf[BUFFER_LEN]; char *p; - int can, cant; + uint32_t can, cant; int retval; dbref who; char *comment; + const char *errptr = NULL; if (access_top) { /* We're reloading the file, so we've got to delete any current @@ -197,8 +229,10 @@ comment = NULL; /* Is this the @sitelock entry? */ if (!strncasecmp(p, "@sitelock", 9)) { - if (!add_access_node("@sitelock", AMBIGUOUS, ACS_SITELOCK, 0, "")) - do_log(LT_ERR, GOD, GOD, T("Failed to add sitelock node!")); + if (!add_access_node("@sitelock", AMBIGUOUS, ACS_SITELOCK, 0, "", + &errptr)) + do_log(LT_ERR, GOD, GOD, T("Failed to add sitelock node: %s"), + errptr); } else { if ((comment = strchr(p, '#'))) { *comment++ = '\0'; @@ -213,8 +247,9 @@ if (!parse_access_options(p, &who, &can, &cant, NOTHING)) /* Nothing listed, so assume we can't do anything! */ cant = ACS_DEFAULT; - if (!add_access_node(buf, who, can, cant, comment)) - do_log(LT_ERR, GOD, GOD, T("Failed to add access node!")); + if (!add_access_node(buf, who, can, cant, comment, &errptr)) + do_log(LT_ERR, GOD, GOD, T("Failed to add access node: %s"), + errptr); } } } @@ -316,8 +351,8 @@ * flags (can't register, isn't suspect) * \endverbatim */ -int -site_can_access(const char *hname, int flag, dbref who) +bool +site_can_access(const char *hname, uint32_t flag, dbref who) { struct access *ap; acsflag *c; @@ -332,11 +367,11 @@ for (ap = access_top; ap; ap = ap->next) { if (!(ap->can & ACS_SITELOCK) && ((ap->can & ACS_REGEXP) - ? (quick_regexp_match(ap->host, hname, 0) - || (p && quick_regexp_match(ap->host, p, 0)) + ? (qcomp_regexp_match(ap->re, hname) + || (p && qcomp_regexp_match(ap->re, p)) #ifdef FORCE_IPV4 - || quick_regexp_match(ip4_to_ip6(ap->host), hname, 0) - || (p && quick_regexp_match(ip4_to_ip6(ap->host), p, 0)) + || qcomp_regexp_match(ip4_to_ip6(ap->re), hname) + || (p && qcomp_regexp_match(ip4_to_ip6(ap->re), p)) #endif ) : (quick_wild(ap->host, hname) @@ -402,11 +437,11 @@ (*rulenum)++; if (!(ap->can & ACS_SITELOCK) && ((ap->can & ACS_REGEXP) - ? (quick_regexp_match(ap->host, hname, 0) - || (p && quick_regexp_match(ap->host, p, 0)) + ? (qcomp_regexp_match(ap->re, hname) + || (p && qcomp_regexp_match(ap->re, p)) #ifdef FORCE_IPV4 - || quick_regexp_match(ip4_to_ip6(ap->host), hname, 0) - || (p && quick_regexp_match(ip4_to_ip6(ap->host), p, 0)) + || qcomp_regexp_match(ip4_to_ip6(ap->host), hname) + || (p && qcomp_regexp_match(ip4_to_ip6(ap->host), p)) #endif ) : (quick_wild(ap->host, hname) @@ -433,7 +468,7 @@ * This function provides an appealing display of an access rule * in the list. */ -int +void format_access(struct access *ap, int rulenum, dbref who __attribute__ ((__unused__)), char *buff, char **bp) { @@ -474,7 +509,6 @@ } else { safe_str(T("No matching access rule"), buff, bp); } - return 0; } @@ -493,29 +527,28 @@ * Build an appropriate comment based on the player and date * \endverbatim */ -int -add_access_sitelock(dbref player, const char *host, dbref who, int can, - int cant) +bool +add_access_sitelock(dbref player, const char *host, dbref who, uint32_t can, + uint32_t cant) { struct access *end; struct access *tmp; + const char *errptr = NULL; - tmp = (struct access *) mush_malloc(sizeof(struct access), "struct_access"); - if (!tmp) - return 0; - tmp->who = who; - tmp->can = can; - tmp->cant = cant; - strcpy(tmp->host, host); - snprintf(tmp->comment, sizeof tmp->comment, - "By %s(#%d) on %s", Name(player), player, show_time(mudtime, 0)); - tmp->next = NULL; + tmp = sitelock_alloc(host, who, can, cant, "", &errptr); + if (!tmp) { + notify_format(player, T("Unable to add sitelock entry: %s"), errptr); + return false; + } + if (!access_top) { /* Add to the beginning, but first add a sitelock marker */ - if (!add_access_node("@sitelock", AMBIGUOUS, ACS_SITELOCK, 0, "")) + if (!add_access_node("@sitelock", AMBIGUOUS, ACS_SITELOCK, 0, "", &errptr)) { + notify_format(player, T("Unable to add @sitelock separator: %s"), errptr); return 0; + } access_top->next = tmp; } else { end = access_top; @@ -524,8 +557,12 @@ /* Now, either we're at the sitelock or the end */ if (end->can != ACS_SITELOCK) { /* We're at the end and there's no sitelock marker. Add one */ - if (!add_access_node("@sitelock", AMBIGUOUS, ACS_SITELOCK, 0, "")) + if (!add_access_node("@sitelock", AMBIGUOUS, ACS_SITELOCK, 0, "", + &errptr)) { + notify_format(player, T("Unable to add @sitelock separator: %s"), + errptr); return 0; + } end = end->next; } else { /* We're in the middle, so be sure we keep the list linked */ @@ -539,11 +576,9 @@ /** Remove an access rule from the linked list. * \param pattern access rule host pattern to match. * \return number of rule removed. - * \verbatim * This function removes an access rule from the list. * Only rules that appear after the "@sitelock" rule can be * removed with this function. - * \endverbatim */ int remove_access_sitelock(const char *pattern) @@ -563,7 +598,9 @@ next = ap->next; if (strcasecmp(pattern, ap->host) == 0) { n++; - mush_free(ap, "struct_access"); + if (ap->re) + free(ap->re); + mush_free(ap, "sitelock.rule"); if (prev) prev->next = next; else @@ -585,7 +622,9 @@ ap = access_top; while (ap) { next = ap->next; - mush_free((Malloc_t) ap, "struct_access"); + if (ap->re) + free(ap->re); + mush_free(ap, "sitelock.rule"); ap = next; } access_top = NULL; @@ -648,8 +687,8 @@ * This makes a copy of the options string, so it's not modified. */ int -parse_access_options(const char *opts, dbref *who, int *can, int *cant, - dbref player) +parse_access_options(const char *opts, dbref *who, uint32_t * can, + uint32_t * cant, dbref player) { char myopts[BUFFER_LEN]; char *p; Index: src/local.dst =================================================================== --- src/local.dst (.../p4) (revision 1119) +++ src/local.dst (.../p5) (revision 1119) @@ -46,7 +46,7 @@ /* Initial size of this hashtable should be close to the number of * add_config()'s you plan to do. */ - hashinit(&local_options, 4, sizeof(PENNCONF)); + hashinit(&local_options, 4); #ifdef EXAMPLE /* Call add_config for each config parameter you want to add. Index: src/funmisc.c =================================================================== --- src/funmisc.c (.../p4) (revision 1119) +++ src/funmisc.c (.../p5) (revision 1119) @@ -82,7 +82,18 @@ orator = saved_orator; } +FUNCTION(fun_message) +{ + int i; + char *argv[10]; + for (i = 0; (i + 3) < nargs; i++) { + argv[i] = args[i + 3]; + } + + do_message_list(executor, executor, args[0], args[2], args[1], 0, i, argv); +} + /* ARGSUSED */ FUNCTION(fun_oemit) { Index: src/speech.c =================================================================== --- src/speech.c (.../p4) (revision 1119) +++ src/speech.c (.../p5) (revision 1119) @@ -12,6 +12,7 @@ #include #include #include +#include #include "conf.h" #include "externs.h" #include "ansi.h" @@ -399,6 +400,63 @@ mush_free((Malloc_t) tbuf, "string"); } +/** Send an @message to a list of dbrefs, using to format it + * if present. + * The list is destructively modified. + * \param player the enactor. + * \param list the list of players to pemit to, destructively modified. + * \param attrib the ufun attribute to use to format the message. + * \param message the default message. + * \param flags PEMIT_* flags + * \param numargs The number of arguments for the ufun. + * \param ... The arguments for the ufun. + */ +void +do_message_list(dbref player, dbref enactor, char *list, char *attrname, + char *message, int flags, int numargs, char *argv[]) +{ + const char *start; + char *current; + char plist[BUFFER_LEN], *pp; + dbref victim; + int first = 0; + ATTR *attrib; + + start = list; + + pp = plist; + *pp = '\0'; + + while (start && *start) { + current = next_in_list(&start); + if (*current == '*') + current = current + 1; + victim = noisy_match_result(player, current, NOTYPE, MAT_EVERYTHING); + if (GoodObject(victim) && !IsGarbage(victim)) { + /* Can we evaluate its ? */ + + attrib = atr_get(victim, upcasestr(attrname)); + if (attrib && CanEvalAttr(player, victim, attrib)) { + if (flags & PEMIT_SPOOF) { + messageformat(victim, attrname, enactor, NA_SPOOF, numargs, argv); + } else { + messageformat(victim, attrname, enactor, 0, numargs, argv); + } + } else { + if (!first) { + safe_chr(' ', plist, &pp); + } + first = 0; + safe_dbref(victim, plist, &pp); + } + } + } + if (plist[0]) { + *pp = '\0'; + do_pemit_list(enactor, plist, message, flags); + } +} + /** Send a message to a list of dbrefs. To avoid repeated generation * of the NOSPOOF string, we set it up the first time we encounter * something Nospoof, and then check for it thereafter. @@ -631,17 +689,53 @@ * \retval 0 The default message was sent. */ int +vmessageformat(dbref player, const char *attribute, dbref enactor, int flags, + int numargs, ...) +{ + va_list ap; + char *s; + int i; + char *argv[10]; + + va_start(ap, numargs); + + for (i = 0; i < 10; i++) { + if (i < numargs) { + /* Pop another char * off the stack. */ + s = va_arg(ap, char *); + argv[i] = s; + } else { + argv[i] = NULL; + } + } + va_end(ap); + + return messageformat(player, attribute, enactor, flags, numargs, argv); +} + +/** messageformat. This is the wrapper that makes calling PAGEFORMAT, + * CHATFORMAT, etc easy. + * + * \param player The victim to call it on. + * \param attribute The attribute on the player to call. + * \param enactor The enactor who caused the message. + * \param flags NA_INTER_HEAR and NA_SPOOF + * \param arg0 First argument + * \param arg4 Last argument. + * \retval 1 The player had the fooformat attribute. + * \retval 0 The default message was sent. + */ +int messageformat(dbref player, const char *attribute, dbref enactor, int flags, - const char *arg0, const char *arg1, const char *arg2, - const char *arg3, const char *arg4, const char *arg5) + int numargs, char *argv[]) { - const char *argv[6] = { arg0, arg1, arg2, arg3, arg4, arg5 }; /* It's only static because I expect this thing to get * called a LOT, so it may or may not save time. */ static char messbuff[BUFFER_LEN]; *messbuff = '\0'; - if (!call_attrib(player, attribute, argv, 6, messbuff, enactor, NULL)) { + if (!call_attrib(player, attribute, (const char **) argv, numargs, + messbuff, enactor, NULL)) { /* We have a returned value. Notify the player. */ if (*messbuff) notify_anything(enactor, na_one, &player, ns_esnotify, flags, messbuff); @@ -685,7 +779,7 @@ ATTR *a; char *alias; - tp2 = tbuf2 = (char *) mush_malloc(BUFFER_LEN, "string"); + tp2 = tbuf2 = (char *) mush_malloc(BUFFER_LEN, "page_buff"); if (!tbuf2) mush_panic("Unable to allocate memory in do_page"); @@ -709,19 +803,19 @@ a = atr_get_noparent(player, "LASTPAGED"); if (!a || !*((hp = head = safe_atr_value(a)))) { notify(player, T("You haven't paged anyone since connecting.")); - mush_free((Malloc_t) tbuf2, "string"); + mush_free((Malloc_t) tbuf2, "page_buff"); return; } if (!message || !*message) { notify_format(player, T("You last paged %s."), head); - mush_free((Malloc_t) tbuf2, "string"); + mush_free((Malloc_t) tbuf2, "page_buff"); if (hp) free((Malloc_t) hp); return; } } - tp = tbuf = (char *) mush_malloc(BUFFER_LEN, "string"); + tp = tbuf = (char *) mush_malloc(BUFFER_LEN, "page_buff"); if (!tbuf) mush_panic("Unable to allocate memory in do_page"); @@ -790,8 +884,8 @@ * anyone, this looks like a spam attack. */ if (gcount == 99) { notify(player, T("You're trying to page too many people at once.")); - mush_free((Malloc_t) tbuf, "string"); - mush_free((Malloc_t) tbuf2, "string"); + mush_free((Malloc_t) tbuf, "page_buff"); + mush_free((Malloc_t) tbuf2, "page_buff"); if (hp) free((Malloc_t) hp); return; @@ -807,8 +901,8 @@ if (!gcount) { /* Well, that was a total waste of time. */ - mush_free((Malloc_t) tbuf, "string"); - mush_free((Malloc_t) tbuf2, "string"); + mush_free((Malloc_t) tbuf, "page_buff"); + mush_free((Malloc_t) tbuf2, "page_buff"); if (hp) free((Malloc_t) hp); return; @@ -817,8 +911,8 @@ /* Can the player afford to pay for this thing? */ if (!payfor(player, PAGE_COST * gcount)) { notify_format(player, T("You don't have enough %s."), MONIES); - mush_free((Malloc_t) tbuf, "string"); - mush_free((Malloc_t) tbuf2, "string"); + mush_free((Malloc_t) tbuf, "page_buff"); + mush_free((Malloc_t) tbuf2, "page_buff"); if (hp) free((Malloc_t) hp); return; @@ -828,10 +922,10 @@ * actually going to someone. We're in this for keeps now. */ /* Evaluate the message if we need to. */ - if (noeval) + if (noeval) { msgbuf = NULL; - else { - mb = msgbuf = (char *) mush_malloc(BUFFER_LEN, "string"); + } else { + mb = msgbuf = (char *) mush_malloc(BUFFER_LEN, "page_buff"); if (!msgbuf) mush_panic("Unable to allocate memory in do_page"); @@ -935,24 +1029,30 @@ } *tp2 = '\0'; for (i = 0; i < gcount; i++) { - if (!messageformat(good[i], "PAGEFORMAT", player, 0, message, - (key == 1) ? (*gap ? ":" : ";") : "\"", - (alias && *alias) ? alias : "", tbuf2, NULL, NULL)) { - /* Player doesn't have Pageformat, or it eval'd to 0 */ - if (!IsPlayer(player) && Nospoof(good[i])) { - notify_format(good[i], "[#%d] %s", player, tbuf); - } else { - notify(good[i], tbuf); + if (!IsPlayer(player) && Nospoof(good[i])) { + if (msgbuf == NULL) { + msgbuf = mush_malloc(BUFFER_LEN, "page buffer"); } + snprintf(msgbuf, BUFFER_LEN, "[#%d] %s", player, tbuf); + /* Swap tbuf and msgbuf */ + tp = tbuf; + tbuf = msgbuf; + msgbuf = tbuf; } + if (!vmessageformat(good[i], "PAGEFORMAT", player, 0, 5, message, + (key == 1) ? (*gap ? ":" : ";") : "\"", + (alias && *alias) ? alias : "", tbuf2, tbuf)) { + /* Player doesn't have Pageformat, or it eval'd to 0 */ + notify(good[i], tbuf); + } page_return(player, good[i], "Idle", "IDLE", NULL); } - mush_free((Malloc_t) tbuf, "string"); - mush_free((Malloc_t) tbuf2, "string"); + mush_free((Malloc_t) tbuf, "page_buff"); + mush_free((Malloc_t) tbuf2, "page_buff"); if (msgbuf) - mush_free((Malloc_t) msgbuf, "string"); + mush_free((Malloc_t) msgbuf, "page_buff"); if (hp) free((Malloc_t) hp); } Index: src/bsd.c =================================================================== --- src/bsd.c (.../p4) (revision 1119) +++ src/bsd.c (.../p5) (revision 1119) @@ -24,6 +24,7 @@ #include #include #include +#include #define EINTR WSAEINTR #define EWOULDBLOCK WSAEWOULDBLOCK #define MAXHOSTNAMELEN 32 @@ -277,7 +278,7 @@ #ifndef BOOLEXP_DEBUGGING #ifdef WIN32SERVICES void shutdown_checkpoint(void); -void mainthread(int argc, char **argv); +int mainthread(int argc, char **argv); #else int main(int argc, char **argv); #endif @@ -386,7 +387,7 @@ /* Under WIN32, MUSH is a "service", so we just start a thread here. * The real "main" is in win32/services.c */ -void +int mainthread(int argc, char **argv) #else /** The main function. @@ -410,7 +411,7 @@ if (getuid() == 0) { fputs("Please run the server as another user.\n", stderr); fputs("PennMUSH will not run as root as a security measure.\n", stderr); - return 1; + return EXIT_FAILURE; } /* Add suid-root checks here. */ #endif @@ -1401,7 +1402,6 @@ d->cmds = 0; d->hide = 0; d->doing[0] = '\0'; - d->mailp = NULL; welcome_user(d); } @@ -1490,7 +1490,6 @@ d->cmds = 0; d->hide = 0; d->doing[0] = '\0'; - d->mailp = NULL; mush_strncpy(d->addr, addr, 100); d->addr[99] = '\0'; mush_strncpy(d->ip, ip, 100); @@ -1596,7 +1595,11 @@ for (qp = &d->output.head; ((cur = *qp) != NULL);) { #ifdef HAVE_WRITEV - if (cur->nxt && !d->ssl) { + if (cur->nxt +#ifdef HAVE_SSL + && !d->ssl +#endif + ) { /* If there's more than one pending block, try to send up to 10 at once with writev(). Doesn't work for SSL connections, and if there's only one block waiting to go out, just use @@ -2300,7 +2303,6 @@ return 0; } } - d->mailp = find_exact_starting_point(player); /* check to see if this is a reconnect and also set DARK status */ is_hidden = Can_Hide(player) && Dark(player); @@ -2567,30 +2569,34 @@ close_sockets(void) { DESC *d, *dnext; + const char *shutmsg; + int shutlen; + shutmsg = T(shutdown_message); + shutlen = strlen(shutmsg); + for (d = descriptor_list; d; d = dnext) { dnext = d->next; +#ifdef HAS_OPENSSL if (!d->ssl) { +#endif #ifdef HAVE_WRITEV struct iovec byebye[2]; - byebye[0].iov_base = (char *) T(shutdown_message); - byebye[0].iov_len = strlen(byebye[0].iov_base); - byebye[1].iov_base = "\r\n"; + byebye[0].iov_base = (char *) shutmsg; + byebye[0].iov_len = shutlen; + byebye[1].iov_base = (char *) "\r\n"; byebye[1].iov_len = 2; writev(d->descriptor, byebye, 2); #else - const char *shutmsg = T(shutdown_message); - send(d->descriptor, shutmsg, strlen(shutmsg), 0); - send(d->descriptor, "\r\n", 2, 0); + send(d->descriptor, shutmsg, shutlen, 0); + send(d->descriptor, (char *) "\r\n", 2, 0); #endif - } #ifdef HAS_OPENSSL - if (d->ssl) { + } else { int offset; - const char *shutmsg = T(shutdown_message); offset = 0; ssl_write(d->ssl, d->ssl_state, 0, 1, (uint8_t *) shutmsg, - strlen(shutmsg), &offset); + shutlen, &offset); offset = 0; ssl_write(d->ssl, d->ssl_state, 0, 1, (uint8_t *) "\r\n", 2, &offset); ssl_close_connection(d->ssl); @@ -3614,11 +3620,28 @@ FUNCTION(fun_nwho) { DESC *d; + dbref victim; int count = 0; - int powered = (*(called_as + 1) != 'M'); + int powered = ((*(called_as + 1) != 'M') && Priv_Who(executor)); + if (nargs && args[0] && *args[0]) { + /* An argument was given. Find the victim and choose the lowest + * perms possible */ + if (!powered) { + safe_str(T(e_perm), buff, bp); + return; + } + if ((victim = noisy_match_result(executor, args[0], NOTYPE, + MAT_EVERYTHING)) == 0) { + safe_str(T(e_notvis), buff, bp); + return; + } + if (!Priv_Who(victim)) + powered = 0; + } + DESC_ITER_CONN(d) { - if (!Hidden(d) || (powered && Priv_Who(executor))) { + if (!Hidden(d) || powered) { count++; } } @@ -4258,71 +4281,6 @@ } -/** Return the mailp of the player closest in db# to player, - * or NULL if there's nobody on-line. - * In the current mail system, mail is stored in a linked list, sorted - * by recipient, which makes the most common operations (listing and reading - * your mail) fast. When a player first connects, we store (on the - * mailp element of their descriptor) a pointer to the beginning of - * their part of the linked list. Rather than search the whole linked - * list to find this location, we look at the mailp's of all the other - * connected players, and find the mailp of the player whose dbref - * is closest to the connecting player, and start our search from that - * point. This scales up nicely - as a mushes get larger, the linked - * list gets larger, but the more people connected at once, the faster - * the search for a newly connecting player's first mail. - * \param player player whose db# we want to get near. - * \return pointer to first mail of connected player with db# closest to - * player. - */ -MAIL * -desc_mail(dbref player) -{ - DESC *d; - int i; - int diff = db_top; - static MAIL *mp; - mp = NULL; - DESC_ITER_CONN(d) { - i = abs(d->player - player); - if (i == 0) - return d->mailp; - if ((i < diff) && d->mailp) { - diff = i; - mp = d->mailp; - } - } - return mp; -} - -/** Set a player's mail position on all their descriptors. - * \param player player to set mail position for. - * \param mp pointer to first mail in their list. - */ -void -desc_mail_set(dbref player, MAIL *mp) -{ - DESC *d; - DESC_ITER_CONN(d) { - if (d->player == player) - d->mailp = mp; - } -} - -/** Clear mail positions on all descriptors. Called from do_mail_nuke(). - */ -void -desc_mail_clear(void) -{ - DESC *d; - DESC_ITER_CONN(d) { - d->mailp = NULL; - } -} - - - - #ifdef SUN_OS /* SunOS's implementation of stdio breaks when you get a file descriptor * greater than 128. Brain damage, brain damage, brain damage! @@ -4586,7 +4544,6 @@ d->raw_input = NULL; d->raw_input_at = NULL; d->quota = options.starting_quota; - d->mailp = NULL; #ifdef HAS_OPENSSL d->ssl = NULL; d->ssl_state = 0; @@ -4630,9 +4587,6 @@ strcpy(poll_msg, getstring_noalloc(f)); globals.first_start_time = getref(f); globals.reboot_count = getref(f) + 1; - DESC_ITER_CONN(d) { - d->mailp = find_exact_starting_point(d->player); - } #ifdef HAS_OPENSSL if (SSLPORT) { sslsock = make_socket(SSLPORT, SOCK_STREAM, NULL, NULL, SSL_IP_ADDR); @@ -4698,7 +4652,7 @@ end_all_logs(); #ifndef WIN32 { - char *args[6]; + const char *args[6]; int n = 0; args[n++] = saved_argv[0]; @@ -4710,7 +4664,7 @@ args[n++] = confname; args[n++] = NULL; - execv(saved_argv[0], args); + execv(saved_argv[0], (char **) args); } #else execl("pennmush.exe", "pennmush.exe", "/run", NULL); Index: src/funstr.c =================================================================== --- src/funstr.c (.../p4) (revision 1119) +++ src/funstr.c (.../p5) (revision 1119) @@ -12,6 +12,7 @@ #include #include #include +#include #include "conf.h" #include "externs.h" #include "ansi.h" @@ -388,7 +389,7 @@ src = parse_ansi_string(args[2]); - ansi_string_insert(dst, pos, src, 0, src->len); + ansi_string_insert(dst, pos, src); safe_ansi_string(dst, 0, dst->len, buff, bp); @@ -458,8 +459,8 @@ dst = parse_ansi_string(args[0]); src = parse_ansi_string(args[3]); - ansi_string_delete(dst, start, len); - ansi_string_insert(dst, start, src, 0, src->len); + ansi_string_replace(dst, start, len, src); + safe_ansi_string(dst, 0, dst->len, buff, bp); free_ansi_string(dst); free_ansi_string(src); @@ -935,45 +936,20 @@ /* ARGSUSED */ FUNCTION(fun_scramble) { - int n, i, j; - ansi_string *as; - ansi_string *dst; - int pos[BUFFER_LEN]; - char tmp[BUFFER_LEN]; + ansi_string *as, *dst; if (!*args[0]) return; - /* Set up the new ansi_string */ - memset(tmp, 0, BUFFER_LEN); - dst = parse_ansi_string(tmp); - - /* Read the current one */ as = parse_ansi_string(args[0]); - - for (i = 0; i < as->len; i++) - pos[i] = i; - - n = as->len; - for (i = 0; i < n; i++) { - int t; - j = get_random_long(0, n - 1); - t = pos[i]; - pos[i] = pos[j]; - pos[j] = t; + dst = scramble_ansi_string(as); + if (dst) { + free_ansi_string(as); + as = dst; } - for (i = 0; i < n; i++) { - ansi_string_insert(dst, dst->len, as, pos[i], 1); - if ((i % 100) == 99) { - optimize_ansi_string(dst); - }