/** * \file strutil.c * * \brief String utilities for PennMUSH. * * */ #include "config.h" #include <ctype.h> #include <string.h> #include <stdlib.h> #include <stdarg.h> #include <limits.h> #ifdef WIN32 #define snprintf(s1,n,s2,s3) sprintf_s((s1), (n), (s2), (s3)) #endif #include "copyrite.h" #include "conf.h" #include "case.h" #include "pueblo.h" #include "parse.h" #include "externs.h" #include "ansi.h" #include "mymalloc.h" #include "log.h" #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_* 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_UNDERSCORE (8) /**< ANSI underscore attribute bit */ #define COL_HILITE (1) /**< ANSI hilite attribute value */ #define COL_UNDERSCORE (4) /**< ANSI underscore attribute value */ #define COL_FLASH (5) /**< ANSI flag attribute value */ #define COL_INVERT (7) /**< ANSI inverse attribute value */ #define COL_BLACK (30) /**< ANSI color black */ #define COL_RED (31) /**< ANSI color red */ #define COL_GREEN (32) /**< ANSI color green */ #define COL_YELLOW (33) /**< ANSI color yellow */ #define COL_BLUE (34) /**< ANSI color blue */ #define COL_MAGENTA (35) /**< ANSI color magenta */ #define COL_CYAN (36) /**< ANSI color cyan */ #define COL_WHITE (37) /**< ANSI color white */ /* Now the code */ HASHTAB htab_tag; /**< Hash table of safe html tags */ static int write_ansi_close(char *buff, char **bp); static int is_ansi_oldstyle(const char *str); 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); /* 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); } #ifdef ANSI_DEBUG /* ARGSUSED */ void inspect_ansi_string(ansi_string *as, dbref who); FUNCTION(fun_ansiinspect) { char *ptr; ansi_string *as; char choice; if (nargs < 2 || !args[1] || !*args[1]) choice = 'r'; else choice = *args[1]; switch (choice) { case 'i': as = parse_ansi_string(args[0]); inspect_ansi_string(as, executor); free_ansi_string(as); break; case 'r': for (ptr = args[0]; *ptr; ptr++) { if (*ptr == TAG_START) *ptr = '<'; if (*ptr == TAG_END) *ptr = '>'; } safe_str(args[0], buff, bp); break; case 'l': safe_integer(arglens[0], buff, bp); break; default: safe_str("i: inspect r: raw l: length", buff, bp); break; } } #endif /* ARGSUSED */ FUNCTION(fun_ansi) { struct ansi_data colors; char *save = *bp; char *p; int i; /* Populate the colors struct */ 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 the colors to buff */ if (write_ansi_data(&colors, buff, bp)) { *bp = save; return; } /* If the contents overrun the buffer, we * place an ANSI_ENDALL tag at the end */ if (safe_str(args[1], buff, bp) || write_ansi_close(buff, bp)) { p = buff + BUFFER_LEN - 6; /* <c/a> */ for (i = 10; i > 0 && *p != TAG_START; i--, p--) ; if (i > 0) { /* There's an extant tag, let's just replace that. */ *bp = p; safe_str(ANSI_ENDALL, buff, bp); } else { *bp = buff + BUFFER_LEN - 6; safe_str(ANSI_ENDALL, buff, bp); } } } /* ARGSUSED */ FUNCTION(fun_html) { if (!Wizard(executor)) safe_str(T(e_perm), buff, bp); else safe_tag(args[0], buff, bp); } /* ARGSUSED */ FUNCTION(fun_tag) { int i; if (!Wizard(executor) && !hash_find(&htab_tag, strupper(args[0]))) { safe_str("#-1", buff, bp); return; } safe_chr(TAG_START, buff, bp); safe_chr(MARKUP_HTML, buff, bp); safe_strl(args[0], arglens[0], buff, bp); for (i = 1; i < nargs; i++) { if (ok_tag_attribute(executor, args[i])) { safe_chr(' ', buff, bp); safe_strl(args[i], arglens[i], buff, bp); } } safe_chr(TAG_END, buff, bp); } /* ARGSUSED */ FUNCTION(fun_endtag) { if (!Wizard(executor) && !hash_find(&htab_tag, strupper(args[0]))) safe_str("#-1", buff, bp); else safe_tag_cancel(args[0], buff, bp); } /* ARGSUSED */ FUNCTION(fun_tagwrap) { if (!Wizard(executor) && !hash_find(&htab_tag, strupper(args[0]))) safe_str("#-1", buff, bp); else { if (nargs == 2) safe_tag_wrap(args[0], NULL, args[1], buff, bp, executor); else safe_tag_wrap(args[0], args[1], args[2], buff, bp, executor); } } /** A version of strlen that ignores ansi and HTML sequences. * \param p string to get length of. * \return length of string p, not including ansi/html sequences. */ int ansi_strlen(const char *p) { int i = 0; if (!p) return 0; while (*p) { if (*p == ESC_CHAR) { while ((*p) && (*p != 'm')) p++; } else if (*p == TAG_START) { while ((*p) && (*p != TAG_END)) p++; } else { i++; } p++; } 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 * characters. The apparent length skips over nonprinting ansi and * tags. * \param p string. * \param numchars maximum size to report. * \return apparent length of string. */ int ansi_strnlen(const char *p, size_t numchars) { size_t i = 0; if (!p) return 0; while (*p && numchars > 0) { if (*p == ESC_CHAR) { while ((*p) && (*p != 'm')) { p++; i++; } } else if (*p == TAG_START) { while ((*p) && (*p != TAG_END)) { p++; i++; } } else numchars--; i++; p++; } return i; } /** 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. * \param orig string to strip. * \param s_len address to store length of stripped string, if provided. * \return pointer to static buffer containing stripped string. */ char * remove_markup(const char *orig, size_t * s_len) { static char buff[BUFFER_LEN]; char *bp = buff; const char *q; size_t len = 0; if (!orig) { if (s_len) *s_len = 0; return NULL; } for (q = orig; *q;) { switch (*q) { case ESC_CHAR: /* Skip over ansi */ while (*q && *q++ != 'm') ; break; case TAG_START: /* Skip over HTML */ while (*q && *q++ != TAG_END) ; break; default: safe_chr(*q++, buff, &bp); len++; } } *bp = '\0'; if (s_len) *s_len = len + 1; return buff; } 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} }; 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; } } int read_raw_ansi_data(struct ansi_data *store, const char *codes) { 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; } static int write_ansi_close(char *buff, char **bp) { int retval = 0; 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); return retval; } int write_ansi_data(struct 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); } else { #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)) retval += safe_chr('h', buff, bp); if (CBIT_SET(cur, CBIT_INVERT)) retval += safe_chr('i', buff, bp); if (CBIT_SET(cur, CBIT_UNDERSCORE)) retval += safe_chr('u', buff, bp); #undef CBIT_SET #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)) retval += safe_chr('H', buff, bp); if (CBIT_SET(cur, CBIT_INVERT)) retval += safe_chr('I', buff, bp); if (CBIT_SET(cur, CBIT_UNDERSCORE)) 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); } retval += safe_chr(TAG_END, buff, bp); 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) { if (cur->fore != 'n') { cur->bits |= old->bits; cur->bits &= ~cur->offbits; if (!cur->fore) cur->fore = old->fore; if (!cur->back) cur->back = old->back; } } int write_raw_ansi_data(struct ansi_data *old, struct ansi_data *cur, char *buff, char **bp) { int f = 0; if (cur->fore == 'n') { if (old->bits || (old->fore != 'n') || old->back) { return safe_str(COL_NORMAL, buff, bp); } } if (cur->fore == 'd') cur->fore = 0; if (cur->back == 'D') cur->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; } cur->bits |= old->bits; cur->bits &= ~cur->offbits; if (old->fore == cur->fore && old->back == cur->back && old->bits == cur->bits) return 0; if (!(cur->fore || cur->back || cur->bits || cur->offbits)) { return safe_str(COL_NORMAL, buff, bp); } safe_str(ANSI_BEGIN, buff, bp); if (EDGE_UP(old, cur, CBIT_FLASH)) { if (f++) safe_chr(';', buff, bp); safe_integer(ansi_codes['f'], buff, bp); } if (EDGE_UP(old, cur, CBIT_HILITE)) { if (f++) safe_chr(';', buff, bp); safe_integer(ansi_codes['h'], buff, bp); } if (EDGE_UP(old, cur, CBIT_INVERT)) { if (f++) safe_chr(';', buff, bp); safe_integer(ansi_codes['i'], buff, bp); } if (EDGE_UP(old, cur, CBIT_UNDERSCORE)) { if (f++) safe_chr(';', buff, bp); safe_integer(ansi_codes['u'], buff, bp); } if (cur->fore && cur->fore != old->fore) { if (f++) safe_chr(';', buff, bp); safe_integer(ansi_codes[(unsigned char) cur->fore], buff, bp); } if (cur->back && cur->back != old->back) { if (f++) safe_chr(';', buff, bp); safe_integer(ansi_codes[(unsigned char) cur->back], buff, bp); } return safe_str(ANSI_FINISH, buff, bp); } void define_ansi_data(struct ansi_data *cur, const char *str) { cur->bits = 0; cur->offbits = 0; cur->fore = 0; cur->back = 0; 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 = 'n'; cur->back = 0; break; case 'f': /* flash */ cur->bits |= CBIT_FLASH; break; case 'h': /* hilite */ cur->bits |= CBIT_HILITE; break; case 'i': /* inverse */ cur->bits |= CBIT_INVERT; break; case 'u': /* underscore */ cur->bits |= CBIT_UNDERSCORE; break; case 'F': /* flash */ cur->offbits |= CBIT_FLASH; break; case 'H': /* hilite */ cur->offbits |= CBIT_HILITE; break; case 'I': /* inverse */ cur->offbits |= CBIT_INVERT; break; case 'U': /* underscore */ cur->offbits |= CBIT_UNDERSCORE; break; case 'b': /* blue fg */ case 'c': /* cyan fg */ case 'g': /* green fg */ case 'm': /* magenta fg */ case 'r': /* red fg */ case 'w': /* white fg */ case 'x': /* black fg */ case 'y': /* yellow fg */ case 'd': /* default fg */ cur->fore = *str; break; case 'B': /* blue bg */ case 'C': /* cyan bg */ case 'G': /* green bg */ case 'M': /* magenta bg */ case 'R': /* red bg */ case 'W': /* white bg */ case 'X': /* black bg */ case 'Y': /* yellow bg */ case 'D': /* default fg */ cur->back = *str; break; } } } /** 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. */ char * skip_leading_ansi(const char *p) { if (!p) return NULL; while (*p == ESC_CHAR || *p == TAG_START) { if (*p == ESC_CHAR) { while (*p && *p != 'm') p++; } else { /* TAG_START */ while (*p && *p != TAG_END) p++; } if (*p) p++; } return (char *) p; } static char * parse_tagname(const char *ptr) { static char tagname[BUFFER_LEN]; char *tag = tagname; if (!ptr || !*ptr) return NULL; while (*ptr && !isspace((unsigned char) *ptr) && *ptr != TAG_END) { *(tag++) = *(ptr++); } *tag = '\0'; return tagname; } static void free_markup_info(markup_information *info) { if (info) { if (info->start_code) { mush_free(info->start_code, "markup_code"); info->start_code = NULL; } if (info->stop_code) { mush_free(info->stop_code, "markup_code"); info->stop_code = NULL; } } } /* 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 <p/ or <c/ - which is newstyle */ return 0; } /* Check for those that don't begin with p or c */ for (ptr = strchr(source, TAG_START); ptr; ptr = strchr(ptr + 1, TAG_START)) { if ((*(ptr + 1) != MARKUP_HTML) && (*(ptr + 1) != MARKUP_COLOR)) { return 1; } } for (ptr = strchr(source, TAG_START); ptr; ptr = strchr(ptr + 1, TAG_START)) { /* Check for <pre ...> */ if (strncasecmp(ptr + 1, "PRE", 3) == 0) return 1; /* And <p> or <p ...> */ 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 * the plain string and the markup codes for each character. * \param source string to parse. * \return pointer to an ansi_string structure representing the src string. */ ansi_string * parse_ansi_string(const char *source) { return parse_ansi_string_real(source, 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 * 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. */ ansi_string * parse_ansi_string_real(const char *source, int oldstyle) { ansi_string *data = NULL; char src[BUFFER_LEN], *sptr; char tagbuff[BUFFER_LEN]; char *ptr, *txt; char *tmp; char type; int i, j; int priority = 0; markup_information *info; 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; /* Set it to zero */ memset(data, 0, sizeof(ansi_string)); txt = data->text; i = 0; 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 */ tmp--; } ptr++; type = oldstyle ? MARKUP_HTML : *(ptr++); switch (type) { case MARKUP_HTML: if (*ptr && *ptr != '/') { /* We're at the start tag. */ info = &(data->markup[data->nmarkups++]); info->start_code = mush_strdup(ptr, "markup_code"); snprintf(tagbuff, BUFFER_LEN, "/%s", parse_tagname(ptr)); info->stop_code = mush_strdup(tagbuff, "markup_code"); info->type = MARKUP_HTML; info->start = i; 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) { break; } } if (j >= 0) { data->markup[j].end = i; } else { /* This is greviously wrong, we can't find the begin tag? * Consider this a standalone tag with no close tag. */ info = &(data->markup[data->nmarkups++]); /* Start code is where we are */ info->stop_code = mush_strdup(ptr, "markup_code"); info->type = MARKUP_HTML; info->priority = priority++; info->start = -1; info->end = i; } } 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) { /* If we're not, this is broken in more ways than I can think */ goto broken_string; } break; } ptr = tmp; 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) */ 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; } } ptr = tmp; if (*tmp) ptr++; break; default: txt[i++] = *(ptr++); } } txt[i] = '\0'; data->len = i; /* 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]); 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 (<IMG>, <BR>, etc) * This is ugly - it's not a "start" but a "stop" */ if (info->end < 0) { mush_free(info->stop_code, "markup_code"); info->stop_code = info->start_code; info->start_code = NULL; info->end = info->start; info->start = -1; } break; } } return data; broken_string: /* This stinks. We treat this as if it's not ansi 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])); } data->nmarkups = 0; return data; } /** Reverse an ansi string, preserving its ansification. * This function destructively modifies the ansi_string passed. * \param as pointer to an ansi string. */ void flip_ansi_string(ansi_string *as) { int i, j; markup_information *info; char tmp; 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]; as->text[i] = as->text[j]; as->text[j] = tmp; } /* Now reverse the markup. */ for (i = as->nmarkups - 1; i >= 0; i--) { int start, end; info = &(as->markup[i]); if (info->start == -1) { /* Standalones */ info->end = len - info->end; } else { end = len - info->start; start = len - info->end; info->start = start; info->end = end; } } } /** Free an ansi_string. * \param as pointer to ansi_string to free. */ void free_ansi_string(ansi_string *as) { int i; if (!as) return; for (i = as->nmarkups - 1; i >= 0; i--) { free_markup_info(&(as->markup[i])); } 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) { markup_information *ai, *bi; ai = (markup_information *) a; bi = (markup_information *) b; // if (ai->start == bi->start) return ai->priority - bi->priority; 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) { /* 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) continue; for (j = i + 1; j < as->nmarkups; j++) { /* Already removed? */ if (as->markup[j].start == -1 && as->markup[j].end == -1) 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) ) { 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; } } } } /* 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)); } j++; } else { free_markup_info(&(as->markup[i])); } } as->nmarkups = j; } /* 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 */ static int copy_start_code(markup_information *info, char *buff, char **bp) { int retval = 0; char *save; save = *bp; if (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 */ 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)) { 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; return retval; } #ifdef ANSI_DEBUG void inspect_ansi_string(ansi_string *as, dbref who) { markup_information *info; int count = 0; int j; notify_format(who, "Inspecting ansi string"); notify_format(who, " Text: %s", as->text); notify_format(who, " Nmarkups: %d", as->nmarkups); for (j = 0; j < as->nmarkups; j++) { info = &(as->markup[j]); if (info->type == MARKUP_HTML) { notify_format(who, " %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"); } #endif /** Delete a portion of an ansi string. * \param as ansi_string to delete from * \param start start point to remove * \param size length of string to remove * \retval 0 success * \reval 1 failure. */ int ansi_string_delete(ansi_string *as, int start, int count) { int i; int end; markup_information *dm; if (start >= as->len) return 0; if (count <= 0) return 0; if ((start + count) > as->len) count = as->len - start; end = start + count; as->optimized = 0; dm = as->markup; /* Remove or shrink the markup on dst */ for (i = 0; i < as->nmarkups; i++) { if (dm[i].start >= start && dm[i].end <= end) { dm[i].start = -1; dm[i].end = -1; } if (dm[i].start >= start) { dm[i].start -= count; if (dm[i].start < start) dm[i].start = start; } if (dm[i].end > start) { dm[i].end -= count; if (dm[i].end < start) dm[i].end = start; } } /* Shift text over */ memmove(as->text + start, as->text + end, as->len - end); as->len -= count; as->text[as->len] = '\0'; return 0; } #define copyto(x,y) \ 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 src ansi_string to insert * \param start start point in src * \param size length of string from src * \retval 0 success * \reval 1 failure. */ int ansi_string_insert(ansi_string *dst, int loc, ansi_string *src, int start, int count) { int i, j; int len; int end, m_end; int rval = 0; markup_information *dm, *sm; if (loc >= dst->len) loc = dst->len; if (start >= src->len) return 0; if (count <= 0) return 0; if ((start + count) > src->len) count = src->len - start; 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 */ 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; } /* Copy markup */ for (j = 0; j < src->nmarkups; j++) { /* It's possible, but not at all easy, to get this much ansi 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)) { copyto(dm[i], sm[j]); if (sm[j].start < 0) { 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; i++; } } dst->nmarkups = i; dst->len += count; if (dst->len >= BUFFER_LEN) { rval = 1; dst->len = BUFFER_LEN - 1; } len = dst->len - m_end; /* 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); } } /* Copy text from src */ if (loc + count >= BUFFER_LEN) count = BUFFER_LEN - 1 - loc; memcpy(dst->text + loc, src->text + start, count); dst->text[dst->len] = '\0'; return rval; } /** Replace a portion of an ansi string with * another ansi string, keeping markups as * straight as possible. * \param dst ansi_string to insert into. * \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. */ int ansi_string_replace(ansi_string *dst, int loc, int len, ansi_string *src, int start, int count) { int i, j; int end, m_end, s_end; int diff; int rval = 0; markup_information *dm, *sm; /* 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; /* Is it really a delete? */ if ((start >= src->len) || (count <= 0)) { return ansi_string_delete(dst, loc, len); } /* Starting location */ if (loc <= 0) loc = 0; if (loc >= dst->len) loc = dst->len; end = loc + len; diff = count - len; m_end = loc + count; dst->optimized = 0; dm = dst->markup; sm = src->markup; /* Modify, remove, stretch, and mangle 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 */ 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) dm[i].start += diff; dm[i].end += diff; } } s_end = start + count; /* Copy markup */ for (j = 0; j < src->nmarkups; j++) { /* It's possible, but not at all easy, to get this much ansi 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)) { copyto(dm[i], sm[j]); if (sm[j].start < 0) { 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; i++; } } if (i >= BUFFER_LEN) i = BUFFER_LEN - 1; dst->nmarkups = i; /* length of original string after replace bits */ len = dst->len - end; dst->len += diff; if (dst->len >= BUFFER_LEN) { rval = 1; dst->len = BUFFER_LEN - 1; } /* 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); } } /* Copy text from src */ if (loc + count >= BUFFER_LEN) count = BUFFER_LEN - (1 + loc); memcpy(dst->text + loc, src->text + start, count); dst->text[dst->len] = '\0'; return rval; } #undef copyto /** Safely append an ansi_string into a buffer as a real string, * \param as pointer to ansi_string to append. * \param start position in as to start copying from. * \param len length in characters to copy from as. * \param buff buffer to insert into. * \param bp pointer to pointer to insertion point of buff. * \retval 0 success. * \retval 1 failure. */ int safe_ansi_string(ansi_string *as, int start, int len, char *buff, char **bp) { int i, j; markup_information *info; int nextstart, nextend, next; int end = start + len; int retval = 0; if (as->optimized == 0) { optimize_ansi_string(as); as->optimized = 1; } if (len <= 0) return 0; i = start; if (start >= as->len) return 0; if (end > as->len) end = as->len; /* 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); } } /* 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 += copy_start_code(info, buff, bp); } } } /* Find the next changes */ nextstart = BUFFER_LEN + 1; 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; } next = (nextend < nextstart) ? nextend : nextstart; if (end < next) next = end; for (; i < next && i < as->len; i++) { if (as->text[i]) safe_chr(as->text[i], buff, bp); } i = next; 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 += copy_stop_code(info, buff, bp); } 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; } } } next = (nextend < nextstart) ? nextend : nextstart; if (end < next) next = end; for (; i < next && i < as->len; i++) { if (as->text[i]) 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); } return retval; } /* Following functions are used for * decompose_str() */ 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) { 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; 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--; 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]]) { retval += safe_chr('\\', buff, bp); retval += safe_chr(in[i], buff, bp); } else { retval += safe_chr(in[i], buff, bp); } } } 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); } } retval += safe_str("%b", buff, bp); } } } return retval; } static int dump_start_code(markup_information *info, 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 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; } 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; } 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 (as->optimized == 0) { optimize_ansi_string(as); as->optimized = 1; } i = start; if (start > as->len) return 0; if (end > as->len) end = as->len; /* 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); } } /* 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); } } } /* Find the next changes */ nextstart = BUFFER_LEN + 1; 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; } next = (nextend < nextstart) ? nextend : nextstart; if (end < next) next = end; if (next > as->len) next = as->len; escape_strn(as->text, i, next - i, buff, bp); i = next; 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; } } 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; } } } next = (nextend < nextstart) ? nextend : nextstart; if (end < next) next = end; escape_strn(as->text, i, next - i, buff, bp); i = next; } /* 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); } return retval; } /** Our version of pcre_copy_substring, with ansi-safeness. * \param as the ansi_string whose .text value was matched against. * \param ovector the offset vectors * \param stringcount the number of subpatterns * \param stringnumber the number of the desired subpattern * \param buff buffer to copy the subpattern to * \param bp pointer to the end of buffer * \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) { int yield; if (stringnumber < 0 || stringnumber >= stringcount) return -1; stringnumber *= 2; yield = ovector[stringnumber + 1] - ovector[stringnumber]; if (!nonempty || yield) { safe_ansi_string(as, ovector[stringnumber], yield, buff, bp); **bp = '\0'; } return yield; } /** Our version of pcre_copy_named_substring, with ansi-safeness. * \param code the pcre compiled code * \param as the ansi_string whose .text value was matched against. * \param ovector the offset vectors * \param stringcount the number of subpatterns * \param stringname the name of the desired subpattern * \param buff buffer to copy the subpattern to * \param bp pointer to the end of buffer * \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, char *buff, char **bp) { int n = pcre_get_stringnumber(code, stringname); if (n <= 0) return -1; return ansi_pcre_copy_substring(as, ovector, stringcount, n, ne, buff, bp); } /** Safely add a tag into a buffer. * If we support pueblo, this function adds the tag start token, * the tag, and the tag end token. If not, it does nothing. * If we can't fit the tag in, we don't put any of it in. * \param a_tag the html tag to add. * \param buf the buffer to append to. * \param bp pointer to address in buf to insert. * \retval 0, successfully added. * \retval 1, tag wouldn't fit in buffer. */ static int safe_markup(char const *a_tag, char *buf, char **bp, char type) { 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); result = safe_chr(TAG_END, buf, bp); /* If it didn't all fit, rewind. */ if (result) *bp = save; return result; } int safe_tag(char const *a_tag, char *buff, char **bp) { if (SUPPORT_PUEBLO) return safe_markup(a_tag, buff, bp, MARKUP_HTML); return 0; } /** Safely add a closing tag into a buffer. * If we support pueblo, this function adds the tag start token, * a slash, the tag, and the tag end token. If not, it does nothing. * If we can't fit the tag in, we don't put any of it in. * \param a_tag the html tag to add. * \param buf the buffer to append to. * \param bp pointer to address in buf to insert. * \retval 0, successfully added. * \retval 1, tag wouldn't fit in buffer. */ static int safe_markup_cancel(char const *a_tag, char *buf, char **bp, char type) { int result = 0; char *save = buf; result = safe_chr(TAG_START, buf, bp); result = safe_chr(type, buf, bp); result = safe_chr('/', buf, bp); result = safe_str(a_tag, buf, bp); result = safe_chr(TAG_END, buf, bp); /* If it didn't all fit, rewind. */ if (result) *bp = save; return result; } int safe_tag_cancel(char const *a_tag, char *buf, char **bp) { if (SUPPORT_PUEBLO) return safe_markup_cancel(a_tag, buf, bp, MARKUP_HTML); return 0; } /** Safely add a tag, some text, and a matching closing tag into a buffer. * If we can't fit the stuff, we don't put any of it in. * \param a_tag the html tag to add. * \param params tag parameters. * \param data the text to wrap the tag around. * \param buf the buffer to append to. * \param bp pointer to address in buf to insert. * \param player the player involved in all this, or NOTHING if internal. * \retval 0, successfully added. * \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) { int result = 0; char *save = buf; if (SUPPORT_PUEBLO) { result = safe_chr(TAG_START, buf, bp); result = safe_chr(MARKUP_HTML, buf, bp); result = safe_str(a_tag, buf, bp); if (params && *params && ok_tag_attribute(player, params)) { result = safe_chr(' ', buf, bp); result = safe_str(params, buf, bp); } result = safe_chr(TAG_END, buf, bp); } result = safe_str(data, buf, bp); if (SUPPORT_PUEBLO) { result = safe_tag_cancel(a_tag, buf, bp); } /* If it didn't all fit, rewind. */ if (result) *bp = save; return result; } /** Initialize the html tag hash table with all the safe tags from HTML 4.0 */ void init_tag_hashtab(void) { static char dummy = 1; int i = 0; static const char *safetags[] = { "A", "B", "I", "U", "STRONG", "EM", "ADDRESS", "BLOCKQUOTE", "CENTER", "DEL", "DIV", "H1", "H2", "H3", "H4", "H5", "H6", "HR", "INS", "P", "PRE", "DIR", "DL", "DT", "DD", "LI", "MENU", "OL", "UL", "TABLE", "CAPTION", "COLGROUP", "COL", "THEAD", "TFOOT", "TBODY", "TR", "TD", "TH", "BR", "FONT", "IMG", "SPAN", "SUB", "SUP", "ABBR", "ACRONYM", "CITE", "CODE", "DFN", "KBD", "SAMP", "VAR", "BIG", "S", "SMALL", "STRIKE", "TT", NULL }; hashinit(&htab_tag, 64, 1); while (safetags[i]) { hashadd(safetags[i], (void *) &dummy, &htab_tag); i++; } }