diff options
author | Achim Gottinger <achim@gentoo.org> | 2000-11-15 16:49:05 +0000 |
---|---|---|
committer | Achim Gottinger <achim@gentoo.org> | 2000-11-15 16:49:05 +0000 |
commit | 5cbc6b50f241178b2c8470db73d92a049ea33135 (patch) | |
tree | 75477eb036c8d93b834735c883b8f1a5569ae6c2 /net-mail/pine-maildir | |
parent | *** empty log message *** (diff) | |
download | historical-5cbc6b50f241178b2c8470db73d92a049ea33135.tar.gz historical-5cbc6b50f241178b2c8470db73d92a049ea33135.tar.bz2 historical-5cbc6b50f241178b2c8470db73d92a049ea33135.zip |
*** empty log message ***
Diffstat (limited to 'net-mail/pine-maildir')
-rw-r--r-- | net-mail/pine-maildir/files/filter.c | 6902 | ||||
-rw-r--r-- | net-mail/pine-maildir/files/os.c | 5673 |
2 files changed, 12575 insertions, 0 deletions
diff --git a/net-mail/pine-maildir/files/filter.c b/net-mail/pine-maildir/files/filter.c new file mode 100644 index 000000000000..9264fa088b3d --- /dev/null +++ b/net-mail/pine-maildir/files/filter.c @@ -0,0 +1,6902 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: filter.c,v 1.1 2000/11/15 16:46:06 achim Exp $"; +#endif +/*---------------------------------------------------------------------- + + T H E P I N E M A I L S Y S T E M + + Laurence Lundblade and Mike Seibel + Networks and Distributed Computing + Computing and Communications + University of Washington + Administration Builiding, AG-44 + Seattle, Washington, 98195, USA + Internet: lgl@CAC.Washington.EDU + mikes@CAC.Washington.EDU + + Please address all bugs and comments to "pine-bugs@cac.washington.edu" + + + Pine and Pico are registered trademarks of the University of Washington. + No commercial use of these trademarks may be made without prior written + permission of the University of Washington. + + Pine, Pico, and Pilot software and its included text are Copyright + 1989-1999 by the University of Washington. + + The full text of our legal notices is contained in the file called + CPYRIGHT, included with this distribution. + + + Pine is in part based on The Elm Mail System: + *********************************************************************** + * The Elm Mail System - Revision: 2.13 * + * * + * Copyright (c) 1986, 1987 Dave Taylor * + * Copyright (c) 1988, 1989 USENET Community Trust * + *********************************************************************** + + + ----------------------------------------------------------------------*/ + +/*====================================================================== + filter.c + + This code provides a generalized, flexible way to allow + piping of data thru filters. Each filter is passed a structure + that it will use to hold its static data while it operates on + the stream of characters that are passed to it. After processing + it will either return or call the next filter in + the pipe with any character (or characters) it has ready to go. This + means some terminal type of filter has to be the last in the + chain (i.e., one that writes the passed char someplace, but doesn't + call another filter). + + See below for more details. + + The motivation is to handle MIME decoding, richtext conversion, + iso_code stripping and anything else that may come down the + pike (e.g., PEM) in an elegant fashion. mikes (920811) + + TODO: + reasonable error handling + + ====*/ + + +#include "headers.h" + + +/* + * Internal prototypes + */ +int gf_so_readc PROTO((unsigned char *)); +int gf_so_writec PROTO((int)); +int gf_sreadc PROTO((unsigned char *)); +int gf_swritec PROTO((int)); +int gf_freadc PROTO((unsigned char *)); +int gf_fwritec PROTO((int)); +void gf_terminal PROTO((FILTER_S *, int)); +char *gf_filter_puts PROTO((char *)); +void gf_filter_eod PROTO((void)); +void gf_error PROTO((char *)); +void gf_8bit_put PROTO((FILTER_S *, int)); +int so_reaquire PROTO((STORE_S *)); +void *so_file_open PROTO((STORE_S *)); +int so_cs_writec PROTO((int, STORE_S *)); +int so_pico_writec PROTO((int, STORE_S *)); +int so_file_writec PROTO((int, STORE_S *)); +int so_cs_readc PROTO((unsigned char *, STORE_S *)); +int so_pico_readc PROTO((unsigned char *, STORE_S *)); +int so_file_readc PROTO((unsigned char *, STORE_S *)); +int so_cs_puts PROTO((STORE_S *, char *)); +int so_pico_puts PROTO((STORE_S *, char *)); +int so_file_puts PROTO((STORE_S *, char *)); + + +/* + * GENERALIZED STORAGE FUNCTIONS. Idea is to allow creation of + * storage objects that can be written into and read from without + * the caller knowing if the storage is core or in a file + * or whatever. + */ +#define MSIZE_INIT 8192 +#define MSIZE_INC 4096 + + +/* + * System specific options + */ +#ifdef DOS +#define NO_PIPE +#endif +#if defined(DOS) || defined(OS2) +#define CRLF_NEWLINES +#endif + +/* + * Various parms for opening and creating files directly and via stdio. + * NOTE: If "binary" mode file exists, use it. + */ +#ifdef O_BINARY +#define STDIO_READ "rb" +#define STDIO_APPEND "a+b" +#else +#define O_BINARY 0 +#define STDIO_READ "r" +#define STDIO_APPEND "a+" +#endif + +#define OP_FL_RDWR (O_RDWR | O_CREAT | O_APPEND | O_BINARY) +#define OP_FL_RDONLY (O_RDONLY | O_BINARY) + +#ifdef S_IREAD +#define OP_MD_USER (S_IREAD | S_IWRITE) +#else +#define OP_MD_USER 0600 +#endif + +#ifdef S_IRUSR +#define OP_MD_ALL (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | \ + S_IROTH | S_IWOTH) +#else +#define OP_MD_ALL 0666 +#endif + + +/* + * allocate resources associated with the specified type of + * storage. If requesting a named file object, open it for + * appending, else just open a temp file. + * + * return the filled in storage object + */ +STORE_S * +so_get(source, name, rtype) + SourceType source; /* requested storage type */ + char *name; /* file name */ + int rtype; /* file access type */ +{ + STORE_S *so = (STORE_S *)fs_get(sizeof(STORE_S)); + + memset(so, 0, sizeof(STORE_S)); + so->flags |= rtype; + + if(name) /* stash the name */ + so->name = cpystr(name); +#ifdef DOS + else if(source == TmpFileStar || source == FileStar){ + /* + * Coerce to TmpFileStar. The MSC library's "tmpfile()" + * doesn't observe the "TMP" or "TEMP" environment vars and + * always wants to write "\". This is problematic in shared, + * networked environments. + */ + source = TmpFileStar; + so->name = temp_nam(NULL, "pi"); + } +#else + else if(source == TmpFileStar) /* make one up! */ + so->name = temp_nam(NULL, "pine-tmp"); +#endif + + so->src = source; + if(so->src == FileStar || so->src == TmpFileStar){ + so->writec = so_file_writec; + so->readc = so_file_readc; + so->puts = so_file_puts; + + /* + * The reason for both FileStar and TmpFileStar types is + * that, named or unnamed, TmpFileStar's are unlinked + * when the object is given back to the system. This is + * useful for keeping us from running out of file pointers as + * the pointer associated with the object can be temporarily + * returned to the system without destroying the object. + * + * The programmer is warned to be careful not to assign the + * TmpFileStar type to any files that are expected to remain + * after the dust has settled! + */ + if(so->name){ + if(!(so->txt = so_file_open(so))){ + dprint(1, (debugfile, "so_get error: %s : %s", so->name, + error_description(errno))); + fs_give((void **)&so->name); + fs_give((void **)&so); /* so freed & set to NULL */ + } + } + else{ + if(!(so->txt = (void *) create_tmpfile())){ + dprint(1, (debugfile, "so_get error: tmpfile : %s", + error_description(errno))); + fs_give((void **)&so); /* so freed & set to NULL */ + } + } + } + else if(so->src == PicoText){ + so->writec = so_pico_writec; + so->readc = so_pico_readc; + so->puts = so_pico_puts; + if(!(so->txt = pico_get())){ + dprint(1, (debugfile, "so_get error: alloc of pico text space")); + if(so->name) + fs_give((void **)&so->name); + fs_give((void **)&so); /* so freed & set to NULL */ + } + } + else{ + so->writec = so_cs_writec; + so->readc = so_cs_readc; + so->puts = so_cs_puts; + so->txt = (void *)fs_get((size_t) MSIZE_INIT * sizeof(char)); + so->dp = so->eod = (unsigned char *) so->txt; + so->eot = so->dp + MSIZE_INIT; + memset(so->eod, 0, so->eot - so->eod); + } + + return(so); +} + + +/* + * so_give - free resources associated with a storage object and then + * the object itself. + */ +void +so_give(so) +STORE_S **so; +{ + if(!so) + return; + + if((*so)->src == FileStar || (*so)->src == TmpFileStar){ + if((*so)->txt) + fclose((FILE *)(*so)->txt); /* disassociate from storage */ + + if((*so)->name && (*so)->src == TmpFileStar) + unlink((*so)->name); /* really disassociate! */ + } + else if((*so)->txt && (*so)->src == PicoText) + pico_give((*so)->txt); + else if((*so)->txt) + fs_give((void **)&((*so)->txt)); + + if((*so)->name) + fs_give((void **)&((*so)->name)); /* blast the name */ + + fs_give((void **)so); /* release the object */ +} + + +/* + * so_file_open + */ +void * +so_file_open(so) + STORE_S *so; +{ + char *type = ((so->flags) & WRITE_ACCESS) ? STDIO_APPEND : STDIO_READ; + int flags = ((so->flags) & WRITE_ACCESS) ? OP_FL_RDWR : OP_FL_RDONLY, + mode = (((so->flags) & OWNER_ONLY) || so->src == TmpFileStar) + ? OP_MD_USER : OP_MD_ALL, + fd; + + /* + * Use open instead of fopen so we can make temp files private. + */ + return(((fd = open(so->name, flags, mode)) >= 0) + ? (so->txt = (void *) fdopen(fd, type)) : NULL); +} + + +/* + * put a character into the specified storage object, + * expanding if neccessary + * + * return 1 on success and 0 on failure + */ +int +so_cs_writec(c, so) + int c; + STORE_S *so; +{ + if(so->dp >= so->eot){ + size_t cur_o = so->dp - (unsigned char *) so->txt; + size_t data_o = so->eod - (unsigned char *) so->txt; + size_t size = (so->eot - (unsigned char *) so->txt) + MSIZE_INC; + + fs_resize(&so->txt, size * sizeof(char)); + so->dp = (unsigned char *) so->txt + cur_o; + so->eod = (unsigned char *) so->txt + data_o; + so->eot = (unsigned char *) so->txt + size; + memset(so->eod, 0, so->eot - so->eod); + } + + *so->dp++ = (unsigned char) c; + if(so->dp > so->eod) + so->eod = so->dp; + + return(1); +} + +int +so_pico_writec(c, so) + int c; + STORE_S *so; +{ + unsigned char ch = (unsigned char) c; + + return(pico_writec(so->txt, ch)); +} + +int +so_file_writec(c, so) + int c; + STORE_S *so; +{ + unsigned char ch = (unsigned char) c; + int rv = 0; + + if(so->txt || so_reaquire(so)) + do + rv = fwrite(&ch,sizeof(unsigned char),(size_t)1,(FILE *)so->txt); + while(!rv && ferror((FILE *)so->txt) && errno == EINTR); + + return(rv); +} + + +/* + * get a character from the specified storage object. + * + * return 1 on success and 0 on failure + */ +int +so_cs_readc(c, so) + unsigned char *c; + STORE_S *so; +{ + return((so->dp < so->eod) ? *c = *(so->dp)++, 1 : 0); +} + +int +so_pico_readc(c, so) + unsigned char *c; + STORE_S *so; +{ + return(pico_readc(so->txt, c)); +} + +int +so_file_readc(c, so) + unsigned char *c; + STORE_S *so; +{ + int rv = 0; + + if(so->txt || so_reaquire(so)) + do + rv = fread(c, sizeof(char), (size_t)1, (FILE *)so->txt); + while(!rv && ferror((FILE *)so->txt) && errno == EINTR); + + return(rv); +} + + +/* + * write a string into the specified storage object, + * expanding if necessary (and cheating if the object + * happens to be a file!) + * + * return 1 on success and 0 on failure + */ +int +so_cs_puts(so, s) + STORE_S *so; + char *s; +{ + int slen = strlen(s); + + if(so->dp + slen >= so->eot){ + register size_t cur_o = so->dp - (unsigned char *) so->txt; + register size_t data_o = so->eod - (unsigned char *) so->txt; + register size_t len = so->eot - (unsigned char *) so->txt; + while(len <= cur_o + slen + 1) + len += MSIZE_INC; /* need to resize! */ + + fs_resize(&so->txt, len * sizeof(char)); + so->dp = (unsigned char *)so->txt + cur_o; + so->eod = (unsigned char *)so->txt + data_o; + so->eot = (unsigned char *)so->txt + len; + memset(so->eod, 0, so->eot - so->eod); + } + + memcpy(so->dp, s, slen); + so->dp += slen; + if(so->dp > so->eod) + so->eod = so->dp; + + return(1); +} + +int +so_pico_puts(so, s) + STORE_S *so; + char *s; +{ + return(pico_puts(so->txt, s)); +} + +int +so_file_puts(so, s) + STORE_S *so; + char *s; +{ + int rv = *s ? 0 : 1; + + if(!rv && (so->txt || so_reaquire(so))) + do + rv = fwrite(s, strlen(s)*sizeof(char), (size_t)1, (FILE *)so->txt); + while(!rv && ferror((FILE *)so->txt) && errno == EINTR); + + return(rv); +} + + +/* + * + */ +int +so_nputs(so, s, n) + STORE_S *so; + char *s; + long n; +{ + while(n--) + if(!so_writec((unsigned char) *s++, so)) + return(0); /* ERROR putting char ! */ + + return(1); +} + + + +/* + * Position the storage object's pointer to the given offset + * from the start of the object's data. + */ +int +so_seek(so, pos, orig) + STORE_S *so; + long pos; + int orig; +{ + if(so->src == CharStar){ + switch(orig){ + case 0 : /* SEEK_SET */ + return((pos < so->eod - (unsigned char *) so->txt) + ? so->dp = (unsigned char *)so->txt + pos, 0 : -1); + case 1 : /* SEEK_CUR */ + return((pos > 0) + ? ((pos < so->eod - so->dp) ? so->dp += pos, 0: -1) + : ((pos < 0) + ? ((-pos < so->dp - (unsigned char *)so->txt) + ? so->dp += pos, 0 : -1) + : 0)); + case 2 : /* SEEK_END */ + return((pos < so->eod - (unsigned char *) so->txt) + ? so->dp = so->eod - pos, 0 : -1); + default : + return(-1); + } + } + else if(so->src == PicoText) + return(pico_seek(so->txt, pos, orig)); + else /* FileStar or TmpFileStar */ + return((so->txt || so_reaquire(so)) + ? fseek((FILE *)so->txt,pos,orig) + : -1); +} + + +/* + * Change the given storage object's size to that specified. If size + * is less than the current size, the internal pointer is adjusted and + * all previous data beyond the given size is lost. + * + * Returns 0 on failure. + */ +int +so_truncate(so, size) + STORE_S *so; + long size; +{ + if(so->src == CharStar){ + if(so->eod < (unsigned char *) so->txt + size){ /* alloc! */ + unsigned char *newtxt = (unsigned char *) so->txt; + register size_t len = so->eot - (unsigned char *) so->txt; + + while(len <= size) + len += MSIZE_INC; /* need to resize! */ + + if(len > so->eot - (unsigned char *) newtxt){ + fs_resize((void **) &newtxt, len * sizeof(char)); + so->eot = newtxt + len; + so->eod = newtxt + (so->eod - (unsigned char *) so->txt); + memset(so->eod, 0, so->eot - so->eod); + } + + so->eod = newtxt + size; + so->dp = newtxt + (so->dp - (unsigned char *) so->txt); + so->txt = newtxt; + } + else if(so->eod > (unsigned char *) so->txt + size){ + if(so->dp > (so->eod = (unsigned char *)so->txt + size)) + so->dp = so->eod; + + memset(so->eod, 0, so->eot - so->eod); + } + + return(1); + } + else if(so->src == PicoText){ + fatal("programmer botch: unsupported so_truncate call"); + /*NOTREACHED*/ + } + else /* FileStar or TmpFileStar */ + return(fflush((FILE *) so->txt) != EOF + && fseek((FILE *) so->txt, size, 0) == 0 + && ftruncate(fileno((FILE *)so->txt), size) == 0); +} + + +/* + * so_release - a rather misnamed function. the idea is to release + * what system resources we can (e.g., open files). + * while maintaining a reference to it. + * it's up to the functions that deal with this object + * next to re-aquire those resources. + */ +int +so_release(so) +STORE_S *so; +{ + if(so->txt && so->name && (so->src == FileStar || so->src == TmpFileStar)){ + if(fgetpos((FILE *)so->txt, (fpos_t *)&(so->used)) == 0){ + fclose((FILE *)so->txt); /* free the handle! */ + so->txt = NULL; + } + } + + return(1); +} + + +/* + * so_reaquire - get any previously released system resources we + * may need for the given storage object. + * NOTE: at the moment, only FILE * types of objects are + * effected, so it only needs to be called before + * references to them. + * + */ +so_reaquire(so) +STORE_S *so; +{ + int rv = 1; + + if(!so->txt && (so->src == FileStar || so->src == TmpFileStar)){ + if(!(so->txt = so_file_open(so))){ + q_status_message2(SM_ORDER,3,5, "ERROR reopening %s : %s", so->name, + error_description(errno)); + rv = 0; + } + else if(fsetpos((FILE *)so->txt, (fpos_t *)&(so->used))){ + q_status_message2(SM_ORDER, 3, 5, "ERROR positioning in %s : %s", + so->name, error_description(errno)); + rv = 0; + } + } + + return(rv); +} + + +/* + * so_text - return a pointer to the text the store object passed + */ +void * +so_text(so) +STORE_S *so; +{ + return((so) ? so->txt : NULL); +} + + +/* + * END OF GENERALIZE STORAGE FUNCTIONS + */ + + +/* + * Start of filters, pipes and various support functions + */ + +/* + * pointer to first function in a pipe, and pointer to last filter + */ +FILTER_S *gf_master = NULL; +static gf_io_t last_filter; +static char *gf_error_string; +static long gf_byte_count; +static jmp_buf gf_error_state; + + +/* + * A list of states used by the various filters. Reused in many filters. + */ +#define DFL 0 +#define EQUAL 1 +#define HEX 2 +#define WSPACE 3 +#define CCR 4 +#define CLF 5 +#define TOKEN 6 +#define TAG 7 +#define HANDLE 8 +#define HDATA 9 + + + +/* + * Macros to reduce function call overhead associated with calling + * each filter for each byte filtered, and to minimize filter structure + * dereferences. NOTE: "queuein" has to do with putting chars into the + * filter structs data queue. So, writing at the queuein offset is + * what a filter does to pass processed data out of itself. Ditto for + * queueout. This explains the FI --> queueout init stuff below. + */ +#define GF_QUE_START(F) (&(F)->queue[0]) +#define GF_QUE_END(F) (&(F)->queue[GF_MAXBUF - 1]) + +#define GF_IP_INIT(F) ip = (F) ? &(F)->queue[(F)->queuein] : NULL +#define GF_EIB_INIT(F) eib = (F) ? GF_QUE_END(F) : NULL +#define GF_OP_INIT(F) op = (F) ? &(F)->queue[(F)->queueout] : NULL +#define GF_EOB_INIT(F) eob = (F) ? &(F)->queue[(F)->queuein] : NULL + +#define GF_IP_END(F) (F)->queuein = ip - GF_QUE_START(F) +#define GF_OP_END(F) (F)->queueout = op - GF_QUE_START(F) + +#define GF_INIT(FI, FO) register unsigned char *GF_OP_INIT(FI); \ + register unsigned char *GF_EOB_INIT(FI); \ + register unsigned char *GF_IP_INIT(FO); \ + register unsigned char *GF_EIB_INIT(FO); + +#define GF_CH_RESET(F) (op = eob = GF_QUE_START(F), \ + (F)->queueout = (F)->queuein = 0) + +#define GF_END(FI, FO) (GF_OP_END(FI), GF_IP_END(FO)) + +#define GF_FLUSH(F) ((int)(GF_IP_END(F), (*(F)->f)((F), GF_DATA), \ + GF_IP_INIT(F), GF_EIB_INIT(F))) + +#define GF_PUTC(F, C) ((int)(*ip++ = (C), (ip >= eib) ? GF_FLUSH(F) : 1)) + +#define GF_GETC(F, C) ((op < eob) ? (((C) = *op++), 1) : GF_CH_RESET(F)) + + +/* + * Generalized getc and putc routines. provided here so they don't + * need to be re-done elsewhere to + */ + +/* + * pointers to objects to be used by the generic getc and putc + * functions + */ +static struct gf_io_struct { + FILE *file; + char *txtp; + unsigned long n; +} gf_in, gf_out; + + +#define GF_SO_STACK struct gf_so_stack +static GF_SO_STACK { + STORE_S *so; + GF_SO_STACK *next; +} *gf_so_in, *gf_so_out; + + +/* + * setup to use and return a pointer to the generic + * getc function + */ +void +gf_set_readc(gc, txt, len, src) + gf_io_t *gc; + void *txt; + unsigned long len; + SourceType src; +{ + gf_in.n = len; + if(src == FileStar){ + gf_in.file = (FILE *)txt; + fseek(gf_in.file, 0L, 0); + *gc = gf_freadc; + } + else{ + gf_in.txtp = (char *)txt; + *gc = gf_sreadc; + } +} + + +/* + * setup to use and return a pointer to the generic + * putc function + */ +void +gf_set_writec(pc, txt, len, src) + gf_io_t *pc; + void *txt; + unsigned long len; + SourceType src; +{ + gf_out.n = len; + if(src == FileStar){ + gf_out.file = (FILE *)txt; + *pc = gf_fwritec; + } + else{ + gf_out.txtp = (char *)txt; + *pc = gf_swritec; + } +} + + +/* + * setup to use and return a pointer to the generic + * getc function + */ +void +gf_set_so_readc(gc, so) + gf_io_t *gc; + STORE_S *so; +{ + GF_SO_STACK *sp = (GF_SO_STACK *) fs_get(sizeof(GF_SO_STACK)); + + sp->so = so; + sp->next = gf_so_in; + gf_so_in = sp; + *gc = gf_so_readc; +} + + +void +gf_clear_so_readc(so) + STORE_S *so; +{ + GF_SO_STACK *sp; + + if(sp = gf_so_in){ + if(so == sp->so){ + gf_so_in = gf_so_in->next; + fs_give((void **) &sp); + } + else + panic("Programmer botch: Can't unstack store readc"); + } + else + panic("Programmer botch: NULL store clearing store readc"); +} + + +/* + * setup to use and return a pointer to the generic + * putc function + */ +void +gf_set_so_writec(pc, so) + gf_io_t *pc; + STORE_S *so; +{ + GF_SO_STACK *sp = (GF_SO_STACK *) fs_get(sizeof(GF_SO_STACK)); + + sp->so = so; + sp->next = gf_so_out; + gf_so_out = sp; + *pc = gf_so_writec; +} + + +void +gf_clear_so_writec(so) + STORE_S *so; +{ + GF_SO_STACK *sp; + + if(sp = gf_so_out){ + if(so == sp->so){ + gf_so_out = gf_so_out->next; + fs_give((void **) &sp); + } + else + panic("Programmer botch: Can't unstack store writec"); + } + else + panic("Programmer botch: NULL store clearing store writec"); +} + + +/* + * put the character to the object previously defined + */ +int +gf_so_writec(c) +int c; +{ + return(so_writec(c, gf_so_out->so)); +} + + +/* + * get a character from an object previously defined + */ +int +gf_so_readc(c) +unsigned char *c; +{ + return(so_readc(c, gf_so_in->so)); +} + + +/* get a character from a file */ +/* assumes gf_out struct is filled in */ +int +gf_freadc(c) +unsigned char *c; +{ + int rv = 0; + + do { + errno = 0; + clearerr(gf_in.file); + rv = fread(c, sizeof(unsigned char), (size_t)1, gf_in.file); + } while(!rv && ferror(gf_in.file) && errno == EINTR); + + return(rv); +} + + +/* put a character to a file */ +/* assumes gf_out struct is filled in */ +int +gf_fwritec(c) + int c; +{ + unsigned char ch = (unsigned char)c; + int rv = 0; + + do + rv = fwrite(&ch, sizeof(unsigned char), (size_t)1, gf_out.file); + while(!rv && ferror(gf_out.file) && errno == EINTR); + + return(rv); +} + + +/* get a character from a string, return nonzero if things OK */ +/* assumes gf_out struct is filled in */ +int +gf_sreadc(c) +unsigned char *c; +{ + return((gf_in.n) ? *c = *(gf_in.txtp)++, gf_in.n-- : 0); +} + + +/* put a character into a string, return nonzero if things OK */ +/* assumes gf_out struct is filled in */ +int +gf_swritec(c) + int c; +{ + return((gf_out.n) ? *(gf_out.txtp)++ = c, gf_out.n-- : 0); +} + + +/* + * output the given string with the given function + */ +int +gf_puts(s, pc) + register char *s; + gf_io_t pc; +{ + while(*s != '\0') + if(!(*pc)((unsigned char)*s++)) + return(0); /* ERROR putting char ! */ + + return(1); +} + + +/* + * output the given string with the given function + */ +int +gf_nputs(s, n, pc) + register char *s; + long n; + gf_io_t pc; +{ + while(n--) + if(!(*pc)((unsigned char)*s++)) + return(0); /* ERROR putting char ! */ + + return(1); +} + + +/* + * Start of generalized filter routines + */ + +/* + * initializing function to make sure list of filters is empty. + */ +void +gf_filter_init() +{ + FILTER_S *flt, *fltn = gf_master; + + while((flt = fltn) != NULL){ /* free list of old filters */ + fltn = flt->next; + fs_give((void **)&flt); + } + + gf_master = NULL; + gf_error_string = NULL; /* clear previous errors */ + gf_byte_count = 0L; /* reset counter */ +} + + + +/* + * link the given filter into the filter chain + */ +void +gf_link_filter(f, data) + filter_t f; + void *data; +{ + FILTER_S *new, *tail; + +#ifdef CRLF_NEWLINES + /* + * If the system's native EOL convention is CRLF, then there's no + * point in passing data thru a filter that's not doing anything + */ + if(f == gf_nvtnl_local || f == gf_local_nvtnl) + return; +#endif + + new = (FILTER_S *)fs_get(sizeof(FILTER_S)); + memset(new, 0, sizeof(FILTER_S)); + + new->f = f; /* set the function pointer */ + new->opt = data; /* set any optional parameter data */ + (*f)(new, GF_RESET); /* have it setup initial state */ + + if(tail = gf_master){ /* or add it to end of existing */ + while(tail->next) /* list */ + tail = tail->next; + + tail->next = new; + } + else /* attach new struct to list */ + gf_master = new; /* start a new list */ +} + + +/* + * terminal filter, doesn't call any other filters, typically just does + * something with the output + */ +void +gf_terminal(f, flg) + FILTER_S *f; + int flg; +{ + if(flg == GF_DATA){ + GF_INIT(f, f); + + while(op < eob) + if((*last_filter)(*op++) <= 0) /* generic terminal filter */ + gf_error(errno ? error_description(errno) : "Error writing pipe"); + + GF_CH_RESET(f); + } + else if(flg == GF_RESET) + errno = 0; /* prepare for problems */ +} + + +/* + * set some outside gf_io_t function to the terminal function + * for example: a function to write a char to a file or into a buffer + */ +void +gf_set_terminal(f) /* function to set generic filter */ + gf_io_t f; +{ + last_filter = f; +} + + +/* + * common function for filter's to make it known that an error + * has occurred. Jumps back to gf_pipe with error message. + */ +void +gf_error(s) + char *s; +{ + /* let the user know the error passed in s */ + gf_error_string = s; + longjmp(gf_error_state, 1); +} + + +/* + * The routine that shoves each byte through the chain of + * filters. It sets up error handling, and the terminal function. + * Then loops getting bytes with the given function, and passing + * it on to the first filter in the chain. + */ +char * +gf_pipe(gc, pc) + gf_io_t gc, pc; /* how to get a character */ +{ + unsigned char c; + +#if defined(DOS) && !defined(_WINDOWS) + MoveCursor(0, 1); + StartInverse(); +#endif + + dprint(4, (debugfile, "-- gf_pipe: ")); + + /* + * set up for any errors a filter may encounter + */ + if(setjmp(gf_error_state)){ +#if defined(DOS) && !defined(_WINDOWS) + ibmputc(' '); + EndInverse(); +#endif + dprint(4, (debugfile, "ERROR: %s\n", + gf_error_string ? gf_error_string : "NULL")); + return(gf_error_string); /* */ + } + + /* + * set and link in the terminal filter + */ + gf_set_terminal(pc); + gf_link_filter(gf_terminal, NULL); + + /* + * while there are chars to process, send them thru the pipe. + * NOTE: it's necessary to enclose the loop below in a block + * as the GF_INIT macro calls some automatic var's into + * existence. It can't be placed at the start of gf_pipe + * because its useful for us to be called without filters loaded + * when we're just being used to copy bytes between storage + * objects. + */ + { + GF_INIT(gf_master, gf_master); + + while((*gc)(&c)){ + gf_byte_count++; +#ifdef DOS + if(!(gf_byte_count & 0x3ff)) +#ifdef _WINDOWS + /* Under windows we yeild to allow event processing. + * Progress display is handled throught the alarm() + * mechinism. + */ + mswin_yeild (); +#else + /* Poor PC still needs spinning bar */ + ibmputc("/-\\|"[((int) gf_byte_count >> 10) % 4]); + MoveCursor(0, 1); +#endif +#endif + + GF_PUTC(gf_master, c & 0xff); + } + + /* + * toss an end-of-data marker down the pipe to give filters + * that have any buffered data the opportunity to dump it + */ + GF_FLUSH(gf_master); + (*gf_master->f)(gf_master, GF_EOD); + } + +#if defined(DOS) && !defined(_WINDOWS) + ibmputc(' '); + EndInverse(); +#endif + + dprint(1, (debugfile, "done.\n")); + return(NULL); /* everything went OK */ +} + + +/* + * return the number of bytes piped so far + */ +long +gf_bytes_piped() +{ + return(gf_byte_count); +} + + +/* + * filter the given input with the given command + * + * Args: cmd -- command string to execute + * prepend -- string to prepend to filtered input + * source_so -- storage object containing data to be filtered + * pc -- function to write filtered output with + * aux_filters -- additional filters to pass data thru after "cmd" + * + * Returns: NULL on sucess, reason for failure (not alloc'd!) on error + */ +char * +gf_filter(cmd, prepend, source_so, pc, aux_filters) + char *cmd, *prepend; + STORE_S *source_so; + gf_io_t pc; + FILTLIST_S *aux_filters; +{ + unsigned char c; + int flags; + char *errstr = NULL, buf[MAILTMPLEN], *rfile = NULL; + PIPE_S *fpipe; + + dprint(4, (debugfile, "so_filter: \"%s\"\n", cmd)); + + gf_filter_init(); + for( ; aux_filters && aux_filters->filter; aux_filters++) + gf_link_filter(aux_filters->filter, aux_filters->data); + + gf_set_terminal(pc); + gf_link_filter(gf_terminal, NULL); + + /* + * Spawn filter feeding it data, and reading what it writes. + */ + so_seek(source_so, 0L, 0); +#ifdef NO_PIPE + /* + * When there're no pipes for IPC, use an output file to collect + * the result... + */ + flags = PIPE_WRITE | PIPE_NOSHELL | PIPE_RESET; + rfile = temp_nam(NULL, "pf"); +#else + flags = PIPE_WRITE | PIPE_READ | PIPE_NOSHELL | PIPE_RESET; +#endif + + if(fpipe = open_system_pipe(cmd, rfile ? &rfile : NULL, NULL, flags, 0)){ +#ifdef NO_PIPE + if(prepend && (fputs(prepend, fpipe->out.f) == EOF + || fputc('\n', fpipe->out.f) == EOF)) + errstr = error_description(errno); + + /* + * Write the output, and deal with the result later... + */ + while(!errstr && so_readc(&c, source_so)) + if(fputc(c, fpipe->out.f) == EOF) + errstr = error_description(errno); +#else +#ifdef NON_BLOCKING_IO + int n; + + if(fcntl(fileno(fpipe->in.f), F_SETFL, NON_BLOCKING_IO) == -1) + errstr = "Can't set up non-blocking IO"; + + if(prepend && (fputs(prepend, fpipe->out.f) == EOF + || fputc('\n', fpipe->out.f) == EOF)) + errstr = error_description(errno); + + while(!errstr){ + /* if the pipe can't hold a K we're sunk (too bad PIPE_MAX + * isn't ubiquitous ;). + */ + for(n = 0; !errstr && fpipe->out.f && n < 1024; n++) + if(!so_readc(&c, source_so)){ + fclose(fpipe->out.f); + fpipe->out.f = NULL; + } + else if(fputc(c, fpipe->out.f) == EOF) + errstr = error_description(errno); + + /* + * Note: We clear errno here and test below, before ferror, + * because *some* stdio implementations consider + * EAGAIN and EWOULDBLOCK equivalent to EOF... + */ + errno = 0; + clearerr(fpipe->in.f); /* fix from <cananian@cananian.mit.edu> */ + + while(!errstr && fgets(buf, MAILTMPLEN, fpipe->in.f)) + errstr = gf_filter_puts(buf); + + /* then fgets failed! */ + if(!errstr && !(errno == EAGAIN || errno == EWOULDBLOCK)){ + if(feof(fpipe->in.f)) /* nothing else interesting! */ + break; + else if(ferror(fpipe->in.f)) /* bummer. */ + errstr = error_description(errno); + } + else if(errno == EAGAIN || errno == EWOULDBLOCK) + clearerr(fpipe->in.f); + } +#else + if(prepend && (fputs(prepend, fpipe->out.f) == EOF + || fputc('\n', fpipe->out.f) == EOF)) + errstr = error_description(errno); + + /* + * Well, do the best we can, and hope the pipe we're writing + * doesn't fill up before we start reading... + */ + while(!errstr && so_readc(&c, source_so)) + if(fputc(c, fpipe->out.f) == EOF) + errstr = error_description(errno); + + fclose(fpipe->out.f); + fpipe->out.f = NULL; + while(!errstr && fgets(buf, MAILTMPLEN, fpipe->in.f)) + errstr = gf_filter_puts(buf); +#endif /* NON_BLOCKING */ +#endif /* NO_PIPE */ + + gf_filter_eod(); + + if(close_system_pipe(&fpipe) && !errstr) + errstr = "Pipe command returned error."; + +#ifdef NO_PIPE + /* + * retrieve filters result... + */ + { + FILE *fp; + + if(fp = fopen(rfile, STDIO_READ)){ + while(!errstr && fgets(buf, MAILTMPLEN, fp)) + errstr = gf_filter_puts(buf); + + fclose(fp); + } + + fs_give((void **)&rfile); + } +#endif + } + + return(errstr); +} + + +/* + * gf_filter_puts - write the given string down the filter's pipe + */ +char * +gf_filter_puts(s) + register char *s; +{ + GF_INIT(gf_master, gf_master); + + /* + * set up for any errors a filter may encounter + */ + if(setjmp(gf_error_state)){ + dprint(4, (debugfile, "ERROR: gf_filter_puts: %s\n", + gf_error_string ? gf_error_string : "NULL")); + return(gf_error_string); + } + + while(*s) + GF_PUTC(gf_master, (*s++) & 0xff); + + GF_END(gf_master, gf_master); + return(NULL); +} + + +/* + * gf_filter_eod - flush pending data filter's input queue and deliver + * the GF_EOD marker. + */ +void +gf_filter_eod() +{ + GF_INIT(gf_master, gf_master); + GF_FLUSH(gf_master); + (*gf_master->f)(gf_master, GF_EOD); +} + + + + +/* + * END OF PIPE SUPPORT ROUTINES, BEGINNING OF FILTERS + * + * Filters MUST use the specified interface (pointer to filter + * structure, the unsigned character buffer in that struct, and a + * cmd flag), and pass each resulting octet to the next filter in the + * chain. Only the terminal filter need not call another filter. + * As a result, filters share a pretty general structure. + * Typically three main conditionals separate initialization from + * data from end-of-data command processing. + * + * Lastly, being character-at-a-time, they're a little more complex + * to write than filters operating on buffers because some state + * must typically be kept between characters. However, for a + * little bit of complexity here, much convenience is gained later + * as they can be arbitrarily chained together at run time and + * consume few resources (especially memory or disk) as they work. + * (NOTE 951005: even less cpu now that data between filters is passed + * via a vector.) + * + * A few notes about implementing filters: + * + * - A generic filter template looks like: + * + * void + * gf_xxx_filter(f, flg) + * FILTER_S *f; + * int flg; + * { + * GF_INIT(f, f->next); // def's var's to speed queue drain + * + * if(flg == GF_DATA){ + * register unsigned char c; + * + * while(GF_GETC(f, c)){ // macro taking data off input queue + * // operate on c and pass it on here + * GF_PUTC(f->next, c); // macro writing output queue + * } + * + * GF_END(f, f->next); // macro to sync pointers/offsets + * //WARNING: DO NOT RETURN BEFORE ALL INCOMING DATA'S PROCESSED + * } + * else if(flg == GF_EOD){ + * // process any buffered data here and pass it on + * GF_FLUSH(f->next); // flush pending data to next filter + * (*f->next->f)(f->next, GF_EOD); + * } + * else if(flg == GF_RESET){ + * // initialize any data in the struct here + * } + * } + * + * - Any free storage allocated during initialization (typically tied + * to the "line" pointer in FILTER_S) is the filter's responsibility + * to clean up when the GF_EOD command comes through. + * + * - Filter's must pass GF_EOD they receive on to the next + * filter in the chain so it has the opportunity to flush + * any buffered data. + * + * - All filters expect NVT end-of-lines. The idea is to prepend + * or append either the gf_local_nvtnl or gf_nvtnl_local + * os-dependant filters to the data on the appropriate end of the + * pipe for the task at hand. + * + * - NOTE: As of 951004, filters no longer take their input as a single + * char argument, but rather get data to operate on via a vector + * representing the input queue in the FILTER_S structure. + * + */ + + + +/* + * BASE64 TO BINARY encoding and decoding routines below + */ + + +/* + * BINARY to BASE64 filter (encoding described in rfc1341) + */ +void +gf_binary_b64(f, flg) + FILTER_S *f; + int flg; +{ + static char *v = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + GF_INIT(f, f->next); + + if(flg == GF_DATA){ + register unsigned char c; + register unsigned char t = f->t; + register long n = f->n; + + while(GF_GETC(f, c)){ + + switch(n++){ + case 0 : case 3 : case 6 : case 9 : case 12: case 15: case 18: + case 21: case 24: case 27: case 30: case 33: case 36: case 39: + case 42: case 45: + GF_PUTC(f->next, v[c >> 2]); + /* byte 1: high 6 bits (1) */ + t = c << 4; /* remember high 2 bits for next */ + break; + + case 1 : case 4 : case 7 : case 10: case 13: case 16: case 19: + case 22: case 25: case 28: case 31: case 34: case 37: case 40: + case 43: + GF_PUTC(f->next, v[(t|(c>>4)) & 0x3f]); + t = c << 2; + break; + + case 2 : case 5 : case 8 : case 11: case 14: case 17: case 20: + case 23: case 26: case 29: case 32: case 35: case 38: case 41: + case 44: + GF_PUTC(f->next, v[(t|(c >> 6)) & 0x3f]); + GF_PUTC(f->next, v[c & 0x3f]); + break; + } + + if(n == 45){ /* start a new line? */ + GF_PUTC(f->next, '\015'); + GF_PUTC(f->next, '\012'); + n = 0L; + } + } + + f->n = n; + f->t = t; + GF_END(f, f->next); + } + else if(flg == GF_EOD){ /* no more data */ + switch (f->n % 3) { /* handle trailing bytes */ + case 0: /* no trailing bytes */ + break; + + case 1: + GF_PUTC(f->next, v[(f->t) & 0x3f]); + GF_PUTC(f->next, '='); /* byte 3 */ + GF_PUTC(f->next, '='); /* byte 4 */ + break; + + case 2: + GF_PUTC(f->next, v[(f->t) & 0x3f]); + GF_PUTC(f->next, '='); /* byte 4 */ + break; + } + + GF_FLUSH(f->next); + (*f->next->f)(f->next, GF_EOD); + } + else if(flg == GF_RESET){ + dprint(9, (debugfile, "-- gf_reset binary_b64\n")); + f->n = 0L; + } +} + + + +/* + * BASE64 to BINARY filter (encoding described in rfc1341) + */ +void +gf_b64_binary(f, flg) + FILTER_S *f; + int flg; +{ + static char v[] = {65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65, + 65,65,65,65,65,65,65,65,65,65,65,65,65,65,65,65, + 65,65,65,65,65,65,65,65,65,65,65,62,65,65,65,63, + 52,53,54,55,56,57,58,59,60,61,62,65,65,64,65,65, + 65, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, + 15,16,17,18,19,20,21,22,23,24,25,65,65,65,65,65, + 65,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, + 41,42,43,44,45,46,47,48,49,50,51,65,65,65,65,65}; + GF_INIT(f, f->next); + + if(flg == GF_DATA){ + register unsigned char c; + register unsigned char t = f->t; + register int n = (int) f->n; + register int state = f->f1; + + while(GF_GETC(f, c)){ + + if(state){ + state = 0; + if (c != '=') { + gf_error("Illegal '=' in base64 text"); + /* NO RETURN */ + } + } + + /* in range, and a valid value? */ + if((c & ~0x7f) || (c = v[c]) > 63){ + if(c == 64){ + switch (n++) { /* check quantum position */ + case 2: + state++; /* expect an equal as next char */ + break; + + case 3: + n = 0L; /* restart quantum */ + break; + + default: /* impossible quantum position */ + gf_error("Internal base64 decoder error"); + /* NO RETURN */ + } + } + } + else{ + switch (n++) { /* install based on quantum position */ + case 0: /* byte 1: high 6 bits */ + t = c << 2; + break; + + case 1: /* byte 1: low 2 bits */ + GF_PUTC(f->next, (t|(c >> 4))); + t = c << 4; /* byte 2: high 4 bits */ + break; + + case 2: /* byte 2: low 4 bits */ + GF_PUTC(f->next, (t|(c >> 2))); + t = c << 6; /* byte 3: high 2 bits */ + break; + + case 3: + GF_PUTC(f->next, t | c); + n = 0L; /* reinitialize mechanism */ + break; + } + } + } + + f->f1 = state; + f->t = t; + f->n = n; + GF_END(f, f->next); + } + else if(flg == GF_EOD){ + GF_FLUSH(f->next); + (*f->next->f)(f->next, GF_EOD); + } + else if(flg == GF_RESET){ + dprint(9, (debugfile, "-- gf_reset b64_binary\n")); + f->n = 0L; /* quantum position */ + f->f1 = 0; /* state holder: equal seen? */ + } +} + + + + +/* + * QUOTED-PRINTABLE ENCODING AND DECODING filters below. + * encoding described in rfc1341 + */ + +#define GF_MAXLINE 80 /* good buffer size */ + +/* + * default action for QUOTED-PRINTABLE to 8BIT decoder + */ +#define GF_QP_DEFAULT(f, c) { \ + if((c) == ' '){ \ + state = WSPACE; \ + /* reset white space! */ \ + (f)->linep = (f)->line; \ + *((f)->linep)++ = ' '; \ + } \ + else if((c) == '='){ \ + state = EQUAL; \ + } \ + else \ + GF_PUTC((f)->next, (c)); \ + } + + +/* + * QUOTED-PRINTABLE to 8BIT filter + */ +void +gf_qp_8bit(f, flg) + FILTER_S *f; + int flg; +{ + GF_INIT(f, f->next); + + if(flg == GF_DATA){ + register unsigned char c; + register int state = f->f1; + + while(GF_GETC(f, c)){ + + switch(state){ + case DFL : /* default case */ + default: + GF_QP_DEFAULT(f, c); + break; + + case CCR : /* non-significant space */ + state = DFL; + if(c == '\012') + continue; /* go on to next char */ + + GF_QP_DEFAULT(f, c); + break; + + case EQUAL : + if(c == '\015'){ /* "=\015" is a soft EOL */ + state = CCR; + break; + } + + if(c == '='){ /* compatibility clause for old guys */ + GF_PUTC(f->next, '='); + state = DFL; + break; + } + + if(!isxdigit((unsigned char)c)){ /* must be hex! */ + fs_give((void **)&(f->line)); + gf_error("Non-hexadecimal character in QP encoding"); + /* NO RETURN */ + } + + if (isdigit ((unsigned char)c)) + f->t = c - '0'; + else + f->t = c - (isupper((unsigned char)c) ? 'A' - 10 : 'a' - 10); + + state = HEX; + break; + + case HEX : + state = DFL; + if(!isxdigit((unsigned char)c)){ /* must be hex! */ + fs_give((void **)&(f->line)); + gf_error("Non-hexadecimal character in QP encoding"); + /* NO RETURN */ + } + + if (isdigit((unsigned char)c)) + c -= '0'; + else + c -= (isupper((unsigned char)c) ? 'A' - 10 : 'a' - 10); + + GF_PUTC(f->next, c + (f->t << 4)); + break; + + case WSPACE : + if(c == ' '){ /* toss it in with other spaces */ + if(f->linep - f->line < GF_MAXLINE) + *(f->linep)++ = ' '; + break; + } + + state = DFL; + if(c == '\015'){ /* not our white space! */ + f->linep = f->line; /* reset buffer */ + GF_PUTC(f->next, '\015'); + break; + } + + /* the spaces are ours, write 'em */ + f->n = f->linep - f->line; + while((f->n)--) + GF_PUTC(f->next, ' '); + + GF_QP_DEFAULT(f, c); /* take care of 'c' in default way */ + break; + } + } + + f->f1 = state; + GF_END(f, f->next); + } + else if(flg == GF_EOD){ + fs_give((void **)&(f->line)); + GF_FLUSH(f->next); + (*f->next->f)(f->next, GF_EOD); + } + else if(flg == GF_RESET){ + dprint(9, (debugfile, "-- gf_reset qp_8bit\n")); + f->f1 = DFL; + f->linep = f->line = (char *)fs_get(GF_MAXLINE * sizeof(char)); + } +} + + + +/* + * USEFUL MACROS TO HELP WITH QP ENCODING + */ + +#define QP_MAXL 75 /* 76th place only for continuation */ + +/* + * Macro to test and wrap long quoted printable lines + */ +#define GF_8BIT_WRAP(f) { \ + GF_PUTC((f)->next, '='); \ + GF_PUTC((f)->next, '\015'); \ + GF_PUTC((f)->next, '\012'); \ + } + +/* + * write a quoted octet in QUOTED-PRINTABLE encoding, adding soft + * line break if needed. + */ +#define GF_8BIT_PUT_QUOTE(f, c) { \ + if(((f)->n += 3) > QP_MAXL){ \ + GF_8BIT_WRAP(f); \ + (f)->n = 3; /* set line count */ \ + } \ + GF_PUTC((f)->next, '='); \ + GF_PUTC((f)->next, HEX_CHAR1(c)); \ + GF_PUTC((f)->next, HEX_CHAR2(c)); \ + } + +/* + * just write an ordinary octet in QUOTED-PRINTABLE, wrapping line + * if needed. + */ +#define GF_8BIT_PUT(f, c) { \ + if((++(f->n)) > QP_MAXL){ \ + GF_8BIT_WRAP(f); \ + f->n = 1L; \ + } \ + if(f->n == 1L && c == '.'){ \ + GF_8BIT_PUT_QUOTE(f, c); \ + f->n = 3; \ + } \ + else \ + GF_PUTC(f->next, c); \ + } + + +/* + * default action for 8bit to quoted printable encoder + */ +#define GF_8BIT_DEFAULT(f, c) if((c) == ' '){ \ + state = WSPACE; \ + } \ + else if(c == '\015'){ \ + state = CCR; \ + } \ + else if(iscntrl(c & 0x7f) || (c == 0x7f) \ + || (c & 0x80) || (c == '=')){ \ + GF_8BIT_PUT_QUOTE(f, c); \ + } \ + else{ \ + GF_8BIT_PUT(f, c); \ + } + + +/* + * 8BIT to QUOTED-PRINTABLE filter + */ +void +gf_8bit_qp(f, flg) + FILTER_S *f; + int flg; +{ + short dummy_dots = 0, dummy_dmap = 1; + GF_INIT(f, f->next); + + if(flg == GF_DATA){ + register unsigned char c; + register int state = f->f1; + + while(GF_GETC(f, c)){ + + /* keep track of "^JFrom " */ + Find_Froms(f->t, dummy_dots, f->f2, dummy_dmap, c); + + switch(state){ + case DFL : /* handle ordinary case */ + GF_8BIT_DEFAULT(f, c); + break; + + case CCR : /* true line break? */ + state = DFL; + if(c == '\012'){ + GF_PUTC(f->next, '\015'); + GF_PUTC(f->next, '\012'); + f->n = 0L; + } + else{ /* nope, quote the CR */ + GF_8BIT_PUT_QUOTE(f, '\015'); + GF_8BIT_DEFAULT(f, c); /* and don't forget about c! */ + } + break; + + case WSPACE: + state = DFL; + if(c == '\015' || f->t){ /* handle the space */ + GF_8BIT_PUT_QUOTE(f, ' '); + f->t = 0; /* reset From flag */ + } + else + GF_8BIT_PUT(f, ' '); + + GF_8BIT_DEFAULT(f, c); /* handle 'c' in the default way */ + break; + } + } + + f->f1 = state; + GF_END(f, f->next); + } + else if(flg == GF_EOD){ + switch(f->f1){ + case CCR : + GF_8BIT_PUT_QUOTE(f, '\015'); /* write the last cr */ + break; + + case WSPACE : + GF_8BIT_PUT_QUOTE(f, ' '); /* write the last space */ + break; + } + + GF_FLUSH(f->next); + (*f->next->f)(f->next, GF_EOD); + } + else if(flg == GF_RESET){ + dprint(9, (debugfile, "-- gf_reset 8bit_qp\n")); + f->f1 = DFL; /* state from last character */ + f->f2 = 1; /* state of "^NFrom " bitmap */ + f->t = 0; + f->n = 0L; /* number of chars in current line */ + } +} + + + +/* + * RICHTEXT-TO-PLAINTEXT filter + */ + +/* + * option to be used by rich2plain (NOTE: if this filter is ever + * used more than once in a pipe, all instances will have the same + * option value) + */ + + +/*---------------------------------------------------------------------- + richtext to plaintext filter + + Args: f -- + flg -- + + This basically removes all richtext formatting. A cute hack is used + to get bold and underlining to work. + Further work could be done to handle things like centering and right + and left flush, but then it could no longer be done in place. This + operates on text *with* CRLF's. + + WARNING: does not wrap lines! + ----*/ +void +gf_rich2plain(f, flg) + FILTER_S *f; + int flg; +{ +/* BUG: qoute incoming \255 values */ + GF_INIT(f, f->next); + + if(flg == GF_DATA){ + register unsigned char c; + register int state = f->f1; + + while(GF_GETC(f, c)){ + + switch(state){ + case TOKEN : /* collect a richtext token */ + if(c == '>'){ /* what should we do with it? */ + state = DFL; /* return to default next time */ + *(f->linep) = '\0'; /* cap off token */ + if(f->line[0] == 'l' && f->line[1] == 't'){ + GF_PUTC(f->next, '<'); /* literal '<' */ + } + else if(f->line[0] == 'n' && f->line[1] == 'l'){ + GF_PUTC(f->next, '\015');/* newline! */ + GF_PUTC(f->next, '\012'); + } + else if(!strcmp("comment", f->line)){ + (f->f2)++; + } + else if(!strcmp("/comment", f->line)){ + f->f2 = 0; + } + else if(!strcmp("/paragraph", f->line)) { + GF_PUTC(f->next, '\r'); + GF_PUTC(f->next, '\n'); + GF_PUTC(f->next, '\r'); + GF_PUTC(f->next, '\n'); + } + else if(!f->opt /* gf_rich_plain */){ + if(!strcmp(f->line, "bold")) { + GF_PUTC(f->next, TAG_EMBED); + GF_PUTC(f->next, TAG_BOLDON); + } else if(!strcmp(f->line, "/bold")) { + GF_PUTC(f->next, TAG_EMBED); + GF_PUTC(f->next, TAG_BOLDOFF); + } else if(!strcmp(f->line, "italic")) { + GF_PUTC(f->next, TAG_EMBED); + GF_PUTC(f->next, TAG_ULINEON); + } else if(!strcmp(f->line, "/italic")) { + GF_PUTC(f->next, TAG_EMBED); + GF_PUTC(f->next, TAG_ULINEOFF); + } else if(!strcmp(f->line, "underline")) { + GF_PUTC(f->next, TAG_EMBED); + GF_PUTC(f->next, TAG_ULINEON); + } else if(!strcmp(f->line, "/underline")) { + GF_PUTC(f->next, TAG_EMBED); + GF_PUTC(f->next, TAG_ULINEOFF); + } + } + /* else we just ignore the token! */ + + f->linep = f->line; /* reset token buffer */ + } + else{ /* add char to token */ + if(f->linep - f->line > 40){ + /* What? rfc1341 says 40 char tokens MAX! */ + fs_give((void **)&(f->line)); + gf_error("Richtext token over 40 characters"); + /* NO RETURN */ + } + + *(f->linep)++ = isupper((unsigned char)c) ? c-'A'+'a' : c; + } + break; + + case CCR : + state = DFL; /* back to default next time */ + if(c == '\012'){ /* treat as single space? */ + GF_PUTC(f->next, ' '); + break; + } + /* fall thru to process c */ + + case DFL : + default: + if(c == '<') + state = TOKEN; + else if(c == '\015') + state = CCR; + else if(!f->f2) /* not in comment! */ + GF_PUTC(f->next, c); + + break; + } + } + + f->f1 = state; + GF_END(f, f->next); + } + else if(flg == GF_EOD){ + if(f->f1 = (f->linep != f->line)){ + /* incomplete token!! */ + gf_error("Incomplete token in richtext"); + /* NO RETURN */ + } + + fs_give((void **)&(f->line)); + GF_FLUSH(f->next); + (*f->next->f)(f->next, GF_EOD); + } + else if(flg == GF_RESET){ + dprint(9, (debugfile, "-- gf_reset rich2plain\n")); + f->f1 = DFL; /* state */ + f->f2 = 0; /* set means we're in a comment */ + f->linep = f->line = (char *)fs_get(45 * sizeof(char)); + } +} + + +/* + * function called from the outside to set + * richtext filter's options + */ +void * +gf_rich2plain_opt(plain) + int plain; +{ + return((void *) plain); +} + + + +/* + * ENRICHED-TO-PLAIN text filter + */ + +#define TEF_QUELL 0x01 +#define TEF_NOFILL 0x02 + + + +/*---------------------------------------------------------------------- + enriched text to plain text filter (ala rfc1523) + + Args: f -- state and input data + flg -- + + This basically removes all enriched formatting. A cute hack is used + to get bold and underlining to work. + + Further work could be done to handle things like centering and right + and left flush, but then it could no longer be done in place. This + operates on text *with* CRLF's. + + WARNING: does not wrap lines! + ----*/ +void +gf_enriched2plain(f, flg) + FILTER_S *f; + int flg; +{ +/* BUG: qoute incoming \255 values */ + GF_INIT(f, f->next); + + if(flg == GF_DATA){ + register unsigned char c; + register int state = f->f1; + + while(GF_GETC(f, c)){ + + switch(state){ + case TOKEN : /* collect a richtext token */ + if(c == '>'){ /* what should we do with it? */ + int off = *f->line == '/'; + char *token = f->line + (off ? 1 : 0); + state = DFL; + *f->linep = '\0'; + if(!strcmp("param", token)){ + if(off) + f->f2 &= ~TEF_QUELL; + else + f->f2 |= TEF_QUELL; + } + else if(!strcmp("nofill", token)){ + if(off) + f->f2 &= ~TEF_NOFILL; + else + f->f2 |= TEF_NOFILL; + } + else if(!f->opt /* gf_enriched_plain */){ + /* Following is a cute hack or two to get + bold and underline on the screen. + See Putline0n() where these codes are + interpreted */ + if(!strcmp("bold", token)) { + GF_PUTC(f->next, TAG_EMBED); + GF_PUTC(f->next, off ? TAG_BOLDOFF : TAG_BOLDON); + } else if(!strcmp("italic", token)) { + GF_PUTC(f->next, TAG_EMBED); + GF_PUTC(f->next, off ? TAG_ULINEOFF : TAG_ULINEON); + } else if(!strcmp("underline", token)) { + GF_PUTC(f->next, TAG_EMBED); + GF_PUTC(f->next, off ? TAG_ULINEOFF : TAG_ULINEON); + } + } + /* else we just ignore the token! */ + + f->linep = f->line; /* reset token buffer */ + } + else if(c == '<'){ /* literal '<'? */ + if(f->linep == f->line){ + GF_PUTC(f->next, '<'); + state = DFL; + } + else{ + fs_give((void **)&(f->line)); + gf_error("Malformed Enriched text: unexpected '<'"); + /* NO RETURN */ + } + } + else{ /* add char to token */ + if(f->linep - f->line > 60){ /* rfc1523 says 60 MAX! */ + fs_give((void **)&(f->line)); + gf_error("Malformed Enriched text: token too long"); + /* NO RETURN */ + } + + *(f->linep)++ = isupper((unsigned char)c) ? c-'A'+'a' : c; + } + break; + + case CCR : + if(c != '\012'){ /* treat as single space? */ + state = DFL; /* lone cr? */ + f->f2 &= ~TEF_QUELL; + GF_PUTC(f->next, '\015'); + goto df; + } + + state = CLF; + break; + + case CLF : + if(c == '\015'){ /* treat as single space? */ + state = CCR; /* repeat crlf's mean real newlines */ + f->f2 |= TEF_QUELL; + GF_PUTC(f->next, '\r'); + GF_PUTC(f->next, '\n'); + break; + } + else{ + state = DFL; + if(!((f->f2) & TEF_QUELL)) + GF_PUTC(f->next, ' '); + + f->f2 &= ~TEF_QUELL; + } + + /* fall thru to take care of 'c' */ + + case DFL : + default : + df : + if(c == '<') + state = TOKEN; + else if(c == '\015' && (!((f->f2) & TEF_NOFILL))) + state = CCR; + else if(!((f->f2) & TEF_QUELL)) + GF_PUTC(f->next, c); + + break; + } + } + + f->f1 = state; + GF_END(f, f->next); + } + else if(flg == GF_EOD){ + if(f->f1 = (f->linep != f->line)){ + /* incomplete token!! */ + gf_error("Incomplete token in richtext"); + /* NO RETURN */ + } + + /* Make sure we end with a newline so everything gets flushed */ + GF_PUTC(f->next, '\015'); + GF_PUTC(f->next, '\012'); + + fs_give((void **)&(f->line)); + + GF_FLUSH(f->next); + (*f->next->f)(f->next, GF_EOD); + } + else if(flg == GF_RESET){ + dprint(9, (debugfile, "-- gf_reset enriched2plain\n")); + f->f1 = DFL; /* state */ + f->f2 = 0; /* set means we're in a comment */ + f->linep = f->line = (char *)fs_get(65 * sizeof(char)); + } +} + + +/* + * function called from the outside to set + * richtext filter's options + */ +void * +gf_enriched2plain_opt(plain) + int plain; +{ + return((void *) plain); +} + + + +/* + * HTML-TO-PLAIN text filter + */ + + +/* OK, here's the plan: + + * a universal output function handles writing chars and worries + * about wrapping. + + * a unversal element collector reads chars and collects params + * and dispatches the appropriate element handler. + + * element handlers are stacked. The most recently dispatched gets + * first crack at the incoming character stream. It passes bytes it's + * done with or not interested in to the next + + * installs that handler as the current one collecting data... + + * stacked handlers take their params from the element collector and + * accept chars or do whatever they need to do. Sort of a vertical + * piping? recursion-like? hmmm. + + * at least I think this is how it'll work. tres simple, non? + + */ + + +/* + * Some important constants + */ +#define HTML_BUF_LEN 1024 /* max scratch buffer length */ +#define MAX_ENTITY 20 /* maximum length of an entity */ +#define MAX_ELEMENT 72 /* maximum length of an element */ +#define HTML_BADVALUE 0x0100 /* good data, but bad entity value */ +#define HTML_BADDATA 0x0200 /* bad data found looking for entity */ +#define HTML_LITERAL 0x0400 /* Literal character value */ +#define HTML_NEWLINE 0x010A /* hard newline */ +#define HTML_DOBOLD 0x0400 /* Start Bold display */ +#define HTML_ID_GET 0 /* indent func: return current val */ +#define HTML_ID_SET 1 /* indent func: set to absolute val */ +#define HTML_ID_INC 2 /* indent func: increment by val */ +#define HTML_HX_CENTER 0x0001 +#define HTML_HX_ULINE 0x0002 + + +/* + * Types used to manage HTML parsing + */ +typedef int (*html_f) PROTO(()); + +/* + * Handler data, state information including function that uses it + */ +typedef struct handler_s { + FILTER_S *html_data; + struct handler_s *below; + html_f f; + long x, y, z; + unsigned char *s; +} HANDLER_S; + + +/* + * to help manage line wrapping. + */ +typedef struct _wrap_line { + char *buf; /* buf to collect wrapped text */ + int used, /* number of chars in buf */ + width, /* text's width as displayed */ + len; /* length of allocated buf */ +} WRAPLINE_S; + + +/* + * to help manage centered text + */ +typedef struct _center_s { + WRAPLINE_S line; /* buf to assembled centered text */ + WRAPLINE_S word; /* word being to append to Line */ + int anchor; + short embedded; + short space; +} CENTER_S; + + +/* + * Collector data and state information + */ +typedef struct collector_s { + char buf[HTML_BUF_LEN]; /* buffer to collect data */ + int len; /* length of that buffer */ + unsigned end_tag:1; /* collecting a closing tag */ + unsigned hit_equal:1; /* collecting right half of attrib */ + unsigned mkup_decl:1; /* markup declaration */ + unsigned start_comment:1; /* markup declaration comment */ + unsigned end_comment:1; /* legit comment format */ + unsigned hyphen:1; /* markup hyphen read */ + unsigned badform:1; /* malformed markup element */ + unsigned overrun:1; /* Overran buf above */ + char quoted; /* quoted element param value */ + char *element; /* element's collected name */ + PARAMETER *attribs; /* element's collected attributes */ + PARAMETER *cur_attrib; /* attribute now being collected */ +} CLCTR_S; + + +/* + * State information for all element handlers + */ +typedef struct html_data { + HANDLER_S *h_stack; /* handler list */ + CLCTR_S *el_data; /* element collector data */ + CENTER_S *centered; /* struct to manage centered text */ + int (*token) PROTO((FILTER_S *, int)); + char quoted; /* quoted, by either ' or ", text */ + short indent_level; /* levels of indention */ + int in_anchor; /* text now being written to anchor */ + int blanks; /* Consecutive blank line count */ + int wrapcol; /* column to wrap lines on */ + int *prefix; /* buffer containing Anchor prefix */ + int prefix_used; + COLOR_PAIR *color; + unsigned wrapstate:1; /* whether or not to wrap output */ + unsigned li_pending:1; /* <LI> next token expected */ + unsigned de_pending:1; /* <DT> or <DD> next token expected */ + unsigned bold_on:1; /* currently bolding text */ + unsigned uline_on:1; /* currently underlining text */ + unsigned center:1; /* center output text */ + unsigned bitbucket:1; /* Ignore input */ + unsigned head:1; /* In doc's HEAD */ + unsigned alt_entity:1; /* use alternative entity values */ +} HTML_DATA_S; + + +/* + * HTML filter options + */ +typedef struct _html_opts { + char *base; /* Base URL for this html file */ + int columns; /* Display columns */ + unsigned strip:1; /* Hilite TAGs allowed */ + unsigned handles:1; /* Anchors as handles requested? */ + unsigned handles_loc:1; /* Local handles requested? */ +} HTML_OPT_S; + + +/* + * Some macros to make life a little easier + */ +#define WRAP_COLS(X) ((X)->opt ? ((HTML_OPT_S *)(X)->opt)->columns : 80) +#define HTML_BASE(X) ((X)->opt ? ((HTML_OPT_S *)(X)->opt)->base : NULL) +#define STRIP(X) ((X)->opt && ((HTML_OPT_S *)(X)->opt)->strip) +#define HANDLES(X) ((X)->opt && ((HTML_OPT_S *)(X)->opt)->handles) +#define HANDLES_LOC(X) ((X)->opt && ((HTML_OPT_S *)(X)->opt)->handles_loc) +#define MAKE_LITERAL(C) (HTML_LITERAL | ((C) & 0xff)) +#define IS_LITERAL(C) (HTML_LITERAL & (C)) +#define HD(X) ((HTML_DATA_S *)(X)->data) +#define ED(X) (HD(X)->el_data) +#define HTML_ISSPACE(C) (IS_LITERAL(C) == 0 && isspace((unsigned char) (C))) +#define NEW_CLCTR(X) { \ + ED(X) = (CLCTR_S *)fs_get(sizeof(CLCTR_S)); \ + memset(ED(X), 0, sizeof(CLCTR_S)); \ + HD(X)->token = html_element_collector; \ + } + +#define FREE_CLCTR(X) { \ + if(ED(X)->attribs){ \ + PARAMETER *p; \ + while(p = ED(X)->attribs){ \ + ED(X)->attribs = ED(X)->attribs->next; \ + if(p->attribute) \ + fs_give((void **)&p->attribute); \ + if(p->value) \ + fs_give((void **)&p->value); \ + fs_give((void **)&p); \ + } \ + } \ + if(ED(X)->element) \ + fs_give((void **) &ED(X)->element); \ + fs_give((void **) &ED(X)); \ + HD(X)->token = NULL; \ + } +#define HANDLERS(X) (HD(X)->h_stack) +#define BOLD_BIT(X) (HD(X)->bold_on) +#define ULINE_BIT(X) (HD(X)->uline_on) +#define CENTER_BIT(X) (HD(X)->center) +#define HTML_FLUSH(X) { \ + html_write(X, (X)->line, (X)->linep - (X)->line); \ + (X)->linep = (X)->line; \ + (X)->f2 = 0L; \ + } +#define HTML_BOLD(X, S) if(! STRIP(X)){ \ + if(S){ \ + html_output((X), TAG_EMBED); \ + html_output((X), TAG_BOLDON); \ + } \ + else if(!(S)){ \ + html_output((X), TAG_EMBED); \ + html_output((X), TAG_BOLDOFF); \ + } \ + } +#define HTML_ULINE(X, S) \ + if(! STRIP(X)){ \ + if(S){ \ + html_output((X), TAG_EMBED); \ + html_output((X), TAG_ULINEON); \ + } \ + else if(!(S)){ \ + html_output((X), TAG_EMBED); \ + html_output((X), TAG_ULINEOFF); \ + } \ + } +#define WRAPPED_LEN(X) ((HD(f)->centered) \ + ? (HD(f)->centered->line.width \ + + HD(f)->centered->word.width \ + + ((HD(f)->centered->line.width \ + && HD(f)->centered->word.width) \ + ? 1 : 0)) \ + : 0) +#define HTML_DUMP_LIT(F, S, L) { \ + int i, c; \ + for(i = 0; i < (L); i++){ \ + c = isspace((S)[i]) \ + ? (S)[i] \ + : MAKE_LITERAL((S)[i]); \ + HTML_TEXT(F, c); \ + } \ + } +#define HTML_PROC(F, C) { \ + if(HD(F)->token){ \ + int i; \ + if(i = (*(HD(F)->token))(F, C)){ \ + if(i < 0){ \ + HTML_DUMP_LIT(F, "<", 1); \ + if(HD(F)->el_data->element){ \ + HTML_DUMP_LIT(F, \ + HD(F)->el_data->element, \ + strlen(HD(F)->el_data->element));\ + } \ + if(HD(F)->el_data->len){ \ + HTML_DUMP_LIT(F, \ + HD(F)->el_data->buf, \ + HD(F)->el_data->len); \ + } \ + HTML_TEXT(F, C); \ + } \ + FREE_CLCTR(F); \ + } \ + } \ + else if((C) == '<'){ \ + NEW_CLCTR(F); \ + } \ + else \ + HTML_TEXT(F, C); \ + } +#define HTML_TEXT(F, C) switch((F)->f1){ \ + case WSPACE : \ + if(HTML_ISSPACE(C)) /* ignore repeated WS */ \ + break; \ + HTML_TEXT_OUT(F, ' '); \ + (F)->f1 = DFL;/* stop sending chars here */ \ + /* fall thru to process 'c' */ \ + case DFL: \ + if(HD(F)->bitbucket) \ + (F)->f1 = DFL; /* no op */ \ + else if(HTML_ISSPACE(C) && HD(F)->wrapstate) \ + (F)->f1 = WSPACE;/* coalesce white space */ \ + else HTML_TEXT_OUT(F, C); \ + break; \ + } +#define HTML_TEXT_OUT(F, C) if(HANDLERS(F)) /* let handlers see C */ \ + (*HANDLERS(F)->f)(HANDLERS(F),(C),GF_DATA); \ + else \ + html_output(F, C); +#ifdef DEBUG +#define HTML_DEBUG_EL(S, D) { \ + dprint(2, (debugfile, "-- html %s: %s\n", \ + S, (D)->element \ + ? (D)->element : "NULL")); \ + if(debug > 5){ \ + PARAMETER *p; \ + for(p = (D)->attribs; \ + p && p->attribute; \ + p = p->next) \ + dprint(6, (debugfile, \ + " PARM: %s%s%s\n", \ + p->attribute \ + ? p->attribute : "NULL",\ + p->value ? "=" : "", \ + p->value ? p->value : ""));\ + } \ + } +#else +#define HTML_DEBUG_EL(S, D) +#endif + + +/* + * Protos for Tag handlers + */ +int html_head PROTO((HANDLER_S *, int, int)); +int html_base PROTO((HANDLER_S *, int, int)); +int html_title PROTO((HANDLER_S *, int, int)); +int html_a PROTO((HANDLER_S *, int, int)); +int html_br PROTO((HANDLER_S *, int, int)); +int html_hr PROTO((HANDLER_S *, int, int)); +int html_p PROTO((HANDLER_S *, int, int)); +int html_tr PROTO((HANDLER_S *, int, int)); +int html_td PROTO((HANDLER_S *, int, int)); +int html_b PROTO((HANDLER_S *, int, int)); +int html_i PROTO((HANDLER_S *, int, int)); +int html_img PROTO((HANDLER_S *, int, int)); +int html_form PROTO((HANDLER_S *, int, int)); +int html_ul PROTO((HANDLER_S *, int, int)); +int html_ol PROTO((HANDLER_S *, int, int)); +int html_menu PROTO((HANDLER_S *, int, int)); +int html_dir PROTO((HANDLER_S *, int, int)); +int html_li PROTO((HANDLER_S *, int, int)); +int html_h1 PROTO((HANDLER_S *, int, int)); +int html_h2 PROTO((HANDLER_S *, int, int)); +int html_h3 PROTO((HANDLER_S *, int, int)); +int html_h4 PROTO((HANDLER_S *, int, int)); +int html_h5 PROTO((HANDLER_S *, int, int)); +int html_h6 PROTO((HANDLER_S *, int, int)); +int html_blockquote PROTO((HANDLER_S *, int, int)); +int html_address PROTO((HANDLER_S *, int, int)); +int html_pre PROTO((HANDLER_S *, int, int)); +int html_center PROTO((HANDLER_S *, int, int)); +int html_div PROTO((HANDLER_S *, int, int)); +int html_dl PROTO((HANDLER_S *, int, int)); +int html_dt PROTO((HANDLER_S *, int, int)); +int html_dd PROTO((HANDLER_S *, int, int)); + +/* + * Proto's for support routines + */ +void html_pop PROTO((FILTER_S *, html_f)); +void html_push PROTO((FILTER_S *, html_f)); +int html_element_collector PROTO((FILTER_S *, int)); +int html_element_flush PROTO((CLCTR_S *)); +void html_element_comment PROTO((FILTER_S *, char *)); +void html_element_output PROTO((FILTER_S *, int)); +int html_entity_collector PROTO((FILTER_S *, int, char **)); +void html_a_prefix PROTO((FILTER_S *)); +void html_a_finish PROTO((HANDLER_S *)); +void html_a_output_prefix PROTO((FILTER_S *, int)); +void html_a_relative PROTO((char *, char *, HANDLE_S *)); +int html_indent PROTO((FILTER_S *, int, int)); +void html_blank PROTO((FILTER_S *, int)); +void html_newline PROTO((FILTER_S *)); +void html_output PROTO((FILTER_S *, int)); +void html_output_flush PROTO((FILTER_S *)); +void html_output_centered PROTO((FILTER_S *, int)); +void html_centered_handle PROTO((int *, char *, int)); +void html_centered_putc PROTO((WRAPLINE_S *, int)); +void html_centered_flush PROTO((FILTER_S *)); +void html_centered_flush_line PROTO((FILTER_S *)); +void html_write_anchor PROTO((FILTER_S *, int)); +void html_write_newline PROTO((FILTER_S *)); +void html_write_indent PROTO((FILTER_S *, int)); +void html_write PROTO((FILTER_S *, char *, int)); +void html_putc PROTO((FILTER_S *, int)); + + +/* + * Named entity table -- most from HTML 2.0 (rfc1866) plus some from + * W3C doc "Additional named entities for HTML" + */ +static struct html_entities { + char *name; /* entity name */ + unsigned char value; /* entity value */ + char *plain; /* plain text representation */ +} entity_tab[] = { + {"quot", 042}, /* Double quote sign */ + {"amp", 046}, /* Ampersand */ + {"bull", 052}, /* Bullet */ + {"ndash", 055}, /* Dash */ + {"mdash", 055}, /* Dash */ + {"lt", 074}, /* Less than sign */ + {"gt", 076}, /* Greater than sign */ + {"nbsp", 0240, " "}, /* no-break space */ + {"iexcl", 0241}, /* inverted exclamation mark */ + {"cent", 0242}, /* cent sign */ + {"pound", 0243}, /* pound sterling sign */ + {"curren", 0244, "CUR"}, /* general currency sign */ + {"yen", 0245}, /* yen sign */ + {"brvbar", 0246, "|"}, /* broken (vertical) bar */ + {"sect", 0247}, /* section sign */ + {"uml", 0250, "\""}, /* umlaut (dieresis) */ + {"copy", 0251, "(C)"}, /* copyright sign */ + {"ordf", 0252, "a"}, /* ordinal indicator, feminine */ + {"laquo", 0253, "<<"}, /* angle quotation mark, left */ + {"not", 0254, "NOT"}, /* not sign */ + {"shy", 0255, "-"}, /* soft hyphen */ + {"reg", 0256, "(R)"}, /* registered sign */ + {"macr", 0257}, /* macron */ + {"deg", 0260, "DEG"}, /* degree sign */ + {"plusmn", 0261, "+/-"}, /* plus-or-minus sign */ + {"sup2", 0262}, /* superscript two */ + {"sup3", 0263}, /* superscript three */ + {"acute", 0264, "'"}, /* acute accent */ + {"micro", 0265}, /* micro sign */ + {"para", 0266}, /* pilcrow (paragraph sign) */ + {"middot", 0267}, /* middle dot */ + {"cedil", 0270}, /* cedilla */ + {"sup1", 0271}, /* superscript one */ + {"ordm", 0272, "o"}, /* ordinal indicator, masculine */ + {"raquo", 0273, ">>"}, /* angle quotation mark, right */ + {"frac14", 0274, " 1/4"}, /* fraction one-quarter */ + {"frac12", 0275, " 1/2"}, /* fraction one-half */ + {"frac34", 0276, " 3/4"}, /* fraction three-quarters */ + {"iquest", 0277}, /* inverted question mark */ + {"Agrave", 0300, "A"}, /* capital A, grave accent */ + {"Aacute", 0301, "A"}, /* capital A, acute accent */ + {"Acirc", 0302, "A"}, /* capital A, circumflex accent */ + {"Atilde", 0303, "A"}, /* capital A, tilde */ + {"Auml", 0304, "AE"}, /* capital A, dieresis or umlaut mark */ + {"Aring", 0305, "A"}, /* capital A, ring */ + {"AElig", 0306, "AE"}, /* capital AE diphthong (ligature) */ + {"Ccedil", 0307, "C"}, /* capital C, cedilla */ + {"Egrave", 0310, "E"}, /* capital E, grave accent */ + {"Eacute", 0311, "E"}, /* capital E, acute accent */ + {"Ecirc", 0312, "E"}, /* capital E, circumflex accent */ + {"Euml", 0313, "E"}, /* capital E, dieresis or umlaut mark */ + {"Igrave", 0314, "I"}, /* capital I, grave accent */ + {"Iacute", 0315, "I"}, /* capital I, acute accent */ + {"Icirc", 0316, "I"}, /* capital I, circumflex accent */ + {"Iuml", 0317, "I"}, /* capital I, dieresis or umlaut mark */ + {"ETH", 0320, "DH"}, /* capital Eth, Icelandic */ + {"Ntilde", 0321, "N"}, /* capital N, tilde */ + {"Ograve", 0322, "O"}, /* capital O, grave accent */ + {"Oacute", 0323, "O"}, /* capital O, acute accent */ + {"Ocirc", 0324, "O"}, /* capital O, circumflex accent */ + {"Otilde", 0325, "O"}, /* capital O, tilde */ + {"Ouml", 0326, "OE"}, /* capital O, dieresis or umlaut mark */ + {"times", 0327, "x"}, /* multiply sign */ + {"Oslash", 0330, "O"}, /* capital O, slash */ + {"Ugrave", 0331, "U"}, /* capital U, grave accent */ + {"Uacute", 0332, "U"}, /* capital U, acute accent */ + {"Ucirc", 0333, "U"}, /* capital U, circumflex accent */ + {"Uuml", 0334, "UE"}, /* capital U, dieresis or umlaut mark */ + {"Yacute", 0335, "Y"}, /* capital Y, acute accent */ + {"THORN", 0336, "P"}, /* capital THORN, Icelandic */ + {"szlig", 0337, "ss"}, /* small sharp s, German (sz ligature) */ + {"agrave", 0340, "a"}, /* small a, grave accent */ + {"aacute", 0341, "a"}, /* small a, acute accent */ + {"acirc", 0342, "a"}, /* small a, circumflex accent */ + {"atilde", 0343, "a"}, /* small a, tilde */ + {"auml", 0344, "ae"}, /* small a, dieresis or umlaut mark */ + {"aring", 0345, "a"}, /* small a, ring */ + {"aelig", 0346, "ae"}, /* small ae diphthong (ligature) */ + {"ccedil", 0347, "c"}, /* small c, cedilla */ + {"egrave", 0350, "e"}, /* small e, grave accent */ + {"eacute", 0351, "e"}, /* small e, acute accent */ + {"ecirc", 0352, "e"}, /* small e, circumflex accent */ + {"euml", 0353, "e"}, /* small e, dieresis or umlaut mark */ + {"igrave", 0354, "i"}, /* small i, grave accent */ + {"iacute", 0355, "i"}, /* small i, acute accent */ + {"icirc", 0356, "i"}, /* small i, circumflex accent */ + {"iuml", 0357, "i"}, /* small i, dieresis or umlaut mark */ + {"eth", 0360, "dh"}, /* small eth, Icelandic */ + {"ntilde", 0361, "n"}, /* small n, tilde */ + {"ograve", 0362, "o"}, /* small o, grave accent */ + {"oacute", 0363, "o"}, /* small o, acute accent */ + {"ocirc", 0364, "o"}, /* small o, circumflex accent */ + {"otilde", 0365, "o"}, /* small o, tilde */ + {"ouml", 0366, "oe"}, /* small o, dieresis or umlaut mark */ + {"divide", 0367, "/"}, /* divide sign */ + {"oslash", 0370, "o"}, /* small o, slash */ + {"ugrave", 0371, "u"}, /* small u, grave accent */ + {"uacute", 0372, "u"}, /* small u, acute accent */ + {"ucirc", 0373, "u"}, /* small u, circumflex accent */ + {"uuml", 0374, "ue"}, /* small u, dieresis or umlaut mark */ + {"yacute", 0375, "y"}, /* small y, acute accent */ + {"thorn", 0376, "p"}, /* small thorn, Icelandic */ + {"yuml", 0377, "y"}, /* small y, dieresis or umlaut mark */ + {NULL, 0} +}; + + +/* + * Table of supported elements and corresponding handlers + */ +static struct element_table { + char *element; + int (*handler) PROTO(()); +} element_table[] = { + {"HTML", NULL}, /* HTML ignore if seen? */ + {"HEAD", html_head}, /* slurp until <BODY> ? */ + {"TITLE", html_title}, /* Document Title */ + {"BASE", html_base}, /* HREF base */ + {"BODY", NULL}, /* (NO OP) */ + {"A", html_a}, /* Anchor */ + {"IMG", html_img}, /* Image */ + {"HR", html_hr}, /* Horizontal Rule */ + {"BR", html_br}, /* Line Break */ + {"P", html_p}, /* Paragraph */ + {"OL", html_ol}, /* Ordered List */ + {"UL", html_ul}, /* Unordered List */ + {"MENU", html_menu}, /* Menu List */ + {"DIR", html_dir}, /* Directory List */ + {"LI", html_li}, /* ... List Item */ + {"DL", html_dl}, /* Definition List */ + {"DT", html_dt}, /* ... Def. Term */ + {"DD", html_dd}, /* ... Def. Definition */ + {"I", html_i}, /* Italic Text */ + {"EM", html_i}, /* Typographic Emphasis */ + {"STRONG", html_i}, /* STRONG Typo Emphasis */ + {"VAR", html_i}, /* Variable Name */ + {"B", html_b}, /* Bold Text */ + {"BLOCKQUOTE", html_blockquote}, /* Blockquote */ + {"ADDRESS", html_address}, /* Address */ + {"CENTER", html_center}, /* Centered Text v3.2 */ + {"DIV", html_div}, /* Document Division 3.2 */ + {"H1", html_h1}, /* Headings... */ + {"H2", html_h2}, + {"H3", html_h3}, + {"H4", html_h4}, + {"H5", html_h5}, + {"H6", html_h6}, + {"PRE", html_pre}, /* Preformatted Text */ + {"KBD", NULL}, /* Keyboard Input (NO OP) */ + {"TT", NULL}, /* Typetype (NO OP) */ + {"SAMP", NULL}, /* Sample Text (NO OP) */ + +/*----- Handlers below are NOT DONE OR CHECKED OUT YET -----*/ + + {"CITE", NULL}, /* Citation */ + {"CODE", NULL}, /* Code Text */ + +/*----- Handlers below UNIMPLEMENTED (and won't until later) -----*/ + + {"FORM", html_form}, /* form within a document */ + {"INPUT", NULL}, /* One input field, options */ + {"OPTION", NULL}, /* One option within Select */ + {"SELECT", NULL}, /* Selection from a set */ + {"TEXTAREA", NULL}, /* A multi-line input field */ + +/*----- Handlers below provide limited support for RFC 1942 Tables -----*/ + + {"CAPTION", html_center}, /* Table Caption */ + {"TR", html_tr}, /* Table Table Row */ + {"TD", html_td}, /* Table Table Data */ + + {NULL, NULL} +}; + + + +/* + * Initialize the given handler, and add it to the stack if it + * requests it. + */ +void +html_push(fd, hf) + FILTER_S *fd; + html_f hf; +{ + HANDLER_S *new; + + new = (HANDLER_S *)fs_get(sizeof(HANDLER_S)); + memset(new, 0, sizeof(HANDLER_S)); + new->html_data = fd; + new->f = hf; + if((*hf)(new, 0, GF_RESET)){ /* stack the handler? */ + new->below = HANDLERS(fd); + HANDLERS(fd) = new; /* push */ + } + else + fs_give((void **) &new); +} + + +/* + * Remove the most recently installed the given handler + * after letting it accept its demise. + */ +void +html_pop(fd, hf) + FILTER_S *fd; + html_f hf; +{ + HANDLER_S *tp; + + for(tp = HANDLERS(fd); tp && hf != tp->f; tp = tp->below) + ; + + if(tp){ + (*tp->f)(tp, 0, GF_EOD); /* may adjust handler list */ + if(tp != HANDLERS(fd)){ + HANDLER_S *p; + + for(p = HANDLERS(fd); p->below != tp; p = p->below) + ; + + if(p) + p->below = tp->below; /* remove from middle of stack */ + /* BUG: else programming botch and we should die */ + } + else + HANDLERS(fd) = tp->below; /* pop */ + + fs_give((void **)&tp); + } + else if(hf == html_p || hf == html_li || hf == html_dt || hf == html_dd){ + /* + * Possible "special case" tag handling here. + * It's for such tags as Paragraph (`</P>'), List Item + * (`</LI>'), Definition Term (`</DT>'), and Definition Description + * (`</DD>') elements, which may be omitted... + */ + HANDLER_S hd; + + memset(&hd, 0, sizeof(HANDLER_S)); + hd.html_data = fd; + hd.f = hf; + + (*hf)(&hd, 0, GF_EOD); + } + /* BUG: else, we should bitch */ +} + + +/* + * Deal with data passed a hander in its GF_DATA state + */ +html_handoff(hd, ch) + HANDLER_S *hd; + int ch; +{ + if(hd->below) + (*hd->below->f)(hd->below, ch, GF_DATA); + else + html_output(hd->html_data, ch); +} + + +/* + * HTML <BR> element handler + */ +int +html_br(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_RESET) + html_output(hd->html_data, HTML_NEWLINE); + + return(0); /* don't get linked */ +} + + +/* + * HTML <HR> (Horizontal Rule) element handler + */ +int +html_hr(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_RESET){ + int i, old_wrap, width, align; + PARAMETER *p; + + width = WRAP_COLS(hd->html_data); + align = 0; + for(p = HD(hd->html_data)->el_data->attribs; + p && p->attribute; + p = p->next) + if(p->value){ + if(!strucmp(p->attribute, "ALIGN")){ + if(!strucmp(p->value, "LEFT")) + align = 1; + else if(!strucmp(p->value, "RIGHT")) + align = 2; + } + else if(!strucmp(p->attribute, "WIDTH")){ + char *cp; + + width = 0; + for(cp = p->value; *cp; cp++) + if(*cp == '%'){ + width = (WRAP_COLS(hd->html_data)*min(100,width))/100; + break; + } + else if(isdigit((unsigned char) *cp)) + width = (width * 10) + (*cp - '0'); + + width = min(width, WRAP_COLS(hd->html_data)); + } + } + + html_blank(hd->html_data, 1); /* at least one blank line */ + + old_wrap = HD(hd->html_data)->wrapstate; + HD(hd->html_data)->wrapstate = 0; + if((i = max(0, WRAP_COLS(hd->html_data) - width)) + && ((align == 0) ? i /= 2 : (align == 2))) + for(; i > 0; i--) + html_output(hd->html_data, ' '); + + for(i = 0; i < width; i++) + html_output(hd->html_data, '_'); + + html_blank(hd->html_data, 1); + HD(hd->html_data)->wrapstate = old_wrap; + } + + return(0); /* don't get linked */ +} + + +/* + * HTML <P> (paragraph) element handler + */ +int +html_p(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_RESET){ + /* Make sure there's at least 1 blank line */ + html_blank(hd->html_data, 1); + + /* adjust indent level if needed */ + if(HD(hd->html_data)->li_pending){ + html_indent(hd->html_data, 4, HTML_ID_INC); + HD(hd->html_data)->li_pending = 0; + } + } + else if(cmd == GF_EOD) + /* Make sure there's at least 1 blank line */ + html_blank(hd->html_data, 1); + + return(0); /* don't get linked */ +} + + +/* + * HTML Table <TR> (paragraph) table row + */ +int +html_tr(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_RESET || cmd == GF_EOD) + /* Make sure there's at least 1 blank line */ + html_blank(hd->html_data, 0); + + return(0); /* don't get linked */ +} + + +/* + * HTML Table <TD> (paragraph) table data + */ +int +html_td(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_RESET){ + PARAMETER *p; + + for(p = HD(hd->html_data)->el_data->attribs; + p && p->attribute; + p = p->next) + if(!strucmp(p->attribute, "nowrap") + && (hd->html_data->f2 || hd->html_data->n)){ + HTML_DUMP_LIT(hd->html_data, " | ", 3); + break; + } + } + + return(0); /* don't get linked */ +} + + +/* + * HTML <I> (italic text) element handler + */ +int +html_i(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_DATA){ + /* include LITERAL in spaceness test! */ + if(hd->x && !isspace((unsigned char) (ch & 0xff))){ + HTML_ULINE(hd->html_data, 1); + hd->x = 0; + } + + html_handoff(hd, ch); + } + else if(cmd == GF_RESET){ + hd->x = 1; + } + else if(cmd == GF_EOD){ + if(!hd->x) + HTML_ULINE(hd->html_data, 0); + } + + return(1); /* get linked */ +} + + +/* + * HTML <b> (Bold text) element handler + */ +int +html_b(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_DATA){ + /* include LITERAL in spaceness test! */ + if(hd->x && !isspace((unsigned char) (ch & 0xff))){ + HTML_ULINE(hd->html_data, 1); + hd->x = 0; + } + + html_handoff(hd, ch); + } + else if(cmd == GF_RESET){ + hd->x = 1; + } + else if(cmd == GF_EOD){ + if(!hd->x) + HTML_ULINE(hd->html_data, 0); + } + + return(1); /* get linked */ +} + + +/* + * HTML <IMG> element handler + */ +int +html_img(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_RESET){ + PARAMETER *p; + char *s = NULL; + + for(p = HD(hd->html_data)->el_data->attribs; + p && p->attribute; + p = p->next) + if(!strucmp(p->attribute, "alt")){ + if(p->value && p->value[0]){ + HTML_DUMP_LIT(hd->html_data, p->value, strlen(p->value)); + HTML_TEXT(hd->html_data, ' '); + } + + return(0); + } + + for(p = HD(hd->html_data)->el_data->attribs; + p && p->attribute; + p = p->next) + if(!strucmp(p->attribute, "src") && p->value) + if((s = strrindex(p->value, '/')) && *++s != '\0'){ + HTML_TEXT(hd->html_data, '['); + HTML_DUMP_LIT(hd->html_data, s, strlen(s)); + HTML_TEXT(hd->html_data, ']'); + HTML_TEXT(hd->html_data, ' '); + return(0); + } + + HTML_DUMP_LIT(hd->html_data, "[IMAGE] ", 7); + } + + return(0); /* don't get linked */ +} + + +/* + * HTML <FORM> (Form) element handler + */ +int +html_form(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_RESET){ + char *p; + + html_blank(hd->html_data, 0); + + HTML_DUMP_LIT(hd->html_data, "[FORM]", 6); + + html_blank(hd->html_data, 0); + } + + return(0); /* don't get linked */ +} + + +/* + * HTML <HEAD> element handler + */ +int +html_head(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_DATA){ + html_handoff(hd, ch); + } + else if(cmd == GF_RESET){ + HD(hd->html_data)->head = 1; + } + else if(cmd == GF_EOD){ + HD(hd->html_data)->head = 0; + } + + return(1); /* get linked */ +} + + +/* + * HTML <BASE> element handler + */ +int +html_base(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_RESET){ + if(HD(hd->html_data)->head && !HTML_BASE(hd->html_data)){ + PARAMETER *p; + + for(p = HD(hd->html_data)->el_data->attribs; + p && p->attribute && strucmp(p->attribute, "HREF"); + p = p->next) + ; + + if(p && p->value && !((HTML_OPT_S *)(hd->html_data)->opt)->base) + ((HTML_OPT_S *)(hd->html_data)->opt)->base = cpystr(p->value); + } + } + + return(0); /* DON'T get linked */ +} + + +/* + * HTML <TITLE> element handler + */ +int +html_title(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_DATA){ + if(hd->x + 1 >= hd->y){ + hd->y += 80; + fs_resize((void **)&hd->s, (size_t)hd->y * sizeof(unsigned char)); + } + + hd->s[hd->x++] = (unsigned char) ch; + } + else if(cmd == GF_RESET){ + hd->x = 0L; + hd->y = 80L; + hd->s = (unsigned char *)fs_get((size_t)hd->y * sizeof(unsigned char)); + } + else if(cmd == GF_EOD){ + /* Down the road we probably want to give these bytes to + * someone... + */ + hd->s[hd->x] = '\0'; + fs_give((void **)&hd->s); + } + + return(1); /* get linked */ +} + + +/* + * HTML <A> (Anchor) element handler + */ +int +html_a(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_DATA){ + html_handoff(hd, ch); + } + else if(cmd == GF_RESET){ + int i, n, x; + char buf[256]; + HANDLE_S *h; + PARAMETER *p, *href = NULL, *name = NULL; + + /* + * Pending Anchor!?!? + * space insertion/line breaking that's yet to get done... + */ + if(HD(hd->html_data)->prefix){ + dprint(1, (debugfile, "-- html_a: NESTED/UNTERMINATED ANCHOR!\n")); + html_a_finish(hd); + } + + /* + * Look for valid Anchor data vis the filter installer's parms + * (e.g., Only allow references to our internal URLs if asked) + */ + for(p = HD(hd->html_data)->el_data->attribs; + p && p->attribute; + p = p->next) + if(!strucmp(p->attribute, "HREF") + && p->value + && (HANDLES_LOC(hd->html_data) + || struncmp(p->value, "x-pine-", 7))) + href = p; + else if(!strucmp(p->attribute, "NAME")) + name = p; + + if(HANDLES(hd->html_data) && (href || name)){ + h = new_handle(); + + /* + * Enhancement: we might want to get fancier and parse the + * href a bit further such that we can launch images using + * our image viewer, or browse local files or directories + * with our internal tools. Of course, having the jump-off + * point into text/html always be the defined "web-browser", + * just might be the least confusing UI-wise... + */ + h->type = URL; + + if(name && name->value) + h->h.url.name = cpystr(name->value); + + /* + * Prepare to build embedded prefix... + */ + HD(hd->html_data)->prefix = (int *) fs_get(64 * sizeof(int)); + x = 0; + + /* + * Is this something that looks like a URL? If not and + * we were giving some "base" string, proceed ala RFC1808... + */ + if(href){ + if(HTML_BASE(hd->html_data) && !rfc1738_scan(href->value, &n)) + html_a_relative(HTML_BASE(hd->html_data), href->value, h); + else + h->h.url.path = cpystr(href->value); + + if(pico_usingcolor()){ + char *fg = NULL, *bg = NULL, *q; + + if(ps_global->VAR_SLCTBL_FORE_COLOR + && colorcmp(ps_global->VAR_SLCTBL_FORE_COLOR, + ps_global->VAR_NORM_FORE_COLOR)) + fg = ps_global->VAR_SLCTBL_FORE_COLOR; + + if(ps_global->VAR_SLCTBL_BACK_COLOR + && colorcmp(ps_global->VAR_SLCTBL_BACK_COLOR, + ps_global->VAR_NORM_BACK_COLOR)) + bg = ps_global->VAR_SLCTBL_BACK_COLOR; + + if(fg || bg){ + COLOR_PAIR *tmp; + + /* + * The blacks are just known good colors for testing + * whether the other color is good. + */ + tmp = new_color_pair(fg ? fg : colorx(COL_BLACK), + bg ? bg : colorx(COL_BLACK)); + if(pico_is_good_colorpair(tmp)){ + q = color_embed(fg, bg); + + for(i = 0; q[i]; i++) + HD(hd->html_data)->prefix[x++] = q[i]; + } + + if(tmp) + free_color_pair(&tmp); + } + + if(F_OFF(F_SLCTBL_ITEM_NOBOLD, ps_global)) + HD(hd->html_data)->prefix[x++] = HTML_DOBOLD; + } + else + HD(hd->html_data)->prefix[x++] = HTML_DOBOLD; + } + + HD(hd->html_data)->prefix[x++] = TAG_EMBED; + HD(hd->html_data)->prefix[x++] = TAG_HANDLE; + + sprintf(buf, "%d", h->key); + HD(hd->html_data)->prefix[x++] = n = strlen(buf); + for(i = 0; i < n; i++) + HD(hd->html_data)->prefix[x++] = buf[i]; + + HD(hd->html_data)->prefix_used = x; + } + } + else if(cmd == GF_EOD){ + html_a_finish(hd); + } + + return(1); /* get linked */ +} + + +void +html_a_prefix(f) + FILTER_S *f; +{ + int *prefix, n; + + /* Do this so we don't visit from html_output... */ + prefix = HD(f)->prefix; + HD(f)->prefix = NULL; + + for(n = 0; n < HD(f)->prefix_used; n++) + html_a_output_prefix(f, prefix[n]); + + fs_give((void **) &prefix); +} + + +/* + * html_a_finish - house keeping associated with end of link tag + */ +void +html_a_finish(hd) + HANDLER_S *hd; +{ + if(HANDLES(hd->html_data)){ + if(HD(hd->html_data)->prefix) + html_a_prefix(hd->html_data); + + if(pico_usingcolor()){ + char *fg = NULL, *bg = NULL, *p; + int i; + + if(ps_global->VAR_SLCTBL_FORE_COLOR + && colorcmp(ps_global->VAR_SLCTBL_FORE_COLOR, + ps_global->VAR_NORM_FORE_COLOR)) + fg = ps_global->VAR_NORM_FORE_COLOR; + + if(ps_global->VAR_SLCTBL_BACK_COLOR + && colorcmp(ps_global->VAR_SLCTBL_BACK_COLOR, + ps_global->VAR_NORM_BACK_COLOR)) + bg = ps_global->VAR_NORM_BACK_COLOR; + + if(F_OFF(F_SLCTBL_ITEM_NOBOLD, ps_global)) + HTML_BOLD(hd->html_data, 0); /* turn OFF bold */ + + if(fg || bg){ + COLOR_PAIR *tmp; + + /* + * The blacks are just known good colors for testing + * whether the other color is good. + */ + tmp = new_color_pair(fg ? fg : colorx(COL_BLACK), + bg ? bg : colorx(COL_BLACK)); + if(pico_is_good_colorpair(tmp)){ + p = color_embed(fg, bg); + + for(i = 0; p[i]; i++) + html_output(hd->html_data, p[i]); + } + + if(tmp) + free_color_pair(&tmp); + } + } + else + HTML_BOLD(hd->html_data, 0); /* turn OFF bold */ + + html_output(hd->html_data, TAG_EMBED); + html_output(hd->html_data, TAG_HANDLEOFF); + } +} + + +/* + * html_output_a_prefix - dump Anchor prefix data + */ +void +html_a_output_prefix(f, c) + FILTER_S *f; + int c; +{ + switch(c){ + case HTML_DOBOLD : + HTML_BOLD(f, 1); + break; + + default : + html_output(f, c); + break; + } +} + + + +/* + * relative_url - put full url path in h based on base and relative url + */ +void +html_a_relative(base_url, rel_url, h) + char *base_url, *rel_url; + HANDLE_S *h; +{ + size_t len; + char tmp[MAILTMPLEN], *p, *q; + char *scheme = NULL, *net = NULL, *path = NULL, + *parms = NULL, *query = NULL, *frag = NULL, + *base_scheme = NULL, *base_net_loc = NULL, + *base_path = NULL, *base_parms = NULL, + *base_query = NULL, *base_frag = NULL, + *rel_scheme = NULL, *rel_net_loc = NULL, + *rel_path = NULL, *rel_parms = NULL, + *rel_query = NULL, *rel_frag = NULL; + + /* Rough parse of base URL */ + rfc1808_tokens(base_url, &base_scheme, &base_net_loc, &base_path, + &base_parms, &base_query, &base_frag); + + /* Rough parse of this URL */ + rfc1808_tokens(rel_url, &rel_scheme, &rel_net_loc, &rel_path, + &rel_parms, &rel_query, &rel_frag); + + scheme = rel_scheme; /* defaults */ + net = rel_net_loc; + path = rel_path; + parms = rel_parms; + query = rel_query; + frag = rel_frag; + if(!scheme && base_scheme){ + scheme = base_scheme; + if(!net){ + net = base_net_loc; + if(path){ + if(*path != '/'){ + if(base_path){ + for(p = q = base_path; /* Drop base path's tail */ + p = strchr(p, '/'); + q = ++p) + ; + + len = q - base_path; + } + else + len = 0; + + if(len + strlen(rel_path) < MAILTMPLEN - 1){ + if(len) + sprintf(path = tmp, "%.*s", len, base_path); + + strcpy(tmp + len, rel_path); + + /* Follow RFC 1808 "Step 6" */ + for(p = tmp; p = strchr(p, '.'); ) + switch(*(p+1)){ + /* + * a) All occurrences of "./", where "." is a + * complete path segment, are removed. + */ + case '/' : + if(p > tmp) + for(q = p; *q = *(q+2); q++) + ; + else + p++; + + break; + + /* + * b) If the path ends with "." as a + * complete path segment, that "." is + * removed. + */ + case '\0' : + if(p == tmp || *(p-1) == '/') + *p = '\0'; + else + p++; + + break; + + /* + * c) All occurrences of "<segment>/../", + * where <segment> is a complete path + * segment not equal to "..", are removed. + * Removal of these path segments is + * performed iteratively, removing the + * leftmost matching pattern on each + * iteration, until no matching pattern + * remains. + * + * d) If the path ends with "<segment>/..", + * where <segment> is a complete path + * segment not equal to "..", that + * "<segment>/.." is removed. + */ + case '.' : + if(p > tmp + 1){ + for(q = p - 2; q > tmp && *q != '/'; q--) + ; + + if(*q == '/') + q++; + + if(q + 1 == p /* no "//.." */ + || (*q == '.' /* and "../.." */ + && *(q+1) == '.' + && *(q+2) == '/')){ + p += 2; + break; + } + + switch(*(p+2)){ + case '/' : + len = (p - q) + 3; + p = q; + for(; *q = *(q+len); q++) + ; + + break; + + case '\0': + *(p = q) = '\0'; + break; + + default: + p += 2; + break; + } + } + else + p += 2; + + break; + + default : + p++; + break; + } + } + else + path = ""; /* lame. */ + } + } + else{ + path = base_path; + if(!parms){ + parms = base_parms; + if(!query) + query = base_query; + } + } + } + } + + len = (scheme ? strlen(scheme) : 0) + (net ? strlen(net) : 0) + + (path ? strlen(path) : 0) + (parms ? strlen(parms) : 0) + + (query ? strlen(query) : 0) + (frag ? strlen(frag ) : 0) + 8; + + h->h.url.path = (char *) fs_get(len * sizeof(char)); + sprintf(h->h.url.path, "%s%s%s%s%s%s%s%s%s%s%s%s", + scheme ? scheme : "", scheme ? ":" : "", + net ? "//" : "", net ? net : "", + (path && *path == '/') ? "" : ((path && net) ? "/" : ""), + path ? path : "", + parms ? ";" : "", parms ? parms : "", + query ? "?" : "", query ? query : "", + frag ? "#" : "", frag ? frag : ""); + + if(base_scheme) + fs_give((void **) &base_scheme); + + if(base_net_loc) + fs_give((void **) &base_net_loc); + + if(base_path) + fs_give((void **) &base_path); + + if(base_parms) + fs_give((void **) &base_parms); + + if(base_query) + fs_give((void **) &base_query); + + if(base_frag) + fs_give((void **) &base_frag); + + if(rel_scheme) + fs_give((void **) &rel_scheme); + + if(rel_net_loc) + fs_give((void **) &rel_net_loc); + + if(rel_parms) + fs_give((void **) &rel_parms); + + if(rel_query) + fs_give((void **) &rel_query); + + if(rel_frag) + fs_give((void **) &rel_frag); + + if(rel_path) + fs_give((void **) &rel_path); +} + + +/* + * HTML <UL> (Unordered List) element handler + */ +int +html_ul(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_DATA){ + html_handoff(hd, ch); + } + else if(cmd == GF_RESET){ + HD(hd->html_data)->li_pending = 1; + html_blank(hd->html_data, 0); + } + else if(cmd == GF_EOD){ + html_blank(hd->html_data, 0); + + if(!HD(hd->html_data)->li_pending) + html_indent(hd->html_data, -4, HTML_ID_INC); + else + HD(hd->html_data)->li_pending = 0; + } + + return(1); /* get linked */ +} + + +/* + * HTML <OL> (Ordered List) element handler + */ +int +html_ol(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_DATA){ + html_handoff(hd, ch); + } + else if(cmd == GF_RESET){ + /* + * Signal that we're expecting to see <LI> as our next elemnt + * and set the the initial ordered count. + */ + HD(hd->html_data)->li_pending = 1; + hd->x = 1L; + html_blank(hd->html_data, 0); + } + else if(cmd == GF_EOD){ + html_blank(hd->html_data, 0); + + if(!HD(hd->html_data)->li_pending) + html_indent(hd->html_data, -4, HTML_ID_INC); + else + HD(hd->html_data)->li_pending = 0; + } + + return(1); /* get linked */ +} + + +/* + * HTML <MENU> (Menu List) element handler + */ +int +html_menu(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_DATA){ + html_handoff(hd, ch); + } + else if(cmd == GF_RESET){ + HD(hd->html_data)->li_pending = 1; + } + else if(cmd == GF_EOD){ + html_blank(hd->html_data, 0); + + if(!HD(hd->html_data)->li_pending) + html_indent(hd->html_data, -4, HTML_ID_INC); + else + HD(hd->html_data)->li_pending = 0; + } + + return(1); /* get linked */ +} + + +/* + * HTML <DIR> (Directory List) element handler + */ +int +html_dir(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_DATA){ + html_handoff(hd, ch); + } + else if(cmd == GF_RESET){ + HD(hd->html_data)->li_pending = 1; + } + else if(cmd == GF_EOD){ + html_blank(hd->html_data, 0); + + if(!HD(hd->html_data)->li_pending) + html_indent(hd->html_data, -4, HTML_ID_INC); + else + HD(hd->html_data)->li_pending = 0; + } + + return(1); /* get linked */ +} + + +/* + * HTML <LI> (List Item) element handler + */ +int +html_li(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_RESET){ + HANDLER_S *p, *found = NULL; + + /* + * There better be a an unordered list, ordered list, + * Menu or Directory handler installed + * or else we crap out... + */ + for(p = HANDLERS(hd->html_data); p; p = p->below) + if(p->f == html_ul || p->f == html_ol + || p->f == html_menu || p->f == html_dir){ + found = p; + break; + } + + if(found){ + char buf[8], *p; + int wrapstate; + + /* Start a new line */ + html_blank(hd->html_data, 0); + + /* adjust indent level if needed */ + if(HD(hd->html_data)->li_pending){ + html_indent(hd->html_data, 4, HTML_ID_INC); + HD(hd->html_data)->li_pending = 0; + } + + if(found->f == html_ul){ + int l = html_indent(hd->html_data, 0, HTML_ID_GET); + + strcpy(buf, " "); + buf[1] = (l < 5) ? '*' : (l < 9) ? '+' : (l < 17) ? 'o' : '#'; + } + else if(found->f == html_ol) + sprintf(buf, "%2ld.", found->x++); + else if(found->f == html_menu) + strcpy(buf, " ->"); + + html_indent(hd->html_data, -4, HTML_ID_INC); + + /* So we don't munge whitespace */ + wrapstate = HD(hd->html_data)->wrapstate; + HD(hd->html_data)->wrapstate = 0; + + html_write_indent(hd->html_data, HD(hd->html_data)->indent_level); + for(p = buf; *p; p++) + html_output(hd->html_data, (int) *p); + + HD(hd->html_data)->wrapstate = wrapstate; + html_indent(hd->html_data, 4, HTML_ID_INC); + } + /* BUG: should really bitch about this */ + } + + return(0); /* DON'T get linked */ +} + + + +/* + * HTML <DL> (Definition List) element handler + */ +int +html_dl(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_DATA){ + html_handoff(hd, ch); + } + else if(cmd == GF_RESET){ + /* + * Set indention level for definition terms and definitions... + */ + hd->x = html_indent(hd->html_data, 0, HTML_ID_GET); + hd->y = hd->x + 2; + hd->z = hd->y + 4; + } + else if(cmd == GF_EOD){ + html_indent(hd->html_data, (int) hd->x, HTML_ID_SET); + html_blank(hd->html_data, 1); + } + + return(1); /* get linked */ +} + + +/* + * HTML <DT> (Definition Term) element handler + */ +int +html_dt(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_RESET){ + HANDLER_S *p; + + /* + * There better be a Definition Handler installed + * or else we crap out... + */ + for(p = HANDLERS(hd->html_data); p && p->f != html_dl; p = p->below) + ; + + if(p){ /* adjust indent level if needed */ + html_indent(hd->html_data, (int) p->y, HTML_ID_SET); + html_blank(hd->html_data, 1); + } + /* BUG: else should really bitch about this */ + } + + return(0); /* DON'T get linked */ +} + + +/* + * HTML <DD> (Definition Definition) element handler + */ +int +html_dd(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_RESET){ + HANDLER_S *p; + + /* + * There better be a Definition Handler installed + * or else we crap out... + */ + for(p = HANDLERS(hd->html_data); p && p->f != html_dl; p = p->below) + ; + + if(p){ /* adjust indent level if needed */ + html_indent(hd->html_data, (int) p->z, HTML_ID_SET); + html_blank(hd->html_data, 0); + } + /* BUG: should really bitch about this */ + } + + return(0); /* DON'T get linked */ +} + + +/* + * HTML <H1> (Headings 1) element handler. + * + * Bold, very-large font, CENTERED. One or two blank lines + * above and below. For our silly character cell's that + * means centered and ALL CAPS... + */ +int +html_h1(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_DATA){ + html_handoff(hd, ch); + } + else if(cmd == GF_RESET){ + /* turn ON the centered bit */ + CENTER_BIT(hd->html_data) = 1; + } + else if(cmd == GF_EOD){ + /* turn OFF the centered bit, add blank line */ + CENTER_BIT(hd->html_data) = 0; + html_blank(hd->html_data, 1); + } + + return(1); /* get linked */ +} + + +/* + * HTML <H2> (Headings 2) element handler + */ +int +html_h2(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_DATA){ + if((hd->x & HTML_HX_ULINE) && !isspace((unsigned char) (ch & 0xff))){ + HTML_ULINE(hd->html_data, 1); + hd->x ^= HTML_HX_ULINE; /* only once! */ + } + + html_handoff(hd, (ch < 128 && islower((unsigned char) ch)) + ? toupper((unsigned char) ch) : ch); + } + else if(cmd == GF_RESET){ + /* + * Bold, large font, flush-left. One or two blank lines + * above and below. + */ + if(CENTER_BIT(hd->html_data)) /* stop centering for now */ + hd->x = HTML_HX_CENTER; + else + hd->x = 0; + + hd->x |= HTML_HX_ULINE; + + CENTER_BIT(hd->html_data) = 0; + hd->y = html_indent(hd->html_data, 0, HTML_ID_SET); + hd->z = HD(hd->html_data)->wrapcol; + HD(hd->html_data)->wrapcol = WRAP_COLS(hd->html_data) - 8; + html_blank(hd->html_data, 1); + } + else if(cmd == GF_EOD){ + /* + * restore previous centering, and indent level + */ + if(!(hd->x & HTML_HX_ULINE)) + HTML_ULINE(hd->html_data, 0); + + html_indent(hd->html_data, hd->y, HTML_ID_SET); + html_blank(hd->html_data, 1); + CENTER_BIT(hd->html_data) = (hd->x & HTML_HX_CENTER) != 0; + HD(hd->html_data)->wrapcol = hd->z; + } + + return(1); /* get linked */ +} + + +/* + * HTML <H3> (Headings 3) element handler + */ +int +html_h3(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_DATA){ + if((hd->x & HTML_HX_ULINE) && !isspace((unsigned char) (ch & 0xff))){ + HTML_ULINE(hd->html_data, 1); + hd->x ^= HTML_HX_ULINE; /* only once! */ + } + + html_handoff(hd, ch); + } + else if(cmd == GF_RESET){ + /* + * Italic, large font, slightly indented from the left + * margin. One or two blank lines above and below. + */ + if(CENTER_BIT(hd->html_data)) /* stop centering for now */ + hd->x = HTML_HX_CENTER; + else + hd->x = 0; + + hd->x |= HTML_HX_ULINE; + CENTER_BIT(hd->html_data) = 0; + hd->y = html_indent(hd->html_data, 2, HTML_ID_SET); + hd->z = HD(hd->html_data)->wrapcol; + HD(hd->html_data)->wrapcol = WRAP_COLS(hd->html_data) - 8; + html_blank(hd->html_data, 1); + } + else if(cmd == GF_EOD){ + /* + * restore previous centering, and indent level + */ + if(!(hd->x & HTML_HX_ULINE)) + HTML_ULINE(hd->html_data, 0); + + html_indent(hd->html_data, hd->y, HTML_ID_SET); + html_blank(hd->html_data, 1); + CENTER_BIT(hd->html_data) = (hd->x & HTML_HX_CENTER) != 0; + HD(hd->html_data)->wrapcol = hd->z; + } + + return(1); /* get linked */ +} + + +/* + * HTML <H4> (Headings 4) element handler + */ +int +html_h4(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_DATA){ + html_handoff(hd, ch); + } + else if(cmd == GF_RESET){ + /* + * Bold, normal font, indented more than H3. One blank line + * above and below. + */ + hd->x = CENTER_BIT(hd->html_data); /* stop centering for now */ + CENTER_BIT(hd->html_data) = 0; + hd->y = html_indent(hd->html_data, 4, HTML_ID_SET); + hd->z = HD(hd->html_data)->wrapcol; + HD(hd->html_data)->wrapcol = WRAP_COLS(hd->html_data) - 8; + html_blank(hd->html_data, 1); + } + else if(cmd == GF_EOD){ + /* + * restore previous centering, and indent level + */ + html_indent(hd->html_data, (int) hd->y, HTML_ID_SET); + html_blank(hd->html_data, 1); + CENTER_BIT(hd->html_data) = hd->x; + HD(hd->html_data)->wrapcol = hd->z; + } + + return(1); /* get linked */ +} + + +/* + * HTML <H5> (Headings 5) element handler + */ +int +html_h5(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_DATA){ + html_handoff(hd, ch); + } + else if(cmd == GF_RESET){ + /* + * Italic, normal font, indented as H4. One blank line + * above. + */ + hd->x = CENTER_BIT(hd->html_data); /* stop centering for now */ + CENTER_BIT(hd->html_data) = 0; + hd->y = html_indent(hd->html_data, 6, HTML_ID_SET); + hd->z = HD(hd->html_data)->wrapcol; + HD(hd->html_data)->wrapcol = WRAP_COLS(hd->html_data) - 8; + html_blank(hd->html_data, 1); + } + else if(cmd == GF_EOD){ + /* + * restore previous centering, and indent level + */ + html_indent(hd->html_data, (int) hd->y, HTML_ID_SET); + html_blank(hd->html_data, 1); + CENTER_BIT(hd->html_data) = hd->x; + HD(hd->html_data)->wrapcol = hd->z; + } + + return(1); /* get linked */ +} + + +/* + * HTML <H6> (Headings 6) element handler + */ +int +html_h6(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_DATA){ + html_handoff(hd, ch); + } + else if(cmd == GF_RESET){ + /* + * Bold, indented same as normal text, more than H5. One + * blank line above. + */ + hd->x = CENTER_BIT(hd->html_data); /* stop centering for now */ + CENTER_BIT(hd->html_data) = 0; + hd->y = html_indent(hd->html_data, 8, HTML_ID_SET); + hd->z = HD(hd->html_data)->wrapcol; + HD(hd->html_data)->wrapcol = WRAP_COLS(hd->html_data) - 8; + html_blank(hd->html_data, 1); + } + else if(cmd == GF_EOD){ + /* + * restore previous centering, and indent level + */ + html_indent(hd->html_data, (int) hd->y, HTML_ID_SET); + html_blank(hd->html_data, 1); + CENTER_BIT(hd->html_data) = hd->x; + HD(hd->html_data)->wrapcol = hd->z; + } + + return(1); /* get linked */ +} + + +/* + * HTML <BlockQuote> element handler + */ +int +html_blockquote(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + int j; +#define HTML_BQ_INDENT 6 + + if(cmd == GF_DATA){ + html_handoff(hd, ch); + } + else if(cmd == GF_RESET){ + /* + * A typical rendering might be a slight extra left and + * right indent, and/or italic font. The Blockquote element + * causes a paragraph break, and typically provides space + * above and below the quote. + */ + html_indent(hd->html_data, HTML_BQ_INDENT, HTML_ID_INC); + j = HD(hd->html_data)->wrapstate; + HD(hd->html_data)->wrapstate = 0; + html_blank(hd->html_data, 1); + HD(hd->html_data)->wrapstate = j; + HD(hd->html_data)->wrapcol -= HTML_BQ_INDENT; + } + else if(cmd == GF_EOD){ + html_blank(hd->html_data, 1); + + j = HD(hd->html_data)->wrapstate; + HD(hd->html_data)->wrapstate = 0; + html_indent(hd->html_data, -(HTML_BQ_INDENT), HTML_ID_INC); + HD(hd->html_data)->wrapstate = j; + HD(hd->html_data)->wrapcol += HTML_BQ_INDENT; + } + + return(1); /* get linked */ +} + + +/* + * HTML <Address> element handler + */ +int +html_address(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + int j; +#define HTML_ADD_INDENT 2 + + if(cmd == GF_DATA){ + html_handoff(hd, ch); + } + else if(cmd == GF_RESET){ + /* + * A typical rendering might be a slight extra left and + * right indent, and/or italic font. The Blockquote element + * causes a paragraph break, and typically provides space + * above and below the quote. + */ + html_indent(hd->html_data, HTML_ADD_INDENT, HTML_ID_INC); + j = HD(hd->html_data)->wrapstate; + HD(hd->html_data)->wrapstate = 0; + html_blank(hd->html_data, 1); + HD(hd->html_data)->wrapstate = j; + } + else if(cmd == GF_EOD){ + html_blank(hd->html_data, 1); + + j = HD(hd->html_data)->wrapstate; + HD(hd->html_data)->wrapstate = 0; + html_indent(hd->html_data, -(HTML_ADD_INDENT), HTML_ID_INC); + HD(hd->html_data)->wrapstate = j; + } + + return(1); /* get linked */ +} + + +/* + * HTML <PRE> (Preformatted Text) element handler + */ +int +html_pre(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_DATA){ + /* + * remove CRLF after '>' in element. + * We see CRLF because wrapstate is off. + */ + switch(hd->y){ + case 2 : + if(ch == '\012'){ + hd->y = 3; + return(1); + } + else + html_handoff(hd, '\015'); + + break; + + case 1 : + if(ch == '\015'){ + hd->y = 2; + return(1); + } + + default : + hd->y = 0; + break; + } + + html_handoff(hd, ch); + } + else if(cmd == GF_RESET){ + html_blank(hd->html_data, 1); + hd->x = HD(hd->html_data)->wrapstate; + HD(hd->html_data)->wrapstate = 0; + hd->y = 1; + } + else if(cmd == GF_EOD){ + HD(hd->html_data)->wrapstate = (hd->x != 0); + html_blank(hd->html_data, 0); + } + + return(1); +} + + + + +/* + * HTML <CENTER> (Centerd Text) element handler + */ +int +html_center(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_DATA){ + html_handoff(hd, ch); + } + else if(cmd == GF_RESET){ + /* turn ON the centered bit */ + CENTER_BIT(hd->html_data) = 1; + } + else if(cmd == GF_EOD){ + /* turn OFF the centered bit */ + CENTER_BIT(hd->html_data) = 0; + } + + return(1); +} + + + +/* + * HTML <DIV> (Document Divisions) element handler + */ +int +html_div(hd, ch, cmd) + HANDLER_S *hd; + int ch, cmd; +{ + if(cmd == GF_DATA){ + html_handoff(hd, ch); + } + else if(cmd == GF_RESET){ + PARAMETER *p; + + for(p = HD(hd->html_data)->el_data->attribs; + p && p->attribute; + p = p->next) + if(!strucmp(p->attribute, "ALIGN")){ + if(p->value){ + /* remember previous values */ + hd->x = CENTER_BIT(hd->html_data); + hd->y = html_indent(hd->html_data, 0, HTML_ID_GET); + + html_blank(hd->html_data, 0); + CENTER_BIT(hd->html_data) = !strucmp(p->value, "CENTER"); + html_indent(hd->html_data, 0, HTML_ID_SET); + /* NOTE: "RIGHT" not supported yet */ + } + } + } + else if(cmd == GF_EOD){ + /* restore centered bit and indentiousness */ + CENTER_BIT(hd->html_data) = hd->y; + html_indent(hd->html_data, hd->y, HTML_ID_SET); + html_blank(hd->html_data, 0); + } + + return(1); +} + + + +/* + * return the function associated with the given element name + */ +html_f +html_element_func(el_name) + char *el_name; +{ + register int i; + + for(i = 0; element_table[i].element; i++) + if(!strucmp(el_name, element_table[i].element)) + return(element_table[i].handler); + + return(NULL); +} + + +/* + * collect element's name and any attribute/value pairs then + * dispatch to the appropriate handler. + * + * Returns 1 : got what we wanted + * 0 : we need more data + * -1 : bogus input + */ +int +html_element_collector(fd, ch) + FILTER_S *fd; + int ch; +{ + if(ch == '>'){ + if(ED(fd)->overrun){ + /* + * If problem processing, don't bother doing anything + * internally, just return such that none of what we've + * digested is displayed. + */ + HTML_DEBUG_EL("too long", ED(fd)); + return(1); /* Let it go, Jim */ + } + else if(ED(fd)->mkup_decl){ + if(ED(fd)->badform){ + dprint(2, (debugfile, "-- html <!-- BAD: %.*s\n", + ED(fd)->len, ED(fd)->buf)); + /* + * Invalid comment -- make some guesses as + * to whether we should stop with this greater-than... + */ + if(ED(fd)->buf[0] != '-' + || ED(fd)->len < 4 + || (ED(fd)->buf[1] == '-' + && ED(fd)->buf[ED(fd)->len - 1] == '-' + && ED(fd)->buf[ED(fd)->len - 2] == '-')) + return(1); + } + else{ + dprint(2, (debugfile, "-- html <!-- OK: %.*s\n", + ED(fd)->len, ED(fd)->buf)); + if(ED(fd)->start_comment == ED(fd)->end_comment){ + if(ED(fd)->len > 10){ + ED(fd)->buf[ED(fd)->len - 2] = '\0'; + html_element_comment(fd, ED(fd)->buf + 2); + } + + return(1); + } + /* else keep collecting comment below */ + } + } + else if(!ED(fd)->quoted || ED(fd)->badform){ + html_f f; + + /* + * We either have the whole thing or all that we could + * salvage from it. Try our best... + */ + + if(HD(fd)->bitbucket) + return(1); /* element inside chtml clause! */ + + if(!ED(fd)->badform && html_element_flush(ED(fd))) + return(1); /* return without display... */ + + /* + * If we ran into an empty tag or we don't know how to deal + * with it, just go on, ignoring it... + */ + if(ED(fd)->element && (f = html_element_func(ED(fd)->element))){ + /* dispatch the element's handler */ + if(ED(fd)->end_tag) + html_pop(fd, f); /* remove it's handler */ + else + html_push(fd, f); /* add it's handler */ + + HTML_DEBUG_EL(ED(fd)->end_tag ? "POP" : "PUSH", ED(fd)); + } + else{ /* else, empty or unrecognized */ + HTML_DEBUG_EL("?", ED(fd)); + } + + return(1); /* all done! see, that didn't hurt */ + } + } + + if(ED(fd)->mkup_decl){ + if((ch &= 0xff) == '-'){ + if(ED(fd)->hyphen){ + ED(fd)->hyphen = 0; + if(ED(fd)->start_comment) + ED(fd)->end_comment = 1; + else + ED(fd)->start_comment = 1; + } + else + ED(fd)->hyphen = 1; + } + else{ + if(ED(fd)->end_comment) + ED(fd)->start_comment = ED(fd)->end_comment = 0; + + /* + * no "--" after ! or non-whitespace between comments - bad + */ + if(ED(fd)->len < 2 || (!ED(fd)->start_comment + && !isspace((unsigned char) ch))) + ED(fd)->badform = 1; /* non-comment! */ + + ED(fd)->hyphen = 0; + } + + /* + * Remember the comment for possible later processing, if + * it get's too long, remember first and last few chars + * so we know when to terminate (and throw some garbage + * in between when we toss out what's between. + */ + if(ED(fd)->len == HTML_BUF_LEN){ + ED(fd)->buf[2] = ED(fd)->buf[3] = 'X'; + ED(fd)->buf[4] = ED(fd)->buf[ED(fd)->len - 2]; + ED(fd)->buf[5] = ED(fd)->buf[ED(fd)->len - 1]; + ED(fd)->len = 6; + } + + ED(fd)->buf[(ED(fd)->len)++] = ch; + return(0); /* comments go in the bit bucket */ + } + else if(ED(fd)->overrun || ED(fd)->badform){ + return(0); /* swallow char's until next '>' */ + } + else if(!ED(fd)->element && !ED(fd)->len){ + if(ch == '/'){ /* validate leading chars */ + ED(fd)->end_tag = 1; + return(0); + } + else if(ch == '!'){ + ED(fd)->mkup_decl = 1; + return(0); + } + else if(!isalpha((unsigned char) ch)) + return(-1); /* can't be a tag! */ + } + else if(ch == '\"' || ch == '\''){ + if(!ED(fd)->hit_equal){ + ED(fd)->badform = 1; /* quote in element name?!? */ + return(0); + } + + if(ED(fd)->quoted){ + if(ED(fd)->quoted == (char) ch){ + ED(fd)->quoted = 0; + return(0); /* continue collecting chars */ + } + /* else fall thru writing other quoting char */ + } + else{ + ED(fd)->quoted = (char) ch; + return(0); /* need more data */ + } + } + + ch &= 0xff; /* strip any "literal" high bits */ + if(ED(fd)->quoted + || isalnum(ch) + || strchr("-.!", ch) + || (ED(fd)->hit_equal && !isspace((unsigned char) ch))){ + if(ED(fd)->len < ((ED(fd)->element || !ED(fd)->hit_equal) + ? HTML_BUF_LEN:MAX_ELEMENT)){ + ED(fd)->buf[(ED(fd)->len)++] = ch; + } + else + ED(fd)->overrun = 1; /* flag it broken */ + } + else if(isspace((unsigned char) ch) || ch == '='){ + if(html_element_flush(ED(fd))){ + ED(fd)->badform = 1; + return(0); /* else, we ain't done yet */ + } + + if(!ED(fd)->hit_equal) + ED(fd)->hit_equal = (ch == '='); + } + else + ED(fd)->badform = 1; /* unrecognized data?? */ + + return(0); /* keep collecting */ +} + + +/* + * Element collector found complete string, integrate it and reset + * internal collection buffer. + * + * Returns zero if element collection buffer flushed, error flag otherwise + */ +int +html_element_flush(el_data) + CLCTR_S *el_data; +{ + int rv = 0; + + if(el_data->hit_equal){ /* adding a value */ + el_data->hit_equal = 0; + if(el_data->cur_attrib){ + if(!el_data->cur_attrib->value){ + el_data->cur_attrib->value = cpystr(el_data->len + ? el_data->buf : ""); + } + else{ + dprint(2, (debugfile, + "** element: unexpected value: %.10s...\n", + el_data->len ? el_data->buf : "\"\"")); + rv = 1; + } + } + else{ + dprint(2, (debugfile, + "** element: missing attribute name: %.10s...\n", + el_data->len ? el_data->buf : "\"\"")); + rv = 2; + } + + el_data->len = 0; + memset(el_data->buf, 0, HTML_BUF_LEN); + } + else if(el_data->len){ + if(!el_data->element){ + el_data->element = cpystr(el_data->buf); + } + else{ + PARAMETER *p = (PARAMETER *)fs_get(sizeof(PARAMETER)); + memset(p, 0, sizeof(PARAMETER)); + if(el_data->attribs){ + el_data->cur_attrib->next = p; + el_data->cur_attrib = p; + } + else + el_data->attribs = el_data->cur_attrib = p; + + p->attribute = cpystr(el_data->buf); + } + + el_data->len = 0; + memset(el_data->buf, 0, HTML_BUF_LEN); + } + + return(rv); /* report whatever happened above */ +} + + +/* + * html_element_comment - "Special" comment handling here + */ +void +html_element_comment(f, s) + FILTER_S *f; + char *s; +{ + char *p; + + while(*s && isspace((unsigned char) *s)) + s++; + + /* + * WARNING: "!--chtml" denotes "Conditional HTML", a UW-ism. + */ + if(!struncmp(s, "chtml ", 6)){ + s += 6; + if(!struncmp(s, "if ", 3)){ + HD(f)->bitbucket = 1; /* default is failure! */ + switch(*(s += 3)){ + case 'P' : + case 'p' : + if(!struncmp(s + 1, "inemode=", 8)){ + if(!strucmp(s = removing_quotes(s + 9), "function_key") + && F_ON(F_USE_FK, ps_global)) + HD(f)->bitbucket = 0; + else if(!strucmp(s, "running")) + HD(f)->bitbucket = 0; + else if(!strucmp(s, "phone_home") && ps_global->phone_home) + HD(f)->bitbucket = 0; +#ifdef _WINDOWS + else if(!strucmp(s, "os_windows")) + HD(f)->bitbucket = 0; +#endif + } + + break; + + case '[' : /* test */ + if(p = strindex(++s, ']')){ + *p = '\0'; /* tie off test string */ + removing_leading_white_space(s); + removing_trailing_white_space(s); + if(*s == '-' && *(s+1) == 'r'){ /* readable file? */ + for(s += 2; *s && isspace((unsigned char) *s); s++) + ; + + + HD(f)->bitbucket = (can_access(removing_quotes(s), + READ_ACCESS) != 0); + } + } + + break; + + default : + break; + } + } + else if(!strucmp(s, "else")){ + HD(f)->bitbucket = !HD(f)->bitbucket; + } + else if(!strucmp(s, "endif")){ + /* Clean up after chtml here */ + HD(f)->bitbucket = 0; + } + } + else if(!HD(f)->bitbucket){ + if(!struncmp(s, "#include ", 9)){ + char buf[MAILTMPLEN], *bufp; + int len, end_of_line; + FILE *fp; + + /* Include the named file */ + if(!struncmp(s += 9, "file=", 5) + && (fp = fopen(removing_quotes(s+5), "r"))){ + html_element_output(f, HTML_NEWLINE); + + while(fgets(buf, MAILTMPLEN, fp)){ + if((len = strlen(buf)) && buf[len-1] == '\n'){ + end_of_line = 1; + buf[--len] = '\0'; + } + else + end_of_line = 0; + + for(bufp = buf; len; bufp++, len--) + html_element_output(f, (int) *bufp); + + if(end_of_line) + html_element_output(f, HTML_NEWLINE); + } + + fclose(fp); + html_element_output(f, HTML_NEWLINE); + HD(f)->blanks = 0; + if(f->f1 == WSPACE) + f->f1 = DFL; + } + } + else if(!struncmp(s, "#echo ", 6)){ + if(!struncmp(s += 6, "var=", 4)){ + char *p, buf[MAILTMPLEN]; + ADDRESS *adr; + extern char datestamp[]; + + if(!strcmp(s = removing_quotes(s + 4), "PINE_VERSION")){ + p = pine_version; + } + else if(!strcmp(s, "PINE_COMPILE_DATE")){ + p = datestamp; + } + else if(!strcmp(s, "PINE_TODAYS_DATE")){ + rfc822_date(p = buf); + } + else if(!strcmp(s, "_LOCAL_FULLNAME_")){ + p = (ps_global->VAR_LOCAL_FULLNAME + && ps_global->VAR_LOCAL_FULLNAME[0]) + ? ps_global->VAR_LOCAL_FULLNAME + : "Local Support"; + } + else if(!strcmp(s, "_LOCAL_ADDRESS_")){ + p = (ps_global->VAR_LOCAL_ADDRESS + && ps_global->VAR_LOCAL_ADDRESS[0]) + ? ps_global->VAR_LOCAL_ADDRESS + : "postmaster"; + adr = rfc822_parse_mailbox(&p, ps_global->maildomain); + sprintf(p = buf, "%s@%s", adr->mailbox, adr->host); + mail_free_address(&adr); + } + else if(!strcmp(s, "_BUGS_FULLNAME_")){ + p = (ps_global->VAR_BUGS_FULLNAME + && ps_global->VAR_BUGS_FULLNAME[0]) + ? ps_global->VAR_BUGS_FULLNAME + : "Place to report Pine Bugs"; + } + else if(!strcmp(s, "_BUGS_ADDRESS_")){ + p = (ps_global->VAR_BUGS_ADDRESS + && ps_global->VAR_BUGS_ADDRESS[0]) + ? ps_global->VAR_BUGS_ADDRESS : "postmaster"; + adr = rfc822_parse_mailbox(&p, ps_global->maildomain); + sprintf(p = buf, "%s@%s", adr->mailbox, adr->host); + mail_free_address(&adr); + } + else if(!strcmp(s, "CURRENT_DIR")){ + getcwd(p = buf, MAILTMPLEN); + } + else if(!strcmp(s, "HOME_DIR")){ + p = ps_global->home_dir; + } + else + p = NULL; + + if(p){ + if(f->f1 == WSPACE){ + html_element_output(f, ' '); + f->f1 = DFL; /* clear it */ + } + + while(*p) + html_element_output(f, (int) *p++); + } + } + } + } +} + + +void +html_element_output(f, ch) + FILTER_S *f; + int ch; +{ + if(HANDLERS(f)) + (*HANDLERS(f)->f)(HANDLERS(f), ch, GF_DATA); + else + html_output(f, ch); +} + + +/* + * collect html entities and return its value when done. + * + * Returns 0 : we need more data + * 1-255 : char value of entity collected + * HTML_BADVALUE : good data, but no named match or out of range + * HTML_BADDATA : invalid input + * + * NOTES: + * - entity format is "'&' tag ';'" and represents a literal char + * - named entities are CASE SENSITIVE. + * - numeric char references (where the tag is prefixed with a '#') + * are a char with that numbers value + * - numeric vals are 0-255 except for the ranges: 0-8, 11-31, 127-159. + */ +int +html_entity_collector(f, ch, alternate) + FILTER_S *f; + int ch; + char **alternate; +{ + static char len = 0; + static char buf[MAX_ENTITY]; + int rv = 0, i; + + if((len == 0) + ? (isalpha((unsigned char) ch) || ch == '#') + : ((isdigit((unsigned char) ch) + || (isalpha((unsigned char) ch) && buf[0] != '#')) + && len < MAX_ENTITY - 1)){ + buf[len++] = ch; + } + else if((isspace((unsigned char) ch) || ch == ';') && len){ + buf[len] = '\0'; /* got something! */ + switch(buf[0]){ + case '#' : + rv = atoi(&buf[1]); + if(F_ON(F_PASS_CONTROL_CHARS, ps_global) + || (rv == '\t' || rv == '\n' || rv == '\r' + || (rv > 31 && rv < 127) || (rv > 159 && rv < 256))){ + if(alternate) + for(i = 0, *alternate = NULL; entity_tab[i].name; i++) + if(entity_tab[i].value == rv){ + *alternate = entity_tab[i].plain; + break; + } + } + else + rv = HTML_BADVALUE; + + break; + + default : + rv = HTML_BADVALUE; /* in case we fail below */ + for(i = 0; entity_tab[i].name; i++) + if(strcmp(entity_tab[i].name, buf) == 0){ + rv = entity_tab[i].value; + if(alternate) + *alternate = entity_tab[i].plain; + + break; + } + + break; + } + } + else + rv = HTML_BADDATA; /* bogus input! */ + + if(rv){ /* nonzero return, clean up */ + if(rv > 0xff && alternate){ /* provide bogus data to caller */ + buf[len] = '\0'; + *alternate = buf; + } + + len = 0; + } + + return(rv); +} + + +/*---------------------------------------------------------------------- + HTML text to plain text filter + + This basically tries to do the best it can with HTML 2.0 (RFC1866) + with bits of RFC 1942 (plus some HTML 3.2 thrown in as well) text + formatting. + + ----*/ +void +gf_html2plain(f, flg) + FILTER_S *f; + int flg; +{ +/* BUG: qoute incoming \255 values (see "yuml" above!) */ + if(flg == GF_DATA){ + register int c; + GF_INIT(f, f->next); + + while(GF_GETC(f, c)){ + /* + * First we have to collect any literal entities... + * that is, IF we're not already collecting one + * AND we're not in element's text or, if we are, we're + * not in quoted text. Whew. + */ + if(f->t){ + int i; + char *alt = NULL; + + switch(i = html_entity_collector(f, c, &alt)){ + case 0: /* more data required? */ + continue; /* go get another char */ + + case HTML_BADVALUE : + case HTML_BADDATA : + /* if supplied, process bogus data */ + HTML_PROC(f, '&'); + for(; *alt; alt++) + HTML_PROC(f, *alt); + + if(c == '&' && !HD(f)->quoted){ + f->t = '&'; + continue; + } + else + f->t = 0; /* don't come back next time */ + + break; + + default : /* thing to process */ + f->t = 0; /* don't come back */ + + /* + * Map some of the undisplayable entities? + */ + if(HD(f)->alt_entity && i > 127 && alt && alt[0]){ + for(; *alt; alt++){ + c = MAKE_LITERAL(*alt); + HTML_PROC(f, c); + } + + continue; + } + + c = MAKE_LITERAL(i); + break; + } + } + else if(c == '&' && !HD(f)->quoted){ + f->t = '&'; + continue; + } + + /* + * then we process whatever we got... + */ + + HTML_PROC(f, c); + } + + GF_OP_END(f); /* clean up our input pointers */ + } + else if(flg == GF_EOD){ + while(HANDLERS(f)) + /* BUG: should complain about "Unexpected end of HTML text." */ + html_pop(f, HANDLERS(f)->f); + + html_output(f, HTML_NEWLINE); + HTML_FLUSH(f); + fs_give((void **)&f->line); + if(HD(f)->color) + free_color_pair(&HD(f)->color); + + fs_give(&f->data); + if(f->opt){ + if(((HTML_OPT_S *)f->opt)->base) + fs_give((void **) &((HTML_OPT_S *)f->opt)->base); + + fs_give(&f->opt); + } + + (*f->next->f)(f->next, GF_DATA); + (*f->next->f)(f->next, GF_EOD); + } + else if(flg == GF_RESET){ + dprint(9, (debugfile, "-- gf_reset html2plain\n")); + f->data = (HTML_DATA_S *) fs_get(sizeof(HTML_DATA_S)); + memset(f->data, 0, sizeof(HTML_DATA_S)); + HD(f)->wrapstate = 1; /* start with flowing text */ + HD(f)->wrapcol = WRAP_COLS(f) - 8; + f->f1 = DFL; /* state */ + f->f2 = 0; /* chars in wrap buffer */ + f->n = 0L; /* chars on line so far */ + f->linep = f->line = (char *)fs_get(HTML_BUF_LEN * sizeof(char)); + HD(f)->alt_entity = (!ps_global->VAR_CHAR_SET + || strucmp(ps_global->VAR_CHAR_SET, + "iso-8859-1")); + } +} + + + +/* + * html_indent - do the requested indent level function with appropriate + * flushing and such. + * + * Returns: indent level prior to set/increment + */ +int +html_indent(f, val, func) + FILTER_S *f; + int val, func; +{ + int old = HD(f)->indent_level; + + /* flush pending data at old indent level */ + switch(func){ + case HTML_ID_INC : + html_output_flush(f); + if((HD(f)->indent_level += val) < 0) + HD(f)->indent_level = 0; + + break; + + case HTML_ID_SET : + html_output_flush(f); + HD(f)->indent_level = val; + break; + + default : + break; + } + + return(old); +} + + + +/* + * html_blanks - Insert n blank lines into output + */ +void +html_blank(f, n) + FILTER_S *f; + int n; +{ + /* Cap off any flowing text, and then write blank lines */ + if(f->f2 || f->n || CENTER_BIT(f) || HD(f)->centered || WRAPPED_LEN(f)) + html_output(f, HTML_NEWLINE); + + if(HD(f)->wrapstate) + while(HD(f)->blanks < n) /* blanks inc'd by HTML_NEWLINE */ + html_output(f, HTML_NEWLINE); +} + + + +/* + * html_newline -- insert a newline mindful of embedded tags + */ +void +html_newline(f) + FILTER_S *f; +{ + html_write_newline(f); /* commit an actual newline */ + + if(f->n){ /* and keep track of blank lines */ + HD(f)->blanks = 0; + f->n = 0L; + } + else + HD(f)->blanks++; +} + + +/* + * output the given char, handling any requested wrapping. + * It's understood that all whitespace handed us is written. In other + * words, junk whitespace is weeded out before it's given to us here. + * + */ +void +html_output(f, ch) + FILTER_S *f; + int ch; +{ + if(CENTER_BIT(f)){ /* center incoming text */ + html_output_centered(f, ch); + } + else{ + static short embedded = 0; /* BUG: reset on entering filter */ + static char *color_ptr = NULL; + + if(HD(f)->centered){ + html_centered_flush(f); + fs_give((void **) &HD(f)->centered->line.buf); + fs_give((void **) &HD(f)->centered->word.buf); + fs_give((void **) &HD(f)->centered); + } + + if(HD(f)->wrapstate){ + if(ch == HTML_NEWLINE){ /* hard newline */ + html_output_flush(f); + html_newline(f); + } + else + HD(f)->blanks = 0; /* reset blank line counter */ + + if(ch == TAG_EMBED){ /* takes up no space */ + embedded = 1; + *(f->linep)++ = TAG_EMBED; + } + else if(embedded){ /* ditto */ + if(ch == TAG_HANDLE) + embedded = -1; /* next ch is length */ + else if(ch == TAG_FGCOLOR || ch == TAG_BGCOLOR){ + if(!HD(f)->color) + HD(f)->color = new_color_pair(NULL, NULL); + + if(ch == TAG_FGCOLOR) + color_ptr = HD(f)->color->fg; + else + color_ptr = HD(f)->color->bg; + + embedded = 11; + } + else if(embedded < 0){ + embedded = ch; /* number of embedded chars */ + } + else{ + embedded--; + if(color_ptr) + *color_ptr++ = ch; + + if(embedded == 0 && color_ptr){ + *color_ptr = '\0'; + color_ptr = NULL; + } + } + + *(f->linep)++ = ch; + } + else if(HTML_ISSPACE(ch)){ + html_output_flush(f); + } + else{ + if(HD(f)->prefix) + html_a_prefix(f); + + if(++f->f2 >= WRAP_COLS(f)){ + HTML_FLUSH(f); + html_newline(f); + if(HD(f)->in_anchor) + html_write_anchor(f, HD(f)->in_anchor); + } + else + *(f->linep)++ = ch & 0xff; + } + } + else{ + if(HD(f)->prefix) + html_a_prefix(f); + + html_output_flush(f); + + switch(embedded){ + case 0 : + switch(ch){ + default : + f->n++; /* inc displayed char count */ + HD(f)->blanks = 0; /* reset blank line counter */ + html_putc(f, ch & 0xff); + break; + + case TAG_EMBED : /* takes up no space */ + html_putc(f, TAG_EMBED); + embedded = -2; + break; + + case HTML_NEWLINE : /* newline handling */ + if(!f->n) + break; + + case '\n' : + html_newline(f); + + case '\r' : + break; + } + + break; + + case -2 : + embedded = 0; + switch(ch){ + case TAG_HANDLE : + embedded = -1; /* next ch is length */ + break; + + case TAG_BOLDON : + BOLD_BIT(f) = 1; + break; + + case TAG_BOLDOFF : + BOLD_BIT(f) = 0; + break; + + case TAG_ULINEON : + ULINE_BIT(f) = 1; + break; + + case TAG_ULINEOFF : + ULINE_BIT(f) = 0; + break; + + case TAG_FGCOLOR : + if(!HD(f)->color) + HD(f)->color = new_color_pair(NULL, NULL); + + color_ptr = HD(f)->color->fg; + embedded = 11; + break; + + case TAG_BGCOLOR : + if(!HD(f)->color) + HD(f)->color = new_color_pair(NULL, NULL); + + color_ptr = HD(f)->color->bg; + embedded = 11; + break; + + case TAG_HANDLEOFF : + ch = TAG_INVOFF; + HD(f)->in_anchor = 0; + break; + + default : + break; + } + + html_putc(f, ch); + break; + + case -1 : + embedded = ch; /* number of embedded chars */ + html_putc(f, ch); + break; + + default : + embedded--; + if(color_ptr) + *color_ptr++ = ch; + + if(embedded == 0 && color_ptr){ + *color_ptr = '\0'; + color_ptr = NULL; + } + + html_putc(f, ch); + break; + } + } + } +} + + +/* + * flush any buffered chars waiting for wrapping. + */ +void +html_output_flush(f) + FILTER_S *f; +{ + if(f->f2){ + if(f->n && ((int) f->n) + f->f2 > HD(f)->wrapcol) + html_newline(f); /* wrap? */ + + if(f->n){ /* text already on the line? */ + html_putc(f, ' '); + f->n++; /* increment count */ + } + else{ + /* write at start of new line */ + html_write_indent(f, HD(f)->indent_level); + + if(HD(f)->in_anchor) + html_write_anchor(f, HD(f)->in_anchor); + } + + f->n += f->f2; + HTML_FLUSH(f); + } +} + + + +/* + * html_output_centered - managed writing centered text + */ +void +html_output_centered(f, ch) + FILTER_S *f; + int ch; +{ + if(!HD(f)->centered){ /* new text? */ + html_output_flush(f); + if(f->n) /* start on blank line */ + html_newline(f); + + HD(f)->centered = (CENTER_S *) fs_get(sizeof(CENTER_S)); + memset(HD(f)->centered, 0, sizeof(CENTER_S)); + /* and grab a buf to start collecting centered text */ + HD(f)->centered->line.len = WRAP_COLS(f); + HD(f)->centered->line.buf = (char *) fs_get(HD(f)->centered->line.len + * sizeof(char)); + HD(f)->centered->line.used = HD(f)->centered->line.width = 0; + HD(f)->centered->word.len = 32; + HD(f)->centered->word.buf = (char *) fs_get(HD(f)->centered->word.len + * sizeof(char)); + HD(f)->centered->word.used = HD(f)->centered->word.width = 0; + } + + if(ch == HTML_NEWLINE){ /* hard newline */ + html_centered_flush(f); + } + else if(ch == TAG_EMBED){ /* takes up no space */ + HD(f)->centered->embedded = 1; + html_centered_putc(&HD(f)->centered->word, TAG_EMBED); + } + else if(HD(f)->centered->embedded){ + static char *color_ptr = NULL; + + if(ch == TAG_HANDLE){ + HD(f)->centered->embedded = -1; /* next ch is length */ + } + else if(ch == TAG_FGCOLOR || ch == TAG_BGCOLOR){ + if(!HD(f)->color) + HD(f)->color = new_color_pair(NULL, NULL); + + if(ch == TAG_FGCOLOR) + color_ptr = HD(f)->color->fg; + else + color_ptr = HD(f)->color->bg; + + HD(f)->centered->embedded = 11; + } + else if(HD(f)->centered->embedded < 0){ + HD(f)->centered->embedded = ch; /* number of embedded chars */ + } + else{ + HD(f)->centered->embedded--; + if(color_ptr) + *color_ptr++ = ch; + + if(HD(f)->centered->embedded == 0 && color_ptr){ + *color_ptr = '\0'; + color_ptr = NULL; + } + } + + html_centered_putc(&HD(f)->centered->word, ch); + } + else if(isspace((unsigned char) ch)){ + if(!HD(f)->centered->space++){ /* end of a word? flush! */ + int i; + + if(WRAPPED_LEN(f) > HD(f)->wrapcol){ + html_centered_flush_line(f); + /* fall thru to put current "word" on blank "line" */ + } + else if(HD(f)->centered->line.width){ + /* put space char between line and appended word */ + html_centered_putc(&HD(f)->centered->line, ' '); + HD(f)->centered->line.width++; + } + + for(i = 0; i < HD(f)->centered->word.used; i++) + html_centered_putc(&HD(f)->centered->line, + HD(f)->centered->word.buf[i]); + + HD(f)->centered->line.width += HD(f)->centered->word.width; + HD(f)->centered->word.used = 0; + HD(f)->centered->word.width = 0; + } + } + else{ + if(HD(f)->prefix) + html_a_prefix(f); + + /* ch is start of next word */ + HD(f)->centered->space = 0; + if(HD(f)->centered->word.width >= WRAP_COLS(f)) + html_centered_flush(f); + + html_centered_putc(&HD(f)->centered->word, ch); + HD(f)->centered->word.width++; + } +} + + +/* + * html_centered_putc -- add given char to given WRAPLINE_S + */ +void +html_centered_putc(wp, ch) + WRAPLINE_S *wp; + int ch; +{ + if(wp->used + 1 >= wp->len){ + wp->len += 64; + fs_resize((void **) &wp->buf, wp->len * sizeof(char)); + } + + wp->buf[wp->used++] = ch; +} + + + +/* + * html_centered_flush - finish writing any pending centered output + */ +void +html_centered_flush(f) + FILTER_S *f; +{ + int i, h; + + /* + * If word present (what about line?) we need to deal with + * appending it... + */ + if(HD(f)->centered->word.width && WRAPPED_LEN(f) > HD(f)->wrapcol) + html_centered_flush_line(f); + + if(WRAPPED_LEN(f)){ + /* figure out how much to indent */ + if((i = (WRAP_COLS(f) - WRAPPED_LEN(f))/2) > 0) + html_write_indent(f, i); + + if(HD(f)->centered->anchor) + html_write_anchor(f, HD(f)->centered->anchor); + + html_centered_handle(&HD(f)->centered->anchor, + HD(f)->centered->line.buf, + HD(f)->centered->line.used); + html_write(f, HD(f)->centered->line.buf, HD(f)->centered->line.used); + + if(HD(f)->centered->word.used){ + if(HD(f)->centered->line.width) + html_putc(f, ' '); + + html_centered_handle(&HD(f)->centered->anchor, + HD(f)->centered->word.buf, + HD(f)->centered->word.used); + html_write(f, HD(f)->centered->word.buf, + HD(f)->centered->word.used); + } + + HD(f)->centered->line.used = HD(f)->centered->word.used = 0; + HD(f)->centered->line.width = HD(f)->centered->word.width = 0; + } + else + HD(f)->blanks++; /* advance the blank line counter */ + + html_newline(f); /* finish the line */ +} + + +/* + * html_centered_handle - scan the line for embedded handles + */ +void +html_centered_handle(h, line, len) + int *h; + char *line; + int len; +{ + int n; + + while(len-- > 0) + if(*line++ == TAG_EMBED && len-- > 0) + switch(*line++){ + case TAG_HANDLE : + if((n = *line++) >= --len){ + *h = 0; + len -= n; + while(n--) + *h = (*h * 10) + (*line++ - '0'); + } + break; + + case TAG_HANDLEOFF : + case TAG_INVOFF : + *h = 0; /* assumption 23,342: inverse off ends tags */ + break; + + default : + break; + } +} + + + +/* + * html_centered_flush_line - flush the centered "line" only + */ +void +html_centered_flush_line(f) + FILTER_S *f; +{ + if(HD(f)->centered->line.used){ + int i, j; + + /* hide "word" from flush */ + i = HD(f)->centered->word.used; + j = HD(f)->centered->word.width; + HD(f)->centered->word.used = 0; + HD(f)->centered->word.width = 0; + html_centered_flush(f); + + HD(f)->centered->word.used = i; + HD(f)->centered->word.width = j; + } +} + + +/* + * html_write_indent - write indention mindful of display attributes + */ +void +html_write_indent(f, indent) + FILTER_S *f; + int indent; +{ + if(! STRIP(f)){ + if(BOLD_BIT(f)){ + html_putc(f, TAG_EMBED); + html_putc(f, TAG_BOLDOFF); + } + + if(ULINE_BIT(f)){ + html_putc(f, TAG_EMBED); + html_putc(f, TAG_ULINEOFF); + } + } + + f->n = indent; + while(indent-- > 0) + html_putc(f, ' '); /* indent as needed */ + + /* + * Resume any previous embedded state + */ + if(! STRIP(f)){ + if(BOLD_BIT(f)){ + html_putc(f, TAG_EMBED); + html_putc(f, TAG_BOLDON); + } + + if(ULINE_BIT(f)){ + html_putc(f, TAG_EMBED); + html_putc(f, TAG_ULINEON); + } + } +} + + +/* + * + */ +void +html_write_anchor(f, anchor) + FILTER_S *f; + int anchor; +{ + char buf[256]; + int i; + + html_putc(f, TAG_EMBED); + html_putc(f, TAG_HANDLE); + sprintf(buf, "%d", anchor); + html_putc(f, (int) strlen(buf)); + + for(i = 0; buf[i]; i++) + html_putc(f, buf[i]); +} + + +/* + * html_write_newline - write a newline mindful of display attributes + */ +void +html_write_newline(f) + FILTER_S *f; +{ + if(! STRIP(f)){ /* First tie, off any embedded state */ + if(HD(f)->in_anchor){ + html_putc(f, TAG_EMBED); + html_putc(f, TAG_INVOFF); + } + + if(BOLD_BIT(f)){ + html_putc(f, TAG_EMBED); + html_putc(f, TAG_BOLDOFF); + } + + if(ULINE_BIT(f)){ + html_putc(f, TAG_EMBED); + html_putc(f, TAG_ULINEOFF); + } + + if(HD(f)->color && HD(f)->color->fg[0] && HD(f)->color->bg[0]){ + char *p; + int i; + + p = color_embed(ps_global->VAR_NORM_FORE_COLOR, + ps_global->VAR_NORM_BACK_COLOR); + for(i = 0; i < 2 * (RGBLEN + 2); i++) + html_putc(f, p[i]); + } + } + + html_write(f, "\015\012", 2); + + if(! STRIP(f)){ /* First tie, off any embedded state */ + if(BOLD_BIT(f)){ + html_putc(f, TAG_EMBED); + html_putc(f, TAG_BOLDON); + } + + if(ULINE_BIT(f)){ + html_putc(f, TAG_EMBED); + html_putc(f, TAG_ULINEON); + } + + if(HD(f)->color && HD(f)->color->fg[0] && HD(f)->color->bg[0]){ + char *p; + int i; + COLOR_PAIR *tmp; + + tmp = new_color_pair(HD(f)->color->fg, HD(f)->color->bg); + if(pico_is_good_colorpair(tmp)){ + p = color_embed(HD(f)->color->fg, HD(f)->color->bg); + for(i = 0; i < 2 * (RGBLEN + 2); i++) + html_putc(f, p[i]); + } + + HD(f)->color->fg[0] = '\0'; + HD(f)->color->bg[0] = '\0'; + + if(tmp) + free_color_pair(&tmp); + } + } +} + + +/* + * html_write - write given int array to the next filter. + */ +void +html_write(f, s, n) + FILTER_S *f; + char *s; + int n; +{ + GF_INIT(f, f->next); + + while(n-- > 0){ + /* keep track of attribute state? Not if last char! */ + if(*s == TAG_EMBED && n-- > 0){ + GF_PUTC(f->next, TAG_EMBED); + switch(*++s){ + case TAG_BOLDON : + BOLD_BIT(f) = 1; + break; + case TAG_BOLDOFF : + BOLD_BIT(f) = 0; + break; + case TAG_ULINEON : + ULINE_BIT(f) = 1; + break; + case TAG_ULINEOFF : + ULINE_BIT(f) = 0; + break; + case TAG_HANDLEOFF : + HD(f)->in_anchor = 0; + GF_PUTC(f->next, TAG_INVOFF); + s++; + continue; + case TAG_HANDLE : + if(n-- > 0){ + int i = *++s; + + GF_PUTC(f->next, TAG_HANDLE); + if(i <= n){ + n -= i; + GF_PUTC(f->next, i); + HD(f)->in_anchor = 0; + while(1){ + HD(f)->in_anchor = (HD(f)->in_anchor * 10) + + (*++s - '0'); + if(--i) + GF_PUTC(f->next, *s); + else + break; + } + } + } + + break; + default: + break; + } + } + + GF_PUTC(f->next, (*s++) & 0xff); + } + + GF_IP_END(f->next); /* clean up next's input pointers */ +} + + +/* + * html_putc -- actual work of writing to next filter. + * NOTE: Small opt not using full GF_END since our input + * pointers don't need adjusting. + */ +void +html_putc(f, ch) + FILTER_S *f; + int ch; +{ + GF_INIT(f, f->next); + GF_PUTC(f->next, ch & 0xff); + GF_IP_END(f->next); /* clean up next's input pointers */ +} + + + +/* + * Only current option is to turn on embedded data stripping for text + * bound to a printer or composer. + */ +void * +gf_html2plain_opt(base, columns, flags) + char *base; + int columns, flags; +{ + HTML_OPT_S *op; + + op = (HTML_OPT_S *) fs_get(sizeof(HTML_OPT_S)); + + op->base = cpystr(base); + op->columns = columns; + op->strip = ((flags & GFHP_STRIPPED) == GFHP_STRIPPED); + op->handles = ((flags & GFHP_HANDLES) == GFHP_HANDLES); + op->handles_loc = ((flags & GFHP_LOCAL_HANDLES) == GFHP_LOCAL_HANDLES); + return((void *) op); +} + + +/* END OF HTML-TO-PLAIN text filter */ + +/* + * ESCAPE CODE FILTER - remove unknown and possibly dangerous escape codes + * from the text stream. + */ + +#define MAX_ESC_LEN 5 + +/* + * the simple filter, removes unknown escape codes from the stream + */ +void +gf_escape_filter(f, flg) + FILTER_S *f; + int flg; +{ + register char *p; + GF_INIT(f, f->next); + + if(flg == GF_DATA){ + register unsigned char c; + register int state = f->f1; + + while(GF_GETC(f, c)){ + + if(state){ + if(c == '\033' || f->n == MAX_ESC_LEN){ + f->line[f->n] = '\0'; + f->n = 0L; + if(!match_escapes(f->line)){ + GF_PUTC(f->next, '^'); + GF_PUTC(f->next, '['); + } + else + GF_PUTC(f->next, '\033'); + + p = f->line; + while(*p) + GF_PUTC(f->next, *p++); + + if(c == '\033') + continue; + else + state = 0; /* fall thru */ + } + else{ + f->line[f->n++] = c; /* collect */ + continue; + } + } + + if(c == '\033') + state = 1; + else + GF_PUTC(f->next, c); + } + + f->f1 = state; + GF_END(f, f->next); + } + else if(flg == GF_EOD){ + if(f->f1){ + if(!match_escapes(f->line)){ + GF_PUTC(f->next, '^'); + GF_PUTC(f->next, '['); + } + else + GF_PUTC(f->next, '\033'); + } + + for(p = f->line; f->n; f->n--, p++) + GF_PUTC(f->next, *p); + + fs_give((void **)&(f->line)); /* free temp line buffer */ + GF_FLUSH(f->next); + (*f->next->f)(f->next, GF_EOD); + } + else if(flg == GF_RESET){ + dprint(9, (debugfile, "-- gf_reset escape\n")); + f->f1 = 0; + f->n = 0L; + f->linep = f->line = (char *)fs_get((MAX_ESC_LEN + 1) * sizeof(char)); + } +} + + + +/* + * CONTROL CHARACTER FILTER - transmogrify control characters into their + * corresponding string representations (you know, ^blah and such)... + */ + +/* + * the simple filter transforms unknown control characters in the stream + * into harmless strings. + */ +void +gf_control_filter(f, flg) + FILTER_S *f; + int flg; +{ + GF_INIT(f, f->next); + + if(flg == GF_DATA){ + register unsigned char c; + + while(GF_GETC(f, c)){ + + if(iscntrl(c & 0x7f) + && !(isspace((unsigned char) c) + || c == '\016' || c == '\017' || c == '\033')){ + GF_PUTC(f->next, '^'); + GF_PUTC(f->next, c + '@'); + } + else + GF_PUTC(f->next, c); + } + + GF_END(f, f->next); + } + else if(flg == GF_EOD){ + GF_FLUSH(f->next); + (*f->next->f)(f->next, GF_EOD); + } +} + + + +/* + * LINEWRAP FILTER - insert CRLF's at end of nearest whitespace before + * specified line width + */ + + +typedef struct wrap_col_s { + unsigned bold:1; + unsigned uline:1; + unsigned inverse:1; + unsigned tags:1; + unsigned do_indent:1; + unsigned on_comma:1; + unsigned quoted:1; + COLOR_PAIR *color; + short embedded, + space_len; + char *lineendp, + space; + int anchor; + int wrap_col, + wrap_max, + indent; + char special[256]; +} WRAP_S; + + +#define WRAP_COL(F) (((WRAP_S *)(F)->opt)->wrap_col) +#define WRAP_MAX_COL(F) (((WRAP_S *)(F)->opt)->wrap_max) +#define WRAP_INDENT(F) (((WRAP_S *)(F)->opt)->indent) +#define WRAP_DO_IND(F) (((WRAP_S *)(F)->opt)->do_indent) +#define WRAP_COMMA(F) (((WRAP_S *)(F)->opt)->on_comma) +#define WRAP_QUOTED(F) (((WRAP_S *)(F)->opt)->quoted) +#define WRAP_TAGS(F) (((WRAP_S *)(F)->opt)->tags) +#define WRAP_BOLD(F) (((WRAP_S *)(F)->opt)->bold) +#define WRAP_ULINE(F) (((WRAP_S *)(F)->opt)->uline) +#define WRAP_INVERSE(F) (((WRAP_S *)(F)->opt)->inverse) +#define WRAP_COLOR(F) (((WRAP_S *)(F)->opt)->color) +#define WRAP_LASTC(F) (((WRAP_S *)(F)->opt)->lineendp) +#define WRAP_EMBED(F) (((WRAP_S *)(F)->opt)->embedded) +#define WRAP_ANCHOR(F) (((WRAP_S *)(F)->opt)->anchor) +#define WRAP_SPACE(F) (((WRAP_S *)(F)->opt)->space) +#define WRAP_SPACES(F) (WRAP_SPACE(F) ? (((WRAP_S *)(F)->opt)->space_len): 0) +#define WRAP_SPC_LEN(F) (((WRAP_S *)(F)->opt)->space_len) +#define WRAP_SPEC(S, C) (S)[C] + +#define WRAP_EOL(F) { \ + if(WRAP_BOLD(F)){ \ + GF_PUTC((F)->next, TAG_EMBED); \ + GF_PUTC((F)->next, TAG_BOLDOFF); \ + } \ + if(WRAP_ULINE(F)){ \ + GF_PUTC((F)->next, TAG_EMBED); \ + GF_PUTC((F)->next, TAG_ULINEOFF); \ + } \ + if(WRAP_INVERSE(F) || WRAP_ANCHOR(F)){ \ + GF_PUTC((F)->next, TAG_EMBED); \ + GF_PUTC((F)->next, TAG_INVOFF); \ + } \ + if(WRAP_COLOR(F)){ \ + char *p; \ + GF_PUTC((F)->next, TAG_EMBED); \ + GF_PUTC((F)->next, TAG_FGCOLOR); \ + p = color_to_asciirgb(ps_global->VAR_NORM_FORE_COLOR);\ + for(; *p; p++) \ + GF_PUTC((F)->next, *p); \ + GF_PUTC((F)->next, TAG_EMBED); \ + GF_PUTC((F)->next, TAG_BGCOLOR); \ + p = color_to_asciirgb(ps_global->VAR_NORM_BACK_COLOR);\ + for(; *p; p++) \ + GF_PUTC((F)->next, *p); \ + } \ + GF_PUTC((F)->next, '\015'); \ + GF_PUTC((F)->next, '\012'); \ + f->n = 0L; \ + WRAP_SPACE(F) = 0; \ + } + +#define WRAP_BOL(F) { \ + if(WRAP_BOLD(F)){ \ + GF_PUTC((F)->next, TAG_EMBED); \ + GF_PUTC((F)->next, TAG_BOLDON); \ + } \ + if(WRAP_ULINE(F)){ \ + GF_PUTC((F)->next, TAG_EMBED); \ + GF_PUTC((F)->next, TAG_ULINEON); \ + } \ + if(WRAP_INVERSE(F)){ \ + GF_PUTC((F)->next, TAG_EMBED); \ + GF_PUTC((F)->next, TAG_INVON); \ + } \ + if(WRAP_COLOR(F)){ \ + char *p; \ + if(WRAP_COLOR(F)->fg[0]){ \ + GF_PUTC((F)->next, TAG_EMBED); \ + GF_PUTC((F)->next, TAG_FGCOLOR); \ + p = color_to_asciirgb(WRAP_COLOR(F)->fg);\ + for(; *p; p++) \ + GF_PUTC((F)->next, *p); \ + } \ + if(WRAP_COLOR(F)->bg[0]){ \ + GF_PUTC((F)->next, TAG_EMBED); \ + GF_PUTC((F)->next, TAG_BGCOLOR); \ + p = color_to_asciirgb(WRAP_COLOR(F)->bg);\ + for(; *p; p++) \ + GF_PUTC((F)->next, *p); \ + } \ + } \ + if(WRAP_ANCHOR(F)){ \ + char buf[64]; int i; \ + GF_PUTC((F)->next, TAG_EMBED); \ + GF_PUTC((F)->next, TAG_HANDLE); \ + sprintf(buf, "%d", WRAP_ANCHOR(F)); \ + GF_PUTC((F)->next, (int) strlen(buf)); \ + for(i = 0; buf[i]; i++) \ + GF_PUTC((F)->next, buf[i]); \ + } \ + } + +#define WRAP_PUTC(F,C) { \ + if((F)->linep == WRAP_LASTC(F)){ \ + size_t offset = (F)->linep - (F)->line; \ + fs_resize((void **) &(F)->line, \ + (2 * offset) * sizeof(char)); \ + (F)->linep = &(F)->line[offset]; \ + WRAP_LASTC(F) = &(F)->line[2*offset-1]; \ + } \ + *(F)->linep++ = (C); \ + } + +#define WRAP_FLUSH(F) { \ + register char *s = f->line; \ + register int n; \ + if(!f->n){ \ + if(WRAP_DO_IND(F)){ \ + if(f->n = (long)(n = WRAP_INDENT(F))) \ + while(n-- > 0) \ + GF_PUTC((F)->next, ' '); \ + WRAP_DO_IND(F) = 0; \ + } \ + WRAP_BOL(F); \ + } \ + if(WRAP_SPACE(F)){ \ + GF_PUTC(f->next, WRAP_SPACE(F)); \ + WRAP_SPACE(F) = 0; \ + f->n += WRAP_SPC_LEN(F); \ + } \ + for(n = f->linep - f->line; n > 0; n--){ \ + if(*s == TAG_EMBED){ \ + if(n-- > 0){ \ + GF_PUTC(f->next, TAG_EMBED); \ + switch(*++s){ \ + case TAG_BOLDON : \ + WRAP_BOLD(f) = 1; \ + break; \ + case TAG_BOLDOFF : \ + WRAP_BOLD(f) = 0; \ + break; \ + case TAG_ULINEON : \ + WRAP_ULINE(f) = 1; \ + break; \ + case TAG_ULINEOFF : \ + WRAP_ULINE(f) = 0; \ + break; \ + case TAG_INVOFF : \ + WRAP_ANCHOR(f) = 0; \ + break; \ + case TAG_HANDLE : \ + if(n-- > 0){ \ + int i = *++s; \ + GF_PUTC(f->next, TAG_HANDLE); \ + if(i <= n){ \ + n -= i; \ + GF_PUTC(f->next, i); \ + WRAP_ANCHOR(f) = 0; \ + while(1){ \ + WRAP_ANCHOR(f) \ + = (WRAP_ANCHOR(f) \ + * 10) \ + + (*++s-'0');\ + if(--i) \ + GF_PUTC(f->next,*s);\ + else \ + break; \ + } \ + } \ + } \ + break; \ + case TAG_FGCOLOR : \ + if(pico_usingcolor() \ + && n >= RGBLEN){ \ + if(!WRAP_COLOR(f)) \ + WRAP_COLOR(f)=new_color_pair(NULL,NULL); \ + strncpy(WRAP_COLOR(f)->fg, \ + s+1, RGBLEN); \ + WRAP_COLOR(f)->fg[RGBLEN]='\0'; \ + i = RGBLEN; \ + n -= i; \ + while(i-- > 0) \ + GF_PUTC(f->next, \ + (*s++) & 0xff); \ + } \ + break; \ + case TAG_BGCOLOR : \ + if(pico_usingcolor() \ + && n >= RGBLEN){ \ + if(!WRAP_COLOR(f)) \ + WRAP_COLOR(f)=new_color_pair(NULL,NULL); \ + strncpy(WRAP_COLOR(f)->bg,\ + s+1, RGBLEN); \ + WRAP_COLOR(f)->bg[RGBLEN]='\0'; \ + i = RGBLEN; \ + n -= i; \ + while(i-- > 0) \ + GF_PUTC(f->next, \ + (*s++) & 0xff); \ + } \ + break; \ + default : \ + break; \ + } \ + } \ + } \ + else \ + f->n++; \ + GF_PUTC(f->next, (*s++) & 0xff); \ + } \ + f->f2 = 0; \ + f->linep = f->line; \ + } + + + + +/* + * the simple filter, breaks lines at end of white space nearest + * to global "gf_wrap_width" in length + */ +void +gf_wrap(f, flg) + FILTER_S *f; + int flg; +{ + register long i; + GF_INIT(f, f->next); + + if(flg == GF_DATA){ + register unsigned char c; + register int state = f->f1; + int wrap_threshold = WRAP_MAX_COL(f) - WRAP_INDENT(f); + char *special = ((WRAP_S *) f->opt)->special; + + while(GF_GETC(f, c)){ + + switch(state){ + case CCR : + state = DFL; /* CRLF or CR in text ? */ + WRAP_FLUSH(f); /* it's a newline to us */ + WRAP_EOL(f); + WRAP_BOL(f); + if(c == '\012') /* process anything but LF */ + break; + + case DFL : + if(WRAP_SPEC(special, c)) + switch(c){ + default : + if(WRAP_QUOTED(f)) + break; + + WRAP_FLUSH(f); /* flush buf */ + switch(WRAP_SPACE(f) = c){ /* remember separator */ + case ' ' : + WRAP_SPC_LEN(f) = 1; + break; + + case TAB : + { + int i = (int) f->n; + while(++i & 0x07) + ; + + WRAP_SPC_LEN(f) = i - (int) f->n; + } + + break; + + default : /* some control char? */ + WRAP_SPC_LEN(f) = 2; + break; + } + + continue; + + case '\"' : + WRAP_QUOTED(f) = !WRAP_QUOTED(f); + break; + + case '\015' : /* already has newline? */ + state = CCR; + continue; + + case '\012' : /* bare LF in text? */ + WRAP_FLUSH(f); /* they must've meant */ + WRAP_EOL(f); /* newline... */ + WRAP_BOL(f); + continue; + + case (unsigned char) TAG_EMBED : + WRAP_PUTC(f, TAG_EMBED); + state = TAG; + continue; + + case ',' : + if(!WRAP_QUOTED(f)){ + WRAP_PUTC(f, ','); + WRAP_FLUSH(f); + WRAP_SPACE(f) = 0; + continue; + } + + break; + } + + if(!f->n && (f->f2 >= wrap_threshold)){ + char *ep, *space = NULL; + int cntr, new_f2; + + /* Flush the buf'd line up to last WS */ + if(WRAP_COMMA(f)){ + char *p; + + new_f2 = cntr = f->f2; + for(p = f->line; p < f->linep; p++){ + /* + * Don't split at WS which is in the middle + * of a tag. + */ + switch((unsigned char)*p){ + case (unsigned char)TAG_EMBED: + if(++p >= f->linep) + break; + + switch(*p){ + case TAG_FGCOLOR: + case TAG_BGCOLOR: + p += RGBLEN; + break; + + case TAG_HANDLE: + if(++p >= f->linep) + break; + + p += (*p); + break; + + default: + break; + } + + continue; + break; + + default: + break; + } + + cntr--; + + if(p < f->linep && isspace((unsigned char)*p)){ + space = p; + new_f2 = cntr; + } + } + + if(space){ + ep = f->linep; + f->linep = space; + } + } + + WRAP_FLUSH(f); /* write buffered */ + WRAP_EOL(f); /* write end of line */ + WRAP_DO_IND(f) = 1; /* indent next line */ + + if(space){ + f->linep = f->line; + while(++space < ep) + *f->linep++ = *space; + + f->f2 = new_f2; + } + } + + WRAP_PUTC(f, c); + + if(c == '\t' && WRAP_COMMA(f)){ + int i = (int) f->n; + while(++i & 0x07) + ; + + f->f2 += i - (int) f->n; + } + else + f->f2++; + + if(f->n && (f->n + f->f2 + WRAP_SPACES(f) >= WRAP_COL(f))){ + WRAP_EOL(f); /* write end of line */ + WRAP_DO_IND(f) = 1; /* indent next line */ + } + + break; + + case TAG : + WRAP_PUTC(f, c); + switch(c){ + case TAG_HANDLE : + WRAP_EMBED(f) = -1; + state = HANDLE; + break; + + case TAG_FGCOLOR : + case TAG_BGCOLOR : + WRAP_EMBED(f) = RGBLEN; + state = HDATA; + break; + + default : + state = DFL; + break; + } + + break; + + case HANDLE : + WRAP_PUTC(f, c); + WRAP_EMBED(f) = c; + state = HDATA; + break; + + case HDATA : + WRAP_PUTC(f, c); + if(!(WRAP_EMBED(f) -= 1)) + state = DFL; + + break; + } + } + + f->f1 = state; + GF_END(f, f->next); + } + else if(flg == GF_EOD){ + WRAP_FLUSH(f); + if(WRAP_COLOR(f)) + free_color_pair(&WRAP_COLOR(f)); + + fs_give((void **) &f->line); /* free temp line buffer */ + fs_give((void **) &f->opt); /* free wrap widths struct */ + GF_FLUSH(f->next); + (*f->next->f)(f->next, GF_EOD); + } + else if(flg == GF_RESET){ + dprint(9, (debugfile, "-- gf_reset wrap\n")); + f->f1 = DFL; + f->n = 0L; /* displayed length of line so far */ + f->f2 = 0; /* displayed length of buffered chars */ + if(! (WRAP_S *) f->opt) + f->opt = gf_wrap_filter_opt(75, 80, 0, 0); + + while(WRAP_INDENT(f) >= WRAP_MAX_COL(f)) + WRAP_INDENT(f) /= 2; + + f->line = (char *) fs_get(WRAP_MAX_COL(f) * sizeof(char)); + f->linep = f->line; + WRAP_LASTC(f) = &f->line[WRAP_MAX_COL(f) - 1]; + + for(i = 0; i < 256; i++) + ((WRAP_S *) f->opt)->special[i] = ((i == '\"' && WRAP_COMMA(f)) + || i == '\015' + || i == '\012' + || (i == (unsigned char) TAG_EMBED + && WRAP_TAGS(f)) + || (i == ',' && WRAP_COMMA(f) + && !WRAP_QUOTED(f)) + || (!WRAP_COMMA(f) + && isspace((unsigned char) i))); + } +} + + +/* + * function called from the outside to set + * wrap filter's width option + */ +void * +gf_wrap_filter_opt(width, width_max, indent, flags) + int width, width_max, indent, flags; +{ + WRAP_S *wrap; + + wrap = (WRAP_S *) fs_get(sizeof(WRAP_S)); + memset(wrap, 0, sizeof(WRAP_S)); + wrap->wrap_col = width; + wrap->wrap_max = width_max; + wrap->indent = indent; + wrap->tags = (GFW_HANDLES & flags) == GFW_HANDLES; + wrap->on_comma = (GFW_ONCOMMA & flags) == GFW_ONCOMMA; + return((void *) wrap); +} + + + + +/* + * LINE PREFIX FILTER - insert given text at beginning of each + * line + */ + + +#define GF_PREFIX_WRITE(s) { \ + register char *p; \ + if(p = (s)) \ + while(*p) \ + GF_PUTC(f->next, *p++); \ + } + + +/* + * the simple filter, prepends each line with the requested prefix. + * if prefix is null, does nothing, and as with all filters, assumes + * NVT end of lines. + */ +void +gf_prefix(f, flg) + FILTER_S *f; + int flg; +{ + GF_INIT(f, f->next); + + if(flg == GF_DATA){ + register unsigned char c; + register int state = f->f1; + register int first = f->f2; + + while(GF_GETC(f, c)){ + + if(first){ /* write initial prefix!! */ + first = 0; /* but just once */ + GF_PREFIX_WRITE((char *) f->opt); + } + else if(state){ + state = 0; + GF_PUTC(f->next, '\015'); + if(c == '\012'){ + GF_PUTC(f->next, '\012'); + GF_PREFIX_WRITE((char *) f->opt); + continue; + } + /* else fall thru to handle 'c' */ + } + + if(c == '\015') /* already has newline? */ + state = 1; + else + GF_PUTC(f->next, c); + } + + f->f1 = state; + f->f2 = first; + GF_END(f, f->next); + } + else if(flg == GF_EOD){ + GF_FLUSH(f->next); + (*f->next->f)(f->next, GF_EOD); + } + else if(flg == GF_RESET){ + dprint(9, (debugfile, "-- gf_reset prefix\n")); + f->f1 = 0; + f->f2 = 1; /* nothing written yet */ + } +} + + +/* + * function called from the outside to set + * prefix filter's prefix string + */ +void * +gf_prefix_opt(prefix) + char *prefix; +{ + return((void *) prefix); +} + + +/* + * LINE TEST FILTER - accumulate lines and offer each to the provided + * test function. + */ + +typedef struct _linetest_s { + linetest_t f; + void *local; +} LINETEST_S; + + +/* accumulator growth increment */ +#define LINE_TEST_BLOCK 1024 + +#define GF_LINE_TEST_EOB(f) \ + ((f)->line + ((f)->f2 - 1)) + +#define GF_LINE_TEST_ADD(f, c) \ + { \ + if(p >= eobuf){ \ + f->f2 += LINE_TEST_BLOCK; \ + fs_resize((void **)&f->line, \ + (size_t) f->f2 * sizeof(char)); \ + eobuf = GF_LINE_TEST_EOB(f); \ + p = eobuf - LINE_TEST_BLOCK; \ + } \ + *p++ = c; \ + } + +#define GF_LINE_TEST_TEST(F, D) \ + { \ + unsigned char c; \ + register char *cp; \ + register int l; \ + LT_INS_S *ins = NULL, *insp; \ + *p = '\0'; \ + (D) = (*((LINETEST_S *) (F)->opt)->f)((F)->n++, \ + (F)->line, &ins, \ + ((LINETEST_S *) (F)->opt)->local); \ + if((D) < 2){ \ + for(insp = ins, cp = (F)->line; cp < p; ){ \ + while(insp && cp == insp->where){ \ + for(l = 0; l < insp->len; l++){ \ + c = (unsigned char) insp->text[l];\ + GF_PUTC((F)->next, c); \ + } \ + insp = insp->next; \ + } \ + GF_PUTC((F)->next, *cp); \ + cp++; \ + } \ + while(insp){ \ + for(l = 0; l < insp->len; l++){ \ + c = (unsigned char) insp->text[l]; \ + GF_PUTC((F)->next, c); \ + } \ + insp = insp->next; \ + } \ + gf_line_test_free_ins(&ins); \ + } \ + } + + + +/* + * this simple filter accumulates characters until a newline, offers it + * to the provided test function, and then passes it on. It assumes + * NVT EOLs. + */ +void +gf_line_test(f, flg) + FILTER_S *f; + int flg; +{ + register char *p = f->linep; + register char *eobuf = GF_LINE_TEST_EOB(f); + GF_INIT(f, f->next); + + if(flg == GF_DATA){ + register unsigned char c; + register int state = f->f1; + + while(GF_GETC(f, c)){ + + if(state){ + state = 0; + if(c == '\012'){ + int done; + + GF_LINE_TEST_TEST(f, done); + + p = (f)->line; + + if(done == 2) /* skip this line! */ + continue; + + GF_PUTC(f->next, '\015'); + GF_PUTC(f->next, '\012'); + /* + * if the line tester returns TRUE, it's + * telling us its seen enough and doesn't + * want to see any more. Remove ourself + * from the pipeline... + */ + if(done){ + if(gf_master == f){ + gf_master = f->next; + } + else{ + FILTER_S *fprev; + + for(fprev = gf_master; + fprev && fprev->next != f; + fprev = fprev->next) + ; + + if(fprev) /* wha??? */ + fprev->next = f->next; + else + continue; + } + + while(GF_GETC(f, c)) /* pass input */ + GF_PUTC(f->next, c); + + GF_FLUSH(f->next); /* and drain queue */ + fs_give((void **)&f->line); + fs_give((void **)&f); /* wax our data */ + return; + } + else + continue; + } + else /* add CR to buffer */ + GF_LINE_TEST_ADD(f, '\015'); + } /* fall thru to handle 'c' */ + + if(c == '\015') /* newline? */ + state = 1; + else + GF_LINE_TEST_ADD(f, c); + } + + f->f1 = state; + GF_END(f, f->next); + } + else if(flg == GF_EOD){ + int i; + + GF_LINE_TEST_TEST(f, i); /* examine remaining data */ + fs_give((void **) &f->line); /* free line buffer */ + fs_give((void **) &f->opt); /* free test struct */ + GF_FLUSH(f->next); + (*f->next->f)(f->next, GF_EOD); + } + else if(flg == GF_RESET){ + dprint(9, (debugfile, "-- gf_reset line_test\n")); + f->f1 = 0; /* state */ + f->n = 0L; /* line number */ + f->f2 = LINE_TEST_BLOCK; /* size of alloc'd line */ + f->line = p = (char *) fs_get(f->f2 * sizeof(char)); + } + + f->linep = p; +} + + +/* + * function called from the outside to operate on accumulated line. + */ +void * +gf_line_test_opt(test_f, local) + linetest_t test_f; + void *local; +{ + LINETEST_S *ltp; + + ltp = (LINETEST_S *) fs_get(sizeof(LINETEST_S)); + memset(ltp, 0, sizeof(LINETEST_S)); + ltp->f = test_f; + ltp->local = local; + return((void *) ltp); +} + + + +LT_INS_S ** +gf_line_test_new_ins(ins, p, s, n) + LT_INS_S **ins; + char *p, *s; + int n; +{ + *ins = (LT_INS_S *) fs_get(sizeof(LT_INS_S)); + if((*ins)->len = n) + strncpy((*ins)->text = (char *) fs_get(n * sizeof(char)), s, n); + + (*ins)->where = p; + (*ins)->next = NULL; + return(&(*ins)->next); +} + + +void +gf_line_test_free_ins(ins) + LT_INS_S **ins; +{ + if(ins && *ins){ + if((*ins)->next) + gf_line_test_free_ins(&(*ins)->next); + + if((*ins)->text) + fs_give((void **) &(*ins)->text); + + fs_give((void **) ins); + } +} + + +/* + * Network virtual terminal to local newline convention filter + */ +void +gf_nvtnl_local(f, flg) + FILTER_S *f; + int flg; +{ + GF_INIT(f, f->next); + + if(flg == GF_DATA){ + register unsigned char c; + register int state = f->f1; + + while(GF_GETC(f, c)){ + if(state){ + state = 0; + if(c == '\012'){ + GF_PUTC(f->next, '\012'); + continue; + } + else + GF_PUTC(f->next, '\015'); + /* fall thru to deal with 'c' */ + } + + if(c == '\015') + state = 1; + else + GF_PUTC(f->next, c); + } + + f->f1 = state; + GF_END(f, f->next); + } + else if(flg == GF_EOD){ + GF_FLUSH(f->next); + (*f->next->f)(f->next, GF_EOD); + } + else if(flg == GF_RESET){ + dprint(9, (debugfile, "-- gf_reset nvtnl_local\n")); + f->f1 = 0; + } +} + + +/* + * local to network newline convention filter + */ +void +gf_local_nvtnl(f, flg) + FILTER_S *f; + int flg; +{ + GF_INIT(f, f->next); + + if(flg == GF_DATA){ + register unsigned char c; + + while(GF_GETC(f, c)){ + if(c == '\012'){ + GF_PUTC(f->next, '\015'); + GF_PUTC(f->next, '\012'); + } + else + GF_PUTC(f->next, c); + } + + GF_END(f, f->next); + } + else if(flg == GF_EOD){ + GF_FLUSH(f->next); + (*f->next->f)(f->next, GF_EOD); + } + else if(GF_RESET){ + dprint(9, (debugfile, "-- gf_reset local_nvtnl\n")); + /* no op */ + } + +} + +#if defined(DOS) || defined(OS2) +/* + * DOS CodePage to Character Set Translation (and back) filters + */ + +/* + * Charset and CodePage mapping table pointer and length + */ +static unsigned char *gf_xlate_tab; +static unsigned gf_xlate_tab_len; + +/* + * the simple filter takes DOS Code Page values and maps them into + * the indicated external CharSet mapping or vice-versa. + */ +void +gf_translate(f, flg) + FILTER_S *f; + int flg; +{ + GF_INIT(f, f->next); + + if(flg == GF_DATA){ + register unsigned char c; + + while(GF_GETC(f, c)) + if((unsigned long) c < ((SIZEDTEXT *) (f->opt))->size) + GF_PUTC(f->next, (int) ((SIZEDTEXT *) (f->opt))->data[c]); + + GF_END(f, f->next); + } + else if(flg == GF_EOD){ + fs_give((void **) &f->opt); /* free up table description */ + GF_FLUSH(f->next); + (*f->next->f)(f->next, GF_EOD); + } + else if(GF_RESET){ + dprint(9, (debugfile, "-- gf_reset translate\n")); + } +} + + +/* + * function called from the outside to set + * prefix filter's prefix string + */ +void * +gf_translate_opt(xlatetab, xlatetablen) + unsigned char *xlatetab; + unsigned xlatetablen; +{ + SIZEDTEXT *xlate_tab = (SIZEDTEXT *) fs_get(sizeof(SIZEDTEXT)); + + xlate_tab->data = xlatetab; + xlate_tab->size = (unsigned long) xlatetablen; + + return((void *) xlate_tab); +} +#endif + +/* + * display something indicating we're chewing on something + * + * NOTE : IF ANY OTHER FILTERS WRITE THE DISPLAY, THIS WILL NEED FIXING + */ +void +gf_busy(f, flg) + FILTER_S *f; + int flg; +{ + static short x = 0; + GF_INIT(f, f->next); + + if(flg == GF_DATA){ + register unsigned char c; + + while(GF_GETC(f, c)){ + + if(!((++(f->f1))&0x7ff)){ /* ding the bell every 2K chars */ + MoveCursor(0, 1); + f->f1 = 0; + if((++x)&0x04) x = 0; + Writechar((x == 0) ? '/' : /* CHEATING! */ + (x == 1) ? '-' : + (x == 2) ? '\\' : '|', 0); + } + + GF_PUTC(f->next, c); + } + + GF_END(f, f->next); + } + else if(flg == GF_EOD){ + MoveCursor(0, 1); + Writechar(' ', 0); + EndInverse(); + GF_FLUSH(f->next); + (*f->next->f)(f->next, GF_EOD); + } + else if(flg == GF_RESET){ + dprint(9, (debugfile, "-- gf_reset busy\n")); + f->f1 = 0; + x = 0; + StartInverse(); + } + + fflush(stdout); +} diff --git a/net-mail/pine-maildir/files/os.c b/net-mail/pine-maildir/files/os.c new file mode 100644 index 000000000000..2fd967904207 --- /dev/null +++ b/net-mail/pine-maildir/files/os.c @@ -0,0 +1,5673 @@ +/*---------------------------------------------------------------------- + + T H E P I N E M A I L S Y S T E M + + Laurence Lundblade and Mike Seibel + Networks and Distributed Computing + Computing and Communications + University of Washington + Administration Builiding, AG-44 + Seattle, Washington, 98195, USA + Internet: lgl@CAC.Washington.EDU + mikes@CAC.Washington.EDU + + Please address all bugs and comments to "pine-bugs@cac.washington.edu" + + + Pine and Pico are registered trademarks of the University of Washington. + No commercial use of these trademarks may be made without prior written + permission of the University of Washington. + + Pine, Pico, and Pilot software and its included text are Copyright + 1989-1998 by the University of Washington. + + The full text of our legal notices is contained in the file called + CPYRIGHT, included with this distribution. + + + Pine is in part based on The Elm Mail System: + *********************************************************************** + * The Elm Mail System - Revision: 2.13 * + * * + * Copyright (c) 1986, 1987 Dave Taylor * + * Copyright (c) 1988, 1989 USENET Community Trust * + *********************************************************************** + + + ----------------------------------------------------------------------*/ + +/*====================================================================== + + This contains most of Pine's interface to the local operating system +and hardware. Hopefully this file, os-xxx.h and makefile.xxx are the +only ones that have to be modified for most ports. Signals.c, ttyin.c, +and ttyout.c also have some dependencies. See the doc/tech-notes for +notes on porting Pine to other platforms. Here is a list of the functions +required for an implementation: + + + File System Access + can_access -- See if a file can be accessed + name_file_size -- Return the number of bytes in the file (by name) + fp_file_size -- Return the number of bytes in the file (by FILE *) + name_file_mtime -- Return the mtime of a file (by name) + fp_file_mtime -- Return the mtime of a file (by FILE *) + file_attrib_copy -- Copy attributes of one file to another. + is_writable_dir -- Check to see if directory exists and is writable + create_mail_dir -- Make a directory + rename_file -- change name of a file + build_path -- Put together a file system path + last_cmpnt -- Returns pointer to last component of path + expand_foldername -- Expand a folder name to full path + fnexpand -- Do filename exansion for csh style "~" + filter_filename -- Make sure file name hasn't got weird chars + cntxt_allowed -- Check whether a pathname is allowed for read/write + disk_quota -- Check the user's disk quota + read_file -- Read whole file into memory (for small files) + create_tmpfile -- Just like ANSI C tmpfile function + temp_nam -- Almost like common tempnam function + fget_pos,fset_pos -- Just like ANSI C fgetpos, fsetpos functions + + Abort + coredump -- Abort running Pine dumping core if possible + + System Name and Domain + hostname -- Figure out the system's host name, only + used internally in this file. + getdomainnames -- Figure out the system's domain name + canonical_name -- Returns canonical form of host name + + Job Control + have_job_control -- Returns 1 if job control exists + stop_process -- What to do to stop process when it's time to stop + (only used if have_job_control returns 1) + + System Error Messages (in case given one is a problem) + error_description -- Returns string describing error + + System Password and Accounts + gcos_name -- Parses full name from system, only used + locally in this file so if you don't use it you + don't need it + get_user_info -- Finds in login name, full name, and homedir + local_name_lookup -- Get full name of user on system + change_passwd -- Calls system password changer + + MIME utilities + mime_can_display -- Can we display this type/subtype? + exec_mailcap_cmd -- Run the mailcap command to view a type/subtype. + exec_mailcap_test_cmd -- Run mailcap test= test command. + + Other stuff + srandom -- Dummy srandom if you don't have this function + init_debug + do_debug + save_debug_on_crash + + ====*/ + + +#include "headers.h" + + + +/*---------------------------------------------------------------------- + Check if we can access a file in a given way + + Args: file -- The file to check + mode -- The mode ala the access() system call, see ACCESS_EXISTS + and friends in pine.h. + + Result: returns 0 if the user can access the file according to the mode, + -1 if he can't (and errno is set). + ----*/ +int +can_access(file, mode) + char *file; + int mode; +{ + return(access(file, mode)); +} + + +/*---------------------------------------------------------------------- + Check if we can access a file in a given way in the given path + + Args: path -- The path to look for "file" in + file -- The file to check + mode -- The mode ala the access() system call, see ACCESS_EXISTS + and friends in pine.h. + + Result: returns 0 if the user can access the file according to the mode, + -1 if he can't (and errno is set). + ----*/ +can_access_in_path(path, file, mode) + char *path, *file; + int mode; +{ + char tmp[MAXPATH], *path_copy, *p, *t; + int rv = -1; + + if(!path || !*path || *file == '/'){ + rv = access(file, mode); + } + else if(*file == '~'){ + strcpy(tmp, file); + rv = fnexpand(tmp, sizeof(tmp)) ? access(tmp, mode) : -1; + } + else{ + for(p = path_copy = cpystr(path); p && *p; p = t){ + if(t = strindex(p, ':')) + *t++ = '\0'; + + sprintf(tmp, "%s/%s", p, file); + if((rv = access(tmp, mode)) == 0) + break; + } + + fs_give((void **)&path_copy); + } + + return(rv); +} + +/*---------------------------------------------------------------------- + Return the number of bytes in given file + + Args: file -- file name + + Result: the number of bytes in the file is returned or + -1 on error, in which case errno is valid + ----*/ +long +name_file_size(file) + char *file; +{ + struct stat buffer; + + if(stat(file, &buffer) != 0) + return(-1L); + + return((long)buffer.st_size); +} + + +/*---------------------------------------------------------------------- + Return the number of bytes in given file + + Args: fp -- FILE * for open file + + Result: the number of bytes in the file is returned or + -1 on error, in which case errno is valid + ----*/ +long +fp_file_size(fp) + FILE *fp; +{ + struct stat buffer; + + if(fstat(fileno(fp), &buffer) != 0) + return(-1L); + + return((long)buffer.st_size); +} + + +/*---------------------------------------------------------------------- + Return the modification time of given file + + Args: file -- file name + + Result: the time of last modification (mtime) of the file is returned or + -1 on error, in which case errno is valid + ----*/ +time_t +name_file_mtime(file) + char *file; +{ + struct stat buffer; + + if(stat(file, &buffer) != 0) + return((time_t)(-1)); + + return(buffer.st_mtime); +} + + +/*---------------------------------------------------------------------- + Return the modification time of given file + + Args: fp -- FILE * for open file + + Result: the time of last modification (mtime) of the file is returned or + -1 on error, in which case errno is valid + ----*/ +time_t +fp_file_mtime(fp) + FILE *fp; +{ + struct stat buffer; + + if(fstat(fileno(fp), &buffer) != 0) + return((time_t)(-1)); + + return(buffer.st_mtime); +} + + +/*---------------------------------------------------------------------- + Copy the mode, owner, and group of sourcefile to targetfile. + + Args: targetfile -- + sourcefile -- + + We don't bother keeping track of success or failure because we don't care. + ----*/ +void +file_attrib_copy(targetfile, sourcefile) + char *targetfile; + char *sourcefile; +{ + struct stat buffer; + + if(stat(sourcefile, &buffer) == 0){ + chmod(targetfile, buffer.st_mode); +#if !defined(DOS) && !defined(OS2) + chown(targetfile, buffer.st_uid, buffer.st_gid); +#endif + } +} + + + +/*---------------------------------------------------------------------- + Check to see if a directory exists and is writable by us + + Args: dir -- directory name + + Result: returns 0 if it exists and is writable + 1 if it is a directory, but is not writable + 2 if it is not a directory + 3 it doesn't exist. + ----*/ +is_writable_dir(dir) + char *dir; +{ + struct stat sb; + + if(stat(dir, &sb) < 0) + /*--- It doesn't exist ---*/ + return(3); + + if(!(sb.st_mode & S_IFDIR)) + /*---- it's not a directory ---*/ + return(2); + + if(can_access(dir, 07)) + return(1); + else + return(0); +} + + + +/*---------------------------------------------------------------------- + Create the mail subdirectory. + + Args: dir -- Name of the directory to create + + Result: Directory is created. Returns 0 on success, else -1 on error + and errno is valid. + ----*/ +create_mail_dir(dir) + char *dir; +{ + if(mkdir(dir, 0700) < 0) + return(-1); + + (void)chmod(dir, 0700); + /* Some systems need this, on others we don't care if it fails */ + (void)chown(dir, getuid(), getgid()); + return(0); +} + + + +/*---------------------------------------------------------------------- + Rename a file + + Args: tmpfname -- Old name of file + fname -- New name of file + + Result: File is renamed. Returns 0 on success, else -1 on error + and errno is valid. + ----*/ +rename_file(tmpfname, fname) + char *tmpfname, *fname; +{ + return(rename(tmpfname, fname)); +} + + + +/*---------------------------------------------------------------------- + Paste together two pieces of a file name path + + Args: pathbuf -- Put the result here + first_part -- of path name + second_part -- of path name + + Result: New path is in pathbuf. No check is made for overflow. Note that + we don't have to check for /'s at end of first_part and beginning + of second_part since multiple slashes are ok. + +BUGS: This is a first stab at dealing with fs naming dependencies, and others +still exist. + ----*/ +void +build_path(pathbuf, first_part, second_part) + char *pathbuf, *first_part, *second_part; +{ + if(!first_part) + strcpy(pathbuf, second_part); + else + sprintf(pathbuf, "%s%s%s", first_part, + (*first_part && first_part[strlen(first_part)-1] != '/') + ? "/" : "", + second_part); +} + + +/*---------------------------------------------------------------------- + Test to see if the given file path is absolute + + Args: file -- file path to test + + Result: TRUE if absolute, FALSE otw + + ----*/ +int +is_absolute_path(path) + char *path; +{ + return(path && (*path == '/' || *path == '~')); +} + + + +/*---------------------------------------------------------------------- + Return pointer to last component of pathname. + + Args: filename -- The pathname. + + Result: Returned pointer points to last component in the input argument. + ----*/ +char * +last_cmpnt(filename) + char *filename; +{ + register char *p = NULL, *q = filename; + + while(q = strchr(q, '/')) + if(*++q) + p = q; + + return(p); +} + + + +/*---------------------------------------------------------------------- + Expand a folder name, taking account of the folders_dir and `~'. + + Args: filename -- The name of the file that is the folder + + Result: The folder name is expanded in place. + Returns 0 and queues status message if unsuccessful. + Input string is overwritten with expanded name. + Returns 1 if successful. + +BUG should limit length to MAXPATH + ----*/ +int +expand_foldername(filename) + char *filename; +{ + char temp_filename[MAXPATH+1]; + + dprint(5, (debugfile, "=== expand_foldername called (%s) ===\n",filename)); + + /* + * We used to check for valid filename chars here if "filename" + * didn't refer to a remote mailbox. This has been rethought + */ + + strcpy(temp_filename, filename); + if(strucmp(temp_filename, "inbox") == 0) { + strcpy(filename, ps_global->VAR_INBOX_PATH == NULL ? "inbox" : + ps_global->VAR_INBOX_PATH); + } else if(temp_filename[0] == '{') { + strcpy(filename, temp_filename); + } else if(ps_global->restricted + && (strindex("./~", temp_filename[0]) != NULL + || srchstr(temp_filename,"/../"))){ + q_status_message(SM_ORDER, 0, 3, "Can only open local folders"); + return(0); + } else if(temp_filename[0] == '*') { + strcpy(filename, temp_filename); + } else if(ps_global->VAR_OPER_DIR && srchstr(temp_filename,"..")){ + q_status_message(SM_ORDER, 0, 3, + "\"..\" not allowed in folder name"); + return(0); + } else if (temp_filename[0] == '~'){ + if(fnexpand(temp_filename, sizeof(temp_filename)) == NULL) { + char *p = strindex(temp_filename, '/'); + if(p != NULL) + *p = '\0'; + q_status_message1(SM_ORDER, 3, 3, + "Error expanding folder name: \"%s\" unknown user", + temp_filename); + return(0); + } + strcpy(filename, temp_filename); + } else if(temp_filename[0] == '/') { + strcpy(filename, temp_filename); + } else if(F_ON(F_USE_CURRENT_DIR, ps_global)){ + strcpy(filename, temp_filename); + } else if(ps_global->VAR_OPER_DIR){ + build_path(filename, ps_global->VAR_OPER_DIR, temp_filename); + } else { + build_path(filename, ps_global->home_dir, temp_filename); + } + dprint(5, (debugfile, "returning \"%s\"\n", filename)); + return(1); +} + + + +struct passwd *getpwnam(); + +/*---------------------------------------------------------------------- + Expand the ~ in a file ala the csh (as home directory) + + Args: buf -- The filename to expand (nothing happens unless begins with ~) + len -- The length of the buffer passed in (expansion is in place) + + Result: Expanded string is returned using same storage as passed in. + If expansion fails, NULL is returned + ----*/ +char * +fnexpand(buf, len) + char *buf; + int len; +{ + struct passwd *pw; + register char *x,*y; + char name[20]; + + if(*buf == '~') { + for(x = buf+1, y = name; *x != '/' && *x != '\0'; *y++ = *x++); + *y = '\0'; + if(x == buf + 1) + pw = getpwuid(getuid()); + else + pw = getpwnam(name); + if(pw == NULL) + return((char *)NULL); + if(strlen(pw->pw_dir) + strlen(buf) > len) { + return((char *)NULL); + } + rplstr(buf, x - buf, pw->pw_dir); + } + return(len ? buf : (char *)NULL); +} + + + +/*---------------------------------------------------------------------- + Filter file names for strange characters + + Args: file -- the file name to check + + Result: Returns NULL if file name is OK + Returns formatted error message if it is not + ----*/ +char * +filter_filename(file, fatal) + char *file; + int *fatal; +{ +#ifdef ALLOW_WEIRD + static char illegal[] = {'\177', '\0'}; +#else + static char illegal[] = {'"', '#', '$', '%', '&', '\'','(', ')','*', + ',', ':', ';', '<', '=', '>', '?', '[', ']', + '\\', '^', '|', '\177', '\0'}; +#endif + static char error[100]; + char ill_file[MAXPATH+1], *ill_char, *ptr, e2[10]; + int i; + + for(ptr = file; *ptr == ' '; ptr++) ; /* leading spaces gone */ + + while(*ptr && (unsigned char)(*ptr) >= ' ' && strindex(illegal, *ptr) == 0) + ptr++; + + *fatal = TRUE; + if(*ptr != '\0') { + if(*ptr == '\n') { + ill_char = "<newline>"; + } else if(*ptr == '\r') { + ill_char = "<carriage return>"; + } else if(*ptr == '\t') { + ill_char = "<tab>"; + *fatal = FALSE; /* just whitespace */ + } else if(*ptr < ' ') { + sprintf(e2, "control-%c", *ptr + '@'); + ill_char = e2; + } else if (*ptr == '\177') { + ill_char = "<del>"; + } else { + e2[0] = *ptr; + e2[1] = '\0'; + ill_char = e2; + *fatal = FALSE; /* less offensive? */ + } + if(!*fatal){ + strcpy(error, ill_char); + } + else if(ptr != file) { + strncpy(ill_file, file, ptr - file); + ill_file[ptr - file] = '\0'; + sprintf(error, + "Character \"%s\" after \"%s\" not allowed in file name", + ill_char, ill_file); + } else { + sprintf(error, + "First character, \"%s\", not allowed in file name", + ill_char); + } + + return(error); + } + + if((i=is_writable_dir(file)) == 0 || i == 1){ + sprintf(error, "\"%s\" is a directory", file); + return(error); + } + + if(ps_global->restricted || ps_global->VAR_OPER_DIR){ + for(ptr = file; *ptr == ' '; ptr++) ; /* leading spaces gone */ + + if((ptr[0] == '.' && ptr[1] == '.') || srchstr(ptr, "/../")){ + sprintf(error, "\"..\" not allowed in filename"); + return(error); + } + } + + return((char *)NULL); +} + + +/*---------------------------------------------------------------------- + Check to see if user is allowed to read or write this folder. + + Args: s -- the name to check + + Result: Returns 1 if OK + Returns 0 and posts an error message if access is denied + ----*/ +int +cntxt_allowed(s) + char *s; +{ + struct variable *vars = ps_global->vars; + int retval = 1; + MAILSTREAM stream; /* fake stream for error message in mm_notify */ + + if(ps_global->restricted + && (strindex("./~", s[0]) || srchstr(s, "/../"))){ + stream.mailbox = s; + mm_notify(&stream, "Restricted mode doesn't allow operation", WARN); + retval = 0; + } + else if(VAR_OPER_DIR + && s[0] != '{' && !(s[0] == '*' && s[1] == '{') + && strucmp(s,ps_global->inbox_name) != 0 + && strcmp(s, ps_global->VAR_INBOX_PATH) != 0){ + char *p, *free_this = NULL; + + p = s; + if(strindex(s, '~')){ + p = strindex(s, '~'); + free_this = (char *)fs_get(strlen(p) + 200); + strcpy(free_this, p); + fnexpand(free_this, strlen(p)+200); + p = free_this; + } + else if(p[0] != '/'){ /* add home dir to relative paths */ + free_this = p = (char *)fs_get(strlen(s) + + strlen(ps_global->home_dir) + 2); + build_path(p, ps_global->home_dir, s); + } + + if(!in_dir(VAR_OPER_DIR, p)){ + char err[200]; + + sprintf(err, "Not allowed outside of %s", VAR_OPER_DIR); + stream.mailbox = p; + mm_notify(&stream, err, WARN); + retval = 0; + } + else if(srchstr(p, "/../")){ /* check for .. in path */ + stream.mailbox = p; + mm_notify(&stream, "\"..\" not allowed in name", WARN); + retval = 0; + } + + if(free_this) + fs_give((void **)&free_this); + } + + return retval; +} + + + +#if defined(USE_QUOTAS) + +/*---------------------------------------------------------------------- + This system doesn't have disk quotas. + Return space left in disk quota on file system which given path is in. + + Args: path - Path name of file or directory on file system of concern + over - pointer to flag that is set if the user is over quota + + Returns: If *over = 0, the number of bytes free in disk quota as per + the soft limit. + If *over = 1, the number of bytes *over* quota. + -1 is returned on an error looking up quota + 0 is returned if there is no quota + +BUG: If there's more than 2.1Gb free this function will break + ----*/ +long +disk_quota(path, over) + char *path; + int *over; +{ + return(0L); +} +#endif /* USE_QUOTAS */ + + + +/*---------------------------------------------------------------------- + Read whole file into memory + + Args: filename -- path name of file to read + + Result: Returns pointer to malloced memory with the contents of the file + or NULL + +This won't work very well if the file has NULLs in it and is mostly +intended for fairly small text files. + ----*/ +char * +read_file(filename) + char *filename; +{ + int fd; + struct stat statbuf; + char *buf; + int nb; + + fd = open(filename, O_RDONLY); + if(fd < 0) + return((char *)NULL); + + fstat(fd, &statbuf); + + buf = fs_get((size_t)statbuf.st_size + 1); + + /* + * On some systems might have to loop here, if one read isn't guaranteed + * to get the whole thing. + */ + if((nb = read(fd, buf, (int)statbuf.st_size)) < 0) + fs_give((void **)&buf); /* NULL's buf */ + else + buf[nb] = '\0'; + + close(fd); + return(buf); +} + + + +/*---------------------------------------------------------------------- + Create a temporary file, the name of which we don't care about +and that goes away when it is closed. Just like ANSI C tmpfile. + ----*/ +FILE * +create_tmpfile() +{ + return(tmpfile()); +} + + + +/*---------------------------------------------------------------------- + Abort with a core dump + ----*/ +void +coredump() +{ + abort(); +} + + + +/*---------------------------------------------------------------------- + Call system gethostname + + Args: hostname -- buffer to return host name in + size -- Size of buffer hostname is to be returned in + + Result: returns 0 if the hostname is correctly set, + -1 if not (and errno is set). + ----*/ +hostname(hostname,size) + char *hostname; + int size; +{ + return(gethostname(hostname, size)); +} + + + +/*---------------------------------------------------------------------- + Get the current host and domain names + + Args: hostname -- buffer to return the hostname in + hsize -- size of buffer above + domainname -- buffer to return domain name in + dsize -- size of buffer above + + Result: The system host and domain names are returned. If the full host + name is akbar.cac.washington.edu then the domainname is + cac.washington.edu. + +On Internet connected hosts this look up uses /etc/hosts and DNS to +figure all this out. On other less well connected machines some other +file may be read. If there is no notion of a domain name the domain +name may be left blank. On a PC where there really isn't a host name +this should return blank strings. The .pinerc will take care of +configuring the domain names. That is, this should only return the +native system's idea of what the names are if the system has such +a concept. + ----*/ +void +getdomainnames(hostname, hsize, domainname, dsize) + char *hostname, *domainname; + int hsize, dsize; +{ + char *dn, hname[MAX_ADDRESS+1]; + struct hostent *he; + char **alias; + char *maybe = NULL; + + gethostname(hname, MAX_ADDRESS); + he = gethostbyname(hname); + hostname[0] = '\0'; + + if(he == NULL) + strncpy(hostname, hname, hsize-1); + else{ + /* + * If no dot in hostname it may be the case that there + * is an alias which is really the fully-qualified + * hostname. This could happen if the administrator has + * (incorrectly) put the unqualified name first in the + * hosts file, for example. The problem with looking for + * an alias with a dot is that now we're guessing, since + * the aliases aren't supposed to be the official hostname. + * We'll compromise and only use an alias if the primary + * name has no dot and exactly one of the aliases has a + * dot. + */ + strncpy(hostname, he->h_name, hsize-1); + if(strindex(hostname, '.') == NULL){ /* no dot in hostname */ + for(alias = he->h_aliases; *alias; alias++){ + if(strindex(*alias, '.') != NULL){ /* found one */ + if(maybe){ /* oops, this is the second one */ + maybe = NULL; + break; + } + else + maybe = *alias; + } + } + + if(maybe) + strncpy(hostname, maybe, hsize-1); + } + } + + hostname[hsize-1] = '\0'; + + + if((dn = strindex(hostname, '.')) != NULL) + strncpy(domainname, dn+1, dsize-1); + else + strncpy(domainname, hostname, dsize-1); + + domainname[dsize-1] = '\0'; +} + + + +/*---------------------------------------------------------------------- + Return canonical form of host name ala c-client (UNIX version). + + Args: host -- The host name + + Result: Canonical form, or input argument (worst case) + ----*/ +char * +canonical_name(host) + char *host; +{ + struct hostent *hent; + char hostname[MAILTMPLEN]; + char tmp[MAILTMPLEN]; + extern char *lcase(); + /* domain literal is easy */ + if (host[0] == '[' && host[(strlen (host))-1] == ']') + return host; + + strcpy (hostname,host); /* UNIX requires lowercase */ + /* lookup name, return canonical form */ + return (hent = gethostbyname (lcase (strcpy (tmp,host)))) ? + hent->h_name : host; +} + + + +/*---------------------------------------------------------------------- + This routine returns 1 if job control is available. Note, thiis + could be some type of fake job control. It doesn't have to be + real BSD-style job control. + ----*/ +have_job_control() +{ + return 1; +} + + +/*---------------------------------------------------------------------- + If we don't have job control, this routine is never called. + ----*/ +stop_process() +{ + SigType (*save_usr2) SIG_PROTO((int)); + + /* + * Since we can't respond to KOD while stopped, the process that sent + * the KOD is going to go read-only. Therefore, we can safely ignore + * any KODs that come in before we are ready to respond... + */ + save_usr2 = signal(SIGUSR2, SIG_IGN); + kill(0, SIGSTOP); + (void)signal(SIGUSR2, save_usr2); +} + + + +/*---------------------------------------------------------------------- + Return string describing the error + + Args: errnumber -- The system error number (errno) + + Result: long string describing the error is returned + ----*/ +char * +error_description(errnumber) + int errnumber; +{ + static char buffer[50+1]; + + if(errnumber >= 0 && errnumber < sys_nerr) + sprintf(buffer, "%.*s", 50, sys_errlist[errnumber]); + else + sprintf(buffer, "Unknown error #%d", errnumber); + + return ( (char *) buffer); +} + + + +/*---------------------------------------------------------------------- + Pull the name out of the gcos field if we have that sort of /etc/passwd + + Args: gcos_field -- The long name or GCOS field to be parsed + logname -- Replaces occurances of & with logname string + + Result: returns pointer to buffer with name + ----*/ +static char * +gcos_name(gcos_field, logname) + char *logname, *gcos_field; +{ + static char fullname[MAX_FULLNAME+1]; + register char *fncp, *gcoscp, *lncp, *end; + + /* full name is all chars up to first ',' (or whole gcos, if no ',') */ + /* replace any & with logname in upper case */ + + for(fncp = fullname, gcoscp= gcos_field, end = fullname + MAX_FULLNAME - 1; + (*gcoscp != ',' && *gcoscp != '\0' && fncp != end); + gcoscp++) { + + if(*gcoscp == '&') { + for(lncp = logname; *lncp; fncp++, lncp++) + *fncp = toupper((unsigned char)(*lncp)); + } else { + *fncp++ = *gcoscp; + } + } + + *fncp = '\0'; + return(fullname); +} + + +/*---------------------------------------------------------------------- + Fill in homedir, login, and fullname for the logged in user. + These are all pointers to static storage so need to be copied + in the caller. + + Args: ui -- struct pointer to pass back answers + + Result: fills in the fields + ----*/ +void +get_user_info(ui) + struct user_info *ui; +{ + struct passwd *unix_pwd; + + unix_pwd = getpwuid(getuid()); + if(unix_pwd == NULL) { + ui->homedir = cpystr(""); + ui->login = cpystr(""); + ui->fullname = cpystr(""); + }else { + ui->homedir = cpystr(unix_pwd->pw_dir); + ui->login = cpystr(unix_pwd->pw_name); + ui->fullname = cpystr(gcos_name(unix_pwd->pw_gecos, unix_pwd->pw_name)); + } +} + + +/*---------------------------------------------------------------------- + Look up a userid on the local system and return rfc822 address + + Args: name -- possible login name on local system + + Result: returns NULL or pointer to alloc'd string rfc822 address. + ----*/ +char * +local_name_lookup(name) + char *name; +{ + struct passwd *pw = getpwnam(name); + + if(pw == NULL){ + char *p; + + for(p = name; *p; p++) + if(isupper((unsigned char)*p)) + break; + + /* try changing it to all lower case */ + if(p && *p){ + char *lcase; + + lcase = cpystr(name); + for(p = lcase; *p; p++) + if(isupper((unsigned char)*p)) + *p = tolower((unsigned char)*p); + + pw = getpwnam(lcase); + + if(pw) + strcpy(name, lcase); + + fs_give((void **)&lcase); + } + } + + if(pw == NULL) + return((char *)NULL); + + return(cpystr(gcos_name(pw->pw_gecos, name))); +} + + + +/*---------------------------------------------------------------------- + Call the system to change the passwd + +It would be nice to talk to the passwd program via a pipe or ptty so the +user interface could be consistent, but we can't count on the the prompts +and responses from the passwd program to be regular so we just let the user +type at the passwd program with some screen space, hope he doesn't scroll +off the top and repaint when he's done. + ----*/ +change_passwd() +{ + char cmd_buf[100]; + + ClearLines(1, ps_global->ttyo->screen_rows - 1); + + MoveCursor(5, 0); + fflush(stdout); + + PineRaw(0); + strcpy(cmd_buf, PASSWD_PROG); + system(cmd_buf); + sleep(3); + PineRaw(1); +} + + + +/*---------------------------------------------------------------------- + Can we display this type/subtype? + + Args: type -- the MIME type to check + subtype -- the MIME subtype + params -- parameters + use_viewer -- tell caller he should run external viewer cmd to view + + Result: Returns: + + MCD_NONE if we can't display this type at all + MCD_INTERNAL if we can display it internally + MCD_EXTERNAL if it can be displayed via an external viewer + + ----*/ +mime_can_display(type, subtype, params) + int type; + char *subtype; + PARAMETER *params; +{ + return((mailcap_can_display(type, subtype, params) + ? MCD_EXTERNAL : MCD_NONE) + | ((type == TYPETEXT || type == TYPEMESSAGE + || MIME_VCARD(type,subtype)) + ? MCD_INTERNAL : MCD_NONE)); +} + + + +/*====================================================================== + pipe + + Initiate I/O to and from a process. These functions are similar to + popen and pclose, but both an incoming stream and an output file are + provided. + + ====*/ + +#ifndef STDIN_FILENO +#define STDIN_FILENO 0 +#endif +#ifndef STDOUT_FILENO +#define STDOUT_FILENO 1 +#endif +#ifndef STDERR_FILENO +#define STDERR_FILENO 2 +#endif + + +/* + * Defs to help fish child's exit status out of wait(2) + */ +#ifdef HAVE_WAIT_UNION +#define WaitType union wait +#ifndef WIFEXITED +#define WIFEXITED(X) (!(X).w_termsig) /* child exit by choice */ +#endif +#ifndef WEXITSTATUS +#define WEXITSTATUS(X) (X).w_retcode /* childs chosen exit value */ +#endif +#else +#define WaitType int +#ifndef WIFEXITED +#define WIFEXITED(X) (!((X) & 0xff)) /* low bits tell how it died */ +#endif +#ifndef WEXITSTATUS +#define WEXITSTATUS(X) (((X) >> 8) & 0xff) /* high bits tell exit value */ +#endif +#endif + + +/* + * Global's to helpsignal handler tell us child's status has changed... + */ +short child_signalled; +short child_jump = 0; +jmp_buf child_state; +int child_pid; + + +/* + * Internal Protos + */ +void pipe_error_cleanup PROTO((PIPE_S **, char *, char *, char *)); +void zot_pipe PROTO((PIPE_S **)); +static SigType pipe_alarm SIG_PROTO((int)); + + + + +/*---------------------------------------------------------------------- + Spawn a child process and optionally connect read/write pipes to it + + Args: command -- string to hand the shell + outfile -- address of pointer containing file to receive output + errfile -- address of pointer containing file to receive error output + mode -- mode for type of shell, signal protection etc... + Returns: pointer to alloc'd PIPE_S on success, NULL otherwise + + The outfile is either NULL, a pointer to a NULL value, or a pointer + to the requested name for the output file. In the pointer-to-NULL case + the caller doesn't care about the name, but wants to see the pipe's + results so we make one up. It's up to the caller to make sure the + free storage containing the name is cleaned up. + + Mode bits serve several purposes. + PIPE_WRITE tells us we need to open a pipe to write the child's + stdin. + PIPE_READ tells us we need to open a pipe to read from the child's + stdout/stderr. *NOTE* Having neither of the above set means + we're not setting up any pipes, just forking the child and exec'ing + the command. Also, this takes precedence over any named outfile. + PIPE_STDERR means we're to tie the childs stderr to the same place + stdout is going. *NOTE* This only makes sense then if PIPE_READ + or an outfile is provided. Also, this takes precedence over any + named errfile. + PIPE_PROT means to protect the child from the usual nasty signals + that might cause premature death. Otherwise, the default signals are + set so the child can deal with the nasty signals in its own way. + PIPE_NOSHELL means we're to exec the command without the aid of + a system shell. *NOTE* This negates the affect of PIPE_USER. + PIPE_USER means we're to try executing the command in the user's + shell. Right now we only look in the environment, but that may get + more sophisticated later. + PIPE_RESET means we reset the terminal mode to what it was before + we started pine and then exec the command. + ----*/ +PIPE_S * +open_system_pipe(command, outfile, errfile, mode, timeout) + char *command; + char **outfile, **errfile; + int mode, timeout; +{ + PIPE_S *syspipe = NULL; + char shellpath[32], *shell; + int p[2], oparentd = -1, ochildd = -1, iparentd = -1, ichildd = -1; + + dprint(5, (debugfile, "Opening pipe: \"%s\" (%s%s%s%s%s%s)\n", command, + (mode & PIPE_WRITE) ? "W":"", (mode & PIPE_READ) ? "R":"", + (mode & PIPE_NOSHELL) ? "N":"", (mode & PIPE_PROT) ? "P":"", + (mode & PIPE_USER) ? "U":"", (mode & PIPE_RESET) ? "T":"")); + + syspipe = (PIPE_S *)fs_get(sizeof(PIPE_S)); + memset(syspipe, 0, sizeof(PIPE_S)); + + /* + * If we're not using the shell's command parsing smarts, build + * argv by hand... + */ + if(mode & PIPE_NOSHELL){ + char **ap, *p; + size_t n; + + /* parse the arguments into argv */ + for(p = command; *p && isspace((unsigned char)(*p)); p++) + ; /* swallow leading ws */ + + if(*p){ + syspipe->args = cpystr(p); + } + else{ + pipe_error_cleanup(&syspipe, "<null>", "execute", + "No command name found"); + return(NULL); + } + + for(p = syspipe->args, n = 2; *p; p++) /* count the args */ + if(isspace((unsigned char)(*p)) + && *(p+1) && !isspace((unsigned char)(*(p+1)))) + n++; + + syspipe->argv = ap = (char **)fs_get(n * sizeof(char *)); + memset(syspipe->argv, 0, n * sizeof(char *)); + + for(p = syspipe->args; *p; ){ /* collect args */ + while(*p && isspace((unsigned char)(*p))) + *p++ = '\0'; + + *ap++ = (*p) ? p : NULL; + while(*p && !isspace((unsigned char)(*p))) + p++; + } + + /* make sure argv[0] exists in $PATH */ + if(can_access_in_path(getenv("PATH"), syspipe->argv[0], + EXECUTE_ACCESS) < 0){ + pipe_error_cleanup(&syspipe, syspipe->argv[0], "access", + error_description(errno)); + return(NULL); + } + } + + /* fill in any output filenames */ + if(!(mode & PIPE_READ)){ + if(outfile && !*outfile) + *outfile = temp_nam(NULL, "pine_p"); /* asked for, but not named? */ + + if(errfile && !*errfile) + *errfile = temp_nam(NULL, "pine_p"); /* ditto */ + } + + /* create pipes */ + if(mode & (PIPE_WRITE | PIPE_READ)){ + if(mode & PIPE_WRITE){ + pipe(p); /* alloc pipe to write child */ + oparentd = p[STDOUT_FILENO]; + ichildd = p[STDIN_FILENO]; + } + + if(mode & PIPE_READ){ + pipe(p); /* alloc pipe to read child */ + iparentd = p[STDIN_FILENO]; + ochildd = p[STDOUT_FILENO]; + } + } + else if(!(mode & PIPE_SILENT)){ + flush_status_messages(0); /* just clean up display */ + ClearScreen(); + fflush(stdout); + } + + if((syspipe->mode = mode) & PIPE_RESET) + PineRaw(0); + +#ifdef SIGCHLD + /* + * Prepare for demise of child. Use SIGCHLD if it's available so + * we can do useful things, like keep the IMAP stream alive, while + * we're waiting on the child. + */ + child_signalled = child_jump = 0; +#endif + + if((syspipe->pid = vfork()) == 0){ + /* reset child's handlers in requested fashion... */ + (void)signal(SIGINT, (mode & PIPE_PROT) ? SIG_IGN : SIG_DFL); + (void)signal(SIGQUIT, (mode & PIPE_PROT) ? SIG_IGN : SIG_DFL); + (void)signal(SIGHUP, (mode & PIPE_PROT) ? SIG_IGN : SIG_DFL); +#ifdef SIGCHLD + (void) signal(SIGCHLD, SIG_DFL); +#endif + + /* if parent isn't reading, and we have a filename to write */ + if(!(mode & PIPE_READ) && outfile){ /* connect output to file */ + int output = creat(*outfile, 0600); + dup2(output, STDOUT_FILENO); + if(mode & PIPE_STDERR) + dup2(output, STDERR_FILENO); + else if(errfile) + dup2(creat(*errfile, 0600), STDERR_FILENO); + } + + if(mode & PIPE_WRITE){ /* connect process input */ + close(oparentd); + dup2(ichildd, STDIN_FILENO); /* tie stdin to pipe */ + close(ichildd); + } + + if(mode & PIPE_READ){ /* connect process output */ + close(iparentd); + dup2(ochildd, STDOUT_FILENO); /* tie std{out,err} to pipe */ + if(mode & PIPE_STDERR) + dup2(ochildd, STDERR_FILENO); + else if(errfile) + dup2(creat(*errfile, 0600), STDERR_FILENO); + + close(ochildd); + } + + if(mode & PIPE_NOSHELL){ + execvp(syspipe->argv[0], syspipe->argv); + } + else{ + if(mode & PIPE_USER){ + char *env, *sh; + if((env = getenv("SHELL")) && (sh = strrchr(env, '/'))){ + shell = sh + 1; + strcpy(shellpath, env); + } + else{ + shell = "csh"; + strcpy(shellpath, "/bin/csh"); + } + } + else{ + shell = "sh"; + strcpy(shellpath, "/bin/sh"); + } + + execl(shellpath, shell, command ? "-c" : 0, command, 0); + } + + fprintf(stderr, "Can't exec %s\nReason: %s", + command, error_description(errno)); + _exit(-1); + } + + if((child_pid = syspipe->pid) > 0){ + syspipe->isig = signal(SIGINT, SIG_IGN); /* Reset handlers to make */ + syspipe->qsig = signal(SIGQUIT, SIG_IGN); /* sure we don't come to */ + syspipe->hsig = signal(SIGHUP, SIG_IGN); /* a premature end... */ + if((syspipe->timeout = timeout) != 0){ + syspipe->alrm = signal(SIGALRM, pipe_alarm); + syspipe->old_timeo = alarm(timeout); + } + + if(mode & PIPE_WRITE){ + close(ichildd); + if(mode & PIPE_DESC) + syspipe->out.d = oparentd; + else + syspipe->out.f = fdopen(oparentd, "w"); + } + + if(mode & PIPE_READ){ + close(ochildd); + if(mode & PIPE_DESC) + syspipe->in.d = iparentd; + else + syspipe->in.f = fdopen(iparentd, "r"); + } + + dprint(5, (debugfile, "PID: %d, COMMAND: %s\n",syspipe->pid,command)); + } + else{ + if(mode & (PIPE_WRITE | PIPE_READ)){ + if(mode & PIPE_WRITE){ + close(oparentd); + close(ichildd); + } + + if(mode & PIPE_READ){ + close(iparentd); + close(ochildd); + } + } + else if(!(mode & PIPE_SILENT)){ + ClearScreen(); + ps_global->mangled_screen = 1; + } + + if(mode & PIPE_RESET) + PineRaw(1); + +#ifdef SIGCHLD + (void) signal(SIGCHLD, SIG_DFL); +#endif + if(outfile) + fs_give((void **) outfile); + + pipe_error_cleanup(&syspipe, command, "fork",error_description(errno)); + } + + return(syspipe); +} + + + +/*---------------------------------------------------------------------- + Write appropriate error messages and cleanup after pipe error + + Args: syspipe -- address of pointer to struct to clean up + cmd -- command we were trying to exec + op -- operation leading up to the exec + res -- result of that operation + + ----*/ +void +pipe_error_cleanup(syspipe, cmd, op, res) + PIPE_S **syspipe; + char *cmd, *op, *res; +{ + q_status_message3(SM_ORDER, 3, 3, "Pipe can't %s \"%.20s\": %s", + op, cmd, res); + dprint(1, (debugfile, "* * PIPE CAN'T %s(%s): %s\n", op, cmd, res)); + zot_pipe(syspipe); +} + + + +/*---------------------------------------------------------------------- + Free resources associated with the given pipe struct + + Args: syspipe -- address of pointer to struct to clean up + + ----*/ +void +zot_pipe(syspipe) + PIPE_S **syspipe; +{ + if((*syspipe)->args) + fs_give((void **) &(*syspipe)->args); + + if((*syspipe)->argv) + fs_give((void **) &(*syspipe)->argv); + + if((*syspipe)->tmp) + fs_give((void **) &(*syspipe)->tmp); + + fs_give((void **)syspipe); +} + + + +/*---------------------------------------------------------------------- + Close pipe previously allocated and wait for child's death + + Args: syspipe -- address of pointer to struct returned by open_system_pipe + Returns: returns exit status of child or -1 if invalid syspipe + ----*/ +int +close_system_pipe(syspipe) + PIPE_S **syspipe; +{ + WaitType stat; + int status; + + if(!(syspipe && *syspipe)) + return(-1); + + if(((*syspipe)->mode) & PIPE_WRITE){ + if(((*syspipe)->mode) & PIPE_DESC){ + if((*syspipe)->out.d >= 0) + close((*syspipe)->out.d); + } + else if((*syspipe)->out.f) + fclose((*syspipe)->out.f); + } + + if(((*syspipe)->mode) & PIPE_READ){ + if(((*syspipe)->mode) & PIPE_DESC){ + if((*syspipe)->in.d >= 0) + close((*syspipe)->in.d); + } + else if((*syspipe)->in.f) + fclose((*syspipe)->in.f); + } + +#ifdef SIGCHLD + { + SigType (*alarm_sig)(); + int old_cue = F_ON(F_SHOW_DELAY_CUE, ps_global); + + /* + * remember the current SIGALRM handler, and make sure it's + * installed when we're finished just in case the longjmp + * out of the SIGCHLD handler caused sleep() to lose it. + * Don't pay any attention to that man behind the curtain. + */ + alarm_sig = signal(SIGALRM, SIG_IGN); + F_SET(F_SHOW_DELAY_CUE, ps_global, 0); + ps_global->noshow_timeout = 1; + while(!child_signalled){ + /* wake up and prod server */ + new_mail(0, 2, ((*syspipe)->mode & PIPE_RESET) + ? NM_NONE : NM_DEFER_SORT); + + if(!child_signalled){ + if(setjmp(child_state) == 0){ + child_jump = 1; /* prepare to wake up */ + sleep(600); /* give it 5mins to happend */ + } + else + our_sigunblock(SIGCHLD); + } + + child_jump = 0; + } + + ps_global->noshow_timeout = 0; + F_SET(F_SHOW_DELAY_CUE, ps_global, old_cue); + (void) signal(SIGALRM, alarm_sig); + } +#endif + + /* + * Call c-client's pid reaper to wait() on the demise of our child, + * then fish out its exit status... + */ + grim_pid_reap_status((*syspipe)->pid, 0, &stat); + status = WIFEXITED(stat) ? WEXITSTATUS(stat) : -1; + + /* + * restore original handlers... + */ + (void)signal(SIGINT, (*syspipe)->isig); + (void)signal(SIGHUP, (*syspipe)->hsig); + (void)signal(SIGQUIT, (*syspipe)->qsig); + + if((*syspipe)->timeout){ + (void)signal(SIGALRM, (*syspipe)->alrm); + alarm((*syspipe)->old_timeo); + child_pid = 0; + } + + if((*syspipe)->mode & PIPE_RESET) /* restore our tty modes */ + PineRaw(1); + + if(!((*syspipe)->mode & (PIPE_WRITE | PIPE_READ | PIPE_SILENT))){ + ClearScreen(); /* No I/O to forked child */ + ps_global->mangled_screen = 1; + } + + zot_pipe(syspipe); + + return(status); +} + + + +static SigType +pipe_alarm SIG_PROTO((int sig)) +{ + if(child_pid) + kill(child_pid, SIGINT); +} + +/*====================================================================== + post_reap + + Manage exit status collection of a child spawned to handle posting + ====*/ + + + +#if defined(BACKGROUND_POST) && defined(SIGCHLD) +/*---------------------------------------------------------------------- + Check to see if we have any posting processes to clean up after + + Args: none + Returns: any finished posting process reaped + ----*/ +post_reap() +{ + WaitType stat; + int r; + + if(ps_global->post && ps_global->post->pid){ + r = waitpid(ps_global->post->pid, &stat, WNOHANG); + if(r == ps_global->post->pid){ + ps_global->post->status = WIFEXITED(stat) ? WEXITSTATUS(stat) : -1; + ps_global->post->pid = 0; + return(1); + } + else if(r < 0 && errno != EINTR){ /* pid's become bogus?? */ + fs_give((void **) &ps_global->post); + } + } + + return(0); +} +#endif + +/*---------------------------------------------------------------------- + Routines used to hand off messages to local agents for sending/posting + + The two exported routines are: + + 1) smtp_command() -- used to get local transport agent to invoke + 2) post_handoff() -- used to pass messages to local posting agent + + ----*/ + + + +/* + * Protos for "sendmail" internal functions + */ +static char *mta_parse_post PROTO((METAENV *, BODY *, char *, char *)); +static long pine_pipe_soutr_nl PROTO((void *, char *)); + + + +/* ---------------------------------------------------------------------- + Figure out command to start local SMTP agent + + Args: errbuf -- buffer for reporting errors (assumed non-NULL) + + Returns an alloc'd copy of the local SMTP agent invocation or NULL + + ----*/ +char * +smtp_command(errbuf) + char *errbuf; +{ +#if defined(SENDMAIL) && defined(SENDMAILFLAGS) + char tmp[256]; + + sprintf(tmp, "%s %s", SENDMAIL, SENDMAILFLAGS); + return(cpystr(tmp)); +#else + strcpy(errbuf, "No default posting command."); + return(NULL); +#endif +} + + + +/*---------------------------------------------------------------------- + Hand off given message to local posting agent + + Args: envelope -- The envelope for the BCC and debugging + header -- The text of the message header + errbuf -- buffer for reporting errors (assumed non-NULL) + + ----*/ +int +mta_handoff(header, body, errbuf) + METAENV *header; + BODY *body; + char *errbuf; +{ + char cmd_buf[256], *cmd = NULL; + + /* + * A bit of complicated policy implemented here. + * There are two posting variables sendmail-path and smtp-server. + * Precedence is in that order. + * They can be set one of 4 ways: fixed, command-line, user, or globally. + * Precedence is in that order. + * Said differently, the order goes something like what's below. + * + * NOTE: the fixed/command-line/user precendence handling is also + * indicated by what's pointed to by ps_global->VAR_*, but since + * that also includes the global defaults, it's not sufficient. + */ + + if(ps_global->FIX_SENDMAIL_PATH + && ps_global->FIX_SENDMAIL_PATH[0]){ + cmd = ps_global->FIX_SENDMAIL_PATH; + } + else if(!(ps_global->FIX_SMTP_SERVER + && ps_global->FIX_SMTP_SERVER[0])){ + if(ps_global->COM_SENDMAIL_PATH + && ps_global->COM_SENDMAIL_PATH[0]){ + cmd = ps_global->COM_SENDMAIL_PATH; + } + else if(!(ps_global->COM_SMTP_SERVER + && ps_global->COM_SMTP_SERVER[0])){ + if(ps_global->USR_SENDMAIL_PATH + && ps_global->USR_SENDMAIL_PATH[0]){ + cmd = ps_global->USR_SENDMAIL_PATH; + } + else if(!(ps_global->USR_SMTP_SERVER + && ps_global->USR_SMTP_SERVER[0])){ + if(ps_global->GLO_SENDMAIL_PATH + && ps_global->GLO_SENDMAIL_PATH[0]){ + cmd = ps_global->GLO_SENDMAIL_PATH; + } +#ifdef DF_SENDMAIL_PATH + /* + * This defines the default method of posting. So, + * unless we're told otherwise use it... + */ + else if(!(ps_global->GLO_SMTP_SERVER + && ps_global->GLO_SMTP_SERVER[0])){ + strcpy(cmd = cmd_buf, DF_SENDMAIL_PATH); + } +#endif + } + } + } + + *errbuf = '\0'; + if(cmd){ + dprint(4, (debugfile, "call_mailer via cmd: %s\n", cmd)); + + (void) mta_parse_post(header, body, cmd, errbuf); + return(1); + } + else + return(0); +} + + + +/*---------------------------------------------------------------------- + Hand off given message to local posting agent + + Args: envelope -- The envelope for the BCC and debugging + header -- The text of the message header + errbuf -- buffer for reporting errors (assumed non-NULL) + + Fork off mailer process and pipe the message into it + Called to post news via Inews when NNTP is unavailable + + ----*/ +char * +post_handoff(header, body, errbuf) + METAENV *header; + BODY *body; + char *errbuf; +{ + char *err = NULL; +#ifdef SENDNEWS + char *s; + + if(s = strstr(header->env->date," (")) /* fix the date format for news */ + *s = '\0'; + + if(err = mta_parse_post(header, body, SENDNEWS, errbuf)) + sprintf(err = errbuf, "News not posted: \"%s\": %s", SENDNEWS, err); + + if(s) + *s = ' '; /* restore the date */ + +#else /* !SENDNEWS */ /* this is the default case */ + sprintf(err = errbuf, "Can't post, NNTP-server must be defined!"); +#endif /* !SENDNEWS */ + return(err); +} + + + +/*---------------------------------------------------------------------- + Hand off message to local MTA; it parses recipients from 822 header + + Args: header -- struct containing header data + body -- struct containing message body data + cmd -- command to use for handoff (%s says where file should go) + errs -- pointer to buf to hold errors + + ----*/ +static char * +mta_parse_post(header, body, cmd, errs) + METAENV *header; + BODY *body; + char *cmd; + char *errs; +{ + char *result = NULL; + PIPE_S *pipe; + + dprint(1, (debugfile, "=== mta_parse_post(%s) ===\n", cmd)); + + if(pipe = open_system_pipe(cmd, &result, NULL, + PIPE_STDERR|PIPE_WRITE|PIPE_PROT|PIPE_NOSHELL|PIPE_DESC, 0)){ + if(!pine_rfc822_output(header, body, pine_pipe_soutr_nl, + (TCPSTREAM *) pipe)) + strcpy(errs, "Error posting."); + + if(close_system_pipe(&pipe) && !*errs){ + sprintf(errs, "Posting program %s returned error", cmd); + if(result) + display_output_file(result, "POSTING ERRORS", errs, 1); + } + } + else + sprintf(errs, "Error running \"%s\"", cmd); + + if(result){ + unlink(result); + fs_give((void **)&result); + } + + return(*errs ? errs : NULL); +} + + +/* + * pine_pipe_soutr - Replacement for tcp_soutr that writes one of our + * pipes rather than a tcp stream + */ +static long +pine_pipe_soutr_nl (stream,s) + void *stream; + char *s; +{ + long rv = T; + char *p; + size_t n; + + while(*s && rv){ + if(n = (p = strstr(s, "\015\012")) ? p - s : strlen(s)) + while((rv = write(((PIPE_S *)stream)->out.d, s, n)) != n) + if(rv < 0){ + if(errno != EINTR){ + rv = 0; + break; + } + } + else{ + s += rv; + n -= rv; + } + + if(p && rv){ + s = p + 2; /* write UNIX EOL */ + while((rv = write(((PIPE_S *)stream)->out.d,"\n",1)) != 1) + if(rv < 0 && errno != EINTR){ + rv = 0; + break; + } + } + else + break; + } + + return(rv); +} + +/* ---------------------------------------------------------------------- + Execute the given mailcap command + + Args: cmd -- the command to execute + image_file -- the file the data is in + needsterminal -- does this command want to take over the terminal? + ----*/ +void +exec_mailcap_cmd(cmd, image_file, needsterminal) +char *cmd; +char *image_file; +int needsterminal; +{ + char *command = NULL, + *result_file = NULL, + *p; + char **r_file_h; + PIPE_S *syspipe; + int mode; + + p = command = (char *)fs_get((32 + strlen(cmd) + (2*strlen(image_file))) + * sizeof(char)); + if(!needsterminal) /* put in background if it doesn't need terminal */ + *p++ = '('; + sprintf(p, "%s ; rm -f %s", cmd, image_file); + p += strlen(p); + if(!needsterminal){ + *p++ = ')'; + *p++ = ' '; + *p++ = '&'; + } + *p++ = '\n'; + *p = '\0'; + dprint(9, (debugfile, "exec_mailcap_cmd: command=%s\n", command)); + + mode = PIPE_RESET; + if(needsterminal == 1) + r_file_h = NULL; + else{ + mode |= PIPE_WRITE | PIPE_STDERR; + result_file = temp_nam(NULL, "pine_cmd"); + r_file_h = &result_file; + } + + if(syspipe = open_system_pipe(command, r_file_h, NULL, mode, 0)){ + close_system_pipe(&syspipe); + if(needsterminal == 1) + q_status_message(SM_ORDER, 0, 4, "VIEWER command completed"); + else if(needsterminal == 2) + display_output_file(result_file, "VIEWER", " command result", 1); + else + display_output_file(result_file, "VIEWER", " command launched", 1); + } + else + q_status_message1(SM_ORDER, 3, 4, "Cannot spawn command : %s", cmd); + + fs_give((void **)&command); + if(result_file) + fs_give((void **)&result_file); +} + + +/* ---------------------------------------------------------------------- + Execute the given mailcap test= cmd + + Args: cmd -- command to execute + Returns exit status + + ----*/ +int +exec_mailcap_test_cmd(cmd) + char *cmd; +{ + PIPE_S *syspipe; + + return((syspipe = open_system_pipe(cmd, NULL, NULL, PIPE_SILENT, 0)) + ? close_system_pipe(&syspipe) : -1); +} + + + +/*====================================================================== + print routines + + Functions having to do with printing on paper and forking of spoolers + + In general one calls open_printer() to start printing. One of + the little print functions to send a line or string, and then + call print_end() when complete. This takes care of forking off a spooler + and piping the stuff down it. No handles or anything here because there's + only one printer open at a time. + + ====*/ + + + +static char *trailer; /* so both open and close_printer can see it */ + +/*---------------------------------------------------------------------- + Open the printer + + Args: desc -- Description of item to print. Should have one trailing blank. + + Return value: < 0 is a failure. + 0 a success. + +This does most of the work of popen so we can save the standard output of the +command we execute and send it back to the user. + ----*/ +int +open_printer(desc) + char *desc; +{ + char command[201], prompt[200]; + int cmd, rc, just_one; + char *p, *init, *nick; + char aname[100]; + char *printer; + int done = 0, i, lastprinter, cur_printer = 0; + HelpType help; + char **list; + static ESCKEY_S ekey[] = { + {'y', 'y', "Y", "Yes"}, + {'n', 'n', "N", "No"}, + {ctrl('P'), 10, "^P", "Prev Printer"}, + {ctrl('N'), 11, "^N", "Next Printer"}, + {-2, 0, NULL, NULL}, + {'c', 'c', "C", "CustomPrint"}, + {KEY_UP, 10, "", ""}, + {KEY_DOWN, 11, "", ""}, + {-1, 0, NULL, NULL}}; +#define PREV_KEY 2 +#define NEXT_KEY 3 +#define CUSTOM_KEY 5 +#define UP_KEY 6 +#define DOWN_KEY 7 + + trailer = NULL; + init = NULL; + nick = NULL; + command[200] = '\0'; + + if(ps_global->VAR_PRINTER == NULL){ + q_status_message(SM_ORDER | SM_DING, 3, 5, + "No printer has been chosen. Use SETUP on main menu to make choice."); + return(-1); + } + + /* Is there just one print command available? */ + just_one = (ps_global->printer_category!=3&&ps_global->printer_category!=2) + || (ps_global->printer_category == 2 + && !(ps_global->VAR_STANDARD_PRINTER + && ps_global->VAR_STANDARD_PRINTER[0] + && ps_global->VAR_STANDARD_PRINTER[1])) + || (ps_global->printer_category == 3 + && !(ps_global->VAR_PERSONAL_PRINT_COMMAND + && ps_global->VAR_PERSONAL_PRINT_COMMAND[0] + && ps_global->VAR_PERSONAL_PRINT_COMMAND[1])); + + if(F_ON(F_CUSTOM_PRINT, ps_global)) + ekey[CUSTOM_KEY].ch = 'c'; /* turn this key on */ + else + ekey[CUSTOM_KEY].ch = -2; /* turn this key off */ + + if(just_one){ + ekey[PREV_KEY].ch = -2; /* turn these keys off */ + ekey[NEXT_KEY].ch = -2; + ekey[UP_KEY].ch = -2; + ekey[DOWN_KEY].ch = -2; + } + else{ + ekey[PREV_KEY].ch = ctrl('P'); /* turn these keys on */ + ekey[NEXT_KEY].ch = ctrl('N'); + ekey[UP_KEY].ch = KEY_UP; + ekey[DOWN_KEY].ch = KEY_DOWN; + /* + * count how many printers in list and find the default in the list + */ + if(ps_global->printer_category == 2) + list = ps_global->VAR_STANDARD_PRINTER; + else + list = ps_global->VAR_PERSONAL_PRINT_COMMAND; + + for(i = 0; list[i]; i++) + if(strcmp(ps_global->VAR_PRINTER, list[i]) == 0) + cur_printer = i; + + lastprinter = i - 1; + } + + help = NO_HELP; + ps_global->mangled_footer = 1; + + while(!done){ + if(init) + fs_give((void **)&init); + + if(trailer) + fs_give((void **)&trailer); + + if(just_one) + printer = ps_global->VAR_PRINTER; + else + printer = list[cur_printer]; + + parse_printer(printer, &nick, &p, &init, &trailer, NULL, NULL); + strncpy(command, p, 200); + fs_give((void **)&p); + sprintf(prompt, "Print %.50s%susing \"%.50s\" ? ", + desc ? desc : "", + (desc && *desc && desc[strlen(desc) - 1] != ' ') ? " " : "", + *nick ? nick : command); + + fs_give((void **)&nick); + + cmd = radio_buttons(prompt, -FOOTER_ROWS(ps_global), + ekey, 'y', 'x', help, RB_NORM); + + switch(cmd){ + case 'y': + q_status_message1(SM_ORDER, 0, 9, + "Printing with command \"%s\"", command); + done++; + break; + + case 10: + cur_printer = (cur_printer>0) + ? (cur_printer-1) + : lastprinter; + break; + + case 11: + cur_printer = (cur_printer<lastprinter) + ? (cur_printer+1) + : 0; + break; + + case 'n': + case 'x': + done++; + break; + + case 'c': + done++; + break; + + default: + break; + } + } + + if(cmd == 'c'){ + if(init) + fs_give((void **)&init); + + if(trailer) + fs_give((void **)&trailer); + + sprintf(prompt, "Enter custom command : "); + command[0] = '\0'; + rc = 1; + help = NO_HELP; + while(rc){ + int flags = OE_APPEND_CURRENT; + + rc = optionally_enter(command, -FOOTER_ROWS(ps_global), 0, + 200, prompt, NULL, help, &flags); + + if(rc == 1){ + cmd = 'x'; + rc = 0; + } + else if(rc == 3) + help = (help == NO_HELP) ? h_custom_print : NO_HELP; + else if(rc == 0){ + removing_trailing_white_space(command); + removing_leading_white_space(command); + q_status_message1(SM_ORDER, 0, 9, + "Printing with command \"%s\"", command); + } + } + } + + if(cmd == 'x' || cmd == 'n'){ + q_status_message(SM_ORDER, 0, 2, "Print cancelled"); + if(init) + fs_give((void **)&init); + + if(trailer) + fs_give((void **)&trailer); + + return(-1); + } + + display_message('x'); + + ps_global->print = (PRINT_S *)fs_get(sizeof(PRINT_S)); + memset(ps_global->print, 0, sizeof(PRINT_S)); + + strcat(strcpy(aname, ANSI_PRINTER), "-no-formfeed"); + if(strucmp(command, ANSI_PRINTER) == 0 + || strucmp(command, aname) == 0){ + /*----------- Printer attached to ansi device ---------*/ + q_status_message(SM_ORDER, 0, 9, + "Printing to attached desktop printer..."); + display_message('x'); + xonxoff_proc(1); /* make sure XON/XOFF used */ + crlf_proc(1); /* AND LF->CR xlation */ + fputs("\033[5i", stdout); + ps_global->print->fp = stdout; + if(strucmp(command, ANSI_PRINTER) == 0){ + /* put formfeed at the end of the trailer string */ + if(trailer){ + int len = strlen(trailer); + + fs_resize((void **)&trailer, len+2); + trailer[len] = '\f'; + trailer[len+1] = '\0'; + } + else + trailer = cpystr("\f"); + } + } + else{ + /*----------- Print by forking off a UNIX command ------------*/ + dprint(4, (debugfile, "Printing using command \"%s\"\n", command)); + ps_global->print->result = temp_nam(NULL, "pine_prt"); + if(ps_global->print->pipe = open_system_pipe(command, + &ps_global->print->result, NULL, + PIPE_WRITE | PIPE_STDERR, 0)){ + ps_global->print->fp = ps_global->print->pipe->out.f; + } + else{ + fs_give((void **)&ps_global->print->result); + q_status_message1(SM_ORDER | SM_DING, 3, 4, + "Error opening printer: %s", + error_description(errno)); + dprint(2, (debugfile, "Error popening printer \"%s\"\n", + error_description(errno))); + if(init) + fs_give((void **)&init); + + if(trailer) + fs_give((void **)&trailer); + + return(-1); + } + } + + ps_global->print->err = 0; + if(init){ + if(*init) + fputs(init, ps_global->print->fp); + + fs_give((void **)&init); + } + + return(0); +} + + + +/*---------------------------------------------------------------------- + Close printer + + If we're piping to a spooler close down the pipe and wait for the process +to finish. If we're sending to an attached printer send the escape sequence. +Also let the user know the result of the print + ----*/ +void +close_printer() +{ + if(trailer){ + if(*trailer) + fputs(trailer, ps_global->print->fp); + + fs_give((void **)&trailer); + } + + if(ps_global->print->fp == stdout) { + fputs("\033[4i", stdout); + fflush(stdout); + if(F_OFF(F_PRESERVE_START_STOP, ps_global)) + xonxoff_proc(0); /* turn off XON/XOFF */ + + crlf_proc(0); /* turn off CF->LF xlantion */ + } else { + (void) close_system_pipe(&ps_global->print->pipe); + display_output_file(ps_global->print->result, "PRINT", NULL, 1); + fs_give((void **)&ps_global->print->result); + } + + fs_give((void **)&ps_global->print); + + q_status_message(SM_ASYNC, 0, 3, "Print command completed"); + display_message('x'); +} + + + +/*---------------------------------------------------------------------- + Print a single character + + Args: c -- char to print + Returns: 1 on success, 0 on ps_global->print->err + ----*/ +int +print_char(c) + int c; +{ + if(!ps_global->print->err && putc(c, ps_global->print->fp) == EOF) + ps_global->print->err = 1; + + return(!ps_global->print->err); +} + + + +/*---------------------------------------------------------------------- + Send a line of text to the printer + + Args: line -- Text to print + + ----*/ + +void +print_text(line) + char *line; +{ + if(!ps_global->print->err && fputs(line, ps_global->print->fp) == EOF) + ps_global->print->err = 1; +} + + + +/*---------------------------------------------------------------------- + printf style formatting with one arg for printer + + Args: line -- The printf control string + a1 -- The 1st argument for printf + ----*/ +void +print_text1(line, a1) + char *line, *a1; +{ + if(!ps_global->print->err + && fprintf(ps_global->print->fp, line, a1) < 0) + ps_global->print->err = 1; +} + + + +/*---------------------------------------------------------------------- + printf style formatting with one arg for printer + + Args: line -- The printf control string + a1 -- The 1st argument for printf + a2 -- The 2nd argument for printf + ----*/ +void +print_text2(line, a1, a2) + char *line, *a1, *a2; +{ + if(!ps_global->print->err + && fprintf(ps_global->print->fp, line, a1, a2) < 0) + ps_global->print->err = 1; +} + + + +/*---------------------------------------------------------------------- + printf style formatting with one arg for printer + + Args: line -- The printf control string + a1 -- The 1st argument for printf + a2 -- The 2nd argument for printf + a3 -- The 3rd argument for printf + ----*/ +void +print_text3(line, a1, a2, a3) + char *line, *a1, *a2, *a3; +{ + if(!ps_global->print->err + && fprintf(ps_global->print->fp, line, a1, a2, a3) < 0) + ps_global->print->err = 1; +} + +#ifdef DEBUG +/*---------------------------------------------------------------------- + Initialize debugging - open the debug log file + + Args: none + + Result: opens the debug logfile for dprints + + Opens the file "~/.pine-debug1. Also maintains .pine-debug[2-4] + by renaming them each time so the last 4 sessions are saved. + ----*/ +void +init_debug() +{ + char nbuf[5]; + char newfname[MAXPATH+1], filename[MAXPATH+1]; + int i, fd; + + if(!(debug || ps_global->debug_imap)) + return; + + for(i = ps_global->debug_nfiles - 1; i > 0; i--){ + build_path(filename, ps_global->home_dir, DEBUGFILE); + strcpy(newfname, filename); + sprintf(nbuf, "%d", i); + strcat(filename, nbuf); + sprintf(nbuf, "%d", i+1); + strcat(newfname, nbuf); + (void)rename_file(filename, newfname); + } + + build_path(filename, ps_global->home_dir, DEBUGFILE); + strcat(filename, "1"); + + debugfile = NULL; + if((fd = open(filename, O_TRUNC|O_RDWR|O_CREAT, 0600)) >= 0) + debugfile = fdopen(fd, "w+"); + + if(debugfile != NULL){ + time_t now = time((time_t *)0); + if(ps_global->debug_flush) + setbuf(debugfile, NULL); + + if(ps_global->debug_nfiles == 0){ + /* + * If no debug files are asked for, make filename a tempfile + * to be used for a record should pine later crash... + */ + if(debug < 9 && !ps_global->debug_flush && ps_global->debug_imap<4) + unlink(filename); + } + + dprint(0, (debugfile, + "Debug output of the Pine program (debug=%d debug_imap=%d). Version %s\n%s\n", + debug, ps_global->debug_imap, pine_version, ctime(&now))); + } +} + + +/*---------------------------------------------------------------------- + Try to save the debug file if we crash in a controlled way + + Args: dfile: pointer to open debug file + + Result: tries to move the appropriate .pine-debugx file to .pine-crash + + Looks through the four .pine-debug files hunting for the one that is + associated with this pine, and then renames it. + ----*/ +void +save_debug_on_crash(dfile) +FILE *dfile; +{ + char nbuf[5], crashfile[MAXPATH+1], filename[MAXPATH+1]; + int i; + struct stat dbuf, tbuf; + time_t now = time((time_t *)0); + + if(!(dfile && fstat(fileno(dfile), &dbuf) != 0)) + return; + + fprintf(dfile, "\nsave_debug_on_crash: Version %s: debug level %d\n", + pine_version, debug); + fprintf(dfile, "\n : %s\n", ctime(&now)); + + build_path(crashfile, ps_global->home_dir, ".pine-crash"); + + fprintf(dfile, "\nAttempting to save debug file to %s\n", crashfile); + fprintf(stderr, + "\n\n Attempting to save debug file to %s\n\n", crashfile); + + /* Blat out last n keystrokes */ + fputs("========== Latest keystrokes ==========\n", dfile); + while((i = key_playback(0)) != -1) + fprintf(dfile, "\t%s\t(0x%04.4x)\n", pretty_command(i), i); + + /* look for existing debug file */ + for(i = 1; i <= ps_global->debug_nfiles; i++){ + build_path(filename, ps_global->home_dir, DEBUGFILE); + sprintf(nbuf, "%d", i); + strcat(filename, nbuf); + if(stat(filename, &tbuf) != 0) + continue; + + /* This must be the current debug file */ + if(tbuf.st_dev == dbuf.st_dev && tbuf.st_ino == dbuf.st_ino){ + rename_file(filename, crashfile); + break; + } + } + + /* if current debug file name not found, write it by hand */ + if(i > ps_global->debug_nfiles){ + FILE *cfp; + char buf[1025]; + + /* + * Copy the debug temp file into the + */ + if(cfp = fopen(crashfile, "w")){ + buf[1024] = '\0'; + fseek(dfile, 0L, 0); + while(fgets(buf, 1025, dfile) && fputs(buf, cfp) != EOF) + ; + + fclose(cfp); + } + } + + fclose(dfile); +} + + +#define CHECK_EVERY_N_TIMES 100 +#define MAX_DEBUG_FILE_SIZE 200000L +/* + * This is just to catch runaway Pines that are looping spewing out + * debugging (and filling up a file system). The stop doesn't have to be + * at all precise, just soon enough to hopefully prevent filling the + * file system. If the debugging level is high (9 for now), then we're + * presumably looking for some problem, so don't truncate. + */ +int +do_debug(debug_fp) +FILE *debug_fp; +{ + static int counter = CHECK_EVERY_N_TIMES; + static int ok = 1; + long filesize; + + if(debug == DEFAULT_DEBUG + && !ps_global->debug_flush + && !ps_global->debug_timestamp + && ps_global->debug_imap < 2 + && ok + && --counter <= 0){ + if((filesize = fp_file_size(debug_fp)) != -1L) + ok = (unsigned long)filesize < (unsigned long)MAX_DEBUG_FILE_SIZE; + + counter = CHECK_EVERY_N_TIMES; + if(!ok){ + fprintf(debug_fp, "\n\n --- No more debugging ---\n"); + fprintf(debug_fp, + " (debug file growing too large - over %ld bytes)\n\n", + MAX_DEBUG_FILE_SIZE); + fflush(debug_fp); + } + } + + if(ok && ps_global->debug_timestamp) + fprintf(debug_fp, "\n%s\n", debug_time(0)); + + return(ok); +} + + +/* + * Returns a pointer to static string for a timestamp. + * + * If timestamp is set .subseconds are added if available. + * If include_date is set the date is appended. + */ +char * +debug_time(include_date) + int include_date; +{ + time_t t; + struct tm *tm_now; + struct timeval tp; + struct timezone tzp; + static char timestring[23]; + char subsecond[8]; + char datestr[7]; + + if(gettimeofday(&tp, &tzp) == 0){ + t = (time_t)tp.tv_sec; + if(include_date){ + tm_now = localtime(&t); + sprintf(datestr, " %d/%d", tm_now->tm_mon+1, tm_now->tm_mday); + } + else + datestr[0] = '\0'; + + if(ps_global->debug_timestamp) + sprintf(subsecond, ".%06ld", tp.tv_usec); + else + subsecond[0] = '\0'; + + sprintf(timestring, "%.8s%s%s", ctime(&t)+11, subsecond, datestr); + } + else + timestring[0] = '\0'; + + return(timestring); +} +#endif /* DEBUG */ + + +/* + * Fills in the passed in structure with the current time. + * + * Returns 0 if ok + * -1 if can't do it + */ +int +get_time(our_time_val) + TIMEVAL_S *our_time_val; +{ + struct timeval tp; + struct timezone tzp; + + if(gettimeofday(&tp, &tzp) == 0){ + our_time_val->sec = tp.tv_sec; + our_time_val->usec = tp.tv_usec; + return 0; + } + else + return -1; +} + + +/* + * Returns the difference between the two values, in microseconds. + * Value returned is first - second. + */ +long +time_diff(first, second) + TIMEVAL_S *first, + *second; +{ + return(1000000L*(first->sec - second->sec) + (first->usec - second->usec)); +} + + + +/*====================================================================== + Things having to do with reading from the tty driver and keyboard + - initialize tty driver and reset tty driver + - read a character from terminal with keyboard escape seqence mapping + - initialize keyboard (keypad or such) and reset keyboard + - prompt user for a line of input + - read a command from keyboard with timeouts. + + ====*/ + + +/* + * Helpful definitions + */ +#define RETURN_CH(X) return(key_recorder((X))) +/* + * Should really be using pico's TERM's t_getchar to read a character but + * we're just calling ttgetc directly for now. Ttgetc is the same as + * t_getchar whenever we use it so we're avoiding the trouble of initializing + * the TERM struct and calling ttgetc directly. + */ +#define READ_A_CHAR() ttgetc(NO_OP_COMMAND, key_recorder, read_bail) + + +/* + * Internal prototypes + */ +int pine_simple_ttgetc PROTO((int (*)(), void (*)())); +void line_paint PROTO((int, int *)); +int process_config_input PROTO((int *)); +int check_for_timeout PROTO((int)); +void read_bail PROTO((void)); + + +/*---------------------------------------------------------------------- + Initialize the tty driver to do single char I/O and whatever else (UNIX) + + Args: struct pine + + Result: tty driver is put in raw mode so characters can be read one + at a time. Returns -1 if unsuccessful, 0 if successful. + +Some file descriptor voodoo to allow for pipes across vforks. See +open_mailer for details. + ----------------------------------------------------------------------*/ +init_tty_driver(ps) + struct pine *ps; +{ +#ifdef MOUSE + if(F_ON(F_ENABLE_MOUSE, ps_global)) + init_mouse(); +#endif /* MOUSE */ + + /* turn off talk permission by default */ + + if(F_ON(F_ALLOW_TALK, ps)) + allow_talk(ps); + else + disallow_talk(ps); + + return(PineRaw(1)); +} + + + +/*---------------------------------------------------------------------- + Set or clear the specified tty mode + + Args: ps -- struct pine + mode -- mode bits to modify + clear -- whether or not to clear or set + + Result: tty driver mode change. + ----------------------------------------------------------------------*/ +void +tty_chmod(ps, mode, func) + struct pine *ps; + int mode; + int func; +{ + char *tty_name; + int new_mode; + struct stat sbuf; + static int saved_mode = -1; + + /* if no problem figuring out tty's name & mode? */ + if((((tty_name = (char *) ttyname(STDIN_FD)) != NULL + && fstat(STDIN_FD, &sbuf) == 0) + || ((tty_name = (char *) ttyname(STDOUT_FD)) != NULL + && fstat(STDOUT_FD, &sbuf) == 0)) + && !(func == TMD_RESET && saved_mode < 0)){ + new_mode = (func == TMD_RESET) + ? saved_mode + : (func == TMD_CLEAR) + ? (sbuf.st_mode & ~mode) + : (sbuf.st_mode | mode); + /* assign tty new mode */ + if(chmod(tty_name, new_mode) == 0){ + if(func == TMD_RESET) /* forget we knew */ + saved_mode = -1; + else if(saved_mode < 0) + saved_mode = sbuf.st_mode; /* remember original */ + } + } +} + + + +/*---------------------------------------------------------------------- + End use of the tty, put it back into it's normal mode (UNIX) + + Args: ps -- struct pine + + Result: tty driver mode change. + ----------------------------------------------------------------------*/ +void +end_tty_driver(ps) + struct pine *ps; +{ + ps = ps; /* get rid of unused parameter warning */ + +#ifdef MOUSE + end_mouse(); +#endif /* MOUSE */ + fflush(stdout); + dprint(2, (debugfile, "about to end_tty_driver\n")); + + tty_chmod(ps, 0, TMD_RESET); + PineRaw(0); +} + + + +/*---------------------------------------------------------------------- + Actually set up the tty driver (UNIX) + + Args: state -- which state to put it in. 1 means go into raw, 0 out of + + Result: returns 0 if successful and < 0 if not. + ----*/ + +PineRaw(state) +int state; +{ + int result; + + result = Raw(state); + + if(result == 0 && state == 1){ + /* + * Only go into 8 bit mode if we are doing something other + * than plain ASCII. This will save the folks that have + * their parity on their serial lines wrong thr trouble of + * getting it right + */ + if(ps_global->VAR_CHAR_SET && ps_global->VAR_CHAR_SET[0] && + strucmp(ps_global->VAR_CHAR_SET, "us-ascii")) + bit_strip_off(); + +#ifdef DEBUG + if(debug < 9) /* only on if full debugging set */ +#endif + quit_char_off(); + ps_global->low_speed = ttisslow(); + crlf_proc(0); + xonxoff_proc(F_ON(F_PRESERVE_START_STOP, ps_global)); + } + + return(result); +} + + +#ifdef RESIZING +jmp_buf winch_state; +int winch_occured = 0; +int ready_for_winch = 0; +#endif + +/*---------------------------------------------------------------------- + This checks whether or not a character (UNIX) + is ready to be read, or it times out. + + Args: time_out -- number of seconds before it will timeout + + Result: Returns a NO_OP_IDLE or a NO_OP_COMMAND if the timeout expires + before input is available, or a KEY_RESIZE if a resize event + occurs, or READY_TO_READ if input is available before the timeout. + ----*/ +int +check_for_timeout(time_out) + int time_out; +{ + int res; + + fflush(stdout); + +#ifdef RESIZING + if(!winch_occured){ + if(setjmp(winch_state) != 0){ + winch_occured = 1; + ready_for_winch = 0; + + /* + * Need to unblock signal after longjmp from handler, because + * signal is normally unblocked upon routine exit from the handler. + */ + our_sigunblock(SIGWINCH); + } + else + ready_for_winch = 1; + } + + if(winch_occured){ + winch_occured = ready_for_winch = 0; + fix_windsize(ps_global); + return(KEY_RESIZE); + } +#endif /* RESIZING */ + + switch(res = input_ready(time_out)){ + case BAIL_OUT: + read_bail(); /* non-tragic exit */ + /* NO RETURN */ + + case PANIC_NOW: + panic1("Select error: %s\n", error_description(errno)); + /* NO RETURN */ + + case READ_INTR: + res = NO_OP_COMMAND; + /* fall through */ + + case NO_OP_IDLE: + case NO_OP_COMMAND: + case READY_TO_READ: +#ifdef RESIZING + ready_for_winch = 0; +#endif + return(res); + } +} + + + +/*---------------------------------------------------------------------- + Read input characters with lots of processing for arrow keys and such (UNIX) + + Args: time_out -- The timeout to for the reads + + Result: returns the character read. Possible special chars. + + This deals with function and arrow keys as well. + + The idea is that this routine handles all escape codes so it done in + only one place. Especially so the back arrow key can work when entering + things on a line. Also so all function keys can be disabled and not + cause weird things to happen. + ---*/ +int +read_char(time_out) + int time_out; +{ + int ch, status, cc; + + /* Get input from initial-keystrokes */ + if(process_config_input(&ch)) + return(ch); + + /* + * We only check for timeouts at the start of read_char, not in the + * middle of escape sequences. + */ + if((ch = check_for_timeout(time_out)) != READY_TO_READ) + goto done; + + ps_global->time_of_last_input = time((time_t *)0); + + switch(status = kbseq(pine_simple_ttgetc, key_recorder, read_bail, &ch)){ + case KEY_DOUBLE_ESC: + /* + * Special hack to get around comm devices eating control characters. + */ + if(check_for_timeout(5) != READY_TO_READ){ + ch = KEY_JUNK; /* user typed ESC ESC, then stopped */ + goto done; + } + else + ch = READ_A_CHAR(); + + ch &= 0x7f; + if(isdigit((unsigned char)ch)){ + int n = 0, i = ch - '0'; + + if(i < 0 || i > 2){ + ch = KEY_JUNK; + goto done; /* bogus literal char value */ + } + + while(n++ < 2){ + if(check_for_timeout(5) != READY_TO_READ + || (!isdigit((unsigned char) (ch = READ_A_CHAR())) + || (n == 1 && i == 2 && ch > '5') + || (n == 2 && i == 25 && ch > '5'))){ + ch = KEY_JUNK; /* user typed ESC ESC #, stopped */ + goto done; + } + + i = (i * 10) + (ch - '0'); + } + + ch = i; + } + else{ + if(islower((unsigned char)ch)) /* canonicalize if alpha */ + ch = toupper((unsigned char)ch); + + ch = (isalpha((unsigned char)ch) || ch == '@' + || (ch >= '[' && ch <= '_')) + ? ctrl(ch) : ((ch == SPACE) ? ctrl('@'): ch); + } + + goto done; + +#ifdef MOUSE + case KEY_XTERM_MOUSE: + if(mouseexist()){ + /* + * Special hack to get mouse events from an xterm. + * Get the details, then pass it past the keymenu event + * handler, and then to the installed handler if there + * is one... + */ + static int down = 0; + int x, y, button; + unsigned cmd; + + clear_cursor_pos(); + button = READ_A_CHAR() & 0x03; + + x = READ_A_CHAR() - '!'; + y = READ_A_CHAR() - '!'; + + ch = NO_OP_COMMAND; + if(button == 0){ /* xterm button 1 down */ + down = 1; + if(checkmouse(&cmd, 1, x, y)) + ch = (int)cmd; + } + else if (down && button == 3){ + down = 0; + if(checkmouse(&cmd, 0, x, y)) + ch = (int)cmd; + } + + goto done; + } + + break; +#endif /* MOUSE */ + + case KEY_UP : + case KEY_DOWN : + case KEY_RIGHT : + case KEY_LEFT : + case KEY_PGUP : + case KEY_PGDN : + case KEY_HOME : + case KEY_END : + case KEY_DEL : + case PF1 : + case PF2 : + case PF3 : + case PF4 : + case PF5 : + case PF6 : + case PF7 : + case PF8 : + case PF9 : + case PF10 : + case PF11 : + case PF12 : + dprint(9, (debugfile, "Read char returning: %d %s\n", + status, pretty_command(status))); + return(status); + + case KEY_SWALLOW_Z: + status = KEY_JUNK; + case KEY_SWAL_UP: + case KEY_SWAL_DOWN: + case KEY_SWAL_LEFT: + case KEY_SWAL_RIGHT: + do + if(check_for_timeout(2) != READY_TO_READ){ + status = KEY_JUNK; + break; + } + while(!strchr("~qz", READ_A_CHAR())); + ch = (status == KEY_JUNK) ? status : status - (KEY_SWAL_UP - KEY_UP); + goto done; + + case KEY_KERMIT: + do{ + cc = ch; + if(check_for_timeout(2) != READY_TO_READ){ + status = KEY_JUNK; + break; + } + else + ch = READ_A_CHAR(); + }while(cc != '\033' && ch != '\\'); + + ch = KEY_JUNK; + goto done; + + case BADESC: + ch = KEY_JUNK; + goto done; + + case 0: /* regular character */ + default: + /* + * we used to strip (ch &= 0x7f;), but this seems much cleaner + * in the face of line noise and has the benefit of making it + * tougher to emit mistakenly labeled MIME... + */ + if((ch & 0x80) && (!ps_global->VAR_CHAR_SET + || !strucmp(ps_global->VAR_CHAR_SET, "US-ASCII"))){ + dprint(9, (debugfile, "Read char returning: %d %s\n", + status, pretty_command(status))); + return(KEY_JUNK); + } + else if(ch == ctrl('Z')){ + dprint(9, (debugfile, "Read char calling do_suspend\n")); + return(do_suspend()); + } + + + done: + dprint(9, (debugfile, "Read char returning: %d %s\n", + ch, pretty_command(ch))); + return(ch); + } +} + + +/*---------------------------------------------------------------------- + Reading input somehow failed and we need to shutdown now + + Args: none + + Result: pine exits + + ---*/ +void +read_bail() +{ + end_signals(1); + if(ps_global->inbox_stream){ + if(ps_global->inbox_stream == ps_global->mail_stream) + ps_global->mail_stream = NULL; + + if(!ps_global->inbox_stream->lock) /* shouldn't be... */ + pine_close_stream(ps_global->inbox_stream); + } + + if(ps_global->mail_stream && !ps_global->mail_stream->lock) + pine_close_stream(ps_global->mail_stream); + + end_keyboard(F_ON(F_USE_FK,ps_global)); + end_tty_driver(ps_global); + if(filter_data_file(0)) + unlink(filter_data_file(0)); + + exit(0); +} + + +int +pine_simple_ttgetc(fi, fv) + int (*fi)(); + void (*fv)(); +{ + int ch; + +#ifdef RESIZING + if(!winch_occured){ + if(setjmp(winch_state) != 0){ + winch_occured = 1; + ready_for_winch = 0; + + /* + * Need to unblock signal after longjmp from handler, because + * signal is normally unblocked upon routine exit from the handler. + */ + our_sigunblock(SIGWINCH); + } + else + ready_for_winch = 1; + } + + if(winch_occured){ + winch_occured = ready_for_winch = 0; + fix_windsize(ps_global); + return(KEY_RESIZE); + } +#endif /* RESIZING */ + + ch = simple_ttgetc(fi, fv); + +#ifdef RESIZING + ready_for_winch = 0; +#endif + + return(ch); +} + + + +extern char term_name[]; +/* ------------------------------------------------------------------- + Set up the keyboard -- usually enable some function keys (UNIX) + + Args: struct pine + +So far all we do here is turn on keypad mode for certain terminals + +Hack for NCSA telnet on an IBM PC to put the keypad in the right mode. +This is the same for a vtXXX terminal or [zh][12]9's which we have +a lot of at UW + ----*/ +void +init_keyboard(use_fkeys) + int use_fkeys; +{ + if(use_fkeys && (!strucmp(term_name,"vt102") + || !strucmp(term_name,"vt100"))) + printf("\033\133\071\071\150"); +} + + + +/*---------------------------------------------------------------------- + Clear keyboard, usually disable some function keys (UNIX) + + Args: pine state (terminal type) + + Result: keyboard state reset + ----*/ +void +end_keyboard(use_fkeys) + int use_fkeys; +{ + if(use_fkeys && (!strcmp(term_name, "vt102") + || !strcmp(term_name, "vt100"))){ + printf("\033\133\071\071\154"); + fflush(stdout); + } +} + + +#ifdef _WINDOWS +#line 3 "osdep/termin.gen" + +static int g_mc_row, g_mc_col; + +int pcpine_oe_cursor PROTO((int, long)); +#endif + + +/* + * Generic tty input routines + */ + + +/*---------------------------------------------------------------------- + Read a character from keyboard with timeout + Input: none + + Result: Returns command read via read_char + Times out and returns a null command every so often + + Calculates the timeout for the read, and does a few other house keeping +things. The duration of the timeout is set in pine.c. + ----------------------------------------------------------------------*/ +int +read_command() +{ + int ch, tm = 0; + long dtime; + + cancel_busy_alarm(-1); + tm = (messages_queued(&dtime) > 1) ? (int)dtime : timeo; + + /* + * Before we sniff at the input queue, make sure no external event's + * changed our picture of the message sequence mapping. If so, + * recalculate the dang thing and run thru whatever processing loop + * we're in again... + */ + if(ps_global->expunge_count){ + q_status_message3(SM_ORDER, 3, 3, + "%s message%s expunged from folder \"%s\"", + long2string(ps_global->expunge_count), + plural(ps_global->expunge_count), + pretty_fn(ps_global->cur_folder)); + ps_global->expunge_count = 0L; + display_message('x'); + } + + if(ps_global->inbox_expunge_count){ + q_status_message3(SM_ORDER, 3, 3, + "%s message%s expunged from folder \"%s\"", + long2string(ps_global->inbox_expunge_count), + plural(ps_global->inbox_expunge_count), + pretty_fn(ps_global->inbox_name)); + ps_global->inbox_expunge_count = 0L; + display_message('x'); + } + + if(ps_global->mail_box_changed && ps_global->new_mail_count){ + dprint(2, (debugfile, "Noticed %ld new msgs! \n", + ps_global->new_mail_count)); + return(NO_OP_COMMAND); /* cycle thru so caller can update */ + } + + ch = read_char(tm); + dprint(9, (debugfile, "Read command returning: %d %s\n", ch, + pretty_command(ch))); + if(ch != NO_OP_COMMAND && ch != NO_OP_IDLE && ch != KEY_RESIZE) + zero_new_mail_count(); + +#ifdef BACKGROUND_POST + /* + * Any expired children to report on? + */ + if(ps_global->post && ps_global->post->pid == 0){ + int winner = 0; + + if(ps_global->post->status < 0){ + q_status_message(SM_ORDER | SM_DING, 3, 3, "Abysmal failure!"); + } + else{ + (void) pine_send_status(ps_global->post->status, + ps_global->post->fcc, tmp_20k_buf, + &winner); + q_status_message(SM_ORDER | (winner ? 0 : SM_DING), 3, 3, + tmp_20k_buf); + + } + + if(!winner) + q_status_message(SM_ORDER, 0, 3, + "Re-send via \"Compose\" then \"Yes\" to \"Continue INTERRUPTED?\""); + + if(ps_global->post->fcc) + fs_give((void **) &ps_global->post->fcc); + + fs_give((void **) &ps_global->post); + } +#endif + + return(ch); +} + + + + +/* + * + */ +static struct display_line { + int row, col; /* where display starts */ + int dlen; /* length of display line */ + char *dl; /* line on display */ + char *vl; /* virtual line */ + int vlen; /* length of virtual line */ + int vused; /* length of virtual line in use */ + int vbase; /* first virtual char on display */ +} dline; + + + +static struct key oe_keys[] = + {{"^G","Help",KS_SCREENHELP}, {"^C","Cancel",KS_NONE}, + {"^T","xxx",KS_NONE}, {"Ret","Accept",KS_NONE}, + {NULL,NULL,KS_NONE}, {NULL,NULL,KS_NONE}, + {NULL,NULL,KS_NONE}, {NULL,NULL,KS_NONE}, + {NULL,NULL,KS_NONE}, {NULL,NULL,KS_NONE}, + {NULL,NULL,KS_NONE}, {NULL,NULL,KS_NONE}}; +INST_KEY_MENU(oe_keymenu, oe_keys); +#define OE_HELP_KEY 0 +#define OE_CANCEL_KEY 1 +#define OE_CTRL_T_KEY 2 +#define OE_ENTER_KEY 3 + + +/*---------------------------------------------------------------------- + Prompt user for a string in status line with various options + + Args: string -- the buffer result is returned in, and original string (if + any) is passed in. + y_base -- y position on screen to start on. 0,0 is upper left + negative numbers start from bottom + x_base -- column position on screen to start on. 0,0 is upper left + field_len -- Maximum length of string to accept + prompt -- The string to prompt with + escape_list -- pointer to array of ESCKEY_S's. input chars matching + those in list return value from list. + help -- Arrary of strings for help text in bottom screen lines + flags -- pointer (because some are return values) to flags + OE_USER_MODIFIED - Set on return if user modified buffer + OE_DISALLOW_CANCEL - No cancel in menu. + OE_DISALLOW_HELP - No help in menu. + OE_KEEP_TRAILING_SPACE - Allow trailing space. + OE_SEQ_SENSITIVE - Caller is sensitive to sequence + number changes. + OE_APPEND_CURRENT - String should not be truncated + before accepting user input. + OE_PASSWD - Don't echo on screen. + + Result: editing input string + returns -1 unexpected errors + returns 0 normal entry typed (editing and return or PF2) + returns 1 typed ^C or PF2 (cancel) + returns 3 typed ^G or PF1 (help) + returns 4 typed ^L for a screen redraw + + WARNING: Care is required with regard to the escape_list processing. + The passed array is terminated with an entry that has ch = -1. + Function key labels and key strokes need to be setup externally! + Traditionally, a return value of 2 is used for ^T escapes. + + Unless in escape_list, tabs are trapped by isprint(). +This allows near full weemacs style editing in the line + ^A beginning of line + ^E End of line + ^R Redraw line + ^G Help + ^F forward + ^B backward + ^D delete +----------------------------------------------------------------------*/ + +optionally_enter(string, y_base, x_base, field_len, + prompt, escape_list, help, flags) + char *string, *prompt; + ESCKEY_S *escape_list; + HelpType help; + int x_base, y_base, field_len; + int *flags; +{ + register char *s2; + register int field_pos; + int i, j, return_v, cols, ch, prompt_len, too_thin, + real_y_base, km_popped, passwd; + char *saved_original = NULL, *k, *kb; + char *kill_buffer = NULL; + char **help_text; + int fkey_table[12]; + struct key_menu *km; + bitmap_t bitmap; + COLOR_PAIR *lastc = NULL, *promptc = NULL; + struct variable *vars = ps_global->vars; +#ifdef _WINDOWS + int cursor_shown; +#endif + + dprint(5, (debugfile, "=== optionally_enter called ===\n")); + dprint(9, (debugfile, "string:\"%s\" y:%d x:%d length: %d append: %d\n", + string, x_base, y_base, field_len, + (flags && *flags & OE_APPEND_CURRENT))); + dprint(9, (debugfile, "passwd:%d prompt:\"%s\" label:\"%s\"\n", + (flags && *flags & OE_PASSWD), + prompt, (escape_list && escape_list[0].ch != -1) + ? escape_list[0].label: "")); + +#ifdef _WINDOWS + if (mswin_usedialog ()) { + MDlgButton button_list[12]; + int b; + int i; + + memset (&button_list, 0, sizeof (MDlgButton) * 12); + b = 0; + for (i = 0; escape_list && escape_list[i].ch != -1 && i < 11; ++i) { + if (escape_list[i].name != NULL + && escape_list[i].ch > 0 && escape_list[i].ch < 256) { + button_list[b].ch = escape_list[i].ch; + button_list[b].rval = escape_list[i].rval; + button_list[b].name = escape_list[i].name; + button_list[b].label = escape_list[i].label; + ++b; + } + } + button_list[b].ch = -1; + + + help_text = get_help_text (help); + return_v = mswin_dialog (prompt, string, field_len, + (flags && *flags & OE_APPEND_CURRENT), + (flags && *flags & OE_PASSWD), + button_list, + help_text, flags ? *flags : OE_NONE); + free_list_array (&help_text); + return (return_v); + } +#endif + + suspend_busy_alarm(); + cols = ps_global->ttyo->screen_cols; + prompt_len = strlen(prompt); + too_thin = 0; + km_popped = 0; + if(y_base > 0) { + real_y_base = y_base; + } else { + real_y_base= y_base + ps_global->ttyo->screen_rows; + if(real_y_base < 2) + real_y_base = ps_global->ttyo->screen_rows; + } + + flush_ordered_messages(); + mark_status_dirty(); + if(flags && *flags & OE_APPEND_CURRENT) /* save a copy in case of cancel */ + saved_original = cpystr(string); + + /* + * build the function key mapping table, skipping predefined keys... + */ + memset(fkey_table, NO_OP_COMMAND, 12 * sizeof(int)); + for(i = 0, j = 0; escape_list && escape_list[i].ch != -1 && i+j < 12; i++){ + if(i+j == OE_HELP_KEY) + j++; + + if(i+j == OE_CANCEL_KEY) + j++; + + if(i+j == OE_ENTER_KEY) + j++; + + fkey_table[i+j] = escape_list[i].ch; + } + +#if defined(HELPFILE) + help_text = (help != NO_HELP) ? get_help_text(help) : (char **)NULL; +#else + help_text = help; +#endif + if(help_text){ /*---- Show help text -----*/ + int width = ps_global->ttyo->screen_cols - x_base; + + if(FOOTER_ROWS(ps_global) == 1){ + km_popped++; + FOOTER_ROWS(ps_global) = 3; + clearfooter(ps_global); + + y_base = -3; + real_y_base = y_base + ps_global->ttyo->screen_rows; + } + + for(j = 0; j < 2 && help_text[j]; j++){ + MoveCursor(real_y_base + 1 + j, x_base); + CleartoEOLN(); + + if(width < strlen(help_text[j])){ + char *tmp = fs_get((width + 1) * sizeof(char)); + strncpy(tmp, help_text[j], width); + tmp[width] = '\0'; + PutLine0(real_y_base + 1 + j, x_base, tmp); + fs_give((void **)&tmp); + } + else + PutLine0(real_y_base + 1 + j, x_base, help_text[j]); + } + +#if defined(HELPFILE) + free_list_array(&help_text); +#endif + + } else { + clrbitmap(bitmap); + clrbitmap((km = &oe_keymenu)->bitmap); /* force formatting */ + if(!(flags && (*flags) & OE_DISALLOW_HELP)) + setbitn(OE_HELP_KEY, bitmap); + + setbitn(OE_ENTER_KEY, bitmap); + if(!(flags && (*flags) & OE_DISALLOW_CANCEL)) + setbitn(OE_CANCEL_KEY, bitmap); + + setbitn(OE_CTRL_T_KEY, bitmap); + + /*---- Show the usual possible keys ----*/ + for(i=0,j=0; escape_list && escape_list[i].ch != -1 && i+j < 12; i++){ + if(i+j == OE_HELP_KEY) + j++; + + if(i+j == OE_CANCEL_KEY) + j++; + + if(i+j == OE_ENTER_KEY) + j++; + + oe_keymenu.keys[i+j].label = escape_list[i].label; + oe_keymenu.keys[i+j].name = escape_list[i].name; + setbitn(i+j, bitmap); + } + + for(i = i+j; i < 12; i++) + if(!(i == OE_HELP_KEY || i == OE_ENTER_KEY || i == OE_CANCEL_KEY)) + oe_keymenu.keys[i].name = NULL; + + draw_keymenu(km, bitmap, cols, 1-FOOTER_ROWS(ps_global), 0, FirstMenu); + } + + if(pico_usingcolor() && VAR_PROMPT_FORE_COLOR && + VAR_PROMPT_BACK_COLOR && + pico_is_good_color(VAR_PROMPT_FORE_COLOR) && + pico_is_good_color(VAR_PROMPT_BACK_COLOR)){ + lastc = pico_get_cur_color(); + if(lastc){ + promptc = new_color_pair(VAR_PROMPT_FORE_COLOR, + VAR_PROMPT_BACK_COLOR); + (void)pico_set_colorp(promptc, PSC_NONE); + } + } + else + StartInverse(); + + /* + * if display length isn't wide enough to support input, + * shorten up the prompt... + */ + if((dline.dlen = cols - (x_base + prompt_len + 1)) < 5){ + prompt_len += (dline.dlen - 5); /* adding negative numbers */ + prompt -= (dline.dlen - 5); /* subtracting negative numbers */ + dline.dlen = 5; + } + + dline.dl = fs_get((size_t)dline.dlen + 1); + memset((void *)dline.dl, 0, (size_t)(dline.dlen + 1) * sizeof(char)); + dline.row = real_y_base; + dline.col = x_base + prompt_len; + dline.vl = string; + dline.vlen = --field_len; /* -1 for terminating NULL */ + dline.vbase = field_pos = 0; + +#ifdef _WINDOWS + cursor_shown = mswin_showcaret(1); +#endif + + PutLine0(real_y_base, x_base, prompt); + /* make sure passed in string is shorter than field_len */ + /* and adjust field_pos.. */ + + while((flags && *flags & OE_APPEND_CURRENT) && + field_pos < field_len && string[field_pos] != '\0') + field_pos++; + + string[field_pos] = '\0'; + dline.vused = (int)(&string[field_pos] - string); + passwd = (flags && *flags & OE_PASSWD) ? 1 : 0; + line_paint(field_pos, &passwd); + + /*---------------------------------------------------------------------- + The main loop + + here field_pos is the position in the string. + s always points to where we are in the string. + loops until someone sets the return_v. + ----------------------------------------------------------------------*/ + return_v = -10; + + while(return_v == -10) { + +#ifdef MOUSE + mouse_in_content(KEY_MOUSE, -1, -1, 0x5, 0); + register_mfunc(mouse_in_content, + real_y_base, x_base + prompt_len, + real_y_base, ps_global->ttyo->screen_cols); +#endif +#ifdef _WINDOWS + mswin_allowpaste(MSWIN_PASTE_LINE); + g_mc_row = real_y_base; + g_mc_col = x_base + prompt_len; + mswin_mousetrackcallback(pcpine_oe_cursor); +#endif + + /* Timeout 10 min to keep imap mail stream alive */ + ch = read_char(600); + +#ifdef MOUSE + clear_mfunc(mouse_in_content); +#endif +#ifdef _WINDOWS + mswin_allowpaste(MSWIN_PASTE_DISABLE); + mswin_mousetrackcallback(NULL); +#endif + + /* + * Don't want to intercept all characters if typing in passwd. + * We select an ad hoc set that we will catch and let the rest + * through. We would have caught the set below in the big switch + * but we skip the switch instead. Still catch things like ^K, + * DELETE, ^C, RETURN. + */ + if(passwd) + switch(ch) { + case ctrl('F'): + case KEY_RIGHT: + case ctrl('B'): + case KEY_LEFT: + case ctrl('U'): + case ctrl('A'): + case KEY_HOME: + case ctrl('E'): + case KEY_END: + case TAB: + goto ok_for_passwd; + } + + if(too_thin && ch != KEY_RESIZE && ch != ctrl('Z') && ch != ctrl('C')) + goto bleep; + + switch(ch) { + + /*--------------- KEY RIGHT ---------------*/ + case ctrl('F'): + case KEY_RIGHT: + if(field_pos >= field_len || string[field_pos] == '\0') + goto bleep; + + line_paint(++field_pos, &passwd); + break; + + /*--------------- KEY LEFT ---------------*/ + case ctrl('B'): + case KEY_LEFT: + if(field_pos <= 0) + goto bleep; + + line_paint(--field_pos, &passwd); + break; + + /*-------------------- WORD SKIP --------------------*/ + case ctrl('@'): + /* + * Note: read_char *can* return NO_OP_COMMAND which is + * the def'd with the same value as ^@ (NULL), BUT since + * read_char has a big timeout (>25 secs) it won't. + */ + + /* skip thru current word */ + while(string[field_pos] + && isalnum((unsigned char) string[field_pos])) + field_pos++; + + /* skip thru current white space to next word */ + while(string[field_pos] + && !isalnum((unsigned char) string[field_pos])) + field_pos++; + + line_paint(field_pos, &passwd); + break; + + /*-------------------- RETURN --------------------*/ + case PF4: + if(F_OFF(F_USE_FK,ps_global)) goto bleep; + case ctrl('J'): + case ctrl('M'): + return_v = 0; + break; + + /*-------------------- Destructive backspace --------------------*/ + case '\177': /* DEL */ + case ctrl('H'): + /* Try and do this with by telling the terminal to delete a + a character. If that fails, then repaint the rest of the + line, acheiving the same much less efficiently + */ + if(field_pos <= 0) + goto bleep; + + field_pos--; + /* drop thru to pull line back ... */ + + /*-------------------- Delete char --------------------*/ + case ctrl('D'): + case KEY_DEL: + if(field_pos >= field_len || !string[field_pos]) + goto bleep; + + dline.vused--; + for(s2 = &string[field_pos]; *s2 != '\0'; s2++) + *s2 = s2[1]; + + *s2 = '\0'; /* Copy last NULL */ + line_paint(field_pos, &passwd); + if(flags) /* record change if requested */ + *flags |= OE_USER_MODIFIED; + + break; + + + /*--------------- Kill line -----------------*/ + case ctrl('K'): + if(kill_buffer != NULL) + fs_give((void **)&kill_buffer); + + if(field_pos != 0 || string[0]){ + if(!passwd && F_ON(F_DEL_FROM_DOT, ps_global)) + dline.vused -= strlen(&string[i = field_pos]); + else + dline.vused = i = 0; + + kill_buffer = cpystr(&string[field_pos = i]); + string[field_pos] = '\0'; + line_paint(field_pos, &passwd); + if(flags) /* record change if requested */ + *flags |= OE_USER_MODIFIED; + + } + + break; + + /*------------------- Undelete line --------------------*/ + case ctrl('U'): + if(kill_buffer == NULL) + goto bleep; + + /* Make string so it will fit */ + kb = cpystr(kill_buffer); + dprint(2, (debugfile, + "Undelete: %d %d\n", strlen(string), field_len)); + if(strlen(kb) + strlen(string) > field_len) + kb[field_len - strlen(string)] = '\0'; + dprint(2, (debugfile, + "Undelete: %d %d\n", field_len - strlen(string), + strlen(kb))); + + if(string[field_pos] == '\0') { + /*--- adding to the end of the string ----*/ + for(k = kb; *k; k++) + string[field_pos++] = *k; + + string[field_pos] = '\0'; + } else { + goto bleep; + /* To lazy to do insert in middle of string now */ + } + + if(*kb && flags) /* record change if requested */ + *flags |= OE_USER_MODIFIED; + + dline.vused = strlen(string); + fs_give((void **)&kb); + line_paint(field_pos, &passwd); + break; + + + /*-------------------- Interrupt --------------------*/ + case ctrl('C'): /* ^C */ + if(F_ON(F_USE_FK,ps_global) + || (flags && ((*flags) & OE_DISALLOW_CANCEL))) + goto bleep; + + goto cancel; + + case PF2: + if(F_OFF(F_USE_FK,ps_global) + || (flags && ((*flags) & OE_DISALLOW_CANCEL))) + goto bleep; + + cancel: + return_v = 1; + if(saved_original) + strcpy(string, saved_original); + + break; + + + case ctrl('A'): + case KEY_HOME: + /*-------------------- Start of line -------------*/ + line_paint(field_pos = 0, &passwd); + break; + + + case ctrl('E'): + case KEY_END: + /*-------------------- End of line ---------------*/ + line_paint(field_pos = dline.vused, &passwd); + break; + + + /*-------------------- Help --------------------*/ + case ctrl('G') : + case PF1: + if(flags && ((*flags) & OE_DISALLOW_HELP)) + goto bleep; + else if(FOOTER_ROWS(ps_global) == 1 && km_popped == 0){ + km_popped++; + FOOTER_ROWS(ps_global) = 3; + clearfooter(ps_global); + if(lastc) + (void)pico_set_colorp(lastc, PSC_NONE); + else + EndInverse(); + + draw_keymenu(km, bitmap, cols, 1-FOOTER_ROWS(ps_global), + 0, FirstMenu); + + if(promptc) + (void)pico_set_colorp(promptc, PSC_NONE); + else + StartInverse(); + + mark_keymenu_dirty(); + y_base = -3; + dline.row = real_y_base = y_base + ps_global->ttyo->screen_rows; + PutLine0(real_y_base, x_base, prompt); + fs_resize((void **)&dline.dl, (size_t)dline.dlen + 1); + memset((void *)dline.dl, 0, (size_t)(dline.dlen + 1)); + line_paint(field_pos, &passwd); + break; + } + + if(FOOTER_ROWS(ps_global) > 1){ + mark_keymenu_dirty(); + return_v = 3; + } + else + goto bleep; + + break; + +#ifdef MOUSE + case KEY_MOUSE : + { + MOUSEPRESS mp; + + mouse_get_last (NULL, &mp); + + /* The clicked line have anything special on it? */ + switch(mp.button){ + case M_BUTTON_LEFT : /* position cursor */ + mp.col -= x_base + prompt_len; /* normalize column */ + if(dline.vbase + mp.col <= dline.vused) + line_paint(field_pos = dline.vbase + mp.col, &passwd); + + break; + + case M_BUTTON_RIGHT : +#ifdef _WINDOWS + mp.col -= x_base + prompt_len; /* normalize column */ + if(dline.vbase + mp.col <= dline.vused) + line_paint(field_pos = dline.vbase + mp.col, &passwd); + + mswin_allowpaste(MSWIN_PASTE_LINE); + mswin_paste_popup(); + mswin_allowpaste(MSWIN_PASTE_DISABLE); + break; +#endif + + case M_BUTTON_MIDDLE : /* NO-OP for now */ + default: /* just ignore */ + break; + } + } + + break; +#endif + + case NO_OP_IDLE: + /* Keep mail stream alive */ + i = new_mail(0, 2, NM_DEFER_SORT); + if(ps_global->expunge_count && + flags && ((*flags) & OE_SEQ_SENSITIVE)) + goto cancel; + + if(i < 0){ + line_paint(field_pos, &passwd); + break; /* no changes, get on with life */ + } + /* Else fall into redraw */ + + /*-------------------- Redraw --------------------*/ + case ctrl('L'): + /*---------------- re size ----------------*/ + case KEY_RESIZE: + + dline.row = real_y_base = y_base > 0 ? y_base : + y_base + ps_global->ttyo->screen_rows; + if(lastc) + (void)pico_set_colorp(lastc, PSC_NONE); + else + EndInverse(); + + ClearScreen(); + redraw_titlebar(); + if(ps_global->redrawer != (void (*)())NULL) + (*ps_global->redrawer)(); + + redraw_keymenu(); + if(promptc) + (void)pico_set_colorp(promptc, PSC_NONE); + else + StartInverse(); + + PutLine0(real_y_base, x_base, prompt); + cols = ps_global->ttyo->screen_cols; + too_thin = 0; + if(cols < x_base + prompt_len + 4) { + Writechar(BELL, 0); + PutLine0(real_y_base, 0, "Screen's too thin. Ouch!"); + too_thin = 1; + } else { + dline.col = x_base + prompt_len; + dline.dlen = cols - (x_base + prompt_len + 1); + fs_resize((void **)&dline.dl, (size_t)dline.dlen + 1); + memset((void *)dline.dl, 0, (size_t)(dline.dlen + 1)); + line_paint(field_pos, &passwd); + } + fflush(stdout); + + dprint(9, (debugfile, + "optionally_enter RESIZE new_cols:%d too_thin: %d\n", + cols, too_thin)); + break; + + case PF3 : /* input to potentially remap */ + case PF5 : + case PF6 : + case PF7 : + case PF8 : + case PF9 : + case PF10 : + case PF11 : + case PF12 : + if(F_ON(F_USE_FK,ps_global) + && fkey_table[ch - PF1] != NO_OP_COMMAND) + ch = fkey_table[ch - PF1]; /* remap function key input */ + + default: + if(escape_list){ /* in the escape key list? */ + for(j=0; escape_list[j].ch != -1; j++){ + if(escape_list[j].ch == ch){ + return_v = escape_list[j].rval; + break; + } + } + + if(return_v != -10) + break; + } + + if(iscntrl(ch & 0x7f)){ + bleep: + putc(BELL, stdout); + continue; + } + + ok_for_passwd: + /*--- Insert a character -----*/ + if(dline.vused >= field_len) + goto bleep; + + /*---- extending the length of the string ---*/ + for(s2 = &string[++dline.vused]; s2 - string > field_pos; s2--) + *s2 = *(s2-1); + + string[field_pos++] = ch; + line_paint(field_pos, &passwd); + if(flags) /* record change if requested */ + *flags |= OE_USER_MODIFIED; + + } /*---- End of switch on char ----*/ + } + +#ifdef _WINDOWS + if(!cursor_shown) + mswin_showcaret(0); +#endif + + fs_give((void **)&dline.dl); + if(saved_original) + fs_give((void **)&saved_original); + + if(kill_buffer) + fs_give((void **)&kill_buffer); + + if (!(flags && (*flags) & OE_KEEP_TRAILING_SPACE)) + removing_trailing_white_space(string); + + if(lastc){ + (void)pico_set_colorp(lastc, PSC_NONE); + free_color_pair(&lastc); + if(promptc) + free_color_pair(&promptc); + } + else + EndInverse(); + + MoveCursor(real_y_base, x_base); /* Move the cursor to show we're done */ + fflush(stdout); + resume_busy_alarm(0); + if(km_popped){ + FOOTER_ROWS(ps_global) = 1; + clearfooter(ps_global); + ps_global->mangled_body = 1; + } + + return(return_v); +} + + +/* + * line_paint - where the real work of managing what is displayed gets done. + * The passwd variable is overloaded: if non-zero, don't + * output anything, else only blat blank chars across line + * once and use this var to tell us we've already written the + * line. + */ +void +line_paint(offset, passwd) + int offset; /* current dot offset into line */ + int *passwd; /* flag to hide display of chars */ +{ + register char *pfp, *pbp; + register char *vfp, *vbp; + int extra = 0; +#define DLEN (dline.vbase + dline.dlen) + + /* + * for now just leave line blank, but maybe do '*' for each char later + */ + if(*passwd){ + if(*passwd > 1) + return; + else + *passwd = 2; /* only blat once */ + + extra = 0; + MoveCursor(dline.row, dline.col); + while(extra++ < dline.dlen) + Writechar(' ', 0); + + MoveCursor(dline.row, dline.col); + return; + } + + /* adjust right margin */ + while(offset >= DLEN + ((dline.vused > DLEN) ? -1 : 1)) + dline.vbase += dline.dlen/2; + + /* adjust left margin */ + while(offset < dline.vbase + ((dline.vbase) ? 2 : 0)) + dline.vbase = max(dline.vbase - (dline.dlen/2), 0); + + if(dline.vbase){ /* off screen cue left */ + vfp = &dline.vl[dline.vbase+1]; + pfp = &dline.dl[1]; + if(dline.dl[0] != '<'){ + MoveCursor(dline.row, dline.col); + Writechar(dline.dl[0] = '<', 0); + } + } + else{ + vfp = dline.vl; + pfp = dline.dl; + if(dline.dl[0] == '<'){ + MoveCursor(dline.row, dline.col); + Writechar(dline.dl[0] = ' ', 0); + } + } + + if(dline.vused > DLEN){ /* off screen right... */ + vbp = vfp + (long)(dline.dlen-(dline.vbase ? 2 : 1)); + pbp = pfp + (long)(dline.dlen-(dline.vbase ? 2 : 1)); + if(pbp[1] != '>'){ + MoveCursor(dline.row, dline.col+dline.dlen); + Writechar(pbp[1] = '>', 0); + } + } + else{ + extra = dline.dlen - (dline.vused - dline.vbase); + vbp = &dline.vl[max(0, dline.vused-1)]; + pbp = &dline.dl[dline.dlen]; + if(pbp[0] == '>'){ + MoveCursor(dline.row, dline.col+dline.dlen); + Writechar(pbp[0] = ' ', 0); + } + } + + while(*pfp == *vfp && vfp < vbp) /* skip like chars */ + pfp++, vfp++; + + if(pfp == pbp && *pfp == *vfp){ /* nothing to paint! */ + MoveCursor(dline.row, dline.col + (offset - dline.vbase)); + return; + } + + /* move backward thru like characters */ + if(extra){ + while(extra >= 0 && *pbp == ' ') /* back over spaces */ + extra--, pbp--; + + while(extra >= 0) /* paint new ones */ + pbp[-(extra--)] = ' '; + } + + if((vbp - vfp) == (pbp - pfp)){ /* space there? */ + while((*pbp == *vbp) && pbp != pfp) /* skip like chars */ + pbp--, vbp--; + } + + if(pfp != pbp || *pfp != *vfp){ /* anything to paint?*/ + MoveCursor(dline.row, dline.col + (int)(pfp - dline.dl)); + + do + Writechar((unsigned char)((vfp <= vbp && *vfp) + ? ((*pfp = *vfp++) == TAB) ? ' ' : *pfp + : (*pfp = ' ')), 0); + while(++pfp <= pbp); + } + + MoveCursor(dline.row, dline.col + (offset - dline.vbase)); +} + + + +/*---------------------------------------------------------------------- + Check to see if the given command is reasonably valid + + Args: ch -- the character to check + + Result: A valid command is returned, or a well know bad command is returned. + + ---*/ +validatekeys(ch) + int ch; +{ +#ifndef _WINDOWS + if(F_ON(F_USE_FK,ps_global)) { + if(ch >= 'a' && ch <= 'z') + return(KEY_JUNK); + } else { + if(ch >= PF1 && ch <= PF12) + return(KEY_JUNK); + } +#else + /* + * In windows menu items are bound to a single key command which + * gets inserted into the input stream as if the user had typed + * that key. But all the menues are bonund to alphakey commands, + * not PFkeys. to distinguish between a keyboard command and a + * menu command we insert a flag (KEY_MENU_FLAG) into the + * command value when setting up the bindings in + * configure_menu_items(). Here we strip that flag. + */ + if(F_ON(F_USE_FK,ps_global)) { + if(ch >= 'a' && ch <= 'z' && !(ch & KEY_MENU_FLAG)) + return(KEY_JUNK); + ch &= ~ KEY_MENU_FLAG; + } else { + ch &= ~ KEY_MENU_FLAG; + if(ch >= PF1 && ch <= PF12) + return(KEY_JUNK); + } +#endif + + return(ch); +} + + + +/*---------------------------------------------------------------------- + Prepend config'd commands to keyboard input + + Args: ch -- pointer to storage for returned command + + Returns: TRUE if we're passing back a useful command, FALSE otherwise + + ---*/ +int +process_config_input(ch) + int *ch; +{ + static char firsttime = (char) 1; + + /* commands in config file */ + if(ps_global->initial_cmds && *ps_global->initial_cmds) { + /* + * There are a few commands that may require keyboard input before + * we enter the main command loop. That input should be interactive, + * not from our list of initial keystrokes. + */ + if(ps_global->dont_use_init_cmds) + return(0); + + *ch = *ps_global->initial_cmds++; + if(!*ps_global->initial_cmds && ps_global->free_initial_cmds){ + fs_give((void **)&(ps_global->free_initial_cmds)); + ps_global->initial_cmds = 0; + } + + return(1); + } + + if(firsttime) { + firsttime = 0; + if(ps_global->in_init_seq) { + ps_global->in_init_seq = 0; + ps_global->save_in_init_seq = 0; + clear_cursor_pos(); + F_SET(F_USE_FK,ps_global,ps_global->orig_use_fkeys); + /* draw screen */ + *ch = ctrl('L'); + return(1); + } + } + + return(0); +} + + +#define TAPELEN 256 +static int tape[TAPELEN]; +static long recorded = 0L; +static short length = 0; + + +/* + * record user keystrokes + * + * Args: ch -- the character to record + * + * Returns: character recorded + */ +int +key_recorder(ch) + int ch; +{ + tape[recorded++ % TAPELEN] = ch; + if(length < TAPELEN) + length++; + + return(ch); +} + + +/* + * playback user keystrokes + * + * Args: ch -- ignored + * + * Returns: character played back or -1 to indicate end of tape + */ +int +key_playback(ch) + int ch; +{ + ch = length ? tape[(recorded + TAPELEN - length--) % TAPELEN] : -1; + return(ch); +} + + +#ifdef _WINDOWS +int +pcpine_oe_cursor(col, row) + int col; + long row; +{ + return((row == g_mc_row + && col >= g_mc_col + && col < ps_global->ttyo->screen_cols) + ? MSWIN_CURSOR_IBEAM + : MSWIN_CURSOR_ARROW); +} +#endif + +/*====================================================================== + Routines for painting the screen + - figure out what the terminal type is + - deal with screen size changes + - save special output sequences + - the usual screen clearing, cursor addressing and scrolling + + + This library gives programs the ability to easily access the + termcap information and write screen oriented and raw input + programs. The routines can be called as needed, except that + to use the cursor / screen routines there must be a call to + InitScreen() first. The 'Raw' input routine can be used + independently, however. (Elm comment) + + Not sure what the original source of this code was. It got to be + here as part of ELM. It has been changed significantly from the + ELM version to be more robust in the face of inconsistent terminal + autowrap behaviour. Also, the unused functions were removed, it was + made to pay attention to the window size, and some code was made nicer + (in my opinion anyways). It also outputs the terminal initialization + strings and provides for minimal scrolling and detects terminals + with out enough capabilities. (Pine comment, 1990) + + +This code used to pay attention to the "am" auto margin and "xn" +new line glitch fields, but they were so often incorrect because many +terminals can be configured to do either that we've taken it out. It +now assumes it dosn't know where the cursor is after outputing in the +80th column. +*/ + +#define PUTLINE_BUFLEN 256 + +static int _lines, _columns; +static int _line = FARAWAY; +static int _col = FARAWAY; +static int _in_inverse; + + +/* + * Internal prototypes + */ +static void moveabsolute PROTO((int, int)); +static void CursorUp PROTO((int)); +static void CursorDown PROTO((int)); +static void CursorLeft PROTO((int)); +static void CursorRight PROTO((int)); + + +extern char *_clearscreen, *_moveto, *_up, *_down, *_right, *_left, + *_setinverse, *_clearinverse, + *_cleartoeoln, *_cleartoeos, + *_startinsert, *_endinsert, *_insertchar, *_deletechar, + *_deleteline, *_insertline, + *_scrollregion, *_scrollup, *_scrolldown, + *_termcap_init, *_termcap_end; +extern char term_name[]; +extern int _tlines, _tcolumns, _bce; + +static enum {NoScroll,UseScrollRegion,InsertDelete} _scrollmode; + +char *tgoto(); /* and the goto stuff */ + + + +/*---------------------------------------------------------------------- + Initialize the screen for output, set terminal type, etc + + Args: tt -- Pointer to variable to store the tty output structure. + + Result: terminal size is discovered and set in pine state + termcap entry is fetched and stored + make sure terminal has adequate capabilites + evaluate scrolling situation + returns status of indicating the state of the screen/termcap entry + + Returns: + -1 indicating no terminal name associated with this shell, + -2..-n No termcap for this terminal type known + -3 Can't open termcap file + -4 Terminal not powerful enough - missing clear to eoln or screen + or cursor motion + ----*/ +int +config_screen(tt) + struct ttyo **tt; +{ + struct ttyo *ttyo; + int err; + + ttyo = (struct ttyo *)fs_get(sizeof (struct ttyo)); + + _line = 0; /* where are we right now?? */ + _col = 0; /* assume zero, zero... */ + + /* + * This is an ugly hack to let vtterminalinfo know it's being called + * from pine. + */ + Pmaster = (PICO *)1; + if(err = vtterminalinfo(F_ON(F_TCAP_WINS, ps_global))) + return(err); + + Pmaster = NULL; + + if(_tlines <= 0) + _lines = DEFAULT_LINES_ON_TERMINAL; + else + _lines = _tlines; + + if(_tcolumns <= 0) + _columns = DEFAULT_COLUMNS_ON_TERMINAL; + else + _columns = _tcolumns; + + get_windsize(ttyo); + + ttyo->header_rows = 2; + ttyo->footer_rows = 3; + + /*---- Make sure this terminal has the capability. + All we need is cursor address, clear line, and + reverse video. + ---*/ + if(_moveto == NULL || _cleartoeoln == NULL || + _setinverse == NULL || _clearinverse == NULL) { + return(-4); + } + + dprint(1, (debugfile, "Terminal type: %s\n", term_name)); + + /*------ Figure out scrolling mode -----*/ + if(_scrollregion != NULL && _scrollregion[0] != '\0' && + _scrollup != NULL && _scrollup[0] != '\0'){ + _scrollmode = UseScrollRegion; + } else if(_insertline != NULL && _insertline[0] != '\0' && + _deleteline != NULL && _deleteline[0] != '\0') { + _scrollmode = InsertDelete; + } else { + _scrollmode = NoScroll; + } + dprint(7, (debugfile, "Scroll mode: %s\n", + _scrollmode==NoScroll ? "No Scroll" : + _scrollmode==InsertDelete ? "InsertDelete" : "Scroll Regions")); + + if (!_left) { + _left = "\b"; + } + + *tt = ttyo; + + return(0); +} + + + +/*---------------------------------------------------------------------- + Initialize the screen with the termcap string + ----*/ +void +init_screen() +{ + if(_termcap_init) /* init using termcap's rule */ + tputs(_termcap_init, 1, outchar); + + /* and make sure there are no scrolling surprises! */ + BeginScroll(0, ps_global->ttyo->screen_rows - 1); + + pico_toggle_color(0); + switch(ps_global->color_style){ + case COL_NONE: + case COL_TERMDEF: + pico_set_color_options(0); + break; + case COL_ANSI8: + pico_set_color_options(COLOR_ANSI8_OPT); + break; + case COL_ANSI16: + pico_set_color_options(COLOR_ANSI16_OPT); + break; + } + + if(ps_global->color_style != COL_NONE) + pico_toggle_color(1); + + /* set colors */ + if(pico_usingcolor()){ + if(ps_global->VAR_NORM_FORE_COLOR) + pico_nfcolor(ps_global->VAR_NORM_FORE_COLOR); + + if(ps_global->VAR_NORM_BACK_COLOR) + pico_nbcolor(ps_global->VAR_NORM_BACK_COLOR); + + if(ps_global->VAR_REV_FORE_COLOR) + pico_rfcolor(ps_global->VAR_REV_FORE_COLOR); + + if(ps_global->VAR_REV_BACK_COLOR) + pico_rbcolor(ps_global->VAR_REV_BACK_COLOR); + + pico_set_normal_color(); + } + + /* and make sure icon text starts out consistent */ + icon_text(NULL); + fflush(stdout); +} + + + + +/*---------------------------------------------------------------------- + Get the current window size + + Args: ttyo -- pointer to structure to store window size in + + NOTE: we don't override the given values unless we know better + ----*/ +int +get_windsize(ttyo) +struct ttyo *ttyo; +{ +#ifdef RESIZING + struct winsize win; + + /* + * Get the window size from the tty driver. If we can't fish it from + * stdout (pine's output is directed someplace else), try stdin (which + * *must* be associated with the terminal; see init_tty_driver)... + */ + if(ioctl(1, TIOCGWINSZ, &win) >= 0 /* 1 is stdout */ + || ioctl(0, TIOCGWINSZ, &win) >= 0){ /* 0 is stdin */ + if(win.ws_row) + _lines = min(win.ws_row, MAX_SCREEN_ROWS); + + if(win.ws_col) + _columns = min(win.ws_col, MAX_SCREEN_COLS); + + dprint(2, (debugfile, "new win size -----<%d %d>------\n", + _lines, _columns)); + } + else + /* Depending on the OS, the ioctl() may have failed because + of a 0 rows, 0 columns setting. That happens on DYNIX/ptx 1.3 + (with a kernel patch that happens to involve the negotiation + of window size in the telnet streams module.) In this case + the error is EINVARG. Leave the default settings. */ + dprint(1, (debugfile, "ioctl(TIOCWINSZ) failed :%s\n", + error_description(errno))); +#endif + + ttyo->screen_cols = min(_columns, MAX_SCREEN_COLS); + ttyo->screen_rows = min(_lines, MAX_SCREEN_ROWS); + return(0); +} + + +/*---------------------------------------------------------------------- + End use of the screen. + Print status message, if any. + Flush status messages. + ----*/ +void +end_screen(message) + char *message; +{ + int footer_rows_was_one = 0; + + dprint(9, (debugfile, "end_screen called\n")); + + if(FOOTER_ROWS(ps_global) == 1){ + footer_rows_was_one++; + FOOTER_ROWS(ps_global) = 3; + mark_status_unknown(); + } + + flush_status_messages(1); + blank_keymenu(_lines - 2, 0); + MoveCursor(_lines - 2, 0); + + /* unset colors */ + if(pico_hascolor()) + pico_endcolor(); + + if(_termcap_end != NULL) + tputs(_termcap_end, 1, outchar); + + if(message){ + printf("%s\r\n", message); + } + + if(F_ON(F_ENABLE_XTERM_NEWMAIL, ps_global) && getenv("DISPLAY")) + icon_text("xterm"); + + fflush(stdout); + + if(footer_rows_was_one){ + FOOTER_ROWS(ps_global) = 1; + mark_status_unknown(); + } +} + + + +/*---------------------------------------------------------------------- + Clear the terminal screen + + Result: The screen is cleared + internal cursor position set to 0,0 + ----*/ +void +ClearScreen() +{ + _line = 0; /* clear leaves us at top... */ + _col = 0; + + if(ps_global->in_init_seq) + return; + + mark_status_unknown(); + mark_keymenu_dirty(); + mark_titlebar_dirty(); + + /* + * If the terminal doesn't have back color erase, then we have to + * erase manually to preserve the background color. + */ + if(pico_usingcolor() && (!_bce || !_clearscreen)){ + ClearLines(0, _lines-1); + MoveCursor(0, 0); + } + else if(_clearscreen){ + tputs(_clearscreen, 1, outchar); + moveabsolute(0, 0); /* some clearscreens don't move correctly */ + } +} + + +/*---------------------------------------------------------------------- + Internal move cursor to absolute position + + Args: col -- column to move cursor to + row -- row to move cursor to + + Result: cursor is moved (variables, not updates) + ----*/ + +static void +moveabsolute(col, row) +{ + + char *stuff, *tgoto(); + + stuff = tgoto(_moveto, col, row); + tputs(stuff, 1, outchar); +} + + +/*---------------------------------------------------------------------- + Move the cursor to the row and column number + Args: row number + column number + + Result: Cursor moves + internal position updated + ----*/ +void +MoveCursor(row, col) + int row, col; +{ + /** move cursor to the specified row column on the screen. + 0,0 is the top left! **/ + + int scrollafter = 0; + + /* we don't want to change "rows" or we'll mangle scrolling... */ + + if(ps_global->in_init_seq) + return; + + if (col < 0) + col = 0; + if (col >= ps_global->ttyo->screen_cols) + col = ps_global->ttyo->screen_cols - 1; + if (row < 0) + row = 0; + if (row > ps_global->ttyo->screen_rows) { + if (col == 0) + scrollafter = row - ps_global->ttyo->screen_rows; + row = ps_global->ttyo->screen_rows; + } + + if (!_moveto) + return; + + if (row == _line) { + if (col == _col) + return; /* already there! */ + + else if (abs(col - _col) < 5) { /* within 5 spaces... */ + if (col > _col && _right) + CursorRight(col - _col); + else if (col < _col && _left) + CursorLeft(_col - col); + else + moveabsolute(col, row); + } + else /* move along to the new x,y loc */ + moveabsolute(col, row); + } + else if (col == _col && abs(row - _line) < 5) { + if (row < _line && _up) + CursorUp(_line - row); + else if (_line > row && _down) + CursorDown(row - _line); + else + moveabsolute(col, row); + } + else if (_line == row-1 && col == 0) { + putchar('\n'); /* that's */ + putchar('\r'); /* easy! */ + } + else + moveabsolute(col, row); + + _line = row; /* to ensure we're really there... */ + _col = col; + + if (scrollafter) { + while (scrollafter--) { + putchar('\n'); + putchar('\r'); + + } + } + + return; +} + + + +/*---------------------------------------------------------------------- + Newline, move the cursor to the start of next line + + Result: Cursor moves + ----*/ +void +NewLine() +{ + /** move the cursor to the beginning of the next line **/ + + Writechar('\n', 0); + Writechar('\r', 0); +} + + + +/*---------------------------------------------------------------------- + Move cursor up n lines with terminal escape sequence + + Args: n -- number of lines to go up + + Result: cursor moves, + internal position updated + + Only for ttyout use; not outside callers + ----*/ +static void +CursorUp(n) +int n; +{ + /** move the cursor up 'n' lines **/ + /** Calling function must check that _up is not null before calling **/ + + _line = (_line-n > 0? _line - n: 0); /* up 'n' lines... */ + + while (n-- > 0) + tputs(_up, 1, outchar); +} + + + +/*---------------------------------------------------------------------- + Move cursor down n lines with terminal escape sequence + + Arg: n -- number of lines to go down + + Result: cursor moves, + internal position updated + + Only for ttyout use; not outside callers + ----*/ +static void +CursorDown(n) + int n; +{ + /** move the cursor down 'n' lines **/ + /** Caller must check that _down is not null before calling **/ + + _line = (_line+n < ps_global->ttyo->screen_rows ? _line + n + : ps_global->ttyo->screen_rows); + /* down 'n' lines... */ + + while (n-- > 0) + tputs(_down, 1, outchar); +} + + + +/*---------------------------------------------------------------------- + Move cursor left n lines with terminal escape sequence + + Args: n -- number of lines to go left + + Result: cursor moves, + internal position updated + + Only for ttyout use; not outside callers + ----*/ +static void +CursorLeft(n) +int n; +{ + /** move the cursor 'n' characters to the left **/ + /** Caller must check that _left is not null before calling **/ + + _col = (_col - n> 0? _col - n: 0); /* left 'n' chars... */ + + while (n-- > 0) + tputs(_left, 1, outchar); +} + + +/*---------------------------------------------------------------------- + Move cursor right n lines with terminal escape sequence + + Args: number of lines to go right + + Result: cursor moves, + internal position updated + + Only for ttyout use; not outside callers + ----*/ +static void +CursorRight(n) +int n; +{ + /** move the cursor 'n' characters to the right (nondestructive) **/ + /** Caller must check that _right is not null before calling **/ + + _col = (_col+n < ps_global->ttyo->screen_cols? _col + n : + ps_global->ttyo->screen_cols); /* right 'n' chars... */ + + while (n-- > 0) + tputs(_right, 1, outchar); + +} + + + +/*---------------------------------------------------------------------- + Insert character on screen pushing others right + + Args: c -- character to insert + + Result: charcter is inserted if possible + return -1 if it can't be done + ----------------------------------------------------------------------*/ +InsertChar(c) + int c; +{ + if(_insertchar != NULL && *_insertchar != '\0') { + tputs(_insertchar, 1, outchar); + Writechar(c, 0); + } else if(_startinsert != NULL && *_startinsert != '\0') { + tputs(_startinsert, 1, outchar); + Writechar(c, 0); + tputs(_endinsert, 1, outchar); + } else { + return(-1); + } + return(0); +} + + + +/*---------------------------------------------------------------------- + Delete n characters from line, sliding rest of line left + + Args: n -- number of characters to delete + + + Result: characters deleted on screen + returns -1 if it wasn't done + ----------------------------------------------------------------------*/ +DeleteChar(n) + int n; +{ + if(_deletechar == NULL || *_deletechar == '\0') + return(-1); + + while(n) { + tputs(_deletechar, 1, outchar); + n--; + } + return(0); +} + + + +/*---------------------------------------------------------------------- + Go into scrolling mode, that is set scrolling region if applicable + + Args: top -- top line of region to scroll + bottom -- bottom line of region to scroll + (These are zero-origin numbers) + + Result: either set scrolling region or + save values for later scrolling + returns -1 if we can't scroll + + Unfortunately this seems to leave the cursor in an unpredictable place + at least the manuals don't say where, so we force it here. +-----*/ +static int __t, __b; + +BeginScroll(top, bottom) + int top, bottom; +{ + char *stuff; + + if(_scrollmode == NoScroll) + return(-1); + + __t = top; + __b = bottom; + if(_scrollmode == UseScrollRegion){ + stuff = tgoto(_scrollregion, bottom, top); + tputs(stuff, 1, outchar); + /*-- a location very far away to force a cursor address --*/ + _line = FARAWAY; + _col = FARAWAY; + } + return(0); +} + + + +/*---------------------------------------------------------------------- + End scrolling -- clear scrolling regions if necessary + + Result: Clear scrolling region on terminal + -----*/ +void +EndScroll() +{ + if(_scrollmode == UseScrollRegion && _scrollregion != NULL){ + /* Use tgoto even though we're not cursor addressing because + the format of the capability is the same. + */ + char *stuff = tgoto(_scrollregion, ps_global->ttyo->screen_rows -1, 0); + tputs(stuff, 1, outchar); + /*-- a location very far away to force a cursor address --*/ + _line = FARAWAY; + _col = FARAWAY; + } +} + + +/* ---------------------------------------------------------------------- + Scroll the screen using insert/delete or scrolling regions + + Args: lines -- number of lines to scroll, positive forward + + Result: Screen scrolls + returns 0 if scroll succesful, -1 if not + + positive lines goes foward (new lines come in at bottom + Leaves cursor at the place to insert put new text + + 0,0 is the upper left + -----*/ +ScrollRegion(lines) + int lines; +{ + int l; + + if(lines == 0) + return(0); + + if(_scrollmode == UseScrollRegion) { + if(lines > 0) { + MoveCursor(__b, 0); + for(l = lines ; l > 0 ; l--) + tputs((_scrolldown == NULL || _scrolldown[0] =='\0') ? "\n" : + _scrolldown, 1, outchar); + } else { + MoveCursor(__t, 0); + for(l = -lines; l > 0; l--) + tputs(_scrollup, 1, outchar); + } + } else if(_scrollmode == InsertDelete) { + if(lines > 0) { + MoveCursor(__t, 0); + for(l = lines; l > 0; l--) + tputs(_deleteline, 1, outchar); + MoveCursor(__b, 0); + for(l = lines; l > 0; l--) + tputs(_insertline, 1, outchar); + } else { + for(l = -lines; l > 0; l--) { + MoveCursor(__b, 0); + tputs(_deleteline, 1, outchar); + MoveCursor(__t, 0); + tputs(_insertline, 1, outchar); + } + } + } else { + return(-1); + } + fflush(stdout); + return(0); +} + + + +/*---------------------------------------------------------------------- + Write a character to the screen, keeping track of cursor position + + Args: ch -- character to output + + Result: character output + cursor position variables updated + ----*/ +void +Writechar(ch, new_esc_len) + register unsigned int ch; + int new_esc_len; +{ + static int esc_len = 0; + + if(ps_global->in_init_seq /* silent */ + || (F_ON(F_BLANK_KEYMENU, ps_global) /* or bottom, */ + && !esc_len /* right cell */ + && _line + 1 == ps_global->ttyo->screen_rows + && _col + 1 == ps_global->ttyo->screen_cols)) + return; + + if(!iscntrl(ch & 0x7f)){ + putchar(ch); + if(esc_len > 0) + esc_len--; + else + _col++; + } + else{ + switch(ch){ + case LINE_FEED: + /*-- Don't have to watch out for auto wrap or newline glitch + because we never let it happen. See below + ---*/ + putchar('\n'); + _line = min(_line+1,ps_global->ttyo->screen_rows); + esc_len = 0; + break; + + case RETURN : /* move to column 0 */ + putchar('\r'); + _col = 0; + esc_len = 0; + break; + + case BACKSPACE : /* move back a space if not in column 0 */ + if(_col != 0) { + putchar('\b'); + _col--; + } /* else BACKSPACE does nothing */ + + break; + + case BELL : /* ring the bell but don't advance _col */ + putchar(ch); + break; + + case TAB : /* if a tab, output it */ + do /* BUG? ignores tty driver's spacing */ + putchar(' '); + while(_col < ps_global->ttyo->screen_cols - 1 + && ((++_col)&0x07) != 0); + break; + + case ESCAPE : + /* If we're outputting an escape here, it may be part of an iso2022 + escape sequence in which case take up no space on the screen. + Unfortunately such sequences are variable in length. + */ + esc_len = new_esc_len - 1; + putchar(ch); + break; + + default : /* Change remaining control characters to ? */ + if(F_ON(F_PASS_CONTROL_CHARS, ps_global)) + putchar(ch); + else + putchar('?'); + + if(esc_len > 0) + esc_len--; + else + _col++; + + break; + } + } + + + /* Here we are at the end of the line. We've decided to make no + assumptions about how the terminal behaves at this point. + What can happen now are the following + 1. Cursor is at start of next line, and next character will + apear there. (autowrap, !newline glitch) + 2. Cursor is at start of next line, and if a newline is output + it'll be ignored. (autowrap, newline glitch) + 3. Cursor is still at end of line and next char will apear + there over the top of what is there now (no autowrap). + We ignore all this and force the cursor to the next line, just + like case 1. A little expensive but worth it to avoid problems + with terminals configured so they don't match termcap + */ + if(_col == ps_global->ttyo->screen_cols) { + _col = 0; + if(_line + 1 < ps_global->ttyo->screen_rows) + _line++; + + moveabsolute(_col, _line); + } +} + + + +/*---------------------------------------------------------------------- + Write string to screen at current cursor position + + Args: string -- string to be output + + Result: String written to the screen + ----*/ +void +Write_to_screen(string) /* UNIX */ + register char *string; +{ + while(*string) + Writechar((unsigned char) *string++, 0); +} + + +/*---------------------------------------------------------------------- + Write no more than n chars of string to screen at current cursor position + + Args: string -- string to be output + n -- number of chars to output + + Result: String written to the screen + ----*/ +void +Write_to_screen_n(string, n) /* UNIX */ + register char *string; + int n; +{ + while(n-- && *string) + Writechar((unsigned char) *string++, 0); +} + + + +/*---------------------------------------------------------------------- + Clear screen to end of line on current line + + Result: Line is cleared + ----*/ +void +CleartoEOLN() +{ + int c, starting_col, starting_line; + char *last_bg_color; + + /* + * If the terminal doesn't have back color erase, then we have to + * erase manually to preserve the background color. + */ + if(pico_usingcolor() && (!_bce || !_cleartoeoln)){ + starting_col = _col; + starting_line = _line; + last_bg_color = pico_get_last_bg_color(); + pico_set_nbg_color(); + for(c = _col; c < _columns; c++) + Writechar(' ', 0); + + MoveCursor(starting_line, starting_col); + if(last_bg_color){ + (void)pico_set_bg_color(last_bg_color); + fs_give((void **)&last_bg_color); + } + } + else if(_cleartoeoln) + tputs(_cleartoeoln, 1, outchar); +} + + + +/*---------------------------------------------------------------------- + Clear screen to end of screen from current point + + Result: screen is cleared + ----*/ +CleartoEOS() +{ + int starting_col, starting_line; + + /* + * If the terminal doesn't have back color erase, then we have to + * erase manually to preserve the background color. + */ + if(pico_usingcolor() && (!_bce || !_cleartoeos)){ + starting_col = _col; + starting_line = _line; + CleartoEOLN(); + ClearLines(_line+1, _lines-1); + MoveCursor(starting_line, starting_col); + } + else if(_cleartoeos) + tputs(_cleartoeos, 1, outchar); +} + + + +/*---------------------------------------------------------------------- + function to output character used by termcap + + Args: c -- character to output + + Result: character output to screen via stdio + ----*/ +void +outchar(c) +int c; +{ + /** output the given character. From tputs... **/ + /** Note: this CANNOT be a macro! **/ + + putc((unsigned char)c, stdout); +} + + + +/*---------------------------------------------------------------------- + function to output string such that it becomes icon text + + Args: s -- string to write + + Result: string indicated become our "icon" text + ----*/ +void +icon_text(s) + char *s; +{ + static char *old_s; + static enum {ukn, yes, no} xterm; + + if(xterm == ukn) + xterm = (getenv("DISPLAY") != NULL) ? yes : no; + + if(F_ON(F_ENABLE_XTERM_NEWMAIL,ps_global) && xterm == yes && (s || old_s)){ + fputs("\033]1;", stdout); + fputs((old_s = s) ? s : ps_global->pine_name, stdout); + fputs("\007", stdout); + fflush(stdout); + } +} + + +#ifdef _WINDOWS +#line 3 "osdep/termout.gen" +#endif + +/* + * Generic tty output routines... + */ + +/*---------------------------------------------------------------------- + Printf style output line to the screen at given position, 0 args + + Args: x -- column position on the screen + y -- row position on the screen + line -- line of text to output + + Result: text is output + cursor position is update + ----*/ +void +PutLine0(x, y, line) + int x,y; + register char *line; +{ + MoveCursor(x,y); + Write_to_screen(line); +} + + + +/*---------------------------------------------------------------------- + Output line of length len to the display observing embedded attributes + + Args: x -- column position on the screen + y -- column position on the screen + line -- text to be output + length -- length of text to be output + + Result: text is output + cursor position is updated + ----------------------------------------------------------------------*/ +void +PutLine0n8b(x, y, line, length, handles) + int x, y, length; + register char *line; + HANDLE_S *handles; +{ + unsigned char c; +#ifdef _WINDOWS + int hkey = 0; +#endif + + MoveCursor(x,y); + while(length-- && (c = (unsigned char)*line++)){ + + if(c == (unsigned char)TAG_EMBED && length){ + length--; + switch(*line++){ + case TAG_INVON : + StartInverse(); + break; + case TAG_INVOFF : + EndInverse(); + break; + case TAG_BOLDON : + StartBold(); + break; + case TAG_BOLDOFF : + EndBold(); + break; + case TAG_ULINEON : + StartUnderline(); + break; + case TAG_ULINEOFF : + EndUnderline(); + break; + case TAG_HANDLE : + length -= *line + 1; /* key length plus length tag */ + if(handles){ + int key, n; + + for(key = 0, n = *line++; n; n--) /* forget Horner? */ + key = (key * 10) + (*line++ - '0'); + +#if _WINDOWS + hkey = key; +#endif + + if(key == handles->key){ + if(pico_usingcolor() && + ps_global->VAR_SLCTBL_FORE_COLOR && + ps_global->VAR_SLCTBL_BACK_COLOR){ + pico_set_normal_color(); + } + else + EndBold(); + + StartInverse(); + } + } + else{ + /* BUG: complain? */ + line += *line + 1; + } + + break; + case TAG_FGCOLOR : + if(length < RGBLEN){ + Writechar(TAG_EMBED, 0); + Writechar(*(line-1), 0); + break; + } + + (void)pico_set_fg_color(line); + length -= RGBLEN; + line += RGBLEN; + break; + case TAG_BGCOLOR : + if(length < RGBLEN){ + Writechar(TAG_EMBED, 0); + Writechar(*(line-1), 0); + break; + } + + (void)pico_set_bg_color(line); + length -= RGBLEN; + line += RGBLEN; + break; + default : /* literal "embed" char? */ + Writechar(TAG_EMBED, 0); + Writechar(*(line-1), 0); + break; + } /* tag with handle, skip it */ + } + else if(c == '\033') /* check for iso-2022 escape */ + Writechar(c, match_escapes(line)); + else + Writechar(c, 0); + } + + +#if _WINDOWS_X + if(hkey) { + char *tmp_file = NULL, ext[32], mtype[128]; + HANDLE_S *h; + extern HANDLE_S *get_handle (HANDLE_S *, int); + + if((h = get_handle(handles, hkey)) && h->type == Attach){ + ext[0] = '\0'; + strcpy(mtype, body_type_names(h->h.attach->body->type)); + if (h->h.attach->body->subtype) { + strcat (mtype, "/"); + strcat (mtype, h->h.attach->body->subtype); + } + + if(!set_mime_extension_by_type(ext, mtype)){ + char *namep, *dotp, *p; + + if(namep = rfc2231_get_param(h->h.attach->body->parameter, + "name", NULL, NULL)){ + for(dotp = NULL, p = namep; *p; p++) + if(*p == '.') + dotp = p + 1; + + if(dotp && strlen(dotp) < sizeof(ext) - 1) + strcpy(ext, dotp); + + fs_give((void **) &namep); + } + } + + if(ext[0] && (tmp_file = temp_nam_ext(NULL, "im", ext))){ + FILE *f = fopen(tmp_file, "w"); + + mswin_registericon(x, h->key, tmp_file); + + fclose(f); + unlink(tmp_file); + fs_give((void **)&tmp_file); + } + } + } +#endif +} + + +/*---------------------------------------------------------------------- + Printf style output line to the screen at given position, 1 arg + + Input: position on the screen + line of text to output + + Result: text is output + cursor position is update + ----------------------------------------------------------------------*/ +void +/*VARARGS2*/ +PutLine1(x, y, line, arg1) + int x, y; + char *line; + void *arg1; +{ + char buffer[PUTLINE_BUFLEN]; + + sprintf(buffer, line, arg1); + PutLine0(x, y, buffer); +} + + +/*---------------------------------------------------------------------- + Printf style output line to the screen at given position, 2 args + + Input: position on the screen + line of text to output + + Result: text is output + cursor position is update + ----------------------------------------------------------------------*/ +void +/*VARARGS3*/ +PutLine2(x, y, line, arg1, arg2) + int x, y; + char *line; + void *arg1, *arg2; +{ + char buffer[PUTLINE_BUFLEN]; + + sprintf(buffer, line, arg1, arg2); + PutLine0(x, y, buffer); +} + + +/*---------------------------------------------------------------------- + Printf style output line to the screen at given position, 3 args + + Input: position on the screen + line of text to output + + Result: text is output + cursor position is update + ----------------------------------------------------------------------*/ +void +/*VARARGS4*/ +PutLine3(x, y, line, arg1, arg2, arg3) + int x, y; + char *line; + void *arg1, *arg2, *arg3; +{ + char buffer[PUTLINE_BUFLEN]; + + sprintf(buffer, line, arg1, arg2, arg3); + PutLine0(x, y, buffer); +} + + +/*---------------------------------------------------------------------- + Printf style output line to the screen at given position, 4 args + + Args: x -- column position on the screen + y -- column position on the screen + line -- printf style line of text to output + + Result: text is output + cursor position is update + ----------------------------------------------------------------------*/ +void +/*VARARGS5*/ +PutLine4(x, y, line, arg1, arg2, arg3, arg4) + int x, y; + char *line; + void *arg1, *arg2, *arg3, *arg4; +{ + char buffer[PUTLINE_BUFLEN]; + + sprintf(buffer, line, arg1, arg2, arg3, arg4); + PutLine0(x, y, buffer); +} + + + +/*---------------------------------------------------------------------- + Printf style output line to the screen at given position, 5 args + + Args: x -- column position on the screen + y -- column position on the screen + line -- printf style line of text to output + + Result: text is output + cursor position is update + ----------------------------------------------------------------------*/ +void +/*VARARGS6*/ +PutLine5(x, y, line, arg1, arg2, arg3, arg4, arg5) + int x, y; + char *line; + void *arg1, *arg2, *arg3, *arg4, *arg5; +{ + char buffer[PUTLINE_BUFLEN]; + + sprintf(buffer, line, arg1, arg2, arg3, arg4, arg5); + PutLine0(x, y, buffer); +} + + + +/*---------------------------------------------------------------------- + Output a line to the screen, centered + + Input: Line number to print on, string to output + + Result: String is output to screen + Returns column number line is output on + ----------------------------------------------------------------------*/ +int +Centerline(line, string) + int line; + char *string; +{ + register int length, col; + + length = strlen(string); + + if (length > ps_global->ttyo->screen_cols) + col = 0; + else + col = (ps_global->ttyo->screen_cols - length) / 2; + + PutLine0(line, col, string); + return(col); +} + + + +/*---------------------------------------------------------------------- + Clear specified line on the screen + + Result: The line is blanked and the cursor is left at column 0. + + ----*/ +void +ClearLine(n) + int n; +{ + if(ps_global->in_init_seq) + return; + + MoveCursor(n, 0); + CleartoEOLN(); +} + + + +/*---------------------------------------------------------------------- + Clear specified lines on the screen + + Result: The lines starting at 'x' and ending at 'y' are blanked + and the cursor is left at row 'x', column 0 + + ----*/ +void +ClearLines(x, y) + int x, y; +{ + int i; + + for(i = x; i <= y; i++) + ClearLine(i); + + MoveCursor(x, 0); +} + + + +/*---------------------------------------------------------------------- + Indicate to the screen painting here that the position of the cursor + has been disturbed and isn't where these functions might think. + ----*/ +void +clear_cursor_pos() +{ + _line = FARAWAY; + _col = FARAWAY; +} + + |