Subject: v18i031: Mail user's shell version 6.4, Part09/19 Newsgroups: comp.sources.unix Sender: sources Approved: rsalz@uunet.UU.NET Submitted-by: Dan Heller Posting-number: Volume 18, Issue 31 Archive-name: mush6.4/part09 #! /bin/sh # This is a shell archive. Remove anything before this line, then unpack # it by saving it into a file and typing "sh file". To overwrite existing # files, type "sh file -c". You can also feed this as standard input via # unshar, or by typing "sh 'bind.c' <<'END_OF_FILE' X/* bind.c */ X X#include "bindings.h" X#include "mush.h" X Xextern char *c_macro(); Xstatic un_bind(); X Xstruct cmd_map *cmd_map, *line_map, *bang_map; X X/* X * Bindings are added here in REVERSE of the order that X * they will be displayed! Display order is based on a X * guess about the frequency of use and (to a lesser X * extent) how hard they are to remember. X * X * The user's own new bindings, if any, will be displayed X * before any of these default bindings. X */ Xinit_bindings() X{ X#ifdef CURSES X /* Help gets displayed last */ X add_bind("?", C_HELP, NULL, &cmd_map); X add_bind("V", C_VERSION, NULL, &cmd_map); X X /* Miscellaneous shell commands */ X add_bind("%", C_CHDIR, NULL, &cmd_map); X add_bind("|", C_PRINT_MSG, NULL, &cmd_map); X add_bind("!", C_SHELL_ESC, NULL, &cmd_map); X add_bind(":", C_CURSES_ESC, NULL, &cmd_map); X X /* Mush customization commands */ X /* NOTE: No default C_MACRO bindings */ X add_bind(")", C_SAVEOPTS, NULL, &cmd_map); X add_bind("(", C_SOURCE, NULL, &cmd_map); X add_bind("&!", C_MAP_BANG, NULL, &cmd_map); X add_bind("&:", C_MAP, NULL, &cmd_map); X add_bind("&&", C_BIND_MACRO, NULL, &cmd_map); X add_bind("v", C_VAR_SET, NULL, &cmd_map); X add_bind("i", C_IGNORE, NULL, &cmd_map); X add_bind("h", C_OWN_HDR, NULL, &cmd_map); X add_bind("B", C_UNBIND, NULL, &cmd_map); X add_bind("b", C_BIND, NULL, &cmd_map); X add_bind("a", C_ALIAS, NULL, &cmd_map); X X /* Display modification commands */ X add_bind("\022", C_REVERSE, NULL, &cmd_map); /* ^R */ X add_bind("\014", C_REDRAW, NULL, &cmd_map); /* ^L */ X add_bind("Z", C_PREV_SCREEN, NULL, &cmd_map); X add_bind("z", C_NEXT_SCREEN, NULL, &cmd_map); X X /* Searching and sorting commands */ X add_bind("\016", C_CONT_SEARCH, NULL, &cmd_map); /* ^N */ X add_bind("\037", C_PREV_SEARCH, NULL, &cmd_map); /* ^/ */ X add_bind("/", C_NEXT_SEARCH, NULL, &cmd_map); X add_bind("O", C_REV_SORT, NULL, &cmd_map); X add_bind("o", C_SORT, NULL, &cmd_map); X X /* Ways to get out */ X add_bind("X", C_EXIT_HARD, NULL, &cmd_map); X add_bind("x", C_EXIT, NULL, &cmd_map); X add_bind("Q", C_QUIT_HARD, NULL, &cmd_map); X add_bind("q", C_QUIT, NULL, &cmd_map); X X /* Folder modification commands */ X add_bind("\025", C_UPDATE, NULL, &cmd_map); /* ^U */ X add_bind("\020", C_PRESERVE, NULL, &cmd_map); /* ^P */ X add_bind("W", C_WRITE_LIST, NULL, &cmd_map); X add_bind("w", C_WRITE_MSG, NULL, &cmd_map); X add_bind("U", C_UNDEL_LIST, NULL, &cmd_map); X add_bind("u", C_UNDEL_MSG, NULL, &cmd_map); X add_bind("S", C_SAVE_LIST, NULL, &cmd_map); X add_bind("s", C_SAVE_MSG, NULL, &cmd_map); X add_bind("f", C_FOLDER, NULL, &cmd_map); X add_bind("D", C_DELETE_LIST, NULL, &cmd_map); X add_bind("d", C_DELETE_MSG, NULL, &cmd_map); X add_bind("C", C_COPY_LIST, NULL, &cmd_map); X add_bind("c", C_COPY_MSG, NULL, &cmd_map); X X /* Cursor movement and message selection */ X add_bind("g", C_GOTO_MSG, NULL, &cmd_map); X add_bind("}", C_BOTTOM_PAGE, NULL, &cmd_map); X add_bind("{", C_TOP_PAGE, NULL, &cmd_map); X add_bind("$", C_LAST_MSG, NULL, &cmd_map); X add_bind("^", C_FIRST_MSG, NULL, &cmd_map); X add_bind("\013",C_PREV_MSG, NULL, &cmd_map); /* ^K */ X add_bind("\012", C_NEXT_MSG, NULL, &cmd_map); /* ^J */ X add_bind("-",C_PREV_MSG, NULL, &cmd_map); X add_bind("+",C_NEXT_MSG, NULL, &cmd_map); X add_bind("K", C_PREV_MSG, NULL, &cmd_map); X add_bind("k", C_PREV_MSG, NULL, &cmd_map); X add_bind("J", C_NEXT_MSG, NULL, &cmd_map); X add_bind("j", C_NEXT_MSG, NULL, &cmd_map); X X /* Mail-sending commands */ X add_bind("R", C_REPLY_ALL, NULL, &cmd_map); X add_bind("r", C_REPLY_SENDER, NULL, &cmd_map); X add_bind("M", C_MAIL_FLAGS, NULL, &cmd_map); X add_bind("m", C_MAIL, NULL, &cmd_map); X X /* Mail-reading commands */ X add_bind(".", C_DISPLAY_MSG, NULL, &cmd_map); X add_bind("T", C_TOP_MSG, NULL, &cmd_map); X add_bind("t", C_DISPLAY_MSG, NULL, &cmd_map); X add_bind("p", C_DISPLAY_MSG, NULL, &cmd_map); X add_bind("n", C_DISPLAY_NEXT, NULL, &cmd_map); X X#endif /* CURSES */ X} X X/* Bindable function names. X * Most of these can't be used if CURSES is not defined, X * but help and lookups get confused if they aren't all here. X */ Xstruct cmd_map map_func_names[] = { X /* These MUST be in numerical order; see bindings.h */ X { C_NULL, "no-op", NULL, NULL_MAP }, X { C_GOTO_MSG, "goto-msg", NULL, NULL_MAP }, X { C_WRITE_LIST, "write-list", NULL, NULL_MAP }, X { C_WRITE_MSG, "write", NULL, NULL_MAP }, X { C_SAVE_LIST, "save-list", NULL, NULL_MAP }, X { C_SAVE_MSG, "save", NULL, NULL_MAP }, X { C_COPY_LIST, "copy-list", NULL, NULL_MAP }, X { C_COPY_MSG, "copy", NULL, NULL_MAP }, X { C_DELETE_LIST, "delete-list", NULL, NULL_MAP }, X { C_DELETE_MSG, "delete", NULL, NULL_MAP }, X { C_UNDEL_LIST, "undelete-list", NULL, NULL_MAP }, X { C_UNDEL_MSG, "undelete", NULL, NULL_MAP }, X { C_REDRAW, "redraw", NULL, NULL_MAP }, X { C_REVERSE, "reverse-video", NULL, NULL_MAP }, X { C_NEXT_MSG, "next-msg", NULL, NULL_MAP }, X { C_PREV_MSG, "back-msg", NULL, NULL_MAP }, X { C_FIRST_MSG, "first-msg", NULL, NULL_MAP }, X { C_LAST_MSG, "last-msg", NULL, NULL_MAP }, X { C_TOP_PAGE, "top-page", NULL, NULL_MAP }, X { C_BOTTOM_PAGE, "bottom-page", NULL, NULL_MAP }, X { C_NEXT_SCREEN, "screen-next", NULL, NULL_MAP }, X { C_PREV_SCREEN, "screen-back", NULL, NULL_MAP }, X { C_SOURCE, "source", NULL, NULL_MAP }, X { C_SAVEOPTS, "saveopts", NULL, NULL_MAP }, X { C_NEXT_SEARCH, "search-next", NULL, NULL_MAP }, X { C_PREV_SEARCH, "search-back", NULL, NULL_MAP }, X { C_CONT_SEARCH, "search-again", NULL, NULL_MAP }, X { C_PRESERVE, "preserve", NULL, NULL_MAP }, X { C_REV_SORT, "sort-reverse", NULL, NULL_MAP }, X { C_SORT, "sort", NULL, NULL_MAP }, X { C_QUIT_HARD, "quit!", NULL, NULL_MAP }, X { C_QUIT, "quit", NULL, NULL_MAP }, X { C_EXIT_HARD, "exit!", NULL, NULL_MAP }, X { C_EXIT, "exit", NULL, NULL_MAP }, X { C_UPDATE, "update", NULL, NULL_MAP }, X { C_FOLDER, "folder", NULL, NULL_MAP }, X { C_SHELL_ESC, "shell-escape", NULL, NULL_MAP }, X { C_CURSES_ESC, "line-mode", NULL, NULL_MAP }, X { C_PRINT_MSG, "lpr", NULL, NULL_MAP }, X { C_CHDIR, "chdir", NULL, NULL_MAP }, X { C_VAR_SET, "variable", NULL, NULL_MAP }, X { C_IGNORE, "ignore", NULL, NULL_MAP }, X { C_ALIAS, "alias", NULL, NULL_MAP }, X { C_OWN_HDR, "my-hdrs", NULL, NULL_MAP }, X { C_VERSION, "version", NULL, NULL_MAP }, X { C_MAIL_FLAGS, "mail-flags", NULL, NULL_MAP }, X { C_MAIL, "mail", NULL, NULL_MAP }, X { C_REPLY_ALL, "reply-all", NULL, NULL_MAP }, X { C_REPLY_SENDER, "reply", NULL, NULL_MAP }, X { C_DISPLAY_NEXT, "display-next", NULL, NULL_MAP }, X { C_DISPLAY_MSG, "display", NULL, NULL_MAP }, X { C_TOP_MSG, "top", NULL, NULL_MAP }, X { C_BIND_MACRO, "bind-macro", NULL, NULL_MAP }, X { C_BIND, "bind", NULL, NULL_MAP }, X { C_UNBIND, "unbind", NULL, NULL_MAP }, X { C_MAP_BANG, "map!", NULL, NULL_MAP }, X { C_MAP, "map", NULL, NULL_MAP }, X { C_MACRO, "macro", NULL, NULL_MAP }, X /* C_HELP Must be the last one! */ X { C_HELP, "help", NULL, NULL_MAP } X}; X X#ifdef CURSES X X/* X * getcmd() is called from curses mode only. It waits for char input from X * the user via m_getchar() (which means that a macro could provide input) X * and then compares the chars input against the "bind"ings set up by the X * user (or the defaults). For example, 'j' could bind to "next msg" which X * is interpreted by the big switch statement in curses_command() (curses.c). X * getcmd() returns the int-value of the curses command the input is "bound" X * to. If the input is unrecognized, C_NULL is returned (curses_command() X * might require some cleanup, so this is valid, too). X * X * Since the input could originate from a macro rather than the terminal, X * check to see if this is the case and search for a '[' char which indicates X * that there is a curses command or other "long" command to be executed. X */ Xgetcmd() X{ X char buf[MAX_BIND_LEN * 3]; X register int c, m, match; X register char *p = buf; X register struct cmd_map *list; X X bzero(buf, MAX_BIND_LEN); X active_cmd = NULL_MAP; X c = m_getchar(); X /* If user did job control (^Z), then the interrupt flag will be X * set. Be sure it's unset before continuing. X */ X turnoff(glob_flags, WAS_INTR); X if (isdigit(c)) { X buf[0] = c; X buf[1] = '\0'; X Ungetstr(buf); /* So mac_flush can clear on error */ X return C_GOTO_MSG; X } X for (;;) { X if (ison(glob_flags, IN_MACRO) && c == MAC_LONG_CMD) X return long_mac_cmd(c, TRUE); X else X *p++ = c; X m = 0; X for (list = cmd_map; list; list = list->m_next) { X if ((match = prefix(buf, list->m_str)) == MATCH) { X if (debug) X print("\"%s\" ", X ctrl_strcpy(buf, X map_func_names[list->m_cmd].m_str, X TRUE)); X if (list->m_cmd == C_MACRO) { X curs_macro(list->x_str); X return getcmd(); X } X active_cmd = list; X return (int)list->m_cmd; X } else if (match != NO_MATCH) X m++; X } X if (m == 0) { X if (debug) { X char tmp[sizeof buf]; X print("No binding for \"%s\" found.", X ctrl_strcpy(tmp, buf, TRUE)); X } X return C_NULL; X } X c = m_getchar(); X } X} X X#endif /* CURSES */ X X/* X * bind_it() is used to set or unset bind, map and map! settings. X * bind is used to accelerate curses commands by mapping key sequences X * to curses commands. map is used to accelerate command mode keysequences X * by simulating stdin. map! is the same, but used when in compose mode. X * X * bind_it() doesn't touch messages; return -1 for curses mode. X * return -2 to have curses command set CNTD_CMD to prevent screen refresh X * to allow user to read output in case of multiple lines. X * X * Since this routine deals with a lot of binding and unbinding of things X * like line-mode "map"s and is interactive (calls Getstr()), be very careful X * not to allow expansions during interaction. X */ Xbind_it(len, argv) Xchar **argv; X{ X char string[MAX_BIND_LEN], buf[256], *name = NULL; X char *rawstr; /* raw format of string (ptr to string if no argv avail) */ X char ascii[MAX_BIND_LEN*2]; /* printable ascii version of string */ X register int x; X SIGRET (*oldint)(), (*oldquit)(); X struct cmd_map **map_list; X int unbind = (argv && **argv == 'u'); X int map = 0, is_bind_macro = 0; X int ret = 0 - iscurses; /* return value */ X X if (argv && !strcmp(name = *argv, "bind-macro")) X is_bind_macro++; X X if (map = (argv && (!strcmp(name, "map!") || !strcmp(name, "unmap!")))) X map_list = &bang_map; X else if (map = (argv && (!strcmp(name, "map") || !strcmp(name, "unmap")))) X map_list = &line_map; X else X map_list = &cmd_map; X X if (argv && *++argv && !strcmp(*argv, "-?")) X /* Subtract ret and iscurses to signal output */ X return help(0, name, cmd_help) - ret - iscurses; X X if (iscurses) X on_intr(); X X if (unbind) { X if (!*argv) { X print("%s what? ", name); X len = Getstr(buf, sizeof buf, 0); X if (len <= 0) { X if (iscurses) X off_intr(); X return -1; X } X rawstr = m_xlate(buf); X } else X rawstr = m_xlate(*argv); X if (!un_bind(rawstr, map_list)) { X ctrl_strcpy(ascii, rawstr, TRUE); X print("\"%s\" isn't bound to a command.\n", ascii); X } X if (iscurses) X off_intr(); X return ret; X } X if (argv && *argv) { X rawstr = m_xlate(*argv); X (void) ctrl_strcpy(ascii, rawstr, TRUE); X if (!*++argv) { X /* X * determine whether "argv" references a "map" or a "bind" X */ X int binding = c_bind(rawstr, *map_list); X if (binding == C_MACRO) { X char *mapping = c_macro(NULL, rawstr, *map_list); X if (mapping) { X print("\"%s\" is mapped to ", ascii); X print_more("\"%s\".\n", X ctrl_strcpy(buf, mapping, FALSE)); X } else X print("\"%s\" isn't mapped.\n", ascii); X } else if (binding) X print("\"%s\" is %s to \"%s\".\n", ascii, X map? "mapped" : "bound", map_func_names[binding].m_str); X else if (map) X print("\"%s\" isn't mapped.\n", ascii); X else X print("\"%s\" isn't bound to a command.\n", ascii); X if (iscurses) X off_intr(); X return ret; X } X } else { X print("%s [=all, -?=help]: ", name); X len = Getstr(string, MAX_BIND_LEN-1, 0); X if (len == 0) { X int add_to_ret = iscurses; X#ifdef CURSES X if (iscurses) X move(LINES-1, 0), refresh(); X#endif X if (map || is_bind_macro) X add_to_ret = !c_macro(name, NULL, *map_list); X else X add_to_ret = !c_bind(NULL, *map_list); X if (iscurses) X off_intr(); X /* signal CTND_CMD if there was output */ X return ret - add_to_ret; X } X if (len < 0) { X if (iscurses) X off_intr(); X return ret; X } X rawstr = m_xlate(string); X (void) ctrl_strcpy(ascii, rawstr, TRUE); X } X /* if a binding was given on the command line */ X if (argv && *argv && !map) X if (is_bind_macro) X (void) strcpy(buf, "macro"); X else X (void) strcpy(buf, *argv++); X else { X /* at this point, "rawstr" and "ascii" should both be set */ X int binding; X X if (!strcmp(ascii, "-?")) { X if (iscurses) X clr_bot_line(); X ret -= help(0, name, cmd_help); X if (iscurses) X off_intr(); X /* Subtract iscurses to signal CNTD_CMD */ X return ret - iscurses; X } X X if (!map && !is_bind_macro) { X binding = c_bind(rawstr, *map_list); X X for (len = 0; len == 0; ) { X print("\"%s\" = <%s>: New binding [ for list]: ", X ascii, (binding? map_func_names[binding].m_str : "unset")); X len = Getstr(buf, sizeof buf, 0); X if (iscurses) X clr_bot_line(); X /* strip any trailing whitespace */ X if (len > 0) X len = no_newln(buf) - buf; X if (len == 0) { X (void) do_pager(NULL, TRUE); X if (iscurses) X putchar('\n'); X for (x = 1; x <= C_HELP; x++) { X if (!(x % 4)) X if (do_pager("\n", FALSE) == EOF) X break; X (void) do_pager(sprintf(buf, "%-15.15s ", X map_func_names[x].m_str), FALSE); X } X (void) do_pager("\n", FALSE); X (void) do_pager(NULL, FALSE); X ret -= iscurses; X } X } X } else /* map */ X (void) strcpy(buf, "macro"), len = 5; X /* if list was printed, ret < -1 -- tells CNTD_CMD to be set and X * prevents screen from being refreshed (lets user read output X */ X if (len == -1) { X if (iscurses) X off_intr(); X return ret; X } X } X for (x = 1; x <= C_HELP; x++) { X if (prefix(buf, map_func_names[x].m_str) == MATCH) { X int add_to_ret; X if (debug) X print("\"%s\" will execute \"%s\".\n", ascii, buf); X if (map_func_names[x].m_cmd == C_MACRO) { X if (argv && *argv) { X (void) argv_to_string(buf, argv); X (void) m_xlate(buf); /* Convert buf to raw chars */ X add_to_ret = X do_bind(rawstr, map_func_names[x].m_cmd, buf, map_list); X } else { X char exp[MAX_MACRO_LEN*2]; /* printable expansion */ X char *mapping = c_macro(NULL, rawstr, *map_list); X X if (mapping) X (void) ctrl_strcpy(exp, mapping, TRUE); X print("\"%s\" = <%s>", ascii, mapping ? exp : "unset"); X putchar('\n'), print("New macro: "); X ret -= iscurses; /* To signal screen messed up */ X /* we are done with buf, so we can trash over it */ X len = Getstr(buf, MAX_MACRO_LEN, 0); X if (len > 0) { X if (iscurses) X clr_bot_line(); X (void) m_xlate(buf); /* Convert buf to raw chars */ X add_to_ret = X do_bind(rawstr, C_MACRO, buf, map_list); X if (debug) { X (void) ctrl_strcpy(exp, buf, TRUE); X print("\"%s\" will execute \"%s\".\n", ascii, exp); X } X } else if (len < 0) { X if (iscurses) X off_intr(); X return ret - add_to_ret; X } else X print("Can't bind to null macro"), putchar('\n'); X } X } else /* not a macro */ { X (void) argv_to_string(buf, argv); X add_to_ret = X do_bind(rawstr, map_func_names[x].m_cmd, buf, map_list); X } X /* if do_bind had no errors, it returned -1. If we already X * messed up the screen, then ret is less than -1. return the X * lesser of the two to make sure that CNTD_CMD gets set right X */ X if (iscurses) X off_intr(); X return min(add_to_ret, ret); X } X } X print("\"%s\": Unknown function.\n", buf); X if (iscurses) X off_intr(); X return ret; X} X X/* X * print current key to command bindings if "str" is NULL. X * else return the integer "m_cmd" which the str is bound to. X */ Xc_bind(str, opts) Xregister char *str; Xregister struct cmd_map *opts; X{ X register int incurses = iscurses; X X if (!str) { X if (!opts) { X print("No command bindings.\n"); X return C_ERROR; X } X if (incurses) X clr_bot_line(), iscurses = FALSE; X (void) do_pager(NULL, TRUE); X (void) do_pager("Current key to command bindings:\n", FALSE); X (void) do_pager("\n", FALSE); X } X X for (; opts; opts = opts->m_next) { X char buf[BUFSIZ], buf2[MAX_BIND_LEN], exp[MAX_MACRO_LEN*2], *xp; X if (!str) { X (void) ctrl_strcpy(buf2, opts->m_str, FALSE); X if ((xp = opts->x_str) && opts->m_cmd == C_MACRO) X xp = ctrl_strcpy(exp, opts->x_str, TRUE); X if (do_pager(sprintf(buf, "%s\t%-15.15s %s\n", X buf2, map_func_names[opts->m_cmd].m_str, X xp? xp : ""), X FALSE) == EOF) X break; X } else X if (strcmp(str, opts->m_str)) X continue; X else X return opts->m_cmd; X } X X iscurses = incurses; X if (!str) X (void) do_pager(NULL, FALSE); X return C_NULL; X} X X/* X * Doesn't touch messages, but changes macros: return -1. X * Error output causes return < -1. X * args is currently the execute string of a macro mapping, but may be X * used in the future as an argument string for any curses command. X */ Xdo_bind(str, func, args, map_list) Xregister char *str, *args; Xstruct cmd_map **map_list; Xlong func; X{ X register int ret = -1; X register struct cmd_map *list; X int match; X X if (func == C_MACRO && !check_mac_bindings(args)) X --ret; X (void) un_bind(str, map_list); X for (list = *map_list; list; list = list->m_next) X if ((match = prefix(str, list->m_str)) != NO_MATCH) { X ret--; X switch (match) { X case MATCH: X puts("Something impossible just happened."); X when A_PREFIX_B: X wprint("Warning: \"%s\" prefixes \"%s\" (%s)\n", str, X list->m_str, map_func_names[list->m_cmd].m_str); X when B_PREFIX_A: X wprint("Warning: \"%s\" (%s) prefixes: \"%s\"\n", X list->m_str, map_func_names[list->m_cmd].m_str, str); X } X } X add_bind(str, func, args, map_list); X /* errors decrement ret. If ret returns less than -1, CNTD_CMD is set X * and no redrawing is done so user can see the warning signs X */ X return ret; X} X X/* X * add a binding to a list. This may include "map"s or other mappings since X * the map_list argument can control that. The "func" is an int defined in X * bindings.h ... the "str" passed is the string the user would have to type X * to get the macro/map/binding expanded. This must in in raw format: no X * \n's to mean \015. Convert first using m_xlate(). X */ Xadd_bind(str, func, args, map_list) Xregister char *str, *args; Xstruct cmd_map **map_list; Xlong func; X{ X register struct cmd_map *tmp; X struct cmd_map *calloc(); X X if (!str || !*str) X return; X X /* now make a new option struct and set fields */ X if (!(tmp = calloc((unsigned)1, sizeof(struct cmd_map)))) { X error("calloc"); X return; X } X tmp->m_next = *map_list; X *map_list = tmp; X X tmp->m_str = savestr(str); X tmp->m_cmd = func; /* strdup handles the NULL case */ X if (args && *args) X tmp->x_str = savestr(args); X else X tmp->x_str = NULL; X} X Xstatic Xun_bind(p, map_list) Xregister char *p; Xstruct cmd_map **map_list; X{ X register struct cmd_map *list = *map_list, *tmp; X X if (!list || !*list->m_str || !p || !*p) X return 0; X X if (!strcmp(p, (*map_list)->m_str)) { X *map_list = (*map_list)->m_next; X xfree (list->m_str); X if (list->x_str) X xfree (list->x_str); X xfree((char *)list); X return 1; X } X for ( ; list->m_next; list = list->m_next) X if (!strcmp(p, list->m_next->m_str)) { X tmp = list->m_next; X list->m_next = list->m_next->m_next; X xfree (tmp->m_str); X if (tmp->x_str) X xfree (tmp->x_str); X xfree ((char *)tmp); X return 1; X } X return 0; X} X Xprefix(a, b) Xregister char *a, *b; X{ X if (!a || !b) X return NO_MATCH; X X while (*a && *b && *a == *b) X a++, b++; X if (!*a && !*b) X return MATCH; X if (!*a && *b) X return A_PREFIX_B; X if (*a && !*b) X return B_PREFIX_A; X return NO_MATCH; X} END_OF_FILE if test 20444 -ne `wc -c <'bind.c'`; then echo shar: \"'bind.c'\" unpacked with wrong size! fi # end of 'bind.c' fi if test -f 'hdrs.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'hdrs.c'\" else echo shar: Extracting \"'hdrs.c'\" \(18781 characters\) sed "s/^X//" >'hdrs.c' <<'END_OF_FILE' X/* hdrs.c (c) copyright 1986 (Dan Heller) */ X X/* X * Routines that deal with message headers inside messages X * msg_get(n, from, count) -- get the From_ line in msg n into "from". X * header_field(n, str) -- get the header named "str" from msg n. X * do_hdrs(argc, argv, list) -- diplay message headers. X * specl_hdrs(argv, list) -- display msgs that share common attributes. X * compose_hdr(cnt) -- compose a message header from msg n. X * reply_to(n, all, buf) -- construct a header based on the To: header of n. X * subject_to(n, buf) -- get the subject for replying to msg n. X * cc_to(n, buf) -- construct a Cc header based on the Cc of message n. X */ X#include "mush.h" X X/* X * Get a message from the current folder by its offset. X * Copy the From_ line to the second argument if the third arg > 0, X * and return the second argument, or NULL on an error. X */ Xchar * Xmsg_get(n, from, count) Xint n, count; Xchar *from; X{ X if (fseek(tmpf, msg[n].m_offset, L_SET) == -1) { X error("fseek in %s (msg %d, folder=%s)", tempfile, n+1, mailfile); X turnon(glob_flags, READ_ONLY); X return NULL; X } X if (count) X#ifndef MSG_SEPARATOR X return fgets(from, count, tmpf); X#else X *from = '\0'; X#endif X return from; X} X X/* X * get which message via the offset and search for the headers which X * match the string "str". there may be more than one of a field (like Cc:) X * so get them all and "cat" them together into the static buffer X * "buf" and return its address. X */ Xchar * Xheader_field(n, str) Xchar *str; X{ X static char buf[HDRSIZ]; X char tmp[HDRSIZ]; X register char *p, *p2, *b = buf; X int contd_hdr; /* true if next line is a continuation of the hdr we want */ X X if (!msg_get(n, tmp, sizeof tmp)) X return NULL; X *b = 0; X while((p = fgets(tmp, sizeof(tmp), tmpf)) && *p != '\n') { X if (*p != ' ' && *p != '\t') { X contd_hdr = 0; X /* strcmp ignoring case */ X for(p2 = str; *p && *p2 && *p2 == lower(*p); ++p, ++p2); X /* MATCH is true if p2 is at the end of str and *p is ':' */ X if (*p2 || *p++ != ':') X continue; X else X contd_hdr = 1; X } else if (!contd_hdr) X continue; X skipspaces(0); X p2 = no_newln(p); X *++p2 = ',', *++p2 = ' ', *++p2 = 0; X if (strlen(p) + (b-buf) < sizeof (buf)) X b += Strcpy(b, p); X } X if (b > buf) /* now get rid of the trailing ", " */ X *--b = 0, *--b = 0; X return (*buf)? buf: NULL; X} X Xdo_hdrs(argc, argv, list) Xregister char **argv, list[]; X{ X register int pageful = 0; X SIGRET (*oldint)(), (*oldquit)(); X int show_deleted; X static int cnt; X register char *p; X char first_char = (argc) ? **argv: 'h'; X X if (argc > 1 && !strcmp(argv[1], "-?")) X return help(0, "headers", cmd_help); X X if (!msg_cnt) { X if (ison(glob_flags, DO_PIPE)) X return 0; X#ifdef CURSES X if (iscurses) X clear(); X#endif /* CURSES */ X#ifdef SUNTOOL X if (istool) X mail_status(0); X#endif /* SUNTOOL */ X return 0; X } X if (first_char == ':' || (argc > 1 && argv[1][0] == ':')) { X if (first_char != ':') X argv++; X return specl_hdrs(argv, list); X } X X on_intr(); X X if (argc && (argv[0][1] == '+' || argc > 1 && !strcmp(argv[1], "+")) || X first_char == 'z' && !argv[1]) X if (msg_cnt > screen) X cnt = min(msg_cnt - screen, n_array[0] + screen); X else X cnt = 0; X else if (argc && (argv[0][1] == '-' || argc > 1 && !strcmp(argv[1], "-"))) X cnt = max((cnt - 2*screen), 0); X else if (argc && *++argv && X (isdigit(**argv) || **argv == '^' || **argv == '$' || **argv == '.') X || ison(glob_flags, IS_PIPE)) { X /* if we're coming from a pipe, start display at the first msg bit X * set in the msg_list X */ X int fnd; X if (ison(glob_flags, IS_PIPE)) { X if (isoff(glob_flags, DO_PIPE)) X for (fnd = 0; fnd < msg_cnt; fnd++) X if (msg_bit(list, fnd)) X wprint("%s\n", compose_hdr(fnd)); X off_intr(); X return 0; X } X /* if a number was given, use it */ X if (!(fnd = chk_msg(*argv))) { X off_intr(); X return -1; X } X for (cnt = fnd - 1; cnt > 0 && cnt + screen > msg_cnt; cnt--) X ; X } else if (current_msg < n_array[0] || current_msg > n_array[screen-1]) X cnt = current_msg; /* adjust if reads have passed screen bounds */ X else if (cnt >= msg_cnt || !argc || !*argv) X cnt = max((cnt - screen), 0); /* adjust window to maintain position */ X X show_deleted = !!do_set(set_options, "show_deleted"); X X /* Make sure we have at least $screen headers to print */ X if (cnt > 0 && !iscurses && !show_deleted && first_char != 'h') { X int tmp = cnt; X /* first count how many messages we can print without adjusting */ X for (pageful = 0; pageful < screen && cnt < msg_cnt; cnt++) X if (isoff(msg[cnt].m_flags, DELETE)) X pageful++; X /* if we can't print a pagefull of hdrs, back up till we can */ X for (cnt = tmp; pageful < screen && cnt; --cnt) X if (isoff(msg[cnt].m_flags, DELETE)) X pageful++; X pageful = 0; /* Used later as an index, so reset */ X } X X for (;pageful screen) { X panel_set(next_scr, PANEL_SHOW_ITEM, TRUE, 0); X panel_set(prev_scr, PANEL_SHOW_ITEM, TRUE, 0); X } X mail_status(0); X } X#endif /* SUNTOOL */ X return 0; X} X X#define NEW 1 X#define ALL 2 X Xspecl_hdrs(argv, list) Xchar **argv, list[]; X{ X u_long special = 0; X int n = 0; X X while (argv[0][++n]) X switch(argv[0][n]) { X case 'a': special = ALL; X when 'n': special = NEW; X when 'u': special = UNREAD; X when 'o': special = OLD; X when 'd': special = DELETE; X when 'r': special = REPLIED; X when 's': special = SAVED; X when 'p': special = PRESERVE; X otherwise: print("choose from n,u,o,d,r,s,p or a"); return -1; X } X if (debug) X (void) check_flags(special); X X for (n = 0; n < msg_cnt; n++) { X /* X * First, see if we're looking for NEW messages. X * If so, then check to see if the msg is unread and not old. X * If special > ALL, then special has a mask of bits describing X * the state of the message. X */ X if (ison(glob_flags, IS_PIPE)&& !msg_bit(list, n)) X continue; X if (special == ALL || special == NEW && X (ison(msg[n].m_flags, UNREAD) && isoff(msg[n].m_flags, OLD))) { X if (isoff(glob_flags, DO_PIPE)) X print("%s\n", compose_hdr(n)); X if (list) X set_msg_bit(list, n); X#ifndef M_XENIX X /* X * XENIX compiler can't handle "special" in ison() macro. X * It only works if the second argument is a constant! X */ X } else if (special > ALL && ison(msg[n].m_flags, special)) { X if (isoff(glob_flags, DO_PIPE)) X print("%s\n", compose_hdr(n)); X if (list) X set_msg_bit(list, n); X#endif /* M_XENIX */ X } else { X if (list) X unset_msg_bit(list, n); X if (debug) { X printf("msg[%d].m_flags: %d", n, msg[n].m_flags); X (void) check_flags(msg[n].m_flags); X } X } X } X return 0; X} X X#define Strncpy(buf,p) (void)(strncpy(buf,p,sizeof(buf)),buf[sizeof(buf)-1]=0) X X/* X * compose a header from the information about a message (from, to, date, X * subject, etc..). The header for message number "cnt" is built and is X * returned in the static buffer "buf". There will be *at least* 9 chars X * in the buffer which will be something like: " 123 >N " The breakdown X * is as follows: 4 chars for the message number, 1 space, 1 char for '>' X * (if current message) and two spaces for message status (new, unread, etc) X * followed by 1 terminating space. X * Read other comments in the routine for more info. X */ Xchar * Xcompose_hdr(cnt) X{ X static char buf[256]; X register char *p, *p2, *b; X int len, do_pad = FALSE, val, pad, got_dot, isauthor = 0, n; X char from[HDRSIZ], subject[256], date[64], lines[16]; X char to[256], addr[256], name[256], status[4]; X char Day[3], Mon[4], Tm[8], Yr[5], Wkday[4], *date_p; X X /* status of the message */ X if (ison(msg[cnt].m_flags, DELETE)) X status[0] = '*'; X else if (ison(msg[cnt].m_flags, PRESERVE)) X status[0] = 'P'; X else if (ison(msg[cnt].m_flags, SAVED)) X status[0] = 'S'; X else if (ison(msg[cnt].m_flags, OLD) && ison(msg[cnt].m_flags, UNREAD)) X status[0] = 'U'; X else if (isoff(msg[cnt].m_flags, UNREAD)) X status[0] = ' '; X else X status[0] = 'N'; X X if (ison(msg[cnt].m_flags, REPLIED)) X status[1] = 'r'; X else X status[1] = ' '; X X to[0] = from[0] = subject[0] = date[0] = lines[0] = addr[0] = X name[0] = Day[0] = Mon[0] = Tm[0] = Yr[0] = Wkday[0] = 0; X X /* who's the message to */ X if ((p = header_field(cnt, "resent-to")) || X (p = header_field(cnt, "to")) || X (p = header_field(cnt, "apparently-to"))) X Strncpy(to, p); X X /* who's the message from */ X if ((p = header_field(cnt, "from")) && strcpy(from, p) X || (p = reply_to(cnt, 0, from))) { X /* NOTE: this fails if the sender has '<' or '!' in X * the RFC822 comment fields -- leading "comment" X * or trailing (comment) -- but that isn't critical X */ X if ((p2 = rindex(p, '!')) || (p2 = index(p, '<'))) X p = p2 + 1; X } else X p = strcpy(from, "unknown"); /* just in case */ X /* If the From field contains the user's login name, then the message is X * from the user -- attempt to give more useful information by telling X * to whom the message was sent. This is not possible if the "to" header X * failed to get info (which is probably impossible). X */ X if (!strncmp(p, login, strlen(login))) { X isauthor = TRUE; X (void) get_name_n_addr(to, name+4, addr+4); X if (addr[4]) X (void) strncpy(addr, "TO: ", 4); X if (name[4]) { /* check to see if a name got added */ X (void) strncpy(name, "TO: ", 4); X Strncpy(from, name); X } else X Strncpy(from, addr); X } else X (void) get_name_n_addr(from, name, addr); X X if (ison(glob_flags, DATE_RECV)) X date_p = msg[cnt].m_date_recv; X else X date_p = msg[cnt].m_date_sent; X date_to_string(date_p, Yr, Mon, Day, Wkday, Tm, date); X X /* and the subject */ X if (p = header_field(cnt, "subject")) X Strncpy(subject, p); X X /* now, construct a header out of a format string */ X if (!hdr_format) X hdr_format = DEF_HDR_FMT; X X (void) sprintf(buf, "%4.d ", cnt+1); X b = buf+5; X *b++ = ((cnt == current_msg && !iscurses)? '>': ' '); X *b++ = status[0], *b++ = status[1]; X *b++ = ' '; X /* Count chars since beginning of buf. Initialize to 9 (strlen(buf) so far) X * This magic number is used in other places in msgs.c and mail.c X */ X n = 9; X for (p = hdr_format; *p; p++) X if (*p == '\\') X switch (*++p) { X case 't': X while (n % 8) X n++, *b++ = ' '; X when 'n': X n = 1, *b++ = '\n'; X otherwise: n++, *b++ = *p; X } X else if (*p == '%') { X char fmt[64]; X X p2 = fmt; X /* first check for string padding: %5n, %.4a, %10.5f, %-.3l etc. */ X do_pad = pad = val = got_dot = 0; X *p2++ = '%'; X if (p[1] != '-') X *p2++ = '-'; X else X ++p; X while (isdigit(*++p) || !got_dot && *p == '.') { X if (*p == '.') X got_dot = TRUE, val = pad, pad = 0; X else X pad = pad * 10 + *p - '0'; X *p2++ = *p; X } X if (!got_dot && isdigit(p[-1])) { X *p2 = 0; /* assure null termination */ X val = atoi(fmt+1); X if (val < 0) X val = -val; X p2 += strlen(sprintf(p2, ".%d", val)); X } X pad = min(pad, val); X *p2++ = 's', *p2 = 0; X switch (*p) { X case 'f': p2 = from, do_pad = TRUE; X when 'a': X if (!*(p2 = addr)) X p2 = from; X do_pad = TRUE; X when 'n': X if (!*(p2 = name)) X p2 = from, do_pad = TRUE; X when '%': p2 = "%"; X when 't': p2 = to; X when 's': p2 = subject; X when 'l': p2 = sprintf(lines, "%d", msg[cnt].m_lines); X when 'c': p2 = sprintf(lines, "%ld", msg[cnt].m_size); X when 'i': p2 = header_field(cnt, "message-id"); X /* date formatting chars */ X when 'd': p2 = date; /* the full date */ X when 'T': p2 = Tm; X when 'M': p2 = Mon; X when 'Y': p2 = Yr; X when 'y': p2 = Yr+2; X when 'N': p2 = Day; X when 'D': case 'W': p2 = Wkday; X otherwise: continue; /* unknown formatting char */ X } X if (do_pad && pad && strlen(p2) > pad) { X char *old_p2 = p2, *p3; X /* if addr is too long, move pointer forward till the X * "important" part is readable only for ! paths/addresses. X */ X while (p3 = index(p2, '!')) { X int tmp = strlen(p3+1); /* xenix has compiler problems */ X p2 = p3+1; X if (tmp + isauthor*4 < pad) { X if (isauthor && (p2 -= 4) < old_p2) X p2 = old_p2; X break; X } X } X if (isauthor && p2 > old_p2+4 && !p3 && strlen(p2) + 4 > pad) X p2 -= 4; X if (old_p2 != p2 && isauthor) X (void) strncpy(p2, "TO: ", 4); /* doesn't null terminate */ X } X len = strlen(sprintf(b, fmt, p2)); X n += len, b += len; X /* Get around a bug in 5.5 IBM RT which pads with NULs not ' ' */ X while (n && !*(b-1)) X b--, n--; X } else X n++, *b++ = *p; X for (*b-- = 0; isspace(*b) && *b != '\n'; --b) X *b = 0; X return buf; X} X X/* X * Using message "n", build a list of recipients that you would mail to if X * you were to reply to this message. If "all" is true, then it will take X * everyone from the To line in addition to the original sender. X * route_addresses() is called from mail.c, not from here. There are too many X * other uses for reply_to to always require reconstruction of return paths. X * Note that we do NOT deal with Cc paths here either. X * Check to make sure that we in fact return a legit address (i.e. not blanks X * or null). If such a case occurs, return login name. Always pad end w/blank. X */ Xchar * Xreply_to(n, all, buf) Xchar buf[]; X{ X register char *p = NULL, *p2, *b = buf, *field; X char line[256], name[256], addr[256]; X X if (field = do_set(set_options, "reply_to_hdr")) { X#ifndef MSG_SEPARATOR X if (!*field) X goto DoFrom; /* special case -- get the colon-less From line */ X#endif /* MSG_SEPARATOR */ X field = lcase_strcpy(line, field); X while (*field) { X if (p2 = any(field, " \t,:")) X *p2 = 0; X#ifndef MSG_SEPARATOR X if (!lcase_strncmp(field, "from_", -1)) X goto DoFrom; X#endif /* MSG_SEPARATOR */ X if ((p = header_field(n, field)) || !p2) X break; X else { X field = p2+1; X while (isspace(*field) || *field == ':' || *field == ',') X field++; X } X } X if (!p) X print("Warning: message contains no `reply_to_hdr' headers.\n"); X } X if (p || (!p && ((p = header_field(n, field = "reply-to")) || X (p = header_field(n, field = "return-path")) || X (p = header_field(n, field = "from"))))) X skipspaces(0); X else if (!p) { X#ifndef MSG_SEPARATOR XDoFrom: X field = "from_"; X /* if all else fails, then get the first token in "From" line */ X if (p2 = msg_get(n, line, sizeof line)) X p = index(p2, ' '); X else X return ""; X skipspaces(1); X if (p2 = index(p, ' ')) X *p2 = 0; X (void) unscramble_addr(p, line); /* p is safely recopied to line */ X p = line; X#else /* MSG_SEPARATOR */ X wprint("Warning: unable to find who msg %d is from!\n", n+1); X#endif /* MSG_SEPARATOR */ X } X get_name_n_addr(p, name, addr); X if (!name[0] && (!lcase_strncmp(field, "return-path", -1) || X !lcase_strncmp(field, "from_", -1))) { X /* X * Get the name of the author of the message we're replying to from the X * From: header since that header contains the author's name. Only do X * this if the address was gotten from the return-path or from_ lines X * because this is the only way to guarantee that the return address X * matches the author's name. Reply-To: may not be the same person! X * Check Resent-From: first since that's presumably more recent. X */ X if ((p = header_field(n, "resent-from")) || X (p = header_field(n, "from"))) X get_name_n_addr(p, name, NULL); X if (!name[0] && (p = header_field(n, "name"))) X (void) strcpy(name, p); X if (name[0]) { X if ((p = any(name, "(<,\"")) && (*p == ',' || *p == '<')) X *b++ = '"'; X b += Strcpy(b, name); X if (p && (*p == ',' || *p == '<')) X *b++ = '"'; X *b++ = ' ', *b++ = '<'; X } X b += Strcpy(b, addr); X if (name[0]) X *b++ = '>', *b = 0; X } else X b += Strcpy(buf, p); X X /* X * if `all' is true, append everyone on the "To:" line. X * cc_to(), called separately, will catch the cc's X */ X if (all && X ((p = header_field(n, "resent-to")) || (p = header_field(n, "to")) || X (p = header_field(n, "apparently-to"))) && *p) { X *b++ = ',', *b++ = ' '; X /* The assumption that HDRSIZ is correct is unwise, but I know it X * to be true for Mush. Be forewarned if you call this routine. X */ X p[HDRSIZ - (b - buf) - 2] = '\0'; /* prevent overflow */ X b += Strcpy(b, p); X } X /* Also append the Resent-From address if there is one. */ X if (all && (p = header_field(n, "resent-from")) && *p) { X *b++ = ',', *b++ = ' '; X /* Another trick to prevent overflow. See warning above. */ X (void) strncpy(b, p, HDRSIZ - (b - buf) - 2); X buf[HDRSIZ - 3] = 0; X } X fix_up_addr(buf); X take_me_off(buf); X for (p = buf; *p == ',' || isspace(*p); p++) X ; X if (!*p) X (void) strcpy(buf, login); X return buf; X} X Xchar * Xsubject_to(n, buf) Xregister char *buf; X{ X register char *p; X buf[0] = 0; /* make sure it's already null terminated */ X if (!(p = header_field(n, "subject"))) X return NULL; X if (lcase_strncmp(p, "Re:", 3)) X (void) strcpy(buf, "Re: "); X return strcat(buf, p); X} X Xchar * Xcc_to(n, buf) Xregister char *buf; X{ X register char *p; X buf[0] = 0; /* make sure it's already null terminated */ X if (!(p = header_field(n, "cc"))) X return NULL; X fix_up_addr(p); X take_me_off(p); X return strcpy(buf, p); X} END_OF_FILE if test 18781 -ne `wc -c <'hdrs.c'`; then echo shar: \"'hdrs.c'\" unpacked with wrong size! fi # end of 'hdrs.c' fi echo shar: End of archive 9 \(of 19\). cp /dev/null ark9isdone MISSING="" for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ; do if test ! -f ark${I}isdone ; then MISSING="${MISSING} ${I}" fi done if test "${MISSING}" = "" ; then echo You have unpacked all 19 archives. rm -f ark[1-9]isdone ark[1-9][0-9]isdone else echo You still need to unpack the following archives: echo " " ${MISSING} fi ## End of shell archive. exit 0