3 * fmt_scan.c -- format string interpretation
5 * This code is Copyright (c) 2002, by the authors of nmh. See the
6 * COPYRIGHT file in the root directory of the nmh distribution for
7 * complete copyright information.
9 * This is the engine that processes the format instructions created by
10 * fmt_compile (found in fmt_compile.c).
14 #include <h/addrsbr.h>
15 #include <h/fmt_scan.h>
17 #include <h/fmt_compile.h>
19 #ifdef HAVE_SYS_TIME_H
20 # include <sys/time.h>
23 #ifdef MULTIBYTE_SUPPORT
29 struct msgs *fmt_current_folder; /* current folder (set by main program) */
32 extern int fmt_norm; /* defined in sbr/fmt_def.c = AD_NAME */
33 struct mailname fmt_mnull = { NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, 0, 0,
39 static int match (char *, char *);
40 static char *get_x400_friendly (char *, char *, int);
41 static int get_x400_comp (char *, char *, char *, int);
45 * test if string "sub" appears anywhere in
46 * string "str" (case insensitive).
50 match (char *str, char *sub)
57 c1 = (isalpha(c1) && isupper(c1)) ? tolower(c1) : c1;
58 while ((c2 = *str++) && c1 != ((isalpha(c2) && isupper(c2)) ? tolower(c2) : c2))
62 s1 = sub + 1; s2 = str;
63 while ((c1 = *s1++) && ((isalpha(c1) && isupper(c1)) ? tolower(c1) : c1) == ((isalpha(c2 =*s2++) && isupper(c2)) ? tolower(c2) : c2))
70 while ((c2 = *str++) && (c1 | 040) != (c2 | 040))
74 s1 = sub + 1; s2 = str;
75 while ((c1 = *s1++) && (c1 | 040) == (*s2++ | 040))
85 * copy a number to the destination subject to a maximum width
88 cpnumber(char **dest, int num, unsigned int wid, char fill, size_t n) {
101 *--sp = (i % 10) + '0';
103 } while (i > 0 && sp > cp);
106 else if ((num) < 0 && sp > cp)
116 * copy string from str to dest padding with the fill character to a size
117 * of wid characters. if wid is negative, the string is right aligned
118 * no more than n bytes are copied
121 cptrimmed(char **dest, char *str, unsigned int wid, char fill, size_t n) {
122 int remaining; /* remaining output width available */
124 int end; /* number of input bytes remaining in str */
125 #ifdef MULTIBYTE_SUPPORT
126 int char_len; /* bytes in current character */
130 char *sp; /* current position in source string */
131 char *cp = *dest; /* current position in destination string */
132 char *ep = cp + n; /* end of destination buffer */
137 if ((remaining = (wid)) < 0) {
138 remaining = -remaining;
142 mbtowc(NULL, NULL, 0); /* reset shift state */
144 while (*sp && remaining > 0 && end > 0) {
145 #ifdef MULTIBYTE_SUPPORT
146 char_len = mbtowc(&wide_char, sp, end);
147 if (char_len <= 0 || (cp + char_len > ep))
152 if (iswcntrl(wide_char) || iswspace(wide_char)) {
157 /* isnctrl(), etc., take an int argument. Cygwin's ctype.h
158 intentionally warns if they are passed a char. */
160 if (iscntrl(c) || isspace(c)) {
173 #ifdef MULTIBYTE_SUPPORT
174 w = wcwidth(wide_char);
175 if (w >= 0 && remaining >= w) {
176 strncpy(cp, sp, char_len);
189 if (cp + remaining > ep)
193 /* copy string to the right */
194 while (--cp >= *dest)
195 *(cp + remaining) = *cp;
196 /* add padding at the beginning */
198 for (c=remaining; c>0; c--)
203 /* pad remaining space */
204 while (remaining-- > 0 && cp < ep)
211 cpstripped (char **dest, char *end, char *str)
213 int prevCtrl = 1; /* This is 1 so we strip out leading spaces */
215 #ifdef MULTIBYTE_SUPPORT
218 #endif /* MULTIBYTE_SUPPORT */
225 #ifdef MULTIBYTE_SUPPORT
226 mbtowc(NULL, NULL, 0); /* Reset shift state */
227 #endif /* MULTIBYTE_SUPPORT */
230 * Process each character at a time; if we have multibyte support
231 * then deal with that here.
234 while (*str != '\0' && len > 0 && *dest < end) {
235 #ifdef MULTIBYTE_SUPPORT
236 char_len = mbtowc(&wide_char, str, len);
238 if (char_len <= 0 || *dest + char_len > end)
243 if (iswcntrl(wide_char) || iswspace(wide_char)) {
245 #else /* MULTIBYTE_SUPPORT */
248 if (iscntrl(c) || isspace(c)) {
250 #endif /* MULTIBYTE_SUPPORT */
261 #ifdef MULTIBYTE_SUPPORT
262 memcpy(*dest, str, char_len);
265 #else /* MULTIBYE_SUPPORT */
267 #endif /* MULTIBYTE_SUPPORT */
271 static char *lmonth[] = { "January", "February","March", "April",
272 "May", "June", "July", "August",
273 "September","October", "November","December" };
276 get_x400_friendly (char *mbox, char *buffer, int buffer_len)
278 char given[BUFSIZ], surname[BUFSIZ];
287 if (get_x400_comp (mbox, "/PN=", buffer, buffer_len)) {
288 for (mbox = buffer; (mbox = strchr(mbox, '.')); )
294 if (!get_x400_comp (mbox, "/S=", surname, sizeof(surname)))
297 if (get_x400_comp (mbox, "/G=", given, sizeof(given)))
298 snprintf (buffer, buffer_len, "%s %s", given, surname);
300 snprintf (buffer, buffer_len, "%s", surname);
306 get_x400_comp (char *mbox, char *key, char *buffer, int buffer_len)
311 if ((idx = stringdex (key, mbox)) < 0
312 || !(cp = strchr(mbox += idx + strlen (key), '/')))
315 snprintf (buffer, buffer_len, "%*.*s", (int)(cp - mbox), (int)(cp - mbox), mbox);
320 fmt_scan (struct format *format, char *scanl, size_t max, int width, int *dat)
324 char *savestr = NULL;
325 unsigned char *str = NULL;
326 char buffer[BUFSIZ], buffer2[BUFSIZ];
335 /* ep keeps track of displayed characters. They're limited by width.
336 The total number of characters, cp - scanl + 1 (for trailing NULL),
337 includes invisible control characters and is limited by max. */
339 ep = scanl + (width <= (int) max ? width : (int) max) - 1;
341 for (fmt = format; fmt->f_type != FT_DONE; fmt++)
342 switch (fmt->f_type) {
345 fmt->f_comp->c_flags &= ~CF_PARSED;
350 case FT_LS_DECODECOMP:
352 * Trim these components of any newlines.
354 * But don't trim the "body" and "text" components.
359 if (! (comp->c_flags & CF_TRIMMED) && comp->c_text) {
360 i = strlen(comp->c_text);
361 if (comp->c_text[i - 1] == '\n' &&
362 strcmp(comp->c_name, "body") != 0 &&
363 strcmp(comp->c_name, "text") != 0)
364 comp->c_text[i - 1] = '\0';
365 comp->c_flags |= CF_TRIMMED;
373 switch (fmt->f_type) {
376 cpstripped (&cp, ep, fmt->f_comp->c_text);
379 cptrimmed (&cp, fmt->f_comp->c_text, fmt->f_width, fmt->f_fill, ep - cp);
384 while( (c = *sp++) && cp < ep)
393 ljust++; /* XXX should do something with this */
395 while( (c = *sp++) && --i >= 0 && cp < ep)
397 while( --i >= 0 && cp < ep)
402 cpstripped (&cp, ep, str);
405 cptrimmed (&cp, str, fmt->f_width, fmt->f_fill, ep - cp);
409 while ((c = *sp++) && cp < ep)
413 size_t len = strlen (str);
415 /* Don't want to emit part of an escape sequence. So if
416 there isn't enough room in the buffer for the entire
417 string, skip it completely. */
418 if (cp - scanl + len + 1 < max) {
419 for (sp = str; *sp; *cp++ = *sp++) continue;
421 /* This string doesn't count against the width. So
422 increase ep the same amount as cp, only if the
423 scan buffer will always be large enough. */
424 if (ep - scanl + len + 1 < max) {
432 adios (NULL, "internal error (FT_STRFW)");
435 n = snprintf(cp, ep - cp + 1, "%d", value);
444 cpnumber (&cp, value, fmt->f_width, fmt->f_fill, ep - cp);
455 if (!(value = (str && *str))) {
462 if (!(value = (str == NULL || *str == 0))) {
469 if (value != fmt->f_value) {
476 if (value == fmt->f_value) {
483 if (value <= fmt->f_value) {
490 if (!(value = (str && match (str, fmt->f_text)))) {
498 value = match (str, fmt->f_text);
504 if (!(value = (str && uprf (str, fmt->f_text)))) {
511 value = uprf (str, fmt->f_text);
515 value = (str != NULL && *str != 0);
519 value = (str == NULL || *str == 0);
523 value = (fmt->f_value == value);
527 value = (fmt->f_value != value);
531 value = (fmt->f_value > value);
542 str = fmt->f_comp->c_text;
548 if (!(str = getenv (fmt->f_text)))
552 if (!(str = context_find (fmt->f_text)))
556 case FT_LS_DECODECOMP:
557 if (decode_rfc2047(fmt->f_comp->c_text, buffer2, sizeof(buffer2)))
560 str = fmt->f_comp->c_text;
564 if (str && decode_rfc2047(str, buffer2, sizeof(buffer2)))
572 strncpy(buffer, str, sizeof(buffer));
573 buffer[sizeof(buffer)-1] = '\0';
575 while (isspace(*str))
578 if ((i = fmt->f_width) < 0) {
583 if (!ljust && i > 0 && (int) strlen(str) > i)
586 xp += strlen(str) - 1;
587 while (xp > str && isspace(*xp))
589 if (ljust && i > 0 && (int) strlen(str) > i)
590 str += strlen(str) - i;
595 value = (fmt->f_comp->c_flags & CF_TRUE) != 0;
598 value = (comp = fmt->f_comp)->c_text ? atoi(comp->c_text) : 0;
601 value = fmt->f_value;
604 value = dat[fmt->f_value];
612 case FT_LV_CHAR_LEFT:
613 value = width - (cp - scanl);
616 value += fmt->f_value;
619 value = fmt->f_value - value;
623 value = value / fmt->f_value;
629 value = value % fmt->f_value;
638 value = fmt->f_comp->c_tws->tw_sec;
641 value = fmt->f_comp->c_tws->tw_min;
644 value = fmt->f_comp->c_tws->tw_hour;
647 value = fmt->f_comp->c_tws->tw_mday;
650 value = fmt->f_comp->c_tws->tw_mon + 1;
653 str = tw_moty[fmt->f_comp->c_tws->tw_mon];
656 str = lmonth[fmt->f_comp->c_tws->tw_mon];
659 str = dtwszone (fmt->f_comp->c_tws);
662 value = fmt->f_comp->c_tws->tw_year;
665 if (!(((tws = fmt->f_comp->c_tws)->tw_flags) & (TW_SEXP|TW_SIMP)))
667 value = tws->tw_wday;
670 if (!(((tws = fmt->f_comp->c_tws)->tw_flags) & (TW_SEXP|TW_SIMP)))
672 str = tw_dotw[tws->tw_wday];
675 if (!(((tws = fmt->f_comp->c_tws)->tw_flags) & (TW_SEXP|TW_SIMP)))
677 str = tw_ldotw[tws->tw_wday];
680 value = fmt->f_comp->c_tws->tw_yday;
683 value = fmt->f_comp->c_tws->tw_zone;
686 if ((value = fmt->f_comp->c_tws->tw_clock) == 0)
687 value = dmktime(fmt->f_comp->c_tws);
690 if ((value = fmt->f_comp->c_tws->tw_clock) == 0)
691 value = dmktime(fmt->f_comp->c_tws);
692 value = time((time_t *) 0) - value;
695 if (!(((tws = fmt->f_comp->c_tws)->tw_flags) & (TW_SEXP|TW_SIMP)))
697 switch (fmt->f_comp->c_tws->tw_flags & TW_SDAY) {
706 if ((fmt->f_comp->c_tws->tw_flags & TW_SZONE) == TW_SZEXP)
712 value = fmt->f_comp->c_tws->tw_flags & TW_DST;
715 str = dasctime (fmt->f_comp->c_tws , TW_ZONE);
718 str = dasctime (fmt->f_comp->c_tws, TW_NULL);
722 str = fmt->f_comp->c_mn->m_pers;
725 str = fmt->f_comp->c_mn->m_mbox;
728 str = fmt->f_comp->c_mn->m_host;
731 str = fmt->f_comp->c_mn->m_path;
734 str = fmt->f_comp->c_mn->m_gname;
737 str = fmt->f_comp->c_mn->m_note;
740 str = adrformat( fmt->f_comp->c_mn );
743 value = fmt->f_comp->c_mn->m_type;
746 value = fmt->f_comp->c_mn->m_ingrp;
749 value = fmt->f_comp->c_mn->m_nohost;
753 if ((mn = fmt->f_comp->c_mn) == &fmt_mnull) {
754 str = fmt->f_comp->c_text;
757 if (fmt->f_type == FT_LS_ADDR)
759 if ((str = mn->m_pers) == NULL) {
760 if ((str = mn->m_note)) {
761 strncpy (buffer, str, sizeof(buffer));
762 buffer[sizeof(buffer)-1] = '\0';
766 sp = str + strlen(str) - 1;
775 } else if (!(str = get_x400_friendly (mn->m_mbox,
776 buffer, sizeof(buffer)))) {
778 switch (mn->m_type) {
783 snprintf (buffer, sizeof(buffer), "%s!%s",
784 mn->m_host, mn->m_mbox);
789 snprintf (buffer, sizeof(buffer), "%s@%s",
790 mn->m_mbox, mn->m_host);
802 /* UNQUOTEs RFC-2822 quoted-string and quoted-pair */
806 strncpy(buffer, str, sizeof(buffer));
807 /* strncpy doesn't NUL-terminate if it fills the buffer */
808 buffer[sizeof(buffer)-1] = '\0';
811 /* we will parse from buffer to buffer2 */
812 n = 0; /* n is the input position in str */
813 m = 0; /* m is the ouput position in buffer2 */
815 while ( str[n] != '\0') {
820 buffer2[m++] = str[n++];
826 buffer2[m++] = str[n++];
837 if ((t = comp->c_tws->tw_clock) == 0)
838 t = dmktime(comp->c_tws);
839 tws = dlocaltime(&t);
845 if ((t = comp->c_tws->tw_clock) == 0)
846 t = dmktime(comp->c_tws);
853 if (comp->c_flags & CF_PARSED)
855 if ((sp = comp->c_text) && (tws = dparsetime(sp))) {
857 comp->c_flags &= ~CF_TRUE;
858 } else if ((comp->c_flags & CF_DATEFAB) == 0) {
859 memset ((char *) comp->c_tws, 0, sizeof *comp->c_tws);
860 comp->c_flags = CF_TRUE;
862 comp->c_flags |= CF_PARSED;
866 /* hook for custom address list formatting (see replsbr.c) */
867 str = formataddr (savestr, str);
871 /* The same as formataddr, but doesn't do duplicate suppression */
872 str = concataddr (savestr, str);
876 /* output the str register as an address component,
877 * splitting it into multiple lines if necessary. The
878 * value reg. contains the max line length. The lit.
879 * field may contain a string to prepend to the result
885 int indent, wid, len;
891 indent = strlen (sp);
894 adios(NULL, "putaddr -- num register (%d) must be greater "
895 "than label width (%d)", value, indent);
897 while( (c = *sp++) && cp < ep)
900 /* try to break at a comma; failing that, break at a
903 lastb = 0; sp = lp + wid;
904 while (sp > lp && (c = *--sp) != ',') {
905 if (! lastb && isspace(c))
909 if (! (sp = lastb)) {
911 while (*sp && *sp != ',' && !isspace(*sp))
918 while (cp < ep && lp <= sp)
925 for (i=indent; cp < ep && i > 0; i--)
929 cpstripped (&cp, ep, lp);
935 if (comp->c_flags & CF_PARSED)
937 if (comp->c_mn != &fmt_mnull)
939 if ((sp = comp->c_text) && (sp = getname(sp)) &&
940 (mn = getm (sp, NULL, 0, fmt_norm, NULL))) {
944 comp->c_flags |= CF_PARSED;
946 while (getname("")) /* XXX */
948 comp->c_mn = &fmt_mnull;
954 * if there's no component, we say true. Otherwise we
955 * say "true" only if we can parse the address and it
956 * matches one of our addresses.
959 if (comp->c_mn != &fmt_mnull)
961 if ((sp = comp->c_text) && (sp = getname(sp)) &&
962 (mn = getm (sp, NULL, 0, AD_NAME, NULL))) {
965 comp->c_flags |= CF_TRUE;
967 comp->c_flags &= ~CF_TRUE;
968 while ((sp = getname(sp)))
969 if ((comp->c_flags & CF_TRUE) == 0 &&
970 (mn = getm (sp, NULL, 0, AD_NAME, NULL)))
972 comp->c_flags |= CF_TRUE;
974 while (getname("")) /* XXX */
976 if (comp->c_text == 0)
977 comp->c_flags |= CF_TRUE;
979 comp->c_flags &= ~CF_TRUE;
980 comp->c_mn = &fmt_mnull;
986 /* If we're working on a folder (as opposed to a file), add the
987 * current msg to sequence given in literal field. Don't
988 * disturb string or value registers.
990 if (fmt_current_folder)
991 seq_addmsg(fmt_current_folder, fmt->f_text, dat[0], -1);
998 /* Emit any trailing sequences of zero display length. */
999 while (fmt->f_type != FT_DONE) {
1000 if (fmt->f_type == FT_LS_LIT) {
1002 } else if (fmt->f_type == FT_STRLITZ) {
1003 /* Don't want to emit part of an escape sequence. So if
1004 there isn't enough room in the buffer for the entire
1005 string, skip it completely. Need room for null
1006 terminator, and maybe trailing newline (added below). */
1007 if (cp - scanl + strlen (str) + 1 < max) {
1008 for (sp = str; *sp; *cp++ = *sp++) continue;
1015 if (cp > scanl && cp[-1] != '\n') {
1016 if (cp - scanl < (int) max - 1) {
1023 return ((struct format *)0);
1027 while (fmt->f_type != FT_DONE)
1032 return (fmt->f_value ? ++fmt : (struct format *) 0);