1 /* ---------------------------------------------------------------------------
2 
3    pcalutil.c
4 
5    Notes:
6 
7       This file contains general-purpose utility routines.
8 
9    Revision history:
10 
11 	4.11.0
12 		B.Marr		2007-12-15
13 
14 		Fix long-standing bug whereby the last line of a configuration
15 		file was silently ignored if it ended without a 'line feed'
16 		(ASCII 10 character).
17 
18 		Rename some variables, structures, and/or routines to be
19 		clearer about their purpose and/or to allow easier searching
20 		with fewer "false positives".
21 
22 		Minor comment typo fixes.
23 
24 	4.10.0
25 		B.Marr		2006-07-19
26 
27 		Reformatted comments and code to match my standards.
28 
29 		B.Marr		2006-07-12
30 
31 		Rename old 'getline()' routine to 'get_pcal_line()' to avoid a
32 		compile-time namespace collision with the standard C library,
33 		which seems to manifest itself only in Windows+Cygwin build
34 		environments.
35 
36 		Provide explicit casting in several spots to avoid warnings in
37 		a "gcc 3.4.2 on Solaris 8" environment, based on a report from
38 		David Mathog <mathog at mendel.bio.caltech.edu>.
39 
40 		Get rid of all the '#ifdef PROTOS' checks, which are pretty
41 		much obsolete these days and just needlessly clutter up the
42 		code.
43 
44 		Drop support for obsolete platforms (Amiga, VMS, OS/2).
45 
46 	4.9.0
47 		B.Marr		2005-08-08
48 
49 		Eliminate the hack to support Esperanto via a custom,
50 		dedicated character encoding.  Esperanto is now handled
51 		generically by the 'Latin3' (ISO 8859-3) character encoding.
52 
53 		B.Marr		2005-01-24
54 
55 		Fix 'off-by-one' error on buffer overflow fix from v4.8.0.
56 
57 	4.8.0
58 		B.Marr		2004-12-15
59 
60 		Prevent potential buffer overflow attack caused by malicious
61 		calendar input file by crudely limiting amount of data copied
62 		in routine 'getline()'.  This security hole was detected by
63 		Danny Lungstrom and reported by D. J. Bernstein.
64 
65 		B.Marr		2004-11-23
66 
67 		Create and support concept of 'input' language versus 'output'
68 		language.
69 
70 		B.Marr		2004-11-15
71 
72 		Remove Ctl-L (page eject) characters from source file.
73 
74 	4.7	AWR	01/25/2000	revised century() function to fix
75 					Y2K-related problems reported under
76 					some flavors of Un*x
77 
78 			02/24/1998	add prototypes (using PROTO macro)
79 					to function pointer declarations
80 
81 			12/21/1997	clean up gcc warnings in -Wall mode
82 
83 			07/27/1997	revise for -H (generate HTML output)
84 					support: expand SET_FONTSTYLE from
85 					macro to function; add html_escape()
86 					to convert HTML escape sequences
87 					when generating non-HTML output
88 
89 	4.6	AWR	05/14/1997	add century() to calculate current
90 					century (instead of assuming 1900)
91 
92 			12/11/1995	add support for HTML-like <{/}[BbIi]>
93 					escape sequences
94 
95 			11/10/1995	support -T flag to select default
96 					font style (Bold/Italic/Roman)
97 
98 		AWR	05/09/1995	add support for \f[BIPR] escape
99 					sequences (set font to Bold, Italic,
100 					previous, or Roman respectively)
101 
102 	4.5	AWR	10/01/1993	add define_font() for redefining font
103 					name and/or size independently; add
104 					define_shading() (replaces old
105 					gen_shading) for date/fill shading
106 
107 			04/28/1993	restructure function definitions so
108 					function name appears in first column
109 					(to facilitate searching for definition
110 					by name)
111 
112 	4.3	AWR	11/22/1991	added special case to loadwords():
113 					split -<flag>"<text>" into two words
114 
115 					removed octal/hex escape functionality
116 					from getline() to new routine
117 					cvt_escape() (for use elsewhere)
118 
119 			10/25/1991	added parameters to loadwords() and
120 					getline() to avoid always using same
121 					global data
122 
123 			10/15/1991	revised UN*X mk_filespec() to translate
124 					"~/" in path name as well as file name
125 
126 	4.2	AWR	10/08/1991	support -[kK] flags (cf. note_box())
127 
128 			10/03/1991	added note_box(), note_day()
129 
130 	4.11	AWR	08/20/1991	documented find_executable()
131 
132 	4.02	AWR	06/07/1991	added find_executable()
133 
134 	4.0	AWR	02/24/1991	Revised getline() and copy_text() to
135 					handle C-style escapes of characters
136 					and octal/hex numbers
137 
138 			02/19/1991	Added support for negative ordinals
139 					in calc_day(), calc_year_day()
140 
141 			02/04/1991	Added calc_year_day()
142 
143 			01/15/1991	Extracted from pcal.c
144 
145 */
146 
147 /* ---------------------------------------------------------------------------
148 
149    Header Files
150 
151 */
152 
153 #include <stdio.h>
154 #include <ctype.h>
155 #include <string.h>
156 #include <time.h>
157 
158 #include "pcaldefs.h"
159 #include "pcallang.h"
160 #include "protos.h"
161 
162 
163 #ifdef BUILD_ENV_UNIX
164 
165 #include <sys/types.h>
166 #include <sys/stat.h>
167 #include <unistd.h>
168 
169 /* X_OK is a #define'd constant used by access() to determine whether or not
170    the specified file is executable by the user.  Investigation of several
171    Un*x systems reveals that 01 appears to be the standard value, but it would
172    be best to #include the appropriate system .h file instead of hard-coding
173    the value (as below).  (The hard-coded approach was adopted reluctantly
174    because there does not appear to be a standardized location for X_OK: some
175    systems put it in <unistd.h>, others in <access.h>, and others elsewhere
176    (if at all).  The access() man page may be helpful.)  (AWR 08/20/91)
177  */
178 #ifndef X_OK
179 /* does user have execute permission? */
180 #define X_OK   01
181 #endif
182 
183 #endif
184 
185 /* ---------------------------------------------------------------------------
186 
187    Type, Struct, & Enum Declarations
188 
189 */
190 
191 /* ---------------------------------------------------------------------------
192 
193    Constant Declarations
194 
195 */
196 
197 /* ---------------------------------------------------------------------------
198 
199    Macro Definitions
200 
201 */
202 
203 /* skip over numeric field and subsequent non-numeric characters */
204 #define SKIP_FIELD(p) \
205 	do { while (*p && isdigit((int)*p)) p++; \
206 	     while (*p && !isdigit((int)*p)) p++; } while (0)
207 
208 /* guarantee that a path is terminated by the END_PATH character */
209 #define TERM_PATH(path) \
210 	do { char *p;	\
211 		if ((p = P_LASTCHAR(path)) && *p != END_PATH) \
212 			*++p = END_PATH, *++p = '\0'; } while (0)
213 
214 /* split string into two substrings at separator 'c' */
215 #define SPLIT(str, str1, str2, c) \
216 	do { char *p; \
217 	str2 = (p = strrchr(str, c)) ? (*p = '\0', ++p) : ""; \
218 	if ((p = strchr(str1 = str, c))) *p = '\0'; } while (0)
219 
220 /* detect an HTML escape sequence of the form <{/}[BbIi]> */
221 #define HTML_ESCAPE(p) \
222 	((p[0] == '<') && \
223 	 ((strchr("BbIi", p[1]) && p[2] == '>') || \
224 	  (p[1] == '/' && strchr("BbIi", p[2]) && p[3] == '>')))
225 
226 /* ---------------------------------------------------------------------------
227 
228    Data Declarations (including externals)
229 
230 */
231 
232 static char currfont[10], prevfont[10];
233 
234 /* ---------------------------------------------------------------------------
235 
236    External Routine References & Function Prototypes
237 
238 */
239 
240 /* ---------------------------------------------------------------------------
241 
242    alloc
243 
244    Notes:
245 
246       This routine interface to 'calloc()'.  It will terminate if
247       unsuccessful.
248 
249 */
alloc(int size)250 char *alloc (int size)
251 {
252    char *p;
253 
254    /* not all calloc()s like null requests */
255    if (size == 0) size = 1;
256 
257    if ((p = (char *)calloc(1, size)) == NULL) {
258       fprintf(stderr, E_ALLOC_ERR, progname);
259       exit(EXIT_FAILURE);
260    }
261 
262    return p;
263 }
264 
265 /* ---------------------------------------------------------------------------
266 
267    ci_strcmp
268 
269    Notes:
270 
271       This routine acts as the case-insensitive version of 'strcmp()'.
272 
273 */
ci_strcmp(register char * s1,register char * s2)274 int ci_strcmp (register char *s1, register char *s2)
275 {
276    register char c1, c2;
277 
278    for ( ; (c1 = tolower(*s1)) == (c2 = tolower(*s2)); s1++, s2++) {
279       if (c1 == '\0') return 0;
280    }
281    return c1 - c2;
282 }
283 
284 /* ---------------------------------------------------------------------------
285 
286    ci_strncmp
287 
288    Notes:
289 
290       This routine acts as the case-insensitive version of 'strncmp()'.
291 
292 */
ci_strncmp(register char * s1,register char * s2,int n)293 int ci_strncmp (register char *s1, register char *s2, int n)
294 {
295    register char c1, c2;
296 
297    for (c1 = c2 = '\0'; --n >= 0 && (c1 = tolower(*s1)) == (c2 = tolower(*s2)); s1++, s2++) {
298       if (c1 == '\0') return 0;
299    }
300 
301    return n < 0 ? 0 : c1 - c2;
302 }
303 
304 /* ---------------------------------------------------------------------------
305 
306    define_font
307 
308    Notes:
309 
310       This routine overwrites "orig_font" with the specified fields from
311       "new_font" (or with "dflt_font" if "new_font" is NULL or null string).
312 
313 */
define_font(char * orig_font,char * new_font,char * dflt_font)314 void define_font (char *orig_font, char *new_font, char *dflt_font)
315 {
316    char *ofont, *nfont, *osize, *nsize, tmp1[STRSIZ], tmp2[STRSIZ];
317 
318    /* use default font/size if new font is null */
319 
320    if (new_font == NULL || new_font[0] == '\0') {
321       strcpy(orig_font, dflt_font);
322       return;
323    }
324 
325    /* split old and new fonts into font/size components */
326    SPLIT(orig_font, ofont, osize, '/');
327 
328    new_font = strcpy(tmp1, new_font);
329    SPLIT(new_font, nfont, nsize, '/');
330 
331    /* use default size if new font size invalid */
332    if (nsize[0] && atoi(nsize) <= 0) nsize = strrchr(dflt_font, '/') + 1;
333 
334    /* replace fields of old font with specified fields from new */
335 
336    strcpy(tmp2, nfont[0] ? nfont : ofont);
337    strcat(tmp2, "/");
338    strcat(tmp2, nsize[0] ? nsize : osize);
339 
340    strcpy(orig_font, tmp2);
341 
342    return;
343 }
344 
345 /* ---------------------------------------------------------------------------
346 
347    define_shading
348 
349    Notes:
350 
351       This routine overwrites "orig_shading" with the specified fields from
352       "new_shading" (or with "dflt_shading" if "new_shading" is NULL or null
353       string).
354 
355 */
define_shading(char * orig_shading,char * new_shading,char * dflt_shading)356 void define_shading (char *orig_shading, char *new_shading, char *dflt_shading)
357 {
358    char *odate, *ndate, *ddate, *ofill, *nfill, *dfill;
359    char tmp1[STRSIZ], tmp2[STRSIZ], tmp3[STRSIZ];
360    double v;
361 
362    /* use default date/fill if new _shading is null */
363    if (new_shading == NULL || new_shading[0] == '\0') {
364       strcpy(orig_shading, dflt_shading);
365       return;
366    }
367 
368    /* split old, new, and default shadings into date/fill components */
369    SPLIT(orig_shading, odate, ofill, '/');
370 
371    new_shading = strcpy(tmp1, new_shading);
372    SPLIT(new_shading, ndate, nfill, '/');
373 
374    dflt_shading = strcpy(tmp2, dflt_shading);
375    SPLIT(dflt_shading, ddate, dfill, '/');
376 
377    /* replace invalid fields from new shading with default values */
378    if (nfill[0] && strchr(nfill, RGB_CHAR) == NULL && ((v = atof(nfill)) <= 0.0 || v > 1.0)) {
379       nfill = dfill;
380    }
381    if (ndate[0] && strchr(ndate, RGB_CHAR) == NULL && ((v = atof(ndate)) <= 0.0 || v > 1.0)) {
382       ndate = ddate;
383    }
384 
385    /* replace fields of old shading with specified fields from new */
386 
387    strcpy(tmp3, ndate[0] ? ndate : odate);
388    strcat(tmp3, "/");
389    strcat(tmp3, nfill[0] ? nfill : ofill);
390 
391    strcpy(orig_shading, tmp3);
392 
393    return;
394 }
395 
396 /* ---------------------------------------------------------------------------
397 
398    set_fontstyle
399 
400    Notes:
401 
402       This routine copies the font style string to the output buffer (prefixed
403       by ' ' for Postscript, '<' for * HTML).  It resets the previous and
404       current font style strings.  It returns a pointer to the next position
405       in the output buffer.
406 
407 */
set_fontstyle(char * p,char * esc)408 char *set_fontstyle (char *p, char *esc)
409 {
410    if (output_type == OUTPUT_PS) {
411       *p++ = ' ';
412       strcpy(p, esc);
413       p += strlen(esc);
414       strcpy(prevfont, currfont);
415       strcpy(currfont, esc);
416    }
417    else if (output_type == OUTPUT_HTML) {
418       *p++ = '<';
419       if (strchr("rpRP", esc[1])) {
420          sprintf(p, "/%c", currfont[1]);
421          p += 2;
422          strcpy(currfont, prevfont);
423       }
424       else {
425          *p++ = esc[1];
426          strcpy(prevfont, currfont);
427          strcpy(currfont, esc);
428       }
429    }
430 
431    return p;
432 }
433 
434 /*
435  * Date calculation routines (see also macros in pcaldefs.h)
436  */
437 
438 /* ---------------------------------------------------------------------------
439 
440    normalize
441 
442    Notes:
443 
444       This routine adjusts the day in case it has crossed a month (or year)
445       boundary.
446 
447       The first parameter is a pointer to the date.
448 
449 */
normalize(date_str * pd)450 void normalize (date_str *pd)
451 {
452    int len;
453 
454    /* adjust if day is in previous or following month */
455 
456    while (pd->dd < 1) {
457       pd->yy = PREV_YEAR(pd->mm, pd->yy);
458       pd->mm = PREV_MONTH(pd->mm, pd->yy);
459       pd->dd += LENGTH_OF(pd->mm, pd->yy);
460    }
461 
462    while (pd->dd > (len = LENGTH_OF(pd->mm, pd->yy))) {
463       pd->dd -= len;
464       pd->yy = NEXT_YEAR(pd->mm, pd->yy);
465       pd->mm = NEXT_MONTH(pd->mm, pd->yy);
466    }
467 
468    return;
469 }
470 
471 /* ---------------------------------------------------------------------------
472 
473    calc_day
474 
475    Notes:
476 
477       This routine calculates the calendar date from the ordinal date (e.g.,
478       "first Friday in November", "last day in October").
479 
480       It returns the calendar date if it exists, 0 if it does not.
481 
482 */
calc_day(int ord,int wkd,int mm)483 int calc_day (int ord, int wkd, int mm)
484 {
485    int first, last, day, (*pfcn) (int, int, int);
486 
487    if (IS_WILD(wkd)) {   /* "day", "weekday", "workday", or "holiday" */
488       pfcn = pdatefcn[wkd - WILD_FIRST];
489       last = LENGTH_OF(mm, curr_year);
490 
491       if (ord < 0) {   /* search backwards */
492          for (day = last;
493               day >= 1 && !((*pfcn)(mm, day, curr_year) && ++ord == 0);
494               day--)
495             ;
496       }
497       else {   /* search forwards */
498          for (day = 1;
499               day <= last && !((*pfcn)(mm, day, curr_year) && --ord == 0);
500               day++)
501             ;
502       }
503       return is_valid(mm, day, curr_year) ? day : 0;
504    }
505    else {   /* fixed weekday - calculate it */
506       first = (wkd - FIRST_OF(mm, curr_year) + 7) % 7 + 1;
507       if (ord < 0) {   /* get last (try 5th, then 4th) */
508          if (!is_valid(mm, last = first + 28, curr_year)) last -= 7;
509          if (!is_valid(mm, day = last + 7 * (ord + 1), curr_year)) day = 0;
510       }
511       else {
512          if (!is_valid(mm, day = first + 7 * (ord - 1), curr_year)) day = 0;
513       }
514       return day;
515    }
516 }
517 
518 /* ---------------------------------------------------------------------------
519 
520    calc_year_day
521 
522    Notes:
523 
524       This routine calculates the calendar date from the ordinal date within
525       year (e.g., "last Friday in year", "10th holiday in year").
526 
527       If the date exists, fill in 'pdate' and return TRUE, otherwise return
528       FALSE.
529 
530 */
calc_year_day(int ord,int wkd,date_str * pdate)531 int calc_year_day (int ord, int wkd, date_str *pdate)
532 {
533    int incr, (*pfcn) (int, int, int);
534    date_str date;
535 
536    if (IS_WILD(wkd)) {   /* "day", "weekday", "workday", or "holiday" */
537       pfcn = pdatefcn[wkd - WILD_FIRST];
538 
539       if (ord < 0) {   /* nth occurrence backwards */
540          MAKE_DATE(date, DEC, 31, curr_year);
541          ord = -ord;
542          incr = -1;
543       }
544       else {   /* nth occurrence forwards */
545          MAKE_DATE(date, JAN, 1, curr_year);
546          incr = 1;
547       }
548 
549       /* search for selected occurrence of specified wildcard */
550 
551       while (date.yy == curr_year && !((*pfcn)(date.mm, date.dd, date.yy) && --ord == 0)) {
552          date.dd += incr;
553          normalize(&date);
554       }
555    }
556    else {   /* fixed weekday - calculate it */
557       if (ord < 0) {
558          MAKE_DATE(date, DEC, calc_day(-1, wkd, DEC) + 7 * (ord + 1), curr_year);
559       }
560       else {
561          MAKE_DATE(date, JAN, calc_day(1, wkd, JAN) + 7 * (ord - 1), curr_year);
562       }
563 
564       normalize(&date);
565    }
566 
567    return date.yy == curr_year ? (*pdate = date, TRUE) : FALSE;
568 }
569 
570 /* ---------------------------------------------------------------------------
571 
572    calc_weekday
573 
574    Notes:
575 
576       This routine returns the weekday (0-6) of mm/dd/yy (mm: 1-12).
577 
578 */
calc_weekday(int mm,int dd,int yy)579 int calc_weekday (int mm, int dd, int yy)
580 {
581    return (yy + (yy-1)/4 - (yy-1)/100 + (yy-1)/400 + OFFSET_OF(mm, yy) + (dd-1)) % 7;
582 }
583 
584 /* ---------------------------------------------------------------------------
585 
586    note_day
587 
588    Notes:
589 
590       This routine translates 'n' (from "note/<n>" spec) to the appropriate
591       note text day for mm/yy.
592 
593       It returns the note text day if it's in range, 0 if not.
594 
595 */
note_day(int mm,int n,int yy)596 int note_day (int mm, int n, int yy)
597 {
598    int day, lastday;
599 
600    if (n == 0) n = NOTE_DEFAULT;   /* convert 0 to appropriate default */
601 
602 
603    /* lastday depends on month length and presence (but not position) of small
604       calendars
605    */
606    lastday = LAST_NOTE_DAY - (LENGTH_OF(mm, yy) - 28) - (small_cal_pos == SC_NONE ? 0 : 2);
607 
608    /* count forward if n is positive, backward if negative */
609    day = (n > 0) ? FIRST_NOTE_DAY + n - 1 : lastday + n + 1;
610 
611    /* make sure result is valid for this month/year */
612    return (day >= FIRST_NOTE_DAY && day <= lastday) ? day : 0;
613 }
614 
615 /* ---------------------------------------------------------------------------
616 
617    note_box
618 
619    Notes:
620 
621       This routine translates 'dd' from the note text day to 'box number' for
622       mm/yy, adjusting for presence and position of small calendars.
623 
624 */
note_box(int mm,int dd,int yy)625 int note_box (int mm, int dd, int yy)
626 {
627    int startbox = START_BOX(mm, yy), pc, nc;
628 
629    /* move starting box to second row if conflict with small calendars */
630    pc = prev_cal_box[small_cal_pos];
631    nc = next_cal_box[small_cal_pos];
632    if (pc == startbox || nc == startbox) startbox += 7;
633 
634    dd -= FIRST_NOTE_DAY;   /* convert to note box number 0..13 */
635    dd += (pc == 0) + (nc == 1);   /* adjust for small calendars in 0, 1 */
636 
637    /* position box after calendar body if no room before */
638    return dd < startbox ? dd : dd + LENGTH_OF(mm, yy);
639 }
640 
641 /* ---------------------------------------------------------------------------
642 
643    is_valid
644 
645    Notes:
646 
647       This routine returns TRUE if m/d/y is a valid date.
648 
649 */
is_valid(int m,int d,int y)650 int is_valid (int m, int d, int y)
651 {
652    return m >= JAN && m <= DEC && d >= 1 && d <= LENGTH_OF(m, y);
653 }
654 
655 /* ---------------------------------------------------------------------------
656 
657    century
658 
659    Notes:
660 
661       This routine returns the current century (CC00).
662 
663       It presumes the standard Unix behavior, that the 'tm_year' field of
664       'struct tm' (see <time.h>, pcaldefs.h) represents years elapsed since
665       1900.
666 
667 */
century(void)668 int century (void)
669 {
670    static int this_century = -1;
671    struct tm *p_tm;
672    time_t tmp;
673 
674    if (this_century < 0) {   /* calculate first time only */
675       time(&tmp);
676       p_tm = localtime(&tmp);
677       this_century = ((TM_YEAR + p_tm->tm_year) / 100) * 100;
678    }
679 
680    return this_century;
681 }
682 
683 /*
684  * Token parsing/remerging routines:
685  */
686 
687 /* ---------------------------------------------------------------------------
688 
689    loadwords
690 
691    Notes:
692 
693       This routine tokenizes the buffer 'buf' into array 'words' and returns the word count.
694 
695       It differs from the old 'loadwords()' in that it handles quoted (" or ')
696       strings and removes escaped quotes.
697 
698 */
loadwords(char ** words,char * buf)699 int loadwords (char **words, char *buf)
700 {
701    register char *ptok;
702    char *delim, **ap, *p1, *p2, c;
703    int nwords;
704 
705    for (ptok = buf, ap = words; TRUE; ap++) {
706 
707       ptok += strspn(ptok, WHITESPACE); /* find next token */
708 
709       if (! *ptok) {   /* end of buf? */
710          *ap = NULL;   /* add null ptr at end */
711          nwords = ap - words;   /* number of non-null ptrs */
712          break;   /* exit loop */
713       }
714 
715       delim = *ptok == '"'  ? "\"" :   /* set closing delimiter */
716          *ptok == '\'' ? "'"  : WHITESPACE;
717 
718       /* split flag followed by quoted string into two words */
719       if (*ptok == '-' && isalpha((int)ptok[1]) &&
720           ((c = ptok[2]) == '"' || c == '\'')) {
721          delim = c == '"' ? "\"" : "'";
722          *ap++ = ptok;
723          ptok[2] = '\0';   /* terminate first token */
724          ptok += 3;   /* start second token */
725       }
726       else if (*ptok == *delim) ptok++;   /* skip opening quote */
727 
728       *ap = ptok;   /* save token ptr */
729 
730       /* find first unescaped string delimiter - handle special case where
731          preceding backslash is itself escaped
732       */
733       do {
734          ptok += strcspn(ptok, delim);
735          if ((c = ptok[-1]) == '\\') {
736             if (ptok[-2] == '\\') break;   /* stop on \\" or \\' */
737             else ptok++;
738          }
739       } while (c == '\\');
740 
741       if (*ptok) *ptok++ = '\0';   /* terminate token */
742    }
743 
744    /* now reprocess the word list, removing remaining escapes */
745    for (ap = words; *ap; ap++) {
746       for (p1 = p2 = *ap; (c = *p2 = *p1++) != '\0'; p2++) {
747          if (c == '\\') *p2 = *p1++;
748       }
749    }
750 
751    return nwords;   /* return word count */
752 
753 }
754 
755 /* ---------------------------------------------------------------------------
756 
757    copy_text
758 
759    Notes:
760 
761       This routine retrieves the remaining text in 'pbuf' and copies it to the
762       output string, separating tokens by a single blank and condensing runs
763       of blanks (all other whitespace has been converted to blanks by now) to
764       one blank.
765 
766       The first parameter is a pointer to the output buffer, which can be
767       'pbuf' itself.
768 
769       The second parameter is a pointer to first text word in "words".
770 
771 */
copy_text(char * pbuf,char ** ptext)772 void copy_text (char *pbuf, char **ptext)
773 {
774    char *p, *pb;
775 
776    /* copy words to pbuf, separating by one blank */
777 
778    for (*(pb = pbuf) = '\0'; (p = *ptext) != NULL; *pb++ = *++ptext ? ' ' : '\0') {
779       for ( ; *p; p++) {
780          if (! (*p == ' ' && (pb == pbuf || pb[-1] == ' '))) *pb++ = *p;
781       }
782       if (pb > pbuf && pb[-1] == ' ') pb--;
783    }
784    return;
785 }
786 
787 /* ---------------------------------------------------------------------------
788 
789    split_date
790 
791    Notes:
792 
793       This routine extracts 1-3 numeric fields (separated by one or more
794       non-numeric characters) from date string.
795 
796       It returns the number of fields.
797 
798       The first parameter is the input string.  The last 3 parameters are the
799       output numbers.
800 
801 */
split_date(char * pstr,int * pn1,int * pn2,int * pn3)802 int split_date (char *pstr, int *pn1, int *pn2, int *pn3)
803 {
804    int i, n, *pn;
805 
806    /* attempt to extract up to three numeric fields */
807    for (n = 0, i = 1; i <= 3; i++) {
808       pn = i == 1 ? pn1 : i == 2 ? pn2 : pn3;   /* crude but portable */
809       if (pn) *pn = *pstr ? (n++, atoi(pstr)) : 0;
810       SKIP_FIELD(pstr);   /* go to next field */
811    }
812    return n;
813 }
814 
815 /*
816  * Escape sequence conversion routines:
817  */
818 
819 /* ---------------------------------------------------------------------------
820 
821    html_esc
822 
823    Notes:
824 
825       This routine recognizes an HTML special character ("&lt;" etc.) and
826       converts it back to its ASCII equivalent.
827 
828       It returns a pointer to the last character in the escape sequence (if
829       found) or a pointer to the original input character.
830 
831       The first parameter points to the start of the string.  The second
832       parameter is the returned ASCII equivalent.
833 
834 */
html_esc(char * p,char * val)835 static char *html_esc (char *p, char *val)
836 {
837    static struct {
838       char *html;
839       char ascii;
840    } *pt, translate[] = {   /* special HTML sequences */
841       { "&lt;", '<' },
842       { "&gt;", '>' },
843       { "&amp;", '&' },
844       { "&quot;", '"' },
845       { "&nbsp;", ' ' },
846       { NULL, 0   }
847    };
848 
849    if (*p == '&') {
850       /* translate "&#NNN;" character sequences back to ASCII */
851       if (p[1] == '#' &&
852           isdigit((int)p[2]) && isdigit((int)p[3]) && isdigit((int)p[4]) &&
853           p[5] == ';') {
854          *val = atoi(p + 2);
855          return p + 5;
856       }
857       /* look up predefined special characters in table above */
858       for (pt = translate; pt->html; pt++) {
859          int l = strlen(pt->html);
860          if (ci_strncmp(p, pt->html, l) == 0) {
861             *val = pt->ascii;
862             return p + l - 1;
863          }
864       }
865    }
866 
867    /* not an escape sequence - return original pointer */
868    *val = *p;
869    return p;
870 }
871 
872 /* ---------------------------------------------------------------------------
873 
874    octal_esc
875 
876    Notes:
877 
878       This routine reads up to 3 octal digits from the specified character
879       string.  It fills in the value of the octal constant and returns a
880       pointer to last character.
881 
882 */
octal_esc(char * buf,char * val)883 static char *octal_esc (char *buf, char *val)
884 {
885    int i, n, c;
886 
887    for (n = 0, i = 0; i < 3 && (c = buf[i]) && isodigit(c); i++) {
888       n = n * 8 + (c - '0');
889    }
890 
891    *val = n & CHAR_MSK;   /* fill in the value */
892    return buf + i - 1;   /* return pointer to last character */
893 }
894 
895 /* ---------------------------------------------------------------------------
896 
897    hex_esc
898 
899    Notes:
900 
901       This routine reads 'x' or 'X' followed by 1 or 2 hex digits from the
902       specified character string.  It fills in the value of the hexadecimal
903       constant (or letter if no hex digits follow) and returns a pointer to
904       the last character.
905 
906 */
hex_esc(char * buf,char * val)907 static char *hex_esc (char *buf, char *val)
908 {
909    int i, n, c;
910 
911    /* assume leading character is known to be 'x' or 'X'; skip it */
912    buf++;
913 
914    for (n = 0, i = 0; i < 2 && (c = buf[i]) && isxdigit(c); i++) {
915       n = n * 16 + (isupper(c) ? c - 'A' + 10 :
916                     islower(c) ? c - 'a' + 10 :
917                     c - '0');
918    }
919 
920    *val = i == 0 ? buf[-1] : n & CHAR_MSK; /* fill in the value */
921    return buf + i - 1;   /* return pointer to last character */
922 }
923 
924 /* ---------------------------------------------------------------------------
925 
926    cvt_escape
927 
928    Notes:
929 
930       This routine copies the string 'ibuf' to the string 'obuf', converting
931       octal / hex / HTML / whitespace escape sequences to the appropriate
932       equivalents.
933 
934       Escaped quotes and backslashes are not converted here; they need to be
935       preserved so that 'loadwords()' can parse quoted strings correctly.
936 
937 */
cvt_escape(char * obuf,char * ibuf)938 void cvt_escape (char *obuf, char *ibuf)
939 {
940    char c, c2, *po, *pi, *p, *dfltfont;
941    static char whitespace[] = "abnrtv"; /* see ANSI spec, 2.2.2 */
942    static char no_cvt[] = "\"'\\";
943 
944    dfltfont = fontstyle[0] == BOLD   ? BOLD_FONT :
945       fontstyle[0] == ITALIC ? ITALIC_FONT :
946       ROMAN_FONT ;
947 
948    strcpy(currfont, dfltfont);   /* defaults for font style */
949    strcpy(prevfont, dfltfont);
950 
951    for (po = obuf, pi = ibuf; (c = *pi) != '\0'; *po++ = c, pi++) {
952 
953       /* handle escape sequences here:
954 
955           escaped whitespace and ANSI escapes (except \f) are all converted to
956           a single space;
957 
958           octal and hex constants are converted in place;
959 
960           escaped single/double quotes are left escaped for loadwords() to
961           handle later on;
962 
963           if the output is PostScript:
964 
965             \f[BIR] is converted to " .[bir] ";
966             \fP is converted to the previous " .[bir] ";
967             <[BI]> is converted to " .[bi] ";
968             </[BI]> is converted to the previous " .[bir] "
969 
970           if the output is HTML:
971 
972             \f[BI] is converted to "<[BI]>";
973             \f[PR] is converted to the previous "</[BI]>";
974             <[BI]> and </[BI]> are copied intact
975 
976        */
977       if (c == '\\') {
978          c2 = *++pi;
979          if (isspace((int)c2) || strchr(whitespace, c2)) {
980             c = ' ';
981          }
982          else if (isodigit(c2)) {   /* octal */
983             pi = octal_esc(pi, &c);
984          }
985          else if (tolower(c2) == 'x') {   /* hex */
986             pi = hex_esc(pi, &c);
987          }
988          else if (c2 == 'f') {   /* \f[BIPR] */
989             c = output_type == OUTPUT_HTML ? '>' : ' ';
990             if (*++pi == BOLD) po = set_fontstyle(po, BOLD_FONT);
991             else if (*pi == ITALIC) po = set_fontstyle(po, ITALIC_FONT);
992             else if (*pi == PREVFONT) po = set_fontstyle(po, prevfont);
993             else if (*pi == ROMAN) po = set_fontstyle(po, ROMAN_FONT);
994             else {   /* unrecognized \f escape */
995                *po++ = ' ';
996                c = *pi;
997             }
998          }
999          else {
1000             if (strchr(no_cvt, c2)) *po++ = c;   /* leave escaped */
1001             c = c2;
1002          }
1003       }
1004       else if (output_type != OUTPUT_HTML && (p = html_esc(pi, &c2)) != pi) {
1005          /* convert special HTML character back to ASCII */
1006          pi = p;
1007          if (c2 == '"') *po++ = '\\';
1008          c = c2;
1009       }
1010       else if (HTML_ESCAPE(pi)) {
1011          /* convert HTML escape to PostScript */
1012          c2 = *++pi;
1013          if ((c2 = toupper(c2)) == BOLD) po = set_fontstyle(po, BOLD_FONT);
1014          else if (c2 == ITALIC) po = set_fontstyle(po, ITALIC_FONT);
1015          else po = set_fontstyle(po, prevfont); /* </[BI]> */
1016 
1017          while ((c = *++pi) != '>')
1018             ;
1019 
1020          c = output_type == OUTPUT_HTML ? '>' : ' ';
1021       }
1022    }
1023 
1024    /* if generating HTML, reset font at end of line if necessary */
1025    if (output_type == OUTPUT_HTML && currfont[1] != dfltfont[1]) {
1026       *po++ = '<';
1027       *po++ = '/';
1028       *po++ = currfont[1];
1029       *po++ = '>';
1030    }
1031    *po = '\0';
1032 
1033    return;
1034 }
1035 
1036 /*
1037  * File input routines:
1038  */
1039 
1040 
1041 /* ---------------------------------------------------------------------------
1042 
1043    get_pcal_line
1044 
1045    Notes:
1046 
1047       This routine reads the next non-null line of the input file into 'buf'
1048 
1049       It returns 0 on EOF.  It strips leading whitespace and trailing
1050       comments.  It also handles escaped newlines and calls 'cvt_escape()' to
1051       translate other escape sequences.
1052 
1053 */
get_pcal_line(FILE * fp,char * buf,int * pline)1054 int get_pcal_line (FILE *fp, char *buf, int *pline)
1055 {
1056    register char *cp;
1057    register int c, c2;
1058    int in_comment;   /* comments: from '#' to end-of-line */
1059    char tmpbuf[LINSIZ];   /* temporary buffer to accumulate line */
1060 
1061    cp = tmpbuf;
1062    *buf = '\0';   /* in case of premature EOF */
1063 
1064    do {
1065       in_comment = FALSE;
1066       while ((c = getc(fp)) != '\n' && c != EOF) {
1067          if (c == COMMENT_CHAR) in_comment = TRUE;
1068 
1069          if (isspace(c)) c = ' ';   /* whitespace => blank */
1070 
1071          /* ignore comments and leading white space */
1072          if (in_comment || (cp == tmpbuf && c == ' ')) continue;
1073 
1074          /* only handle escape newlines and '#' here; other escapes are now
1075             handled by cvt_escape() or loadwords() (q.v.)
1076          */
1077          if (c == '\\') {
1078             if ((c2 = getc(fp)) == EOF) return FALSE;
1079 
1080             if (c2 == '\n') {
1081                c = ' ';
1082                (*pline)++;
1083             }
1084             else if (c2 == COMMENT_CHAR) c = '#';
1085             else ungetc(c2, fp);
1086          }
1087 
1088          /*
1089             Crudely prevent buffer overflow to prevent exploit by malicious
1090             calendar input file.
1091          */
1092          if ((cp - tmpbuf) < (LINSIZ - 1)) *cp++ = c;
1093          else return FALSE;
1094       }
1095 
1096       if (c == EOF) {
1097          /*
1098             15 Dec 2007:
1099 
1100             Prior to the 4.11.0 release, the code just returned 'FALSE' here,
1101             with no other test being performed.
1102 
1103             That was causing a bug whereby the last line of a configuration
1104             file was silently ignored if it ended without a 'line feed' (ASCII
1105             10 character).
1106 
1107             Adding a test, as done below, to see if anything is in the line
1108             buffer (before deciding to return 'FALSE') fixes this bug.
1109 
1110          */
1111          if ((cp - tmpbuf) == 0) return FALSE;    /* no more input lines */
1112       }
1113 
1114       (*pline)++;   /* bump line number */
1115 
1116    } while (cp == tmpbuf);   /* ignore empty lines */
1117 
1118    *cp = '\0';
1119    cvt_escape(buf, tmpbuf);   /* convert escape sequences */
1120    return TRUE;
1121 }
1122 
1123 /*
1124  * Routines dealing with translation of file specifications
1125  */
1126 
1127 /* ---------------------------------------------------------------------------
1128 
1129    mk_path
1130 
1131    Notes:
1132 
1133       This routine extracts the path component from a Unix file specification.
1134 
1135       The first parameter is the output path.
1136 
1137       The second parameter is input file specification.
1138 
1139 */
mk_path(char * path,char * filespec)1140 char *mk_path (char *path, char *filespec)
1141 {
1142    char *p;
1143 
1144    strcpy(path, filespec);
1145    if (!(p = strrchr(path, END_PATH))) {
1146       p = path - 1; /* return null string if no path */
1147    }
1148 
1149    *++p = '\0';
1150    return path;
1151 }
1152 
1153 /* ---------------------------------------------------------------------------
1154 
1155    mk_filespec
1156 
1157    Notes:
1158 
1159       This routine merges the Unix path and file names, where the latter can
1160       be relative.
1161 
1162       The first parameter is the output file specification.
1163 
1164       The second parameter is input path.
1165 
1166       The third parameter is the input file name.
1167 
1168 */
mk_filespec(char * filespec,char * path,char * name)1169 char *mk_filespec (char *filespec, char *path, char *name)
1170 {
1171    char *p;
1172 
1173    *filespec = '\0';
1174 
1175    /* copy name intact if absolute; else merge path and relative name */
1176 
1177    /* if name starts with "~/", translate it for user */
1178    if (strncmp(name, "~/", 2) == 0 && (p = getenv(HOME_DIR)) != NULL) {
1179       strcpy(filespec, p);
1180       TERM_PATH(filespec);
1181       name += 2;   /* skip "~/" */
1182    }
1183    else if (*name != START_PATH) {   /* relative path */
1184       /* if path starts with "~/", translate it for user */
1185       if (strncmp(path, "~/", 2) == 0 && (p = getenv(HOME_DIR)) != NULL) {
1186          strcpy(filespec, p);
1187          TERM_PATH(filespec);
1188          path += 2;   /* skip "~/"; append rest below */
1189       }
1190       strcat(filespec, path);
1191       TERM_PATH(filespec);
1192    }
1193 
1194    return strcat(filespec, name);
1195 }
1196 
1197 #ifdef BUILD_ENV_UNIX
1198 
1199 /* ---------------------------------------------------------------------------
1200 
1201    find_executable
1202 
1203    Notes:
1204 
1205       This routine returns the full path name of the executable file.
1206 
1207       NOTE: This routine is highly Unix-dependent.  Probably no other build
1208       environment can use it.
1209 
1210 */
find_executable(char * prog)1211 char *find_executable (char *prog)
1212 {
1213    char *pathvar, *p, *pnext;
1214    struct stat st;
1215    static char filepath[STRSIZ];
1216 
1217    /* if 'prog' is an absolute/relative path name or environment variable
1218       'PATH' does not exist, return 'prog'; otherwise, search the directories
1219       specified in 'PATH' for the executable
1220    */
1221    if (strchr(prog, END_PATH) == 0 && (p = getenv(PATH_ENV_VAR)) != 0) {
1222       pathvar = alloc(strlen(p) + 1);
1223       strcpy(pathvar, p);
1224 
1225       /* assumes PATH is of form "dir1:dir2:...:dirN"; strtok() would be handy
1226          here, but not everybody has it yet
1227       */
1228       for (p = pathvar; *p; p = pnext) {
1229          if ((pnext = strchr(p, ':')) != NULL) *pnext++ = '\0';
1230          else pnext = p + strlen(p);
1231 
1232          /* assume the executable lives in the path, and construct its
1233             complete file name
1234          */
1235          filepath[0] = '\0';
1236          if (strcmp(p, ".") != 0) {
1237             strcat(filepath, p);
1238             strcat(filepath, "/");
1239          }
1240          strcat(filepath, prog);
1241 
1242          /* check that file a) exists, b) is a "normal" file, and c) is
1243             executable - if so, return its full path
1244           */
1245          if (stat(filepath, &st) >= 0 &&
1246              (st.st_mode & S_IFMT) == S_IFREG &&
1247              access(filepath, X_OK) == 0) {
1248             free(pathvar);
1249             return filepath;
1250          }
1251       }
1252       free(pathvar);
1253    }
1254 
1255    return prog;
1256 }
1257 
1258 #else
1259 
1260 /* ---------------------------------------------------------------------------
1261 
1262    find_executable
1263 
1264    Notes:
1265 
1266       This routine returns the full path name of the executable file.
1267 
1268       NOTE: This routine is for non-Unix build environments only.
1269 
1270 */
find_executable(char * prog)1271 char *find_executable (char *prog)
1272 {
1273    return prog;   /* non-Un*x flavor always returns its argument */
1274 }
1275 
1276 #endif
1277