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 ("<" 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 { "<", '<' },
842 { ">", '>' },
843 { "&", '&' },
844 { """, '"' },
845 { " ", ' ' },
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