]> diplodocus.org Git - nmh/blobdiff - sbr/fmt_scan.c
Fix invalid pointer arithmetic.
[nmh] / sbr / fmt_scan.c
index b27f7c6c9f259fa7108f6eb802678e4cc4abb4e5..791146a21a83203925743d1791b40bc247dafe52 100644 (file)
@@ -1,12 +1,11 @@
-
-/*
- * fmt_scan.c -- format string interpretation
- *
- * $Id$
+/* fmt_scan.c -- format string interpretation
  *
  * This code is Copyright (c) 2002, by the authors of nmh.  See the
  * COPYRIGHT file in the root directory of the nmh distribution for
  * complete copyright information.
+ *
+ * This is the engine that processes the format instructions created by
+ * fmt_compile (found in fmt_compile.c).
  */
 
 #include <h/mh.h>
 #include <h/fmt_scan.h>
 #include <h/tws.h>
 #include <h/fmt_compile.h>
+#include <h/utils.h>
+#include "unquote.h"
 
-#ifdef TIME_WITH_SYS_TIME
+#ifdef HAVE_SYS_TIME_H
 # include <sys/time.h>
-# include <time.h>
-#else
-# ifdef TM_IN_SYS_TIME
-#  include <sys/time.h>
-# else
-#  include <time.h>
-# endif
 #endif
-#include <wchar.h>
-
-#define        NFMTS MAXARGS
-
-extern char *formataddr ();    /* hook for custom address formatting */
-
-#ifdef LBL
-struct msgs *fmt_current_folder; /* current folder (set by main program) */
+#include <time.h>
+#ifdef MULTIBYTE_SUPPORT
+#  include <wctype.h>
+#  include <wchar.h>
 #endif
 
-extern int fmt_norm;           /* defined in sbr/fmt_def.c = AD_NAME */
-struct mailname fmt_mnull;
+struct mailname fmt_mnull = { NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, 0, 0,
+                             NULL, NULL };
 
 /*
  * static prototypes
  */
-static int match (char *, char *);
+static int match (char *, char *) PURE;
 static char *get_x400_friendly (char *, char *, int);
 static int get_x400_comp (char *, char *, char *, int);
 
@@ -57,103 +47,149 @@ match (char *str, char *sub)
     int c1, c2;
     char *s1, *s2;
 
-#ifdef LOCALE
-    while ((c1 = *sub)) {
-       c1 = (isalpha(c1) && isupper(c1)) ? tolower(c1) : c1;
-       while ((c2 = *str++) && c1 != ((isalpha(c2) && isupper(c2)) ? tolower(c2) : c2))
-           ;
-       if (! c2)
-           return 0;
-       s1 = sub + 1; s2 = str;
-       while ((c1 = *s1++) && ((isalpha(c1) && isupper(c1)) ? tolower(c1) : c1) == ((isalpha(c2 =*s2++) && isupper(c2)) ? tolower(c2) : c2))
-           ;
-       if (! c1)
-           return 1;
-    }
-#else
     while ((c1 = *sub)) {
-       while ((c2 = *str++) && (c1 | 040) != (c2 | 040))
+       c1 = tolower((unsigned char)c1);
+       while ((c2 = *str++) && c1 != tolower((unsigned char)c2))
            ;
        if (! c2)
            return 0;
        s1 = sub + 1; s2 = str;
-       while ((c1 = *s1++) && (c1 | 040) == (*s2++ | 040))
+       while ((c1 = *s1++) &&
+            tolower((unsigned char)c1) == tolower((unsigned char)(c2 = *s2++)))
            ;
        if (! c1)
            return 1;
     }
-#endif
     return 1;
 }
 
 /*
  * copy a number to the destination subject to a maximum width
  */
-static void
-cpnumber(char **dest, int num, unsigned int wid, char fill, size_t n) {
-    int i, c;
-    char *sp;
-    char *cp = *dest;
-    char *ep = cp + n;
-
-    if (cp + wid < ep) {
-       if ((i = (num)) < 0)
-           i = -(num);
-       if ((c = (wid)) < 0)
-           c = -c;
-       sp = cp + c;
+void
+cpnumber(charstring_t dest, int num, int wid, char fill, size_t max) {
+    /* Maybe we should handle left padding at some point? */
+    if (wid == 0)
+       return;
+    if (wid < 0)
+        wid = -wid; /* OK because wid originally a short. */
+    if ((size_t)wid < (num >= 0 ? max : max-1)) {
+       /* Build up the string representation of num in reverse. */
+       charstring_t rev = charstring_create (0);
+       int i = num >= 0 ? num : -num;
+
        do {
-           *--sp = (i % 10) + '0';
+           charstring_push_back (rev, i % 10  +  '0');
            i /= 10;
-       } while (i > 0 && sp > cp);
-       if (i > 0)
-           *sp = '?';
-       else if ((num) < 0 && sp > cp)
-           *--sp = '-';
-       while (sp > cp)
-           *--sp = fill;
-       cp += c;
+       } while (--wid > 0  &&  i > 0);
+       if (i > 0) {
+           /* Overflowed the field (wid). */
+           charstring_push_back (rev, '?');
+       } else if (num < 0  &&  wid > 0) {
+           /* Shouldn't need the wid > 0 check, that's why the condition
+              at the top checks wid < max-1 when num < 0. */
+           --wid;
+           if (fill == ' ') {
+               charstring_push_back (rev, '-');
+           }
+       }
+       while (wid-- > 0  &&  fill != 0) {
+           charstring_push_back (rev, fill);
+       }
+       if (num < 0  &&  fill == '0') {
+           charstring_push_back (rev, '-');
+       }
+
+       {
+           /* Output the string in reverse. */
+           size_t b = charstring_bytes (rev);
+           const char *cp = b  ?  &charstring_buffer (rev)[b]  :  NULL;
+
+           for (; b > 0; --b) {
+               charstring_push_back (dest, *--cp);
+           }
+       }
+
+       charstring_free (rev);
     }
-    *dest = cp;
 }
 
 /*
- * copy string from str to dest padding with the fill character to a size
- * of wid characters. if wid is negative, the string is right aligned
- * no more than n bytes are copied
+ * copy string from str to dest padding with the fill character to a
+ * size of wid characters. if wid is negative, the string is right
+ * aligned no more than max characters are copied
  */
-static void
-cptrimmed(char **dest, char *str, unsigned int wid, char fill, size_t n) {
+void
+cptrimmed(charstring_t dest, char *str, int wid, char fill, size_t max) {
     int remaining;     /* remaining output width available */
-    int c, ljust, w;
-    int end;           /* number of input bytes remaining in str */
+    int rjust;
+    struct charstring *trimmed;
+    size_t end;        /* number of input bytes remaining in str */
+#ifdef MULTIBYTE_SUPPORT
     int char_len;      /* bytes in current character */
+    int w;
     wchar_t wide_char;
+    char *altstr = NULL;
+#endif
     char *sp;          /* current position in source string */
-    char *cp = *dest;  /* current position in destination string */
-    char *ep = cp + n; /* end of destination buffer */
     int prevCtrl = 1;
 
     /* get alignment */
-    ljust = 0;
-    if ((remaining = (wid)) < 0) {
+    rjust = 0;
+    if ((remaining = wid) < 0) {
        remaining = -remaining;
-       ljust++;
+       rjust++;
     }
-    if ((sp = (str))) {
-       mbtowc(NULL, NULL, 0); /* reset shift state */
+    if (remaining > (int) max) { remaining = max; }
+
+    trimmed = rjust ? charstring_create(remaining) : dest;
+
+    if ((sp = str)) {
+#ifdef MULTIBYTE_SUPPORT
+       if (mbtowc(NULL, NULL, 0)) {} /* reset shift state */
+#endif
        end = strlen(str);
        while (*sp && remaining > 0 && end > 0) {
+#ifdef MULTIBYTE_SUPPORT
            char_len = mbtowc(&wide_char, sp, end);
-           if (char_len <= 0 || (cp + char_len > ep))
+
+           /*
+            * See the relevant comments in cpstripped() to explain what's
+            * going on here; we want to handle the case where we get
+            * characters that mbtowc() cannot handle
+            */
+
+           if (char_len < 0) {
+               altstr = "?";
+               char_len = mbtowc(&wide_char, altstr, 1);
+           }
+
+           if (char_len <= 0) {
+               break;
+           }
+
+           w = wcwidth(wide_char);
+
+           /* If w > remaining, w must be positive. */
+           if (w > remaining) {
                break;
+           }
 
            end -= char_len;
 
            if (iswcntrl(wide_char) || iswspace(wide_char)) {
                sp += char_len;
+#else
+            int c;
+           end--;
+            /* isnctrl(), etc., take an int argument.  Cygwin's ctype.h
+               intentionally warns if they are passed a char. */
+            c = (unsigned char) *sp;
+           if (iscntrl(c) || isspace(c)) {
+               sp++;
+#endif
                if (!prevCtrl) {
-                   *cp++ = ' ';
+                    charstring_push_back (trimmed, ' ');
                    remaining--;
                }
 
@@ -162,71 +198,148 @@ cptrimmed(char **dest, char *str, unsigned int wid, char fill, size_t n) {
            }
            prevCtrl = 0;
 
-           w = wcwidth(wide_char);
+#ifdef MULTIBYTE_SUPPORT
            if (w >= 0 && remaining >= w) {
-               strncpy(cp, sp, char_len);
-               cp += char_len;
+                charstring_push_back_chars (trimmed, altstr ? altstr : sp,
+                                           char_len, w);
                remaining -= w;
+               altstr = NULL;
            }
            sp += char_len;
+#else
+            charstring_push_back (trimmed, *sp++);
+           remaining--;
+#endif
        }
     }
 
-    if (ljust) {
-       if (cp + remaining > ep)
-           remaining = ep - cp;
-       ep = cp + remaining;
-       if (remaining > 0) {
-           /* copy string to the right */
-           while (--cp >= *dest)
-               *(cp + remaining) = *cp;
-           /* add padding at the beginning */
-           cp += remaining;
-           for (c=remaining; c>0; c--)
-               *cp-- = fill;
-       }
-       *dest = ep;
-    } else {
-       /* pad remaining space */
-       while (remaining-- > 0 && cp < ep)
-               *cp++ = fill;
-       *dest = cp;
+    while (remaining-- > 0) {
+        charstring_push_back(dest, fill);
+    }
+
+    if (rjust) {
+        charstring_append(dest, trimmed);
+        charstring_free(trimmed);
     }
 }
 
+#ifdef MULTIBYTE_SUPPORT
 static void
-cpstripped (char **start, char *end, char *str)
+cpstripped (charstring_t dest, size_t max, char *str)
 {
+    static bool deja_vu;
+    static char *oddchar;
+    static size_t oddlen;
+    static char *spacechar;
+    static size_t spacelen;
+    char *end;
+    bool squash;
+    char *src;
+    int srclen;
+    wchar_t rune;
+    int w;
+
+    if (!deja_vu) {
+        size_t two;
+
+        deja_vu = true;
+
+        two = MB_CUR_MAX * 2; /* Varies at run-time. */
+
+        oddchar = mh_xmalloc(two);
+        oddlen = wcstombs(oddchar, L"?", two);
+        assert(oddlen > 0);
+
+        assert(wcwidth(L' ') == 1); /* Need to pad in ones. */
+        spacechar = mh_xmalloc(two);
+        spacelen = wcstombs(spacechar, L" ", two);
+        assert(spacelen > 0);
+    }
+
+    if (!str)
+        return; /* It's unclear why no padding in this case. */
+    end = str + strlen(str);
+
+    if (mbtowc(NULL, NULL, 0))
+        {} /* Reset shift state. */
+
+    squash = true; /* Trim `space' or `cntrl' from the start. */
+    while (max) {
+        if (!*str)
+            return; /* It's unclear why no padding in this case. */
+
+        srclen = mbtowc(&rune, str, end - str);
+        if (srclen == -1) {
+            /* Invalid rune, or not enough bytes to finish it. */
+            rune = L'?';
+            src = oddchar;
+            srclen = oddlen;
+            str++; /* Skip one byte. */
+        } else {
+            src = str;
+            str += srclen;
+        }
+
+        if (iswspace(rune) || iswcntrl(rune)) {
+            if (squash)
+                continue; /* Amidst a run of these. */
+            rune = L' ';
+            src = spacechar;
+            srclen = spacelen;
+            squash = true;
+        } else
+            squash = false;
+
+        w = wcwidth(rune);
+        if (w == -1) {
+            rune = L'?';
+            w = wcwidth(rune);
+            assert(w != -1);
+            src = oddchar;
+            srclen = oddlen;
+        }
+
+        if ((size_t)w > max) {
+            /* No room for rune;  pad. */
+            while (max--)
+                charstring_push_back_chars(dest, spacechar, spacelen, 1);
+            return;
+        }
+
+        charstring_push_back_chars(dest, src, srclen, w);
+        max -= w;
+    }
+}
+#endif
+
+#ifndef MULTIBYTE_SUPPORT
+static void
+cpstripped (charstring_t dest, size_t max, char *str)
+{
+    bool squash;
     int c;
-    char *s = str;
 
-    if (!s)
+    if (!str)
        return;
 
-    /* skip any initial control characters or spaces */
-    while ((c = (unsigned char) *s) &&
-#ifdef LOCALE
-           (iscntrl(c) || isspace(c)))
-#else
-           (c <= 32))
-#endif
-       s++;
-
-    /* compact repeated control characters and spaces into a single space */
-    while((c = (unsigned char) *s++) && *start < end)
-       if (!iscntrl(c) && !isspace(c))
-           *(*start)++ = c;
-       else {
-           while ((c = (unsigned char) *s) &&
-#ifdef LOCALE
-                   (iscntrl(c) || isspace(c)))
-#else
-                   (c <= 32))
-#endif
-               s++;
-           *(*start)++ = ' ';
-       }
+    squash = true; /* Strip leading cases. */
+    while (max--) {
+        c = (unsigned char)*str++;
+        if (!c)
+            return;
+
+       if (isspace(c) || iscntrl(c)) {
+            if (squash)
+                continue;
+            c = ' ';
+            squash = true;
+        } else
+            squash = false;
+
+       charstring_push_back(dest, (char)c);
+    }
 }
+#endif
 
 static char *lmonth[] = { "January",  "February","March",   "April",
                          "May",      "June",    "July",    "August",
@@ -272,26 +385,33 @@ get_x400_comp (char *mbox, char *key, char *buffer, int buffer_len)
            || !(cp = strchr(mbox += idx + strlen (key), '/')))
        return 0;
 
-    snprintf (buffer, buffer_len, "%*.*s", cp - mbox, cp - mbox, mbox);
+    snprintf (buffer, buffer_len, "%*.*s", (int)(cp - mbox), (int)(cp - mbox), mbox);
     return 1;
 }
 
 struct format *
-fmt_scan (struct format *format, char *scanl, int width, int *dat)
+fmt_scan (struct format *format, charstring_t scanlp, int width, int *dat,
+         struct fmt_callbacks *callbacks)
 {
-    char *cp, *ep, *sp;
-    char *savestr, *str = NULL;
-    char buffer[BUFSIZ], buffer2[BUFSIZ];
-    int i, c, ljust, n;
-    int value = 0;
+    char *sp;
+    char *savestr, *str;
+    char buffer[NMH_BUFSIZ], buffer2[NMH_BUFSIZ];
+    int i, c, rjust;
+    int value;
     time_t t;
+    size_t max;
     struct format *fmt;
     struct comp *comp;
     struct tws *tws;
     struct mailname *mn;
 
-    cp = scanl;
-    ep = scanl + width - 1;
+    /*
+     * max is the same as width, but unsigned so comparisons
+     * with charstring_chars() won't raise compile warnings.
+     */
+    max = width;
+    savestr = str = NULL;
+    value = 0;
 
     for (fmt = format; fmt->f_type != FT_DONE; fmt++)
        switch (fmt->f_type) {
@@ -299,69 +419,167 @@ fmt_scan (struct format *format, char *scanl, int width, int *dat)
        case FT_PARSEDATE:
            fmt->f_comp->c_flags &= ~CF_PARSED;
            break;
+       case FT_COMP:
+       case FT_COMPF:
+       case FT_LS_COMP:
+       case FT_LS_DECODECOMP:
+           /*
+            * Trim these components of any newlines.
+            *
+            * But don't trim the "body" and "text" components.
+            */
+
+           comp = fmt->f_comp;
+
+           if (! (comp->c_flags & CF_TRIMMED) && comp->c_text &&
+               (i = strlen(comp->c_text)) > 0) {
+               if (comp->c_text[i - 1] == '\n' &&
+                   strcmp(comp->c_name, "body") != 0 &&
+                   strcmp(comp->c_name, "text") != 0)
+                   comp->c_text[i - 1] = '\0';
+               comp->c_flags |= CF_TRIMMED;
+           }
+           break;
        }
 
     fmt = format;
 
-    while (cp < ep) {
+    for ( ; charstring_chars (scanlp) < max; ) {
        switch (fmt->f_type) {
 
        case FT_COMP:
-           cpstripped (&cp, ep, fmt->f_comp->c_text);
+           cpstripped (scanlp, max - charstring_chars (scanlp),
+                        fmt->f_comp->c_text);
            break;
        case FT_COMPF:
-           cptrimmed (&cp, fmt->f_comp->c_text, fmt->f_width, fmt->f_fill, ep - cp);
+           cptrimmed (scanlp, fmt->f_comp->c_text, fmt->f_width,
+                      fmt->f_fill, max - charstring_chars (scanlp));
            break;
 
        case FT_LIT:
            sp = fmt->f_text;
-           while( (c = *sp++) && cp < ep)
-               *cp++ = c;
+           while ((c = *sp++) && charstring_chars (scanlp) < max) {
+               charstring_push_back (scanlp, c);
+           }
            break;
        case FT_LITF:
            sp = fmt->f_text;
-           ljust = 0;
+           rjust = 0;
            i = fmt->f_width;
            if (i < 0) {
                i = -i;
-               ljust++;                /* XXX should do something with this */
+               rjust++;                /* XXX should do something with this */
+           }
+           while ((c = *sp++) && --i >= 0 && charstring_chars (scanlp) < max) {
+               charstring_push_back (scanlp, c);
+           }
+           while (--i >= 0 && charstring_chars (scanlp) < max) {
+               charstring_push_back (scanlp, fmt->f_fill);
            }
-           while( (c = *sp++) && --i >= 0 && cp < ep)
-               *cp++ = c;
-           while( --i >= 0 && cp < ep)
-               *cp++ = fmt->f_fill;
            break;
 
        case FT_STR:
-           cpstripped (&cp, ep, str);
+           cpstripped (scanlp, max - charstring_chars (scanlp), str);
            break;
        case FT_STRF:
-           cptrimmed (&cp, str, fmt->f_width, fmt->f_fill, ep - cp);
+           cptrimmed (scanlp, str, fmt->f_width, fmt->f_fill,
+                      max - charstring_chars (scanlp));
+           break;
+       case FT_STRLIT:
+           if (str) {
+               sp = str;
+               while ((c = *sp++) && charstring_chars (scanlp) < max) {
+                   charstring_push_back (scanlp, c);
+               }
+           }
+           break;
+       case FT_STRLITZ:
+           if (str) charstring_push_back_chars (scanlp, str, strlen (str), 0);
            break;
        case FT_STRFW:
            adios (NULL, "internal error (FT_STRFW)");
 
-       case FT_NUM:
-           n = snprintf(cp, ep - cp + 1, "%d", value);
-           if (n >= 0)
-               if (n >= ep - cp) {
-                   cp = ep;
-               } else
-                   cp += n;
+       case FT_NUM: {
+           int num = value;
+           unsigned int wid;
+
+            for (wid = num <= 0; num; ++wid, num /= 10) {}
+           cpnumber (scanlp, value, wid, ' ',
+                     max - charstring_chars (scanlp));
+           break;
+        }
+       case FT_LS_KILO:
+       case FT_LS_KIBI:
+           {
+               char *unitcp;
+               unsigned int whole, tenths;
+               unsigned int scale = 0;
+               unsigned int val = (unsigned int)value;
+               char *kibisuff = NULL;
+
+               switch (fmt->f_type) {
+               case FT_LS_KILO: scale = 1000; kibisuff = ""; break;
+               case FT_LS_KIBI: scale = 1024; kibisuff = "i"; break;
+               }
+
+               if (val < scale) {
+                   snprintf(buffer, sizeof(buffer), "%u", val);
+               } else {
+                   /* To prevent divide by 0, found by clang static
+                      analyzer. */
+                   if (scale == 0) { scale = 1; }
+
+                   /* find correct scale for size (Kilo/Mega/Giga/Tera) */
+                   for (unitcp = "KMGT"; val > (scale * scale); val /= scale) {
+                       if (!*++unitcp)
+                           break;
+                   }
+
+                   if (!*unitcp) {
+                       strcpy(buffer, "huge");
+                   } else {
+                       /* val is scale times too big.  we want tenths */
+                       val *= 10;
+
+                       /* round up */
+                       val += (scale - 1);
+                       val /= scale;
+
+                       whole = val / 10;
+                       tenths = val - (whole * 10);
+
+                       if (tenths) {
+                           snprintf(buffer, sizeof(buffer), "%u.%u%c%s",
+                                   whole, tenths, *unitcp, kibisuff);
+                       } else {
+                           snprintf(buffer, sizeof(buffer), "%u%c%s",
+                                   whole, *unitcp, kibisuff);
+                       }
+                   }
+               }
+               str = buffer;
+           }
            break;
        case FT_NUMF:
-           cpnumber (&cp, value, fmt->f_width, fmt->f_fill, ep - cp);
+           cpnumber (scanlp, value, fmt->f_width, fmt->f_fill,
+                     max - charstring_chars (scanlp));
            break;
 
        case FT_CHAR:
-           *cp++ = fmt->f_char;
+           charstring_push_back (scanlp, fmt->f_char);
            break;
 
        case FT_DONE:
+           if (callbacks && callbacks->trace_func)
+               callbacks->trace_func(callbacks->trace_context, fmt, value,
+                                     str, charstring_buffer (scanlp));
            goto finished;
 
        case FT_IF_S:
            if (!(value = (str && *str))) {
+               if (callbacks && callbacks->trace_func)
+                   callbacks->trace_func(callbacks->trace_context, fmt, value,
+                                         str, charstring_buffer (scanlp));
                fmt += fmt->f_skip;
                continue;
            }
@@ -369,6 +587,9 @@ fmt_scan (struct format *format, char *scanl, int width, int *dat)
 
        case FT_IF_S_NULL:
            if (!(value = (str == NULL || *str == 0))) {
+               if (callbacks && callbacks->trace_func)
+                   callbacks->trace_func(callbacks->trace_context, fmt, value,
+                                         str, charstring_buffer (scanlp));
                fmt += fmt->f_skip;
                continue;
            }
@@ -376,6 +597,9 @@ fmt_scan (struct format *format, char *scanl, int width, int *dat)
 
        case FT_IF_V_EQ:
            if (value != fmt->f_value) {
+               if (callbacks && callbacks->trace_func)
+                   callbacks->trace_func(callbacks->trace_context, fmt, value,
+                                         str, charstring_buffer (scanlp));
                fmt += fmt->f_skip;
                continue;
            }
@@ -383,6 +607,9 @@ fmt_scan (struct format *format, char *scanl, int width, int *dat)
 
        case FT_IF_V_NE:
            if (value == fmt->f_value) {
+               if (callbacks && callbacks->trace_func)
+                   callbacks->trace_func(callbacks->trace_context, fmt, value,
+                                         str, charstring_buffer (scanlp));
                fmt += fmt->f_skip;
                continue;
            }
@@ -390,6 +617,9 @@ fmt_scan (struct format *format, char *scanl, int width, int *dat)
 
        case FT_IF_V_GT:
            if (value <= fmt->f_value) {
+               if (callbacks && callbacks->trace_func)
+                   callbacks->trace_func(callbacks->trace_context, fmt, value,
+                                         str, charstring_buffer (scanlp));
                fmt += fmt->f_skip;
                continue;
            }
@@ -397,6 +627,9 @@ fmt_scan (struct format *format, char *scanl, int width, int *dat)
 
        case FT_IF_MATCH:
            if (!(value = (str && match (str, fmt->f_text)))) {
+               if (callbacks && callbacks->trace_func)
+                   callbacks->trace_func(callbacks->trace_context, fmt, value,
+                                         str, charstring_buffer (scanlp));
                fmt += fmt->f_skip;
                continue;
            }
@@ -411,6 +644,9 @@ fmt_scan (struct format *format, char *scanl, int width, int *dat)
 
        case FT_IF_AMATCH:
            if (!(value = (str && uprf (str, fmt->f_text)))) {
+               if (callbacks && callbacks->trace_func)
+                   callbacks->trace_func(callbacks->trace_context, fmt, value,
+                                         str, charstring_buffer (scanlp));
                fmt += fmt->f_skip;
                continue;
            }
@@ -441,6 +677,9 @@ fmt_scan (struct format *format, char *scanl, int width, int *dat)
            break;
 
        case FT_GOTO:
+           if (callbacks && callbacks->trace_func)
+               callbacks->trace_func(callbacks->trace_context, fmt, value,
+                                     str, charstring_buffer (scanlp));
            fmt += fmt->f_skip;
            continue;
 
@@ -478,24 +717,25 @@ fmt_scan (struct format *format, char *scanl, int width, int *dat)
            if (str) {
                    char *xp;
 
-                   strncpy(buffer, str, sizeof(buffer));
+                   if (str != buffer)
+                       strncpy(buffer, str, sizeof(buffer));
                    buffer[sizeof(buffer)-1] = '\0';
                    str = buffer;
-                   while (isspace(*str))
+                   while (isspace((unsigned char) *str))
                            str++;
-                   ljust = 0;
+                   rjust = 0;
                    if ((i = fmt->f_width) < 0) {
                            i = -i;
-                           ljust++;
+                           rjust++;
                    }
 
-                   if (!ljust && i > 0 && strlen(str) > i)
+                   if (!rjust && i > 0 && (int) strlen(str) > i)
                            str[i] = '\0';
                    xp = str;
                    xp += strlen(str) - 1;
-                   while (xp > str && isspace(*xp))
+                   while (xp > str && isspace((unsigned char) *xp))
                            *xp-- = '\0';
-                   if (ljust && i > 0 && strlen(str) > i)
+                   if (rjust && i > 0 && (int) strlen(str) > i)
                        str += strlen(str) - i;
            }
            break;
@@ -519,7 +759,7 @@ fmt_scan (struct format *format, char *scanl, int width, int *dat)
                    value = 0;
            break;
        case FT_LV_CHAR_LEFT:
-           value = width - (cp - scanl);
+           value = max - charstring_bytes (scanlp);
            break;
        case FT_LV_PLUS_L:
            value += fmt->f_value;
@@ -527,15 +767,18 @@ fmt_scan (struct format *format, char *scanl, int width, int *dat)
        case FT_LV_MINUS_L:
            value = fmt->f_value - value;
            break;
+       case FT_LV_MULTIPLY_L:
+           value *= fmt->f_value;
+           break;
        case FT_LV_DIVIDE_L:
            if (fmt->f_value)
-               value = value / fmt->f_value;
+               value /= fmt->f_value;
            else
                value = 0;
            break;
        case FT_LV_MODULO_L:
            if (fmt->f_value)
-               value = value % fmt->f_value;
+               value %= fmt->f_value;
            else
                value = 0;
            break;
@@ -598,7 +841,7 @@ fmt_scan (struct format *format, char *scanl, int width, int *dat)
        case FT_LV_RCLOCK:
            if ((value = fmt->f_comp->c_tws->tw_clock) == 0)
                value = dmktime(fmt->f_comp->c_tws);
-           value = time((time_t *) 0) - value;
+           value = time(NULL) - value;
            break;
        case FT_LV_DAYF:
            if (!(((tws = fmt->f_comp->c_tws)->tw_flags) & (TW_SEXP|TW_SIMP)))
@@ -611,14 +854,15 @@ fmt_scan (struct format *format, char *scanl, int width, int *dat)
                default:
                    value = -1; break;
            }
+           break;
        case FT_LV_ZONEF:
-           if ((fmt->f_comp->c_tws->tw_flags & TW_SZONE) == TW_SZEXP)
+           if (fmt->f_comp->c_tws->tw_flags & TW_SZEXP)
                    value = 1;
            else
                    value = -1;
            break;
        case FT_LV_DST:
-           value = fmt->f_comp->c_tws->tw_flags & TW_DST;
+           value = fmt->f_comp->c_tws->tw_flags & TW_DST ? 1 : 0;
            break;
        case FT_LS_822DATE:
            str = dasctime (fmt->f_comp->c_tws , TW_ZONE);
@@ -667,7 +911,8 @@ fmt_scan (struct format *format, char *scanl, int width, int *dat)
                goto unfriendly;
            if ((str = mn->m_pers) == NULL) {
                if ((str = mn->m_note)) {
-                   strncpy (buffer, str, sizeof(buffer));
+                   if (str != buffer)
+                       strncpy (buffer, str, sizeof(buffer));
                    buffer[sizeof(buffer)-1] = '\0';
                    str = buffer;
                    if (*str == '(')
@@ -683,7 +928,7 @@ fmt_scan (struct format *format, char *scanl, int width, int *dat)
                    }
                } else if (!(str = get_x400_friendly (mn->m_mbox,
                                buffer, sizeof(buffer)))) {
-       unfriendly: ;
+       unfriendly:
                  switch (mn->m_type) {
                    case LOCALHOST:
                        str = mn->m_mbox;
@@ -711,32 +956,11 @@ fmt_scan (struct format *format, char *scanl, int width, int *dat)
                /* UNQUOTEs RFC-2822 quoted-string and quoted-pair */
        case FT_LS_UNQUOTE:
            if (str) {          
-               int m;
-               strncpy(buffer, str, sizeof(buffer));
+               if (str != buffer)
+                   strncpy(buffer, str, sizeof(buffer));
                /* strncpy doesn't NUL-terminate if it fills the buffer */
                buffer[sizeof(buffer)-1] = '\0';
-               str = buffer;
-       
-               /* we will parse from buffer to buffer2 */
-               n = 0; /* n is the input position in str */
-               m = 0; /* m is the ouput position in buffer2 */
-
-               while ( str[n] != '\0') {
-                   switch ( str[n] ) {
-                       case '\\':
-                           n++;
-                           if ( str[n] != '\0')
-                               buffer2[m++] = str[n++];
-                           break;
-                       case '"':
-                           n++;
-                           break;
-                       default:
-                           buffer2[m++] = str[n++];
-                           break;
-                       }
-               }
-               buffer2[m] = '\0';
+               unquote_string(buffer, buffer2);
                str = buffer2;
             }
            break;
@@ -765,7 +989,7 @@ fmt_scan (struct format *format, char *scanl, int width, int *dat)
                *comp->c_tws = *tws;
                comp->c_flags &= ~CF_TRUE;
            } else if ((comp->c_flags & CF_DATEFAB) == 0) {
-               memset ((char *) comp->c_tws, 0, sizeof *comp->c_tws);
+               ZERO(comp->c_tws);
                comp->c_flags = CF_TRUE;
            }
            comp->c_flags |= CF_PARSED;
@@ -773,7 +997,18 @@ fmt_scan (struct format *format, char *scanl, int width, int *dat)
 
        case FT_FORMATADDR:
            /* hook for custom address list formatting (see replsbr.c) */
-           str = formataddr (savestr, str);
+           if (callbacks && callbacks->formataddr)
+               str = callbacks->formataddr (savestr, str);
+           else
+               str = formataddr (savestr, str);
+           break;
+
+       case FT_CONCATADDR:
+           /* The same as formataddr, but doesn't do duplicate suppression */
+           if (callbacks && callbacks->concataddr)
+               str = callbacks->concataddr (savestr, str);
+           else
+               str = concataddr (savestr, str);
            break;
 
        case FT_PUTADDR:
@@ -789,43 +1024,53 @@ fmt_scan (struct format *format, char *scanl, int width, int *dat)
 
            lp = str;
            wid = value;
-           len = strlen (str);
+           len = str ? strlen (str) : 0;
            sp = fmt->f_text;
            indent = strlen (sp);
            wid -= indent;
-           while( (c = *sp++) && cp < ep)
-               *cp++ = c;
+           if (wid <= 0) {
+               adios(NULL, "putaddr -- num register (%d) must be greater "
+                           "than label width (%d)", value, indent);
+           }
+           while ((c = *sp++) && charstring_chars (scanlp) < max) {
+               charstring_push_back (scanlp, c);
+           }
            while (len > wid) {
                /* try to break at a comma; failing that, break at a
                 * space.
                 */
                lastb = 0; sp = lp + wid;
-               while (sp > lp && (c = *--sp) != ',') {
+               while (sp > lp && (c = (unsigned char) *--sp) != ',') {
                    if (! lastb && isspace(c))
                        lastb = sp - 1;
                }
                if (sp == lp) {
                    if (! (sp = lastb)) {
                        sp = lp + wid - 1;
-                       while (*sp && *sp != ',' && !isspace(*sp))
+                       while (*sp && *sp != ',' &&
+                                               !isspace((unsigned char) *sp))
                            sp++;
                        if (*sp != ',')
                            sp--;
                    }
                }
                len -= sp - lp + 1;
-               while (cp < ep && lp <= sp)
-                   *cp++ = *lp++;
-               while (isspace(*lp))
+               while (lp <= sp && charstring_chars (scanlp) < max) {
+                   charstring_push_back (scanlp, *lp++);
+               }
+               while (isspace((unsigned char) *lp))
                    lp++, len--;
                if (*lp) {
-                   if (cp < ep)
-                       *cp++ = '\n';
-                   for (i=indent; cp < ep && i > 0; i--)
-                       *cp++ = ' ';
+                   if (charstring_chars (scanlp) < max) {
+                       charstring_push_back (scanlp, '\n');
+                    }
+                   for (i=indent;
+                        charstring_chars (scanlp) < max && i > 0;
+                        i--)
+                       charstring_push_back (scanlp, ' ');
                }
            }
-           cpstripped (&cp, ep, lp);
+           cpstripped (scanlp, max - charstring_chars (scanlp), lp);
            }
            break;
 
@@ -836,7 +1081,7 @@ fmt_scan (struct format *format, char *scanl, int width, int *dat)
            if (comp->c_mn != &fmt_mnull)
                mnfree (comp->c_mn);
            if ((sp = comp->c_text) && (sp = getname(sp)) &&
-               (mn = getm (sp, NULL, 0, fmt_norm, NULL))) {
+               (mn = getm (sp, NULL, 0, NULL, 0))) {
                comp->c_mn = mn;
                while (getname(""))
                    ;
@@ -849,6 +1094,8 @@ fmt_scan (struct format *format, char *scanl, int width, int *dat)
            break;
 
        case FT_MYMBOX:
+       case FT_GETMYMBOX:
+       case FT_GETMYADDR:
            /*
             * if there's no component, we say true.  Otherwise we
             * say "true" only if we can parse the address and it
@@ -858,56 +1105,106 @@ fmt_scan (struct format *format, char *scanl, int width, int *dat)
            if (comp->c_mn != &fmt_mnull)
                mnfree (comp->c_mn);
            if ((sp = comp->c_text) && (sp = getname(sp)) &&
-               (mn = getm (sp, NULL, 0, AD_NAME, NULL))) {
+               (mn = getm (sp, NULL, 0, NULL, 0))) {
                comp->c_mn = mn;
-               if (ismymbox(mn))
+               if (ismymbox(mn)) {
                    comp->c_flags |= CF_TRUE;
-               else
+                   /* Set str for use with FT_GETMYMBOX.  With
+                      FT_GETMYADDR, comp->c_mn will be run through
+                      FT_LS_ADDR, which will strip off any pers
+                      name. */
+                   str = mn->m_text;
+               } else {
                    comp->c_flags &= ~CF_TRUE;
+               }
                while ((sp = getname(sp)))
                    if ((comp->c_flags & CF_TRUE) == 0 &&
-                       (mn = getm (sp, NULL, 0, AD_NAME, NULL)))
-                       if (ismymbox(mn))
+                       (mn = getm (sp, NULL, 0, NULL, 0)))
+                       if (ismymbox(mn)) {
                            comp->c_flags |= CF_TRUE;
+                           /* Set str and comp->c_text for use with
+                              FT_GETMYMBOX.  With FT_GETMYADDR,
+                              comp->c_mn will be run through
+                              FT_LS_ADDR, which will strip off any
+                              pers name. */
+                           free (comp->c_text);
+                           comp->c_text = str = strdup (mn->m_text);
+                           comp->c_mn = mn;
+                       }
+               comp->c_flags |= CF_PARSED;
            } else {
                while (getname(""))             /* XXX */
                    ;
                if (comp->c_text == 0)
                    comp->c_flags |= CF_TRUE;
-               else
+               else {
                    comp->c_flags &= ~CF_TRUE;
+               }
                comp->c_mn = &fmt_mnull;
            }
-           break;
-
-       case FT_ADDTOSEQ:
-#ifdef LBL
-           /* If we're working on a folder (as opposed to a file), add the
-            * current msg to sequence given in literal field.  Don't
-            * disturb string or value registers.
-            */
-           if (fmt_current_folder)
-                   seq_addmsg(fmt_current_folder, fmt->f_text, dat[0], -1);
-#endif
+           if ((comp->c_flags & CF_TRUE) == 0  &&
+               (fmt->f_type == FT_GETMYMBOX || fmt->f_type == FT_GETMYADDR)) {
+               /* Fool FT_LS_ADDR into not producing an address. */
+               comp->c_mn = &fmt_mnull; comp->c_text = NULL;
+           }
            break;
        }
+
+       /*
+        * Call our tracing callback function, if one was supplied
+        */
+
+       if (callbacks && callbacks->trace_func)
+           callbacks->trace_func(callbacks->trace_context, fmt, value,
+                                 str, charstring_buffer (scanlp));
        fmt++;
     }
-#ifndef JLR
-    finished:;
-    if (cp[-1] != '\n')
-       *cp++ = '\n';
-    *cp   = 0;
-    return ((struct format *)0);
-#else /* JLR */
-    if (cp[-1] != '\n')
-       *cp++ = '\n';
-    while (fmt->f_type != FT_DONE)
+
+    /* Emit any trailing sequences of zero display length. */
+    while (fmt->f_type != FT_DONE) {
+       if (fmt->f_type == FT_LS_LIT) {
+           str = fmt->f_text;
+           if (callbacks && callbacks->trace_func)
+               callbacks->trace_func(callbacks->trace_context, fmt, value,
+                                     str, charstring_buffer (scanlp));
+       } else if (fmt->f_type == FT_STRLITZ) {
+           /* Don't want to emit part of an escape sequence.  So if
+              there isn't enough room in the buffer for the entire
+              string, skip it completely.  Need room for null
+              terminator, and maybe trailing newline (added below). */
+           if (str) {
+               for (sp = str; *sp; ++sp) {
+                   charstring_push_back (scanlp, *sp);
+               }
+           }
+           if (callbacks && callbacks->trace_func)
+               callbacks->trace_func(callbacks->trace_context, fmt, value,
+                                     str, charstring_buffer (scanlp));
+       }
        fmt++;
+    }
 
-    finished:;
-    *cp = '\0';
-    return (fmt->f_value ? ++fmt : (struct format *) 0);
+    finished:
+    if (charstring_bytes (scanlp) > 0) {
+       /*
+        * Append a newline if the last character wasn't.
+        */
+#ifdef MULTIBYTE_SUPPORT
+       /*
+        * It's a little tricky because the last byte might be part of
+        * a multibyte character, in which case we assume that wasn't
+        * a newline.
+        */
+       size_t last_char_len = charstring_last_char_len (scanlp);
+#else  /* ! MULTIBYTE_SUPPORT */
+       size_t last_char_len = 1;
+#endif /* ! MULTIBYTE_SUPPORT */
+
+       if (last_char_len > 1  ||
+           charstring_buffer (scanlp)[charstring_bytes (scanlp) - 1] != '\n') {
+           charstring_push_back (scanlp, '\n');
+       }
+    }
 
-#endif /* JLR */
+    return NULL;
 }