1 /* $Id: rt-util.c,v 3.0 1992/12/14 00:14:12 davison Trn $
2 */
3 /* The authors make no claims as to the fitness or correctness of this software
4  * for any use whatsoever, and it is provided as is. Any use of this software
5  * is at the user's own risk.
6  */
7 
8 #include "EXTERN.h"
9 #include "common.h"
10 #include "cache.h"
11 #include "ngdata.h"
12 #include "artio.h"
13 #include "rthread.h"
14 #include "rt-select.h"
15 #include "term.h"
16 #include "nntp.h"
17 #include "INTERN.h"
18 #include "rt-util.h"
19 
20 /* Name-munging routines written by Ross Ridge.
21 ** Enhanced by Wayne Davison.
22 */
23 
24 /* Extract the full-name part of an email address, returning NULL if not
25 ** found.
26 */
27 char *
extract_name(name)28 extract_name(name)
29 char *name;
30 {
31     char *s;
32     char *lparen, *rparen;
33     char *langle;
34 
35     while (isspace(*name)) {
36 	name++;
37     }
38 
39     lparen = index(name, '(');
40     rparen = rindex(name, ')');
41     langle = index(name, '<');
42     if (!lparen && !langle) {
43 	return NULL;
44     } else
45     if (langle && (!lparen || !rparen || lparen > langle || rparen < langle)) {
46 	if (langle == name) {
47 	    return NULL;
48 	}
49 	*langle = '\0';
50     } else {
51 	name = lparen;
52 	*name++ = '\0';
53 	while (isspace(*name)) {
54 	    name++;
55 	}
56 	if (name == rparen) {
57 	    return NULL;
58 	}
59 	if (rparen != NULL) {
60 	    *rparen = '\0';
61 	}
62     }
63 
64     if (*name == '"') {
65 	name++;
66 	while (isspace(*name)) {
67 	    name++;
68 	}
69 	if ((s = rindex(name, '"')) != NULL) {
70 	    *s = '\0';
71 	}
72     }
73     return name;
74 }
75 
76 /* If necessary, compress a net user's full name by playing games with
77 ** initials and the middle name(s).  If we start with "Ross Douglas Ridge"
78 ** we try "Ross D Ridge", "Ross Ridge", "R D Ridge" and finally "R Ridge"
79 ** before simply truncating the thing.  We also turn "R. Douglas Ridge"
80 ** into "Douglas Ridge" and "Ross Ridge D.D.S." into "Ross Ridge" as a
81 ** first step of the compaction, if needed.
82 */
83 char *
compress_name(name,max)84 compress_name(name, max)
85 char *name;
86 int max;
87 {
88     register char *s, *last, *mid, *d;
89     register int len, namelen, midlen;
90     int notlast;
91 
92 try_again:
93     /* First remove white space from both ends. */
94     while (isspace(*name)) {
95 	name++;
96     }
97     if ((len = strlen(name)) == 0) {
98 	return name;
99     }
100     s = name + len - 1;
101     while (isspace(*s)) {
102 	s--;
103     }
104     s[1] = '\0';
105     if (s - name + 1 <= max) {
106 	return name;
107     }
108 
109     /* Look for characters that likely mean the end of the name
110     ** and the start of some hopefully uninteresting additional info.
111     ** Spliting at a comma is somewhat questionalble, but since
112     ** "Ross Ridge, The Great HTMU" comes up much more often than
113     ** "Ridge, Ross" and since "R HTMU" is worse than "Ridge" we do
114     ** it anyways.
115     */
116     for (d = name + 1; *d; d++) {
117 	if (*d == ',' || *d == ';' || *d == '(' || *d == '@'
118 	 || (*d == '-' && (d[1] == '-' || d[1] == ' '))) {
119 	    *d-- = '\0';
120 	    s = d;
121 	    break;
122 	}
123     }
124 
125     /* Find the last name */
126     do {
127 	notlast = 0;
128 	while (isspace(*s)) {
129 	    s--;
130 	}
131 	s[1] = '\0';
132 	len = s - name + 1;
133 	if (len <= max) {
134 	    return name;
135 	}
136 	/* If the last name is an abbreviation it's not the one we want. */
137 	if (*s == '.')
138 	    notlast = 1;
139 	while (!isspace(*s)) {
140 	    if (s == name) {	/* only one name */
141 		name[max] = '\0';
142 		return name;
143 	    }
144 	    if (isdigit(*s)) {	/* probably a phone number */
145 		notlast = 1;	/* so chuck it */
146 	    }
147 	    s--;
148 	}
149     } while (notlast);
150 
151     last = s-- + 1;
152 
153     /* Look for a middle name */
154     while (isspace(*s)) {	/* get rid of any extra space */
155 	len--;
156 	s--;
157     }
158     mid = name;
159     while (!isspace(*mid)) {
160 	mid++;
161     }
162     namelen = mid - name + 1;
163     if (mid == s+1) {	/* no middle name */
164 	mid = 0;
165 	midlen = 0;
166     } else {
167 	*mid++ = '\0';
168 	while (isspace(*mid)) {
169 	    len--;
170 	    mid++;
171 	}
172 	midlen = s - mid + 2;
173 	/* If first name is an initial and middle isn't and it all fits
174 	** without the first initial, drop it. */
175 	if (len > max && mid != s) {
176 	    if (len - namelen <= max
177 	     && ((mid[1] != '.' && (!name[1] || (name[1] == '.' && !name[2])))
178 	      || (*mid == '"' && *s == '"'))) {
179 		len -= namelen;
180 		name = mid;
181 		namelen = midlen;
182 		mid = 0;
183 	    }
184 	    else if (*mid == '"' && *s == '"') {
185 		if (midlen > max) {
186 		    name = mid+1;
187 		    *s = '\0';
188 		    goto try_again;
189 		}
190 		len = midlen;
191 		last = mid;
192 		namelen = 0;
193 		mid = 0;
194 	    }
195 	}
196     }
197     s[1] = '\0';
198     if (mid && len > max) {
199 	/* Turn middle names into intials */
200 	len -= s - mid + 2;
201 	d = s = mid;
202 	while (*s) {
203 	    if (isalpha(*s)) {
204 		if (d != mid) {
205 		    *d++ = ' ';
206 		}
207 		*d++ = *s++;
208 	    }
209 	    while (*s && !isspace(*s)) {
210 		s++;
211 	    }
212 	    while (isspace(*s)) {
213 		s++;
214 	    }
215 	}
216 	if (d != mid) {
217 	    *d = '\0';
218 	    midlen = d - mid + 1;
219 	    len += midlen;
220 	} else {
221 	    mid = 0;
222 	}
223     }
224     if (len > max) {
225 	/* If the first name fits without the middle initials, drop them */
226 	if (mid && len - midlen <= max) {
227 	    len -= midlen;
228 	    mid = 0;
229 	} else if (namelen > 0) {
230 	    /* Turn the first name into an initial */
231 	    len -= namelen - 2;
232 	    name[1] = '\0';
233 	    namelen = 2;
234 	    if (len > max) {
235 		/* Dump the middle initials (if present) */
236 		if (mid) {
237 		    len -= midlen;
238 		    mid = 0;
239 		}
240 		if (len > max) {
241 		    /* Finally just truncate the last name */
242 		    last[max - 2] = '\0';
243 		}
244 	    }
245 	} else
246 	    namelen = 0;
247     }
248 
249     /* Paste the names back together */
250     d = name + namelen;
251     if (namelen)
252 	d[-1] = ' ';
253     if (mid) {
254 	strcpy(d, mid);
255 	d += midlen;
256 	d[-1] = ' ';
257     }
258     strcpy(d, last);
259     return name;
260 }
261 
262 /* Compress an email address, trying to keep as much of the local part of
263 ** the addresses as possible.  The order of precence is @ ! %, but
264 ** @ % ! may be better...
265 */
266 static char *
compress_address(name,max)267 compress_address(name, max)
268 char *name;
269 int max;
270 {
271     char *s, *at, *bang, *hack, *start;
272     int len;
273 
274     /* Remove white space from both ends. */
275     while (isspace(*name)) {
276 	name++;
277     }
278     if ((len = strlen(name)) == 0) {
279 	return name;
280     }
281     s = name + len - 1;
282     while (isspace(*s)) {
283 	s--;
284     }
285     s[1] = '\0';
286     if (*name == '<') {
287 	name++;
288 	if (*s == '>') {
289 	    *s-- = '\0';
290 	}
291     }
292     if ((len = s - name + 1) <= max) {
293 	return name;
294     }
295 
296     at = bang = hack = NULL;
297     for (s = name + 1; *s; s++) {
298 	/* If there's whitespace in the middle then it's probably not
299 	** really an email address. */
300 	if (isspace(*s)) {
301 	    name[max] = '\0';
302 	    return name;
303 	}
304 	switch (*s) {
305 	case '@':
306 	    if (at == NULL) {
307 		at = s;
308 	    }
309 	    break;
310 	case '!':
311 	    if (at == NULL) {
312 		bang = s;
313 		hack = NULL;
314 	    }
315 	    break;
316 	case '%':
317 	    if (at == NULL && hack == NULL) {
318 		hack = s;
319 	    }
320 	    break;
321 	}
322     }
323     if (at == NULL) {
324 	at = name + len;
325     }
326 
327     if (hack != NULL) {
328 	if (bang != NULL) {
329 	    if (at - bang - 1 >= max) {
330 		start = bang + 1;
331 	    } else if (at - name >= max) {
332 		start = at - max;
333 	    } else {
334 		start = name;
335 	    }
336 	} else {
337 	    start = name;
338 	}
339     } else if (bang != NULL) {
340 	if (at - name >= max) {
341 	    start = at - max;
342 	} else {
343 	    start = name;
344 	}
345     } else {
346 	start = name;
347     }
348     if (len - (start - name) > max) {
349 	start[max] = '\0';
350     }
351     return start;
352 }
353 
354 /* Fit the author name in <max> chars.  Uses the comment portion if present
355 ** and pads with spaces.
356 */
357 char *
compress_from(ap,size)358 compress_from(ap, size)
359 ARTICLE *ap;
360 int size;
361 {
362     char *s, *t;
363     int len;
364 
365     for (t = cmd_buf, s = ap && ap->from? ap->from : nullstr; *s; ) {
366 	if ((unsigned char)*s < ' ')
367 	    *t++ = ' ', s++;
368 	else
369 	    *t++ = *s++;
370     }
371     *t = '\0';
372     if ((s = extract_name(cmd_buf)) != NULL)
373 	s = compress_name(s, size);
374     else
375 	s = compress_address(cmd_buf, size);
376     len = strlen(s);
377     if (!len) {
378 	strcpy(s,"NO NAME");
379 	len = 7;
380     }
381     while (len < size)
382 	s[len++] = ' ';
383     s[size] = '\0';
384     return s;
385 }
386 
387 #define EQ(x,y) ((isupper(x) ? tolower(x) : (x)) == (y))
388 
389 /* Parse the subject to skip past any "Re[:^]"s at the start.
390 */
391 char *
get_subject_start(str)392 get_subject_start(str)
393 register char *str;
394 {
395     while (*str && (unsigned char)*str <= ' ')
396 	str++;
397     while (EQ(str[0], 'r') && EQ(str[1], 'e')) {	/* check for Re: */
398       register char *cp = str + 2;
399 	if (*cp == '^') {				/* allow Re^2: */
400 	    while (*++cp <= '9' && *cp >= '0')
401 		;
402 	}
403 	if (*cp != ':')
404 	    break;
405 	while (*++cp == ' ')
406 	    ;
407 	str = cp;
408     }
409     return str;
410 }
411 
412 /* Output a subject in <max> chars.  Does intelligent trimming that tries to
413 ** save the last two words on the line, excluding "(was: blah)" if needed.
414 */
415 char *
compress_subj(ap,max)416 compress_subj(ap, max)
417 ARTICLE *ap;
418 int max;
419 {
420     register char *cp;
421     register int len;
422     ARTICLE *first;
423 
424     if (!ap)
425 	return "<MISSING>";
426 
427     /* Put a preceeding '>' on subjects that are replies to other articles */
428     cp = buf;
429     first = (ThreadedGroup? ap->subj->thread : ap->subj->articles);
430     if (ap != first || (ap->flags & AF_HAS_RE)
431      || !(!(ap->flags&AF_READ) ^ sel_rereading))
432 	*cp++ = '>';
433     strcpy(cp, ap->subj->str + 4);
434 
435     /* Remove "(was: oldsubject)", because we already know the old subjects.
436     ** Also match "(Re: oldsubject)".  Allow possible spaces after the ('s.
437     */
438     for (cp = buf; (cp = index(cp+1, '(')) != Nullch;) {
439 	while (*++cp == ' ')
440 	    ;
441 	if (EQ(cp[0], 'w') && EQ(cp[1], 'a') && EQ(cp[2], 's')
442 	 && (cp[3] == ':' || cp[3] == ' ')) {
443 	    *--cp = '\0';
444 	    break;
445 	}
446 	if (EQ(cp[0], 'r') && EQ(cp[1], 'e')
447 	 && ((cp[2]==':' && cp[3]==' ') || (cp[2]=='^' && cp[4]==':'))) {
448 	    *--cp = '\0';
449 	    break;
450 	}
451     }
452     len = strlen(buf);
453     if (!unbroken_subjects && len > max) {
454 	char *last_word;
455 	/* Try to include the last two words on the line while trimming */
456 	if ((last_word = rindex(buf, ' ')) != Nullch) {
457 	    char *next_to_last;
458 	    *last_word = '\0';
459 	    if ((next_to_last = rindex(buf, ' ')) != Nullch) {
460 		if (next_to_last-buf >= len - max + 3 + 10-1)
461 		    cp = next_to_last;
462 		else
463 		    cp = last_word;
464 	    } else
465 		cp = last_word;
466 	    *last_word = ' ';
467 	    if (cp-buf >= len - max + 3 + 10-1) {
468 		sprintf(buf + max - (len-(cp-buf)+3), "...%s", cp + 1);
469 		len = max;
470 	    }
471 	}
472     }
473     if (len > max)
474 	buf[max] = '\0';
475     return buf;
476 }
477 
478 #ifndef HAS_STRCASECMP
479 static unsigned char casemap[256] = {
480     0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
481     0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
482     0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,
483     0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
484     0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,
485     0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
486     0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,
487     0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
488     0x40,0x61,0x62,0x63,0x64,0x65,0x66,0x67,
489     0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
490     0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,
491     0x78,0x79,0x7A,0x7B,0x5C,0x5D,0x5E,0x5F,
492     0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,
493     0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
494     0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,
495     0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,
496     0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,
497     0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
498     0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,
499     0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
500     0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,
501     0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
502     0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,
503     0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
504     0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,
505     0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
506     0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,
507     0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,
508     0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,
509     0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,
510     0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,
511     0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF
512 };
513 
514 int
strcasecmp(s1,s2)515 strcasecmp(s1, s2)
516 register char *s1, *s2;
517 {
518     do {
519 	if (casemap[(unsigned)*s1++] != casemap[(unsigned)*s2])
520 	    return casemap[(unsigned)s1[-1]] - casemap[(unsigned)*s2];
521     } while (*s2++ != '\0');
522     return 0;
523 }
524 
525 int
strncasecmp(s1,s2,len)526 strncasecmp(s1, s2, len)
527 register char *s1, *s2;
528 register int len;
529 {
530     while (len--) {
531 	if (casemap[(unsigned)*s1++] != casemap[(unsigned)*s2])
532 	    return casemap[(unsigned)s1[-1]] - casemap[(unsigned)*s2];
533 	if (*s2++ == '\0')
534 	    break;
535     }
536     return 0;
537 }
538 #endif
539 
540 /* Modified version of a spinner originally found in Clifford Adams' strn. */
541 
542 static char spinchars[] = {'|','/','-','\\'};
543 static int spin_place;		/* represents place in spinchars array */
544 static int spin_count;		/* counter for when to spin */
545 static int spin_level INIT(0);	/* used to allow non-interfering nested spins */
546 static int spin_mode;
547 static ART_NUM spin_art;
548 static ART_POS spin_tell;
549 
550 void
setspin(mode)551 setspin(mode)
552 int mode;
553 {
554     switch (mode) {
555     case SPIN_FOREGROUND:
556     case SPIN_BACKGROUND:
557 	if (!spin_level++) {
558 	    if ((spin_art = openart) != 0 && artfp)
559 		spin_tell = tellart();
560 	    spin_count = 1;	/* not 0 to prevent immediate spin display */
561 	    spin_place = 1;	/* start with slash... */
562 	}
563 	spin_mode = mode;
564 	break;
565     case SPIN_POP:
566 	if (--spin_level > 0)
567 	    break;
568 	/* FALL THROUGH */
569     case SPIN_OFF:
570 	spin_level = 0;
571 	if (spin_place > 1) {	/* we have spun at least once */
572 	    putchar(spin_char); /* get rid of spin character */
573 	    backspace();
574 	    fflush(stdout);
575 	    spin_place = 0;
576 	}
577 	if (spin_art) {
578 	    artopen(spin_art);
579 	    if (artfp)
580 		seekart(spin_tell);   /* do not screw up the pager */
581 	    spin_art = 0;
582 	}
583 	spin_mode = SPIN_OFF;
584 	break;
585     }
586 }
587 
588 void
spin(count)589 spin(count)
590 int count;		/* modulus for the spin... */
591 {
592     if (!spin_level || (!bkgnd_spinner && spin_mode == SPIN_BACKGROUND))
593 	return;
594     if (!(spin_count++%count)) {
595 	if (spin_mode == SPIN_FOREGROUND)
596 	    putchar('.');
597 	else {
598 	    putchar(spinchars[spin_place++%4]);
599 	    backspace();
600 	}
601 	fflush(stdout);
602     }
603 }
604 
605 bool
inbackground()606 inbackground()
607 {
608     return spin_mode == SPIN_BACKGROUND;
609 }
610