1 /* -*- mode: C; mode: fold; -*- */
2 /*
3  This file is part of SLRN.
4 
5  Copyright (c) 1994, 1999, 2007-2016 John E. Davis <jed@jedsoft.org>
6  Copyright (c) 2001-2006 Thomas Schultz <tststs@gmx.de>
7 
8  This program is free software; you can redistribute it and/or modify it
9  under the terms of the GNU General Public License as published by the Free
10  Software Foundation; either version 2 of the License, or (at your option)
11  any later version.
12 
13  This program is distributed in the hope that it will be useful, but WITHOUT
14  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
16  more details.
17 
18  You should have received a copy of the GNU General Public License along
19  with this program; if not, write to the Free Software Foundation, Inc.,
20  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 */
22 #include "config.h"
23 #include "slrnfeat.h"
24 
25 /*{{{ system include files */
26 
27 #include <stdio.h>
28 #include <string.h>
29 #include <time.h>
30 
31 #ifdef HAVE_UNISTD_H
32 # include <unistd.h>
33 #endif
34 
35 #ifdef HAVE_STDLIB_H
36 # include <stdlib.h>
37 #endif
38 
39 #include <ctype.h>
40 #include <slang.h>
41 #include "jdmacros.h"
42 
43 #include "slrn.h"
44 #include "group.h"
45 #include "util.h"
46 #include "strutil.h"
47 #include "server.h"
48 #include "art.h"
49 #include "misc.h"
50 #include "post.h"
51 /* #include "clientlib.h" */
52 #include "startup.h"
53 #include "hash.h"
54 #include "score.h"
55 #include "menu.h"
56 #include "xover.h"
57 #include "print.h"
58 
59 /*}}}*/
60 
61 static int Art_Hide_Quote_Level = 1;
62 int Slrn_Wrap_Mode = 3;
63 int Slrn_Wrap_Method = 2;
64 int Slrn_Wrap_Width = -1;
65 
66 static char *Super_Cite_Regexp = "^[^A-Za-z0-9]*\"\\([-_a-zA-Z/]+\\)\" == .+";
67 
hide_article_lines(Slrn_Article_Type * a,unsigned int mask)68 static void hide_article_lines (Slrn_Article_Type *a, unsigned int mask) /*{{{*/
69 {
70    Slrn_Article_Line_Type *l;
71 
72    if (a == NULL)
73      return;
74 
75    a->needs_sync = 1;
76    l = a->lines;
77 
78    while (l != NULL)
79      {
80 	if (l->flags & mask)
81 	  l->flags |= HIDDEN_LINE;
82 
83 	l = l->next;
84      }
85 }
86 
87 /*}}}*/
88 
unhide_article_lines(Slrn_Article_Type * a,unsigned int mask)89 static void unhide_article_lines (Slrn_Article_Type *a, unsigned int mask) /*{{{*/
90 {
91    Slrn_Article_Line_Type *l;
92 
93    if (a == NULL)
94      return;
95 
96    a->needs_sync = 1;
97    l = a->lines;
98 
99    while (l != NULL)
100      {
101 	if (l->flags & mask)
102 	  l->flags &= ~HIDDEN_LINE;
103 
104 	l = l->next;
105      }
106 }
107 
108 /*}}}*/
109 
110 /* The function that set the needs_sync flag article line number start with _
111  */
112 
_slrn_art_unhide_quotes(Slrn_Article_Type * a)113 int _slrn_art_unhide_quotes (Slrn_Article_Type *a) /*{{{*/
114 {
115    if (a == NULL)
116      return -1;
117 
118    unhide_article_lines (a, QUOTE_LINE);
119    a->quotes_hidden = 0;
120    return 0;
121 }
122 
123 /*}}}*/
124 
is_matching_line(unsigned char * b,SLRegexp_Type ** r)125 static unsigned char *is_matching_line (unsigned char *b, SLRegexp_Type **r) /*{{{*/
126 {
127    unsigned int len;
128    len = strlen ((char *) b);
129 
130    while (*r != NULL)
131      {
132 	SLRegexp_Type *re;
133 	unsigned int match_len;
134 
135 	re = *r++;
136 #if SLANG_VERSION < 20000
137 	if ((re->min_length > len)
138 	    || (b != SLang_regexp_match (b, len, re)))
139 	  continue;
140 	match_len = re->end_matches[0];
141 #else
142 	if (b != (unsigned char *) SLregexp_match (re, (char *) b, len))
143 	  continue;
144 	(void) SLregexp_nth_match (re, 0, NULL, &match_len);
145 #endif
146 	return b + match_len;
147      }
148    return NULL;
149 }
150 
151 /*}}}*/
152 
_slrn_art_hide_quotes(Slrn_Article_Type * a,int reset)153 int _slrn_art_hide_quotes (Slrn_Article_Type *a, int reset) /*{{{*/
154 {
155    Slrn_Article_Line_Type *l, *last;
156 
157    if (a == NULL)
158      return -1;
159 
160    _slrn_art_unhide_quotes (a);
161 
162    a->needs_sync = 1;
163 
164    if (!reset)
165      Art_Hide_Quote_Level = Slrn_Quotes_Hidden_Mode;
166 
167    l = a->lines;
168    last = NULL;
169 
170    while (l != NULL)
171      {
172 	if (l->flags & QUOTE_LINE)
173 	  {
174 	     if (Art_Hide_Quote_Level > 1)
175 	       {
176 		  if (l->v.quote_level + 1 >= (unsigned int) Art_Hide_Quote_Level)
177 		    l->flags |= HIDDEN_LINE;
178 	       }
179 	     /* Show first line of quoted material; try to pick one that
180 	      * actually has text on it! */
181 	     else
182 	       {
183 		  if (last != NULL)
184 		    l->flags |= HIDDEN_LINE;
185 		  else
186 		    {
187 		       last = l;
188 		       if ((l->next != NULL) && (l->next->flags & QUOTE_LINE))
189 			 {
190 			    unsigned char *str, *line = (unsigned char*)l->buf;
191 			    while (NULL != (str = is_matching_line
192 					    (line, Slrn_Ignore_Quote_Regexp)))
193 			      line = str;
194 			    if (0 == *(slrn_skip_whitespace((char*)line)))
195 			      {
196 				 l->flags |= HIDDEN_LINE;
197 				 last = NULL;
198 			      }
199 			 }
200 		    }
201 	       }
202 	  }
203 	else last = NULL;
204 	l = l->next;
205      }
206    a->quotes_hidden = Art_Hide_Quote_Level;
207    return 0;
208 }
209 
210 /*}}}*/
211 
212 #if SLRN_HAS_SPOILERS
slrn_art_mark_spoilers(Slrn_Article_Type * a)213 void slrn_art_mark_spoilers (Slrn_Article_Type *a) /*{{{*/
214 {
215    Slrn_Article_Line_Type *l;
216 
217    if ((Slrn_Spoiler_Char == 0) || (a == NULL))
218      return;
219 
220    l = a->lines;
221 
222    while ((l != NULL) && (l->flags & HEADER_LINE))
223      l = l->next; /* skip header */
224 
225    while ((l != NULL) && (NULL == slrn_strbyte (l->buf, 12)))
226      l = l->next; /* skip to first formfeed */
227 
228    while (l != NULL)
229      {
230 	l->flags |= SPOILER_LINE;
231 	l = l->next;
232      }
233 }
234 
235 /*}}}*/
236 #endif
237 
try_supercite(Slrn_Article_Line_Type * l)238 static int try_supercite (Slrn_Article_Line_Type *l) /*{{{*/
239 {
240    Slrn_Article_Line_Type *last, *lsave;
241 #if SLANG_VERSION < 20000
242    static unsigned char compiled_pattern_buf[256];
243    static SLRegexp_Type re_buf;
244    SLRegexp_Type *re;
245 #else
246    static SLRegexp_Type *re = NULL;
247 #endif
248    unsigned char *b;
249    int count;
250    char name[32];
251    unsigned int len;
252    unsigned int ofs;
253    int ret;
254 
255 #if SLANG_VERSION < 20000
256    re = &re_buf;
257    re->pat = (unsigned char *) Super_Cite_Regexp;
258    re->buf = compiled_pattern_buf;
259    re->case_sensitive = 1;
260    re->buf_len = sizeof (compiled_pattern_buf);
261    if ((*compiled_pattern_buf == 0) && SLang_regexp_compile (re))
262      return -1;
263 #else
264    if ((re == NULL)
265        && (NULL == (re = SLregexp_compile (Super_Cite_Regexp, 0))))
266      return -1;
267 #endif
268 
269    /* skip header --- I should look for Xnewsreader: gnus */
270    while ((l != NULL) && (*l->buf != 0)) l = l->next;
271 
272    /* look at the first 15 lines on first attempt.
273     * After that, scan the whole buffer looking for more citations */
274    count = 15;
275    lsave = l;
276    ret = -1;
277    while (1)
278      {
279 	while (count && (l != NULL))
280 	  {
281 	     if ((l->flags & QUOTE_LINE) == 0)
282 	       {
283 		  if (NULL != slrn_regexp_match (re, l->buf))
284 		    {
285 		       l->flags |= QUOTE_LINE;
286 		       break;
287 		    }
288 	       }
289 	     l = l->next;
290 	     count--;
291 	  }
292 
293 	if ((l == NULL) || (count == 0)) return ret;
294 
295 	/* Now find out what is used for citing. */
296 	if (-1 == SLregexp_nth_match (re, 1, &ofs, &len))
297 	  return ret;
298 
299 	b = (unsigned char *) l->buf + ofs;
300 	if (len > sizeof (name) - 2) return ret;
301 
302 	ret = 0;
303 	strncpy (name, (char *) b, len); name[len] = 0;
304 
305 	while (l != NULL)
306 	  {
307 	     unsigned char ch;
308 
309 	     b = (unsigned char *) l->buf;
310 	     last = l;
311 	     l = l->next;
312 	     if (last->flags & QUOTE_LINE) continue;
313 
314 	     b = (unsigned char *) slrn_skip_whitespace ((char *) b);
315 
316 	     if (!strncmp ((char *) b, name, len)
317 		 && (((ch = b[len] | 0x20) < 'a')
318 		     || (ch > 'z')))
319 	       {
320 		  last->flags |= QUOTE_LINE;
321 
322 		  while (l != NULL)
323 		    {
324 		       b = (unsigned char *) slrn_skip_whitespace (l->buf);
325 		       if (strncmp ((char *) b, name, len)
326 			   || (((ch = b[len] | 0x20) >= 'a')
327 			       && (ch <= 'z')))
328 			 break;
329 		       l->flags |= QUOTE_LINE;
330 		       l = l->next;
331 		    }
332 	       }
333 	  }
334 	count = -1;
335 	l = lsave;
336      }
337 }
338 
339 /*}}}*/
340 
slrn_art_mark_quotes(Slrn_Article_Type * a)341 void slrn_art_mark_quotes (Slrn_Article_Type *a) /*{{{*/
342 {
343    Slrn_Article_Line_Type *l;
344    unsigned char *b;
345 
346    if (a == NULL)
347      return;
348 
349    if (0 == try_supercite (a->lines))
350      {
351 	/* return; */
352      }
353 
354    if (Slrn_Ignore_Quote_Regexp[0] == NULL) return;
355 
356    /* skip header */
357    l = a->lines;
358    while ((l != NULL) && (l->flags == HEADER_LINE))
359      l = l->next;
360 
361    while (l != NULL)
362      {
363 	int level;
364 	b = (unsigned char *) l->buf;
365 
366 	if (NULL == (b = is_matching_line (b, Slrn_Ignore_Quote_Regexp)))
367 	  {
368 	     l = l->next;
369 	     continue;
370 	  }
371 	l->flags |= QUOTE_LINE;
372 	level = 1;
373 	while (NULL != (b = is_matching_line (b, Slrn_Ignore_Quote_Regexp)))
374 	  level++;
375 
376 	l->v.quote_level = level;
377 	l = l->next;
378      }
379    a->is_modified = 1;
380 }
381 
382 /*}}}*/
383 
slrn_art_mark_signature(Slrn_Article_Type * a)384 void slrn_art_mark_signature (Slrn_Article_Type *a) /*{{{*/
385 {
386    Slrn_Article_Line_Type *l;
387 
388    if (a == NULL)
389      return;
390 
391    l = a->lines;
392    if (l == NULL) return;
393    /* go to end of article */
394    while (l->next != NULL) l = l->next;
395 
396    /* skip back until a line matches the signature RegExp */
397 
398    while ((l != NULL) && (0 == (l->flags & HEADER_LINE))
399 	  && ((l->flags & VERBATIM_LINE) ||
400 	      (NULL == is_matching_line ((unsigned char *) l->buf,
401 					 Slrn_Strip_Sig_Regexp))))
402      l = l->prev;
403 
404    if ((l == NULL) || (l->flags & HEADER_LINE)) return;
405 
406    while (l != NULL)
407      {
408         l->flags |= SIGNATURE_LINE;
409         l->flags &= ~(
410 		      QUOTE_LINE  /* if in a signature, not a quote */
411 #if SLRN_HAS_SPOILERS
412 		      | SPOILER_LINE     /* not a spoiler */
413 #endif
414 		      );
415 	l = l->next;
416      }
417 }
418 
419 /*}}}*/
420 
421 /* The input line is assumed to be the first wrapped portion of a line.  For
422  * example, if a series of lines denoted as A/B is wrapped: A0/A1/A2/B0/B1,
423  * then to unwrap A, A1 is passed and B0 is returned.
424  */
unwrap_line(Slrn_Article_Type * a,Slrn_Article_Line_Type * l)425 static Slrn_Article_Line_Type *unwrap_line (Slrn_Article_Type *a, /*{{{*/
426 					    Slrn_Article_Line_Type *l) /*{{{*/
427 {
428    char *b;
429    Slrn_Article_Line_Type *next, *ll;
430 
431    a->needs_sync = 1;
432    a->is_modified = 1;
433 
434    ll = l->prev;
435    b = ll->buf;
436    do
437      {
438 	b += strlen (b);
439 	/* skip the space at beginning of the wrapped line: */
440 	strcpy (b, l->buf + 1); /* safe */
441 	next = l->next;
442 	slrn_free ((char *)l->buf);
443 	slrn_free ((char *)l);
444 	if (l == a->cline) a->cline = ll;
445 	l = next;
446      }
447    while ((l != NULL) && (l->flags & WRAPPED_LINE));
448 
449    ll->next = l;
450    if (l != NULL) l->prev = ll;
451    return l;
452 }
453 
454 /*}}}*/
455 
456 /*}}}*/
457 
_slrn_art_unwrap_article(Slrn_Article_Type * a)458 int _slrn_art_unwrap_article (Slrn_Article_Type *a) /*{{{*/
459 {
460    Slrn_Article_Line_Type *l;
461 
462    if (a == NULL)
463      return -1;
464 
465    a->needs_sync = 1;
466    a->is_modified = 1;
467    l = a->lines;
468 
469    while (l != NULL)
470      {
471 	if (l->flags & WRAPPED_LINE)
472 	  l = unwrap_line (a, l);      /* cannot fail */
473 	else l = l->next;
474      }
475    a->is_wrapped = 0;
476    return 0;
477 }
478 
479 /*}}}*/
480 
_slrn_art_wrap_article(Slrn_Article_Type * a)481 int _slrn_art_wrap_article (Slrn_Article_Type *a) /*{{{*/
482 {
483    unsigned char *buf, ch;
484    Slrn_Article_Line_Type *l;
485    unsigned int wrap_mode = Slrn_Wrap_Mode;
486    int wrap_width = Slrn_Wrap_Width;
487 
488    if ((wrap_width < 5) || (wrap_width > SLtt_Screen_Cols))
489      wrap_width = SLtt_Screen_Cols;
490 
491    if (a == NULL)
492      return -1;
493 
494    a->is_modified = 1;
495    a->needs_sync = 1;
496 
497    if (-1 == _slrn_art_unwrap_article (a))
498      return -1;
499 
500    l = a->lines;
501 
502 #if SLANG_VERSION >= 20000
503    SLsmg_gotorc(0,0); /* used by Slsmg_strbytes later on */
504 #endif
505 
506    while (l != NULL)
507      {
508 	unsigned char header_char_delimiter = 0;
509 
510 	if (l->flags & HEADER_LINE)
511 	  {
512 	     if ((wrap_mode & 1) == 0)
513 	       {
514 		  l = l->next;
515 		  continue;
516 	       }
517 
518 	     if (0 == slrn_case_strncmp ("Path: ", l->buf, 6))
519 	       header_char_delimiter = '!';
520 	     else if (0 == slrn_case_strncmp ( "Newsgroups: ", l->buf, 12))
521 	       header_char_delimiter = ',';
522 	  }
523 	else if (l->flags & QUOTE_LINE)
524 	  {
525 	     if ((wrap_mode & 2) == 0)
526 	       {
527 		  l = l->next;
528 		  continue;
529 	       }
530 	  }
531 
532 	buf = (unsigned char *) l->buf;
533 	ch = *buf;
534 	while (ch != 0)
535 	  {
536 	     unsigned int bytes = SLsmg_strbytes (buf, buf+strlen((char *)buf),
537 						  (unsigned int) wrap_width);
538 	     buf += bytes;
539 	     while ((*buf == ' ') || (*buf == '\t'))
540 	       buf++;
541 	     if (*buf) /* we need to wrap */
542 	       {
543 		  Slrn_Article_Line_Type *new_l;
544 		  unsigned char *buf0, *lbuf;
545 
546 		  /* Try to break the line on a word boundary.
547 		   * For now, I will only break on space characters.
548 		   */
549 		  buf0 = buf;
550 		  lbuf = (unsigned char *) l->buf;
551 
552 		  lbuf += 1;	       /* avoid space at beg of line */
553 
554 		  if (Slrn_Wrap_Method == 0 || Slrn_Wrap_Method == 2)
555 		    {
556 		       while (buf0 > lbuf)
557 			 {
558 			    if ((*buf0 == ' ') || (*buf0 == '\t')
559 				|| (header_char_delimiter
560 				    && (*buf0 == header_char_delimiter)))
561 			      {
562 				 buf = buf0;
563 				 break;
564 			      }
565 			    buf0--;
566 			 }
567 
568 		       if (buf0 == lbuf)
569 			 {
570 			    if (Slrn_Wrap_Method == 0)
571 			      {
572 		       /* Could not find a place to break the line.  Ok, so
573 			* we will not break this.  Perhaps it is a URL.
574 			* If not, it is a long word and who cares about it.
575 			*/
576 				 while (((ch = *buf) != 0)
577 					&& (ch != ' ') && (ch != '\t'))
578 				   buf++;
579 
580 				 if (ch == 0)
581 				   continue;
582 			      }
583 			    else {
584 			       lbuf = (unsigned char *) l->buf;
585 			       lbuf += 1; /* avoid space at beg of line */
586 			    }
587 			 }
588 		    }
589 
590 		  /* Start wrapped lines with a space.  To do this, I will
591 		   * _temporally_ modify the previous character for the purpose
592 		   * of creating the new space.
593 		   */
594 		  buf--;
595 		  ch = *buf;
596 		  *buf = ' ';
597 
598 		  new_l = (Slrn_Article_Line_Type *) slrn_malloc (sizeof (Slrn_Article_Line_Type), 1, 1);
599 		  if (new_l == NULL)
600 		    return -1;
601 
602 		  if (NULL == (new_l->buf = slrn_strmalloc ((char *)buf, 1)))
603 		    {
604 		       slrn_free ((char *) new_l);
605 		       return -1;
606 		    }
607 
608 		  *buf++ = ch;
609 		  *buf = 0;
610 
611 		  new_l->next = l->next;
612 		  new_l->prev = l;
613 		  l->next = new_l;
614 		  if (new_l->next != NULL) new_l->next->prev = new_l;
615 
616 		  new_l->flags = l->flags | WRAPPED_LINE;
617 
618 		  if (new_l->flags & QUOTE_LINE)
619 		    new_l->v.quote_level = l->v.quote_level;
620 
621 		  l = new_l;
622 		  buf = (unsigned char *) new_l->buf;
623 		  a->is_wrapped = 1;
624 	       }
625 #if SLANG_VERSION < 20000
626 	     else buf++;
627 #endif
628 
629 	     ch = *buf;
630 	  }
631 	l = l->next;
632      }
633    return 0;
634 }
635 
636 /*}}}*/
637 
is_blank_line(unsigned char * b)638 static int is_blank_line (unsigned char *b) /*{{{*/
639 {
640    b = (unsigned char *) slrn_skip_whitespace ((char *) b);
641    return (*b == 0);
642 }
643 
644 /*}}}*/
645 
_slrn_art_skip_quoted_text(Slrn_Article_Type * a)646 void _slrn_art_skip_quoted_text (Slrn_Article_Type *a) /*{{{*/
647 {
648    Slrn_Article_Line_Type *l;
649 
650    if (a == NULL)
651      return;
652 
653    l = a->cline;
654    a->needs_sync = 1;
655 
656    /* look for a quoted line */
657    while (l != NULL)
658      {
659 	if ((l->flags & HIDDEN_LINE) == 0)
660 	  {
661 	     a->cline = l;
662 	     if (l->flags & QUOTE_LINE) break;
663 	  }
664 	l = l->next;
665      }
666 
667    /* Now we are either at the end of the buffer or on a quote line. Skip
668     * past other quote lines.
669     */
670 
671    if (l == NULL)
672      return;
673 
674    l = l->next;
675 
676    while (l != NULL)
677      {
678 	if (l->flags & HIDDEN_LINE)
679 	  {
680 	     l = l->next;
681 	     continue;
682 	  }
683 	a->cline = l;
684 	if ((l->flags & QUOTE_LINE) == 0)
685 	  {
686 	     /* Check to see if it is blank */
687 	     if (is_blank_line ((unsigned char *) l->buf) == 0) break;
688 	  }
689 	l = l->next;
690      }
691 }
692 
693 /*}}}*/
694 
_slrn_art_skip_digest_forward(Slrn_Article_Type * a)695 int _slrn_art_skip_digest_forward (Slrn_Article_Type *a) /*{{{*/
696 {
697    Slrn_Article_Line_Type *l;
698    int num_passes;
699 
700    if (a == NULL)
701      return -1;
702 
703    /* We are looking for:
704     * <blank line>  (actually, most digests do not have this-- even the FAQ that suggests it!!)
705     * ------------------------------
706     * <blank line>
707     * Subject: something
708     *
709     * In fact, most digests do not conform to this.  So, I will look for:
710     * <blank line>
711     * Subject: something
712     *
713     * Actually, most faqs, etc... do not support this.  So, look for any line
714     * beginning with a number on second pass.  Sigh.
715     */
716    num_passes = 0;
717    while (num_passes < 2)
718      {
719 	l = a->cline->next;
720 
721 	while (l != NULL)
722 	  {
723 	     char ch;
724 	     char *buf;
725 
726 	     if ((l->flags & HIDDEN_LINE) || (l->flags & HEADER_LINE))
727 	       {
728 		  l = l->next;
729 		  continue;
730 	       }
731 
732 	     buf = l->buf;
733 	     if (num_passes == 0)
734 	       {
735 		  if ((strncmp ("Subject:", buf, 8))
736 		      || (((ch = buf[8]) != ' ') && (ch != '\t')))
737 		    {
738 		       l = l->next;
739 		       continue;
740 		    }
741 	       }
742 	     else
743 	       {
744 		  ch = *buf;
745 		  if ((ch > '9') || (ch < '0'))
746 		    {
747 		       l = l->next;
748 		       continue;
749 		    }
750 	       }
751 
752 	     a->cline = l;
753 	     a->needs_sync = 1;
754 	     return 0;
755 	  }
756 	num_passes++;
757      }
758    return -1;
759 }
760 
761 /*}}}*/
762 
slrn_art_extract_header(char * hdr,unsigned int len)763 char *slrn_art_extract_header (char *hdr, unsigned int len) /*{{{*/
764 {
765    Slrn_Article_Line_Type *l;
766    Slrn_Article_Type *a = Slrn_Current_Article;
767 
768    if (a == NULL)
769      return NULL;
770 
771    l = a->lines;
772 
773    while ((l != NULL)
774 	  && (*l->buf != 0))
775      {
776 	if (0 == slrn_case_strncmp ( hdr,
777 				     l->buf, len))
778 	  {
779 	     char *result;
780 
781 	     if ((l->next != NULL) && (l->next->flags & WRAPPED_LINE))
782 	       {
783 		  (void) unwrap_line (a, l->next);
784 		  slrn_art_sync_article (a);
785 	       }
786 
787 	     /* Return the data after the colon */
788 	     result = slrn_strbyte (l->buf, ':');
789 	     if (result == NULL)
790 	       result = l->buf + len;
791 	     else result += 1;
792 
793 	     return slrn_skip_whitespace (result);
794 	  }
795 	l = l->next;
796      }
797    return NULL;
798 }
799 
800 /*}}}*/
801 
802 typedef struct _Visible_Header_Type /*{{{*/
803 {
804    char *header;
805    unsigned int len;
806    struct _Visible_Header_Type *next;
807 }
808 
809 /*}}}*/
810 Visible_Header_Type;
811 
812 static Visible_Header_Type *Visible_Headers;
813 
814 char *Slrn_Visible_Headers_String;     /* for interpreter */
815 
free_visible_header_list(void)816 static void free_visible_header_list (void) /*{{{*/
817 {
818    while (Visible_Headers != NULL)
819      {
820 	Visible_Header_Type *next;
821 	next = Visible_Headers->next;
822 	SLang_free_slstring (Visible_Headers->header);   /* NULL ok */
823 	SLfree ((char *) Visible_Headers);
824 	Visible_Headers = next;
825      }
826    SLang_free_slstring (Slrn_Visible_Headers_String);
827    Slrn_Visible_Headers_String = NULL;
828 }
829 
830 /*}}}*/
831 
slrn_set_visible_headers(char * headers)832 int slrn_set_visible_headers (char *headers) /*{{{*/
833 {
834    char buf[256];
835    unsigned int nth;
836 
837    free_visible_header_list ();
838 
839    Slrn_Visible_Headers_String = SLang_create_slstring (headers);
840    if (Slrn_Visible_Headers_String == NULL)
841      return -1;
842 
843    nth = 0;
844    while (-1 != SLextract_list_element (headers, nth, ',', buf, sizeof(buf)))
845      {
846 	Visible_Header_Type *next;
847 
848 	next = (Visible_Header_Type *) SLmalloc (sizeof (Visible_Header_Type));
849 	if (next == NULL)
850 	  return -1;
851 	memset ((char *) next, 0, sizeof(Visible_Header_Type));
852 	if (NULL == (next->header = SLang_create_slstring (buf)))
853 	  {
854 	     SLfree ((char *) next);
855 	     return -1;
856 	  }
857 	next->len = strlen (buf);
858 	next->next = Visible_Headers;
859 	Visible_Headers = next;
860 
861 	nth++;
862      }
863    if ((Slrn_Current_Article != NULL) &&
864        (Slrn_Current_Article->headers_hidden))
865      _slrn_art_hide_headers (Slrn_Current_Article); /* commit the changes */
866    return 0;
867 }
868 
869 /*}}}*/
870 
_slrn_art_hide_headers(Slrn_Article_Type * a)871 void _slrn_art_hide_headers (Slrn_Article_Type *a) /*{{{*/
872 {
873    Slrn_Article_Line_Type *l;
874    char ch;
875 
876    if (a == NULL)
877      return;
878 
879    _slrn_art_unhide_headers (a);
880 
881    a->needs_sync = 1;
882 
883    l = a->lines;
884 
885    while ((l != NULL) && (l->flags & HEADER_LINE))
886      {
887 	int hide_header;
888 	Visible_Header_Type *v;
889 
890 	ch = l->buf[0];
891 	ch |= 0x20;
892 
893 	hide_header = 1;
894 
895 	v = Visible_Headers;
896 	while (v != NULL)
897 	  {
898 	     int hide = (v->header[0] == '!') ? 1 : 0;
899 	     char chv = (0x20 | v->header[hide]);
900 
901 	     if ((chv == ch)
902 		 && (0 == slrn_case_strncmp (l->buf,
903 					     v->header + hide,
904 					     (v->len) - hide)))
905 	       {
906 		  hide_header = hide;
907 		  break;
908 	       }
909 
910 	     v = v->next;
911 	  }
912 
913 	do
914 	  {
915 	     if (hide_header)
916 	       l->flags |= HIDDEN_LINE;
917 
918 	     l = l->next;
919 	  }
920 	while ((l != NULL) && ((*l->buf == ' ') || (*l->buf == '\t')));
921      }
922    a->headers_hidden = 1;
923 }
924 
925 /*}}}*/
926 
_slrn_art_unhide_headers(Slrn_Article_Type * a)927 void _slrn_art_unhide_headers (Slrn_Article_Type *a) /*{{{*/
928 {
929    if (a == NULL)
930      return;
931 
932    unhide_article_lines (a, HEADER_LINE);
933    a->headers_hidden = 0;
934 }
935 
936 /*}}}*/
937 
_slrn_art_unfold_header_lines(Slrn_Article_Type * a)938 int _slrn_art_unfold_header_lines (Slrn_Article_Type *a) /*{{{*/
939 {
940    Slrn_Article_Line_Type *l;
941    char ch;
942 
943    if (a == NULL)
944      return -1;
945 
946    l = a->lines->next;
947    a->is_modified = 1;
948    a->needs_sync = 1;
949 
950    while ((l != NULL) && (l->flags & HEADER_LINE))
951      {
952 	ch = l->buf[0];
953 	if ((ch == ' ') || (ch == '\t'))
954 	  {
955 	     unsigned int len0, len1;
956 	     Slrn_Article_Line_Type *prev;
957 	     char *new_buf;
958 
959 	     l->buf[0] = ' ';
960 
961 	     prev = l->prev;
962 
963 	     len0 = strlen (prev->buf);
964 	     len1 = len0 + strlen (l->buf) + 1;
965 
966 	     new_buf = slrn_realloc (prev->buf, len1, 1);
967 	     if (new_buf == NULL)
968 	       return -1;
969 
970 	     prev->buf = new_buf;
971 
972 	     strcpy (new_buf + len0, l->buf); /* safe */
973 	     prev->next = l->next;
974 	     if (l->next != NULL) l->next->prev = prev;
975 	     slrn_free ((char *)l->buf);
976 	     slrn_free ((char *) l);
977 	     l = prev;
978 	  }
979 
980 	l = l->next;
981      }
982    return 0;
983 }
984 
985 /*}}}*/
986 
slrn_mark_header_lines(Slrn_Article_Type * a)987 void slrn_mark_header_lines (Slrn_Article_Type *a) /*{{{*/
988 {
989    Slrn_Article_Line_Type *l;
990 
991    if (a == NULL)
992      return;
993    l = a->lines;
994 
995    while ((l != NULL) && (l->buf[0] != 0))
996      {
997 	l->flags = HEADER_LINE;
998 	l = l->next;
999      }
1000 }
1001 
1002 /*}}}*/
1003 
_slrn_art_hide_signature(Slrn_Article_Type * a)1004 void _slrn_art_hide_signature (Slrn_Article_Type *a) /*{{{*/
1005 {
1006    if (a == NULL)
1007      return;
1008 
1009    hide_article_lines (a, SIGNATURE_LINE);
1010    a->signature_hidden = 1;
1011 }
1012 
1013 /*}}}*/
1014 
_slrn_art_unhide_signature(Slrn_Article_Type * a)1015 void _slrn_art_unhide_signature (Slrn_Article_Type *a) /*{{{*/
1016 {
1017    if (a == NULL)
1018      return;
1019 
1020    unhide_article_lines (a, SIGNATURE_LINE);
1021    a->signature_hidden = 0;
1022 }
1023 
1024 /*}}}*/
1025 
slrn_art_mark_pgp_signature(Slrn_Article_Type * a)1026 void slrn_art_mark_pgp_signature (Slrn_Article_Type *a) /*{{{*/
1027 {
1028    Slrn_Article_Line_Type *l;
1029 
1030    if (a == NULL)
1031      return;
1032 
1033    l = a->lines;
1034    while (l != NULL)
1035      {
1036 	Slrn_Article_Line_Type *l0;
1037 	int count;
1038 
1039 	if ((l->buf[0] == '-')
1040 	    && !strcmp (l->buf, "-----BEGIN PGP SIGNED MESSAGE-----"))
1041 	  {
1042 	     l->flags |= PGP_SIGNATURE_LINE;
1043 	     l->flags &= ~QUOTE_LINE;
1044 
1045 	     if ((NULL != (l = l->next)) &&
1046 		 !strncmp (l->buf, "Hash: ", 6))
1047 	       {
1048 		  /* catch the `Hash: ... ' line */
1049 		  l->flags |= PGP_SIGNATURE_LINE;
1050 		  l->flags &= ~QUOTE_LINE;
1051 
1052 		  l = l->next;
1053 	       }
1054 	     if ((NULL != l) &&
1055 		 !strncmp (l->buf, "NotDashEscaped: ", 16))
1056 	       {
1057 		  /* the optional "NotDashEscaped: ..." line */
1058 		  l->flags |= PGP_SIGNATURE_LINE;
1059 		  l->flags &= ~QUOTE_LINE;
1060 
1061 		  l = l->next;
1062 	       }
1063 
1064 	     continue;
1065 	  }
1066 
1067 	if ((l->buf[0] != '-')
1068 	    || strcmp (l->buf, "-----BEGIN PGP SIGNATURE-----"))
1069 	  {
1070 	     l = l->next;
1071 	     continue;
1072 	  }
1073 	l0 = l;
1074 	l = l->next;
1075 
1076 	count = 256;		       /* arbitrary */
1077 	while ((l != NULL) && count)
1078 	  {
1079 	     if ((l->buf[0] != '-')
1080 		 || strcmp (l->buf, "-----END PGP SIGNATURE-----"))
1081 	       {
1082 		  count--;
1083 		  l = l->next;
1084 		  continue;
1085 	       }
1086 
1087 	     l0->flags |= PGP_SIGNATURE_LINE;
1088 	     l0->flags &= ~(QUOTE_LINE|SIGNATURE_LINE);
1089 	     do
1090 	       {
1091 		  l0 = l0->next;
1092 		  l0->flags |= PGP_SIGNATURE_LINE;
1093 		  l0->flags &= ~(QUOTE_LINE|SIGNATURE_LINE);
1094 	       }
1095 	     while (l0 != l);
1096 	     return;
1097 	  }
1098 	l = l0->next;
1099      }
1100 }
1101 
1102 /*}}}*/
1103 
_slrn_art_hide_pgp_signature(Slrn_Article_Type * a)1104 void _slrn_art_hide_pgp_signature (Slrn_Article_Type *a)
1105 {
1106    if (a == NULL)
1107      return;
1108 
1109    hide_article_lines (a, PGP_SIGNATURE_LINE);
1110    a->pgp_signature_hidden = 1;
1111 }
1112 
1113 /*}}}*/
1114 
_slrn_art_unhide_pgp_signature(Slrn_Article_Type * a)1115 void _slrn_art_unhide_pgp_signature (Slrn_Article_Type *a) /*{{{*/
1116 {
1117    if (a == NULL)
1118      return;
1119 
1120    unhide_article_lines (a, PGP_SIGNATURE_LINE);
1121    a->pgp_signature_hidden = 0;
1122 }
1123 
1124 /*}}}*/
1125 
slrn_art_mark_verbatim(Slrn_Article_Type * a)1126 void slrn_art_mark_verbatim (Slrn_Article_Type *a)
1127 {
1128    Slrn_Article_Line_Type *l;
1129    unsigned char chon, choff;
1130    unsigned int mask, next_mask;
1131    char *von, *voff;
1132 
1133    if (a == NULL)
1134      return;
1135 
1136    von = "#v+";
1137    voff = "#v-";
1138 
1139    chon = von[0];
1140    choff = voff[0];
1141 
1142    l = a->lines;
1143    next_mask = mask = 0;
1144 
1145    while (l != NULL)
1146      {
1147 	if (mask == 0)
1148 	  {
1149 	     if ((l->buf[0] == chon)
1150 		 && (0 == strcmp ((char *) l->buf, von)))
1151 	       {
1152 		  mask = VERBATIM_LINE|VERBATIM_MARK_LINE;
1153 		  next_mask = VERBATIM_LINE;
1154 	       }
1155 	  }
1156 	else if ((l->buf[0] == choff)
1157 		 && (0 == strcmp ((char *) l->buf, voff)))
1158 	  {
1159 	     mask = VERBATIM_LINE|VERBATIM_MARK_LINE;
1160 	     next_mask = 0;
1161 	  }
1162 
1163 	if (mask)
1164 	  {
1165 	     l->flags &= ~(QUOTE_LINE|SIGNATURE_LINE|PGP_SIGNATURE_LINE);
1166 	     l->flags |= mask;
1167 	     mask = next_mask;
1168 	  }
1169 
1170 	l = l->next;
1171      }
1172 }
1173 
_slrn_art_unhide_verbatim(Slrn_Article_Type * a)1174 void _slrn_art_unhide_verbatim (Slrn_Article_Type *a) /*{{{*/
1175 {
1176    if (a == NULL)
1177      return;
1178 
1179    unhide_article_lines (a, VERBATIM_LINE);
1180    a->verbatim_hidden = 0;
1181 }
1182 
1183 /*}}}*/
1184 
_slrn_art_hide_verbatim(Slrn_Article_Type * a)1185 void _slrn_art_hide_verbatim (Slrn_Article_Type *a) /*{{{*/
1186 {
1187    if (a == NULL)
1188      return;
1189 
1190    hide_article_lines (a, VERBATIM_LINE);
1191    a->verbatim_hidden = 1;
1192 }
1193 
1194 /*}}}*/
1195