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